Пројектовање софтвера/К1 2024

Извор: SI Wiki
Пређи на навигацију Пређи на претрагу

Први колоквијум 2024. године одржан је 27. октобра 2024. године. Биле су доступне презентације о свим пројектним обрасцима, као и Java документација. Време за израду је било 120 минута.

1. задатак

Поставка

Посматра се систем за приказ и обраду дигиталних слика. Треба обезбедити то да се на оригиналну слику може применити произвољан број филтера (или ниједан), у произвољном редоследу, при чему остатак система који приказује и користи слику (“клијент”) не треба да види разлику у односу на то да ли су на слику примењени филтери или нису. Свака слика садржи низ пиксела. Пиксел има R, G и B компоненте (интензитет црвене, зелене и плаве боје), где свака има целобројну вредност од 0 до 255. Филтери који могу да се примене у овом систему су за сада (касније могу бити осмишљени и додати нови филтери):

  • Greyscale – сваки пиксел се претвара у одговарајућу нијансу сиве, где се R, G и B компоненте рачунају по следећој формули: R, G, B = 0.3 * R + 0.59 * G + 0.11 * B
  • Invert – R, G и B компоненте сваког пиксела се инвертују по следећој формули: R = 255 – R, G = 255 – G, B = 255 – B

Клијент који користи слику може да прочита вредности њених пиксела; уколико су на слику примењени филтери, то су вредности пиксела оригиналне слике измењене дејством додатих филтера (у случају да ниједан филтер није примењен над оригиналном сликом, враћа се неизмењен низ пиксела оригиналне слике). Коришћењем пројектног обрасца Декоратер (Decorator) потребно је имплементирати описани део система и навести расподелу улога из обрасца класама у овом решењу; опис дати у текстуалном фајлу z1.txt.

Написати пример коришћења дате сарадње. Направити слику са филтерима Greyscale и Invert, са неким насумичним вредностима пиксела, а затим дохватити пикселе такве слике и исписати их. Имплементација пиксела приказана је у следећој класи:

package zad1;

public class Pixel {
    private int R, G, B;

    public Pixel(int R, int G, int B) {
        this.R = R;
        this.G = G;
        this.B = B;
    }

    public int getR() {
        return R;
    }

    public void setR(int R) {
        this.R = R;
    }

    public int getG() {
        return G;
    }

    public void setG(int G) {
        this.G = G;
    }

    public int getB() {
        return B;
    }

    public void setB(int B) {
        this.B = B;
    }
}

Решење

Сарадња

У овом задатку потребно је користити узорак Декоратер, а у изради се користио и узорак Шаблонски метод.

Декоратер
Учесници:
  • Компонента (Image)
  • Субјекат (RawImage)
  • Допуна (Filter)
  • КонкретнаДопуна (Greyscale, Inverse)
Шаблонски метод
Учесници:
  • АпстрактнаКласа (Filter)
  • КонкретнаКласа (Greyscale, Inverse)

Image.java

package z1;

import java.util.List;

public interface Image {
    public List<Pixel> getPixels();
}

RawImage.java

package z1;

import java.util.ArrayList;
import java.util.List;

public class RawImage implements Image {
    protected List<Pixel> pixels = new ArrayList<Pixel>();

    public RawImage(List<Pixel> pixels) {
        this.pixels = pixels;
    }

    @Override
    public List<Pixel> getPixels() {
        return pixels;
    }
}

Filter.java

package z1;

import java.util.ArrayList;
import java.util.List;

public abstract class Filter implements Image{
    private Image image; //image that we wrap/decorate

    public Filter(Image image){
        this.image = image;
    }

    protected abstract Pixel modifyPixel(Pixel p);

    private List<Pixel> applyFilter(List<Pixel> pixels){
        List<Pixel> modifiedPixels = new ArrayList<Pixel>();
        for (Pixel pixel : pixels){
            modifiedPixels.add(modifyPixel(pixel));
        }
        return modifiedPixels;
    }

    @Override
    public List<Pixel> getPixels() {
        return applyFilter(this.image.getPixels());
    }
}

Greyscale.java

package z1;

public class Greyscale extends Filter{
    public Greyscale(Image i) {
        super(i);
    }

    @Override
    protected Pixel modifyPixel(Pixel p){
        int grey = (int) (p.getR()*0.3) +  (int) (p.getG()*0.59) + (int) (p.getB()*0.11);
        return new Pixel(grey, grey, grey
        );
    }
}

Invert.java

package z1;

public class Invert extends Filter {
    public Invert(Image i) {
        super(i);
    }

    private final int invertFactor = 255;

    protected Pixel modifyPixel(Pixel p) {
        return new Pixel(
                invertFactor - p.getR(),
                invertFactor - p.getG(),
                invertFactor - p.getB()
        );
    }

}

Main.java

package z1;

import java.util.Arrays;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Pixel> pixels = Arrays.asList(new Pixel(2, 2, 2), new Pixel(2, 3, 4), new Pixel(100, 200, 255));
        Image myImage = new RawImage(pixels);
        for (Pixel p : myImage.getPixels()) {
            System.out.println(p.toString());
        }
        Image myFilteredImage = new Invert(new Greyscale(myImage));
        for  (Pixel p : myFilteredImage.getPixels()) {
            System.out.println(p.toString());
        }
    }
}

2. задатак

Поставка

Посматра се део система који симулира карташку игру. Пре почетка игре, играчи могу да се пријаве делиоцу карата („крупијеу“) за учествовање у игри и могу да се одјаве након завршене игре. Игра се игра тако да у њој може учествовати различит број играча и током игре играчи не разговарају један са другим. Број играча је увек довољно мали да је са њима прихватљива синхрона комуникација. Након што се игра покрене, сваки играч од крупијеа добија по две насумичне карте (којих је само он свестан). Потом крупије открива једну од преосталих карата и сви играчи се обавештавају о карти која је откривена. На основу ових информација, сваки играч може да одлучи колико ће новца уложити. Неки играчи играју агресивно, а неки штедљиво; начин играња може да се конфигурише за сваког играча различито, при чему се предвиђа осмишљавање нових различитих начина игре, па различити играчи са истим информацијама улажу различиту количину новца. Након што сви играчи уложе свој новац, следи поново откривање нове карте око које се играчи обавештавају и након чега сваки може поново да уложи новац. Ово се понавља пет пута по игри, на крају чега играч са најбољом комбинацијом карата добија сав новац и игра се завршава. Логика одабира победника се такође може мењати од игре до игре, мада конкретна имплементација ове логике није од интереса за овај задатак.

Дорадити и исправити дату имплементацију система коришћењем одговарајућих пројектних образаца. Навести све примене пројектних образаца у систему са кратким образложењем у текстуалном фајлу z2.txt (једна до две реченице).

Решење

Сарадња

У овом задатку су искоришћени узорци Уникат, Посматрач и Стратегија.

Уникат
Учесници:
  • Уникат (Croupier)
Посматрач
Учесници:
  • КонкретанСубјекат (Croupier)
  • КонкретанПосматрач (Player)
  • С обзиром на то да се Крупије ослања на познавање Играча, звањем методе за постављање "руке", изабран је нешто запрљанији приступ овог обрасца, тј. да је ово засновано на Посматрачу.
Стратегија
Учесници:
  • Контекст (Player)
  • Стратегија (PlayingStrategy)
  • КонкретнаСтратегија (AggresiveStrategy, ScroogeStrategy)

С обзиром на то да ће реалистично играчи стратегије заснивати на основу конкретних карата, сваки пут ће стратегија анализирати све карте на столу. У реалној имплементацији система, вероватно је скупо сваки пут довлачити све карте (крупије вероватно не би био на нашем рачунару), оптималније је чувати све карте код себе.

Card.java

package z2;

public class Card {

    public enum CardSuit {DIAMOND, CLUB, HEART, SPADE};
    public static final int NUMBER_OF_CARDS = 14;
    public static final int NUMBER_OF_SUITS = 4;
    private int number;
    private CardSuit cardSuit;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public CardSuit getCardSuit() {
        return cardSuit;
    }

    public void setCardSuit(CardSuit cardSuit) {
        this.cardSuit = cardSuit;
    }

    public Card(int number, int cardSuitIndex) {
        this.number = number;
        this.cardSuit = CardSuit.values()[cardSuitIndex];
    }
}

PlayingStrategy.java

package z2;

import java.util.List;

public abstract class PlayingStrategy {
    public abstract double playAMove(Card[] myCards, List<Card> tableCards);
}

ScroogeStrategy.java

package z2;

import java.util.List;

public class ScroogeStrategy extends PlayingStrategy {
    @Override
    public double playAMove(Card[] cardsInHand, List<Card> tableCards) {
        double bettingMoney;
        int numCardsOnTable = tableCards.size();
        bettingMoney = numCardsOnTable * 0.2 + cardsInHand.length * 0.1;
        return bettingMoney;
    }
}

AggresiveStrategy.java

package z2;

import java.util.List;

public class AggresiveStrategy extends PlayingStrategy {
    @Override
    public double playAMove(Card[] cardsInHand, List<Card> tableCards) {
        double bettingMoney;
        int numCardsOnTable = tableCards.size();
        bettingMoney = numCardsOnTable * 0.5 + cardsInHand.length * 0.3;
        return bettingMoney;
    }
}

Croupier.java

package z2;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import static z2.Card.*;

public class Croupier {

    private static final int NUMBER_OF_TURNS = 5;
    private boolean isGameInProgress = false;

    private Random randomGenerator = new Random();
    private List<Player> playersInGame = new ArrayList<>();
    private List<Card> cardsOnTable = new ArrayList<>();

    private double moneyPot = 0;

    private Croupier() {}

    private volatile static Croupier instance = null;

    public static Croupier getInstance() {
        //Thread safe variant with minimal critical section
        if (instance == null) {
            synchronized (Croupier.class) {
                if (instance == null) {
                    instance = new Croupier();
                }
            }
        }
        return instance;
    }

    /* join and leave are subscription methods for concrete subject in Observer */
    public void join(Player player) {
        if(!isGameInProgress) {
            this.playersInGame.add(player);
        }
    }

    public void leave(Player player) {
        if(!isGameInProgress) {
            this.playersInGame.remove(player);
        }
    }

    public List<Card> getTable(){
        return cardsOnTable;
    }

    public void simulateGame() {
        isGameInProgress = true;
        for (Player player: playersInGame) {
            player.setCardsInHand(
                    new Card[]{
                            drawCardFromDeck(),
                            drawCardFromDeck()
                    }
            );
        }
        cardsOnTable.clear();
        moneyPot = 0;
        for(int i = 0; i < NUMBER_OF_TURNS; i++) {
            Card newCard = drawCardFromDeck();
            cardsOnTable.add(newCard);
            for(Player player: playersInGame) {
                moneyPot += player.update(newCard);
            }
        }
        Player player = defineWinner();
        player.addMoney(moneyPot);
        System.out.println(
                "Winning player with ID: " + player.getPlayer_id() +
                ", currently has: " + player.getMoney());
        isGameInProgress = false;
    }

    private Player defineWinner() {
        // TODO: Should be a few declaring winner tactics, not currently important.
        return playersInGame.get(randomGenerator.nextInt(playersInGame.size()));
    }

    private Card drawCardFromDeck() {
        return new Card(
                randomGenerator.nextInt(NUMBER_OF_CARDS),
                randomGenerator.nextInt(NUMBER_OF_SUITS)
        );
    }
}

Player.java

package z2;

import java.util.ArrayList;
import java.util.List;

public class Player {
    private Croupier croupier;
    private PlayingStrategy playingStrategy;
    public Player(Croupier c, PlayingStrategy playingStrategy) {
        croupier = c;
        this.playingStrategy = playingStrategy;
    }

    private static final double STARTING_MONEY = 10;
    private Card cardsInHand[];
    private double money = STARTING_MONEY;
    private boolean aggressive = true;
    private static int ID = 0;
    private int player_id = ID++;

    private List<Card> tableCards = new ArrayList<Card>();


    public int getPlayer_id() {
        return player_id;
    }

    public Card[] getCardsInHand() {
        return cardsInHand;
    }

    public void setCardsInHand(Card[] cardsInHand) {
        this.cardsInHand = cardsInHand;
    }

    public double getMoney() {
        return money;
    }

    public void addMoney(double money) {
        this.money += money;
    }

    public boolean isAggressive() {
        return aggressive;
    }

    public void setAggressive(boolean aggressive) {
        this.aggressive = aggressive;
    }

    public double update(Card newCard) {
        tableCards.add(newCard);
        double bettingMoney;
        bettingMoney = playingStrategy.playAMove(cardsInHand, tableCards);
        bettingMoney = bettingMoney > money ? money : bettingMoney;
        money -= bettingMoney;
        return bettingMoney;
    }

}