Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


Expand
titleDel 1 - Dice -klassenog DiceIterator


Expand
titleInnledning til del 1

Denne delen av oppgaven omhandler Dice-klassen, som brukes til å representere (verdien av) én eller flere terninger (engelsk: dice = terninger, die = terning). En slik klasse kan være nyttig i mange typer terningspill, for å representere terninger som nettopp er slått eller de av et sett terninger som en får poeng for.

Merk at poengene ikkeer en del av Dice-klassen, de håndteres av andre klasser som beskrives senere.

Dice-klassen er vist under, ??? erstatter kodefragmenter som det spørres om i oppgaveteksten., mens ... står for kode som er utelatt fordi det ikke er viktig. Vi kreverher at terningverdiene skal representeres på en bestemt måte, som et sett tellere for hver mulig terningverdi, og har derfor oppgitt deklarasjonen av valueCounters. Det skal ikkevære nødvendig å deklarere andre felt enn dette.

Eksempel: Hvis et Dice-objekt representerer de fire verdiene 1, 2, 2 og 4, så vil tellerverdiene i valueCountersvære 1, 2, 0, 1, 0 og 0, altså 1 ener, 2 toere, ingen treere, 1 firer, ingen femmere og ingen seksere. For getDieValue-metoden sin del, vil eneren være på indeks 0, de 2 toerne vil være på indeks 1 og 2, og fireren vil være på indeks 3.

Code Block
/**
 * Represents a set of die values.
 */
public class Dice implements Iterable<Integer> {
    /**
     * Counters for each possible die value.
     * The counter at index i (0-5) is the counter for the die value i+1 (1-6).
     * I.e. the value at index 2 is the counter for die value 3.
     */
    private final int[] valueCounters;
 
    /**
     * Initializes this Dice with the values in dieValues.
     * @param dieValues sequence of die values, not counter values
     */
    public Dice(Iterator<Integer> dieValues) {
        ???
    }

    /**
     * Initializes this Dice with the values in dieValues.
     * @param dieValues sequence of die values, not counter values
     */
    public Dice(Iterable<Integer> dieValues) {
        ???
    }
 
    @Override
    public Iterator<Integer> iterator() {
        return new DiceIterator(this);
    }
 
    /**
     * @return the number of die values
     */
    public int getDieCount() {
        ???
    }
 
    /**
     * Die values are considered ordered, with the smallest die values
     * at the lowest index. The value at a specific index
     * must be computed from the counters in valueCounters.
     * @param dieNum
     * @return the value of die number dieNum
     * @throws an appropriate exception, if dieNum is out of range
     */
    public int getDieValue(int dieNum) {
        ???
    }
 
    /**
     * @param value
     * @return the number of dice with the provided value
     */
    public int getValueCount(int value) {
        ???
    }

    /**
     * @param dice
     * @return true if all die values in the Dice argument appear in this Dice
     */
    public boolean contains(Dice dice) {
        ???
    }
 
    /**
     * @param dice
     * @return true if this Dice and the one provided have exactly the same die values
     */
    public boolean isSame(Dice dice) {
        ???
    }
 
    /**
     * @param dice a Dice object
     * @return a new Dice instance with the all the die values in
     * this Dice and the Dice argument combined
     */
    public Dice add(Dice dice) {
        ???
    }
 
    /**
     * @param dice
     * @return a new Dice instance with the die values from this Dice, but
     * without those from the Dice argument
     */
    public Dice remove(Dice dice) {
        ???
    }
}


Oppgave a) - Konstruktører (8 poeng)

Skriv kode for de to konstruktørene (merk forskjellen i argumenttype), slik at valueCounters blir riktig initialisert. Du SKAL IKKE innføre andre felt. Du kan lage hjelpemetoder om du ønsker. For begge konstruktørene er argumentet en sekvens med terningverdier (altså ikke tellere) som den nyinitialiserte instansen skal representere.

Merk at det er lov å lage ekstra konstruktører for å gjøre andre metoder, f.eks. add og remove, enklere å skrive.

Expand
titleLF
Code Block
 

Vanlige feil:

 

Oppgave b) - Terningverdier (8 poeng)

Skriv metodene getDieCountgetDieValueog getValueCountiht. forklaringen og koden gitt tidligere (API-beskrivelsen).

Expand
titleLF
Code Block
 

Vanlige feil:

  • Et ekstra kommaskilletegn i toString-resultatet.
  • Ikke sjekke om score var ulik -1, før en evt. legger den til på slutten etter likhetstegnet.
  • Ideelt sett skal en sjekke syntaks og utløse hjelpsomme unntak. Men hvis en antar at syntaksen er riktig, og implisitt blir utløst unntak likevel, når en f.eks. antar at strengen inneholder et likhetstegn, så går det i praksis for det samme. Det er nesten verre å å lage så vanntett valueOf-kode at det ikke utløses unntak når syntaksen er feil.
  • Bruk av en annen type enn String[] for resultatet fra split, f.eks. List.
Oppgave c) - Flere metoder (8 poeng)

Skriv metodene containsisSameadd og remove, som alle tar et Dice-objekt som eneste argument. Merk at ingen av disse endrer på verken this-objektet eller argumentet.

Her er noen eksempler på bruken av disse metodene, hvor [t1, t2, t3, ... tn] brukes som pseudo-synaks for et Dice-objekt som representerer terningverdiene t1-tn:

[1, 2, 2, 4].contains([1, 4]) returnerer true, mens

[1, 2, 4].contains([2, 2]) returnerer false.

[1, 2, 2, 4].isSame([1, 2, 2, 4]) returnerer true, mens

[1, 2, 2, 4].isSame([1, 2, 4]) returnerer false

[1, 2].add([1, 4]) returnerer [1, 1, 2, 4]

[1, 1, 2].remove([1, 4]) returnerer [1, 2]

Merk at removei kke har samme logikk som Collectionsin removeAll-metode.

Expand
titleLF
Code Block
 

Vanlige feil:

 

Oppgave d) - DiceIterator (6 poeng)

Lag en implementasjon av Iterator<Integer>kalt DiceIterator, slik at den kan brukes slik det er vist i Dice sin iterator()-metode. Du kan anta at valueCounters-feltet i Dice er synlig i DiceIterator-klassen.

Expand
titleLF
Code Block
 

Vanlige feil:

 

Expand
titleDel 2 - Beregning av poeng med DiceScorer-grensesnittet
Expand
titleIntroduksjon til del 2

Denne deloppgaven handler om representasjon av poeng og poengberegning basert på et sett terningverdier. Felles for mange terningspill er at man kaster terninger og så finner ut hvor mange poeng en får basert på terningverdiene. Visse kombinasjoner gir mer eller mindre poeng, og noen gir ingen. I Yatzy har en mange poenggivende kombinasjoner, f.eks. ett par (to like), to par, tre og fire like, liten (1-5) og stor (2-6) straight, hus (ett par og tre like) og Yatzy (fem like), og poengene en får er stort sett summen av terningverdiene som inngår i kombinasjonen. I Farkle er det andre regler, og her begrenser vi oss til å implementere tre regler (se nedenfor).

DiceScore-klassen innføres (se kode nedenfor) for å knytte poeng til et sett poenggivende terninger. Et DiceScore-objekt har data om:

  • Dice-objektet med de poenggivende terningverdiene
  • Poengene selv

Eksempel:

Et Dice-objekt representerer terningverdiene 1, 2, 2, 2, 3, og en får 200 poeng for de tre toerne. Dette representeres av et DiceScore-objekt med:

  • et (nytt) Dice-objekt som representerer 2, 2, 2, (altså de som gir poeng) og
  • tallet 200 (poengene selv)

 

Code Block
/**
* Represents the score given to a set of die values
*/
public class DiceScore {
 
    ??? fields
 
    /**
    * Initialises a DiceScore object with the provided data
    * @param scoringDice the Dice object containing
    * only the die values contibuting to the score
    * @param score the score itself
    */
    public DiceScore(Dice scoringDice, int score) {
        ???
    }
 
    ??? methods
}

Selve beregningen av poeng, og dermed opprettelsen av DiceScore-objekter, gjøres av implementasjoner av DiceScorer-grensesnittet. DiceScorer-grensesnittet representerer altså en poengregel generelt, og det er én implementasjonsklasse for hver (type) regel. DiceScorer-grensesnittet har kun én metode, getScore, som tar inn et Dice-objekt med alle terningverdiene som skal vurderes samlet og returnerer et nytt DiceScore-objekt med poengene en får og de av terningene som ga poeng, som beskrevet over.

Code Block
/**
* Interface for scoring rules, i.e.
* logic for computing a score for a subset of dice in a Dice
*/
public interface DiceScorer {
 
    /**
    * Computes a score for (a subset of) the dice in the Dice argument.
    * The returned DiceScore object stores a Dice object
    * with those die values that contributed to the score and
    * of course the score itself.
    * @param dice
    * @return The result of applying the rule to the provided Dice, or
    * null of the rule doesn't apply.
    */
    DiceScore getScore(Dice dice);
}

Vi begrenser oss som nevnt til tre poengregler, representert ved tre DiceScorer-implementasjoner:

  • SingleValue - gir poeng for enkeltvise enere og -femmere
  • Straight- gir poeng når alle terningverdiene utgjør en serie
  • Nothing- gir poeng når en slår minst et visst antall terninger og de andre reglene ikke gir poeng!
Oppgave a) - DiceScore-klassen (6 poeng)

Skriv ferdig DiceScore-klassen, med egnede felt, konstruktørkode og tilgangsmetoder, med utgangspunkt at DiceScore-objekter ikke skal kunne endres etter at de er opprettet.

Expand
titleLF
Code Block
 

Vanlige feil/svakheter:

 

Oppgave b) - SingleValue-klassen (6 poeng)

Skriv kode for klassen SingleValue(enkeltverdi), som implementerer DiceScorermed følgende logikk:

For et Dice-argument som ikke inneholder den spesifikke verdien returneres null. Ellers returneres et (nytt) DiceScore-objekt med riktige verdier satt. 

Merk at klassen bare gir poeng for én av den angitte verdien, selv om Dice-objektet inneholder flere av denne verdien.

Eksempel:

Et SingleValue-objekt opprettet med new SingleValue(5, 50), vil gi 50 poeng for et Diceobjekt med én eller flere femmere. DiceScore-objektet som returneres skal inneholde et Dice-objekt med bare én femmer og (poeng)tallet 50.

Code Block
/**
* Implementation of DiceScorer that gives a specific score til a specific die value.
* In Farkle it is used for giving 50 to fives and 100 to ones.
*/
public class SingleValue implements DiceScorer {
 
    ???
 
    /**
    * Initializes this SingleValue object with the (die) value and the corresponding score.
    * In Farkle you will typically create two of these, with
    * new SingleValue(5, 50) and new SingleValue(1, 100)
    * @param value
    * @param score
    */
    public SingleValue(int value, int score) {
        ???
    }
 
    /**
    * Looks for at least one of the specific value and
    * if found, returns a DiceScore object with a Dice object
    * with the value contributing to the score and
    * the corresponding score.
    */
    @Override
    public DiceScore getScore(Dice dice) {
        ???
    }
}
Expand
titleLF
Code Block
 

Vanlige feil/svakheter:

 

Oppgave c) - Straight-klassen (8 poeng)

Skriv kode for klassen Straight(alle terningene utgjør en serie), som implementerer DiceScorermed følgende logikk:

For et Dice-argument uten straightreturneres null. Ellers returneres et (nytt) DiceScore-objekt med riktige verdier satt. Poengene som gis er en spesifikk verdi (konstant) som settes når Straight-objektet opprettes.

Merk at koden skal virke selv om det er (mange) færre eller flere terninger enn mulige terningverdier (1-6). Et Dice-objekt med bare én terningverdi vil f.eks. alltid gi straight, mens et Dice-objekt med flere enn 6 terningverdier aldri kan gi straight.

Code Block
/**
* Implementation of DiceScorer that gives a specific score til a so-called straight,
* which is when all values give a series of consecutive values, e.g. 1, 2, 3.
* All the die values must be used, so with six dice, the only possibility is 1, 2, 3, 4, 5, 6.
* With five dice, there are two possibilities, 1, 2, 3, 4, 5 and 2, 3, 4, 5, 6.
*/
public class Straight implements DiceScorer {
 
    ???
 
    /**
    * Initializes this Straight object with the specific score.
    * @param score
    */
    public Straight(int score) {
        ???
    }
 
    /**
    * Checks that all die values in the provided Dice form a series of consecutive values.
    * If this is the case returns a DiceScore object with a Dice with
    * the die values contributing to the score (necessarily all of them) and
    * the corresponding score.
    */
    @Override
    public DiceScore getScore(Dice dice) {
        ???
    }
}
Expand
titleLF
Code Block
 

Her er poenget å skjønne hvordan tabeller virker (opprettes, leses fra og skrives til) og logikken bak valueCounters-feltet, samt hvordan iterere med Iterator (hasNext() og next()) og Iterable (iterator()).

Code Block
public Dice(Iterator<Integer> dieValues) {
	this.valueCounters = new int[6];
	while (dieValues.hasNext()) {
		int dieValue = dieValues.next();
		if (! (dieValue >= 1 && dieValue <= 6)) {
			throw new IllegalArgumentException("A die value must be in the range 1-6");
		}
		// add one to the corresponding counter
		this.valueCounters[dieValue - 1]++;
	}
}

public Dice(Iterable<Integer> dieValues) {
	this(dieValues.iterator());
}

Vanlige feil: En del skrev kode som om de skulle lagre enkeltverdiene, ikke tellerne for hver mulig verdi. Da blir løsningen som på den ordinære eksamen, og det gir ikke uttelling.

Oppgave b) - Terningverdier (8 poeng)

Skriv metodene getDieCountgetDieValue og getValueCount iht. forklaringen og koden gitt tidligere (API-beskrivelsen).

Expand
titleLF

Når en først har tellerne i valueCounters, så er getDieCount- og getValueCount-metodene trivielle. getDieValue er litt mer kinkig, da en må beregne hvilket tall som vil falle på en bestemt indeks, uten noen liste over alle terningverdiene.

Code Block
public int getDieCount() {
	int count = 0;
	for (int counter : valueCounters) {
		count += counter;
	}
	return count;
}

public int getDieValue(int dieNum) {
	if (dieNum < 0 || dieNum >= getDieCount()) {
		throw new IllegalArgumentException(dieNum + " is out of range");
	}
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		int counter = valueCounters[dieValue - 1];
		if (dieNum < counter) {
			return dieValue;
		}
		dieNum -= counter;
	}
	// should never come here
	throw new IllegalArgumentException();
}

public int getValueCount(final int value) {
	return valueCounters[value - 1];
}


Vanlige feil: Det var en del som ikke skjønte hva metodene skulle gjøre, selv om det var de samme metodene som på ordinær eksamen. De må imidlertid skrives annerledes, siden terningene er representert på en annen måte.

 

Oppgave c) - Flere metoder (8 poeng)

Skriv metodene containsisSameadd og remove, som alle tar et Dice-objekt som eneste argument. Merk at ingen av disse endrer på verken this-objektet eller argumentet.

Her er noen eksempler på bruken av disse metodene, hvor [t1, t2, t3, ... tn] brukes som pseudo-synaks for et Dice-objekt som representerer terningverdiene t1-tn:

[1, 2, 2, 4].contains([1, 4]) returnerer true, mens

[1, 2, 4].contains([2, 2]) returnerer false.

[1, 2, 2, 4].isSame([1, 2, 2, 4]) returnerer true, mens

[1, 2, 2, 4].isSame([1, 2, 4]) returnerer false

[1, 2].add([1, 4]) returnerer [1, 1, 2, 4]

[1, 1, 2].remove([1, 4]) returnerer [1, 2]

Merk at removei kke har samme logikk som Collectionsin removeAll-metode.

Expand
titleLF

Disse metodene kan forsåvidt skrives kun vha. getDieValue-metoden og konstruktørene (som de fleste gjorde på den ordinære eksamenen), men det er mye enklere å utnytte at vi representerer terningene med tellere. Her utnytter vi også at vi kan lage tomme Dice-instanser, og så etterpå justere tellerne i valueCounters.

Code Block
// Et Dice-objekt inneholder et annet hvis det er minst like mange av hver terning-verdi
public boolean contains(final Dice dice) {
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		if (this.valueCounters[dieValue - 1] < dice.getValueCount(dieValue)) {
			return false;
		}
	}
	return true;
}

// To Dice-objekter er like hvis det ene inneholder det andre og omvendt!
// Evt. kan kan sjekke at alle valueCounters-verdiene er like
public boolean isSame(final Dice dice) {
	return contains(dice) && dice.contains(this);
}

// To Dice-objekter kan slås sammen ved å legge sammen tellerne
public Dice add(final Dice dice) {
	// Lager et tomt Dice-objekt og justerer tellerne i løkka
	Dice result = new Dice(Arrays.asList());
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		result.valueCounters[dieValue - 1] = this.valueCounters[dieValue - 1] + dice.valueCounters[dieValue - 1];
	}
	return result;
}

// En kan trekke et Dice-objekt fra et annet ved å trekke den enes tellere fra den andres.
// En må huske å unngå negative tellere.
public Dice remove(final Dice dice) {
	// Lager et tomt Dice-objekt og justerer tellerne i løkka
	Dice result = new Dice(Arrays.asList());
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		result.valueCounters[dieValue - 1] = Math.max(0, this.valueCounters[dieValue - 1] - dice.getValueCount(dieValue));
	}
	return result;
}

Vanlige feil:

 

Oppgave d) - DiceIterator (6 poeng)

Lag en implementasjon av Iterator<Integer>kalt DiceIterator, slik at den kan brukes slik det er vist i Dice sin iterator()-metode. Du kan anta at valueCounters-feltet i Dice er synlig i DiceIterator-klassen.

Expand
titleLF

Trikset her er å skjønne at terningverdiene skal komme i samme rekkefølge som det en får fra getDieValue med stigende indekser. Her blir altså logikken helt uavhengig av hvordan terningsverdiene er representert internt i Dice-klassen.

Code Block
public class DiceIterator implements Iterator<Integer> {
	private final Dice dice;
	private int dieNum = 0;

	public DiceIterator(final Dice dice) {
		this.dice = dice;
	}

	@Override
	public boolean hasNext() {
		return dieNum < dice.getDieCount();
	}

	@Override
	public Integer next() {
		final int value = dice.getDieValue(dieNum);
		dieNum++;
		return value;
	}
}

Vanlige feil:

 



Expand
titleDel 2 - Håndtering av poeng med DiceScore-klassen og DiceScorer-grensesnittet


Expand
titleIntroduksjon til del 2

Denne deloppgaven handler om representasjon av poeng og poengberegning basert på et sett terningverdier. Felles for mange terningspill er at man kaster terninger og så finner ut hvor mange poeng en får basert på terningverdiene. Visse kombinasjoner gir mer eller mindre poeng, og noen gir ingen. I Yatzy har en mange poenggivende kombinasjoner, f.eks. ett par (to like), to par, tre og fire like, liten (1-5) og stor (2-6) straight, hus (ett par og tre like) og Yatzy (fem like), og poengene en får er stort sett summen av terningverdiene som inngår i kombinasjonen. I Farkle er det andre regler, og her begrenser vi oss til å implementere tre regler (se nedenfor).

DiceScore-klassen innføres (se kode nedenfor) for å knytte poeng til et sett poenggivende terninger. Et DiceScore-objekt har data om:

  • Dice-objektet med de poenggivende terningverdiene
  • Poengene selv

Eksempel:

Et Dice-objekt representerer terningverdiene 1, 2, 2, 2, 3, og en får 200 poeng for de tre toerne. Dette representeres av et DiceScore-objekt med:

  • et (nytt) Dice-objekt som representerer 2, 2, 2, (altså de som gir poeng) og
  • tallet 200 (poengene selv)

 

Code Block
/**
* Represents the score given to a set of die values
*/
public class DiceScore {
 
    ??? fields
 
    /**
    * Initialises a DiceScore object with the provided data
    * @param scoringDice the Dice object containing
    * only the die values contibuting to the score
    * @param score the score itself
    */
    public DiceScore(Dice scoringDice, int score) {
        ???
    }
 
    ??? methods
}

Selve beregningen av poeng, og dermed opprettelsen av DiceScore-objekter, gjøres av implementasjoner av DiceScorer-grensesnittet. DiceScorer-grensesnittet representerer altså en poengregel generelt, og det er én implementasjonsklasse for hver (type) regel. DiceScorer-grensesnittet har kun én metode, getScore, som tar inn et Dice-objekt med alle terningverdiene som skal vurderes samlet og returnerer et nytt DiceScore-objekt med poengene en får og de av terningene som ga poeng, som beskrevet over.

Code Block
/**
* Interface for scoring rules, i.e.
* logic for computing a score for a subset of dice in a Dice
*/
public interface DiceScorer {
 
    /**
    * Computes a score for (a subset of) the dice in the Dice argument.
    * The returned DiceScore object stores a Dice object
    * with those die values that contributed to the score and
    * of course the score itself.
    * @param dice
    * @return The result of applying the rule to the provided Dice, or
    * null of the rule doesn't apply.
    */
    DiceScore getScore(Dice dice);
}

Vi begrenser oss som nevnt til tre poengregler, representert ved tre DiceScorer-implementasjoner:

  • SingleValue - gir poeng for enkeltvise enere og -femmere
  • Straight- gir poeng når alle terningverdiene utgjør en serie
  • Nothing- gir poeng når en slår minst et visst antall terninger og de andre reglene ikke gir poeng!
Oppgave a) - DiceScore-klassen (6 poeng)

Skriv ferdig DiceScore-klassen, med egnede felt, konstruktørkode og tilgangsmetoder, med utgangspunkt at DiceScore-objekter ikke skal kunne endres etter at de er opprettet.

Expand
titleLF

Rett frem koding av verdi-klasse.

Code Block
public class DiceScore {

	private final Dice dice;
	private final int score;

	public DiceScore(Dice dice, int score) {
		this.dice = dice;
		this.score = score;
	}

	public Dice getDice() {
		return dice;
	}

	public int getScore() {
		return score;
	}
}

Vanlige feil/svakheter:

 

Oppgave b) - SingleValue-klassen (6 poeng)

Skriv kode for klassen SingleValue(enkeltverdi), som implementerer DiceScorermed følgende logikk:

For et Dice-argument som ikke inneholder den spesifikke verdien returneres null. Ellers returneres et (nytt) DiceScore-objekt med riktige verdier satt. 

Merk at klassen bare gir poeng for én av den angitte verdien, selv om Dice-objektet inneholder flere av denne verdien.

Eksempel:

Et SingleValue-objekt opprettet med new SingleValue(5, 50), vil gi 50 poeng for et Diceobjekt med én eller flere femmere. DiceScore-objektet som returneres skal inneholde et Dice-objekt med bare én femmer og (poeng)tallet 50.

Code Block
/**
* Implementation of DiceScorer that gives a specific score til a specific die value.
* In Farkle it is used for giving 50 to fives and 100 to ones.
*/
public class SingleValue implements DiceScorer {
 
    ???
 
    /**
    * Initializes this SingleValue object with the (die) value and the corresponding score.
    * In Farkle you will typically create two of these, with
    * new SingleValue(5, 50) and new SingleValue(1, 100)
    * @param value
    * @param score
    */
    public SingleValue(int value, int score) {
        ???
    }
 
    /**
    * Looks for at least one of the specific value and
    * if found, returns a DiceScore object with a Dice object
    * with the value contributing to the score and
    * the corresponding score.
    */
    @Override
    public DiceScore getScore(Dice dice) {
        ???
    }
}


Expand
titleLF

Her skal det bare sjekkes om Dice-argumentet har minst én av den aktuelle verdien. I så fall returneres et DiceScore-objekt med et Dice-objekt med denne ene verdien og de aktuelle poengene. Det er strengt tatt galt å regne med alle terningene av den aktuelle verdien og gange opp poengene, men det er vi ikke så nøye på. Men da må selvsagt også Dice-objektet ha tilsvarende mange av den verdien.

Code Block
public class SingleValue implements DiceScorer {


	private final int score;
	private final int value;

	public SingleValue(final int value, final int score) {
		this.score = score;
		this.value = value;
	}

	@Override
	public DiceScore getScore(final Dice dice) {
		if (dice.getValueCount(value) > 0) {
			return new DiceScore(new Dice(Arrays.asList(value)), score);
		}
		return null;
	}
}

Vanlige feil/svakheter:

 

Oppgave c) - Straight-klassen (8 poeng)

Skriv kode for klassen Straight(alle terningene utgjør en serie), som implementerer DiceScorermed følgende logikk:

For et Dice-argument uten straightreturneres null. Ellers returneres et (nytt) DiceScore-objekt med riktige verdier satt. Poengene som gis er en spesifikk verdi (konstant) som settes når Straight-objektet opprettes.

Merk at koden skal virke selv om det er (mange) færre eller flere terninger enn mulige terningverdier (1-6). Et Dice-objekt med bare én terningverdi vil f.eks. alltid gi straight, mens et Dice-objekt med flere enn 6 terningverdier aldri kan gi straight.

Code Block
/**
* Implementation of DiceScorer that gives a specific score til a so-called straight,
* which is when all values give a series of consecutive values, e.g. 1, 2, 3.
* All the die values must be used, so with six dice, the only possibility is 1, 2, 3, 4, 5, 6.
* With five dice, there are two possibilities, 1, 2, 3, 4, 5 and 2, 3, 4, 5, 6.
*/
public class Straight implements DiceScorer {
 
    ???
 
    /**
    * Initializes this Straight object with the specific score.
    * @param score
    */
    public Straight(int score
Expand
titleDel 3 - Introduksjon av et Dice-grensesnitt

Det finnes flere måter å implementere Dice-klassen og alle dens metoder, med ulike fordeler og ulemper. En måte å tillate bruk av flere implementasjoner er å gjøre om Dice til et grensesnitt og så ha en eller flere implementasjonsklasser, hvor den eksisterende Dice-klassen blir en av disse:

public interface Dice {

   ...

}

public class DiceImpl1 implements Dice { ... tilsvarer løsningen i deloppgavene 1-6 ... }

public class DiceImpl2 implements Dice { ... alternativ løsning ... }

 

Navnene på implementasjonsklassene kan selvsagt være mer forklarende.

Oppgave a) - Dice-grensesnittet (4 poeng)

Dette var en flervalgsoppgave med to spørsmål og valg av ett av flere alternativer.

 

Spørsmål 1: Tre alternative grensesnitt er foreslått, basert på den nåværende Dice-klassen:

Code Block
// Alternativ 1, alle metoder og konstruktører:
public interface Dice ... {
    static Collection<Integer> randomDieValues(int dieCount);
    Dice(Collection<Integer> dieValues, int score);
    Dice(int dieCount);
    Dice(Dice dice, int score);
    static Dice valueOf(String s);
    int getScore();
    void setScore(int score);
    int getDieCount();
    int getDieValue(int dieNum);
    int getValueCount(int value);
    boolean contains(Dice dice);
    Dice add(Dice dice);
    Dice remove(Dice dice);
}
 
// Alternativ 2, alle metoder:
public interface Dice ... {
    static Collection<Integer> randomDieValues(int dieCount);
    static Dice valueOf(String s);
    int getScore();
    void setScore(int score);
    int getDieCount();
    int getDieValue(int dieNum);
    int getValueCount(int value);
    boolean contains(Dice dice);
    Dice add(Dice dice);
    Dice remove(Dice dice);
}
 
// Alternativ 3, utvalgte metoder:
public interface Dice ... {
    int getScore();
    void setScore(int score);
    int getDieCount();
    int getDieValue(int dieNum);
    int getValueCount(int value);
    boolean contains(Dice dice);
    Dice add(Dice dice);
    Dice remove(Dice dice);
}

Spørsmål 2: Den opprinnelige Dice-klassen implementerer Iterable<Integer>. Spørsmålet er hvordan dette skal håndteres ved overgangen til et Dice-grensesnitt.

  1. Dice-grensesnittet utvide (extends) Iterable<Integer>.
  2. Dice-grensesnittet både utvide (extends) Iterable<Integer> og liste opp metoden(e) fra Iterable.
  3. Dice-grensesnittet  liste opp metoden(e) fra Iterable.
  4. Dice-grensesnittet utvide (extends) Iterable<Integer> og kan liste opp metoden(e) fra Iterable.

 

Expand
titleLF

På det første spørsmålet er alternativ 3 riktig, for et grensesnitt skal ikke ha konstruktører og statiske metoder. Det er faktisk lov å ha statiske metoder i grensesnitt i Java, men det hører ikke med i objektorientert tenkning.

På det andre spørsmålet er alternativ 4 riktig, for et grensesnitt kan gjenta metoder fra et grensesnitt det utvider, men må ikke. Her ga vi (manuelt) poeng for alternativ 1.

Oppgave b) - Arv (6 poeng)

Hvis en har flere implementasjoner av det nye Dice-grensesnittet, så kan en regne med at visse deler av disse vil bli nokså eller helt like.

Ett aspekt som typisk vil bli (nokså) likt er håndtering av poengene (score). Forklar med tekst og/eller kode hvordan du vil håndtere det vha. arvingsmekanismen, slik at løsningen tillater stor grad av gjenbruk av kode i subklasser og blir ren og ryddig.  

public abstract class AbstractDice implements Dice { private int score = -1; protected AbstractDiceImpl(int score) { this.score = score; } @Override public String toString() { ... kan implementeres fordi getDieCount og getDieValue er deklarert (selv om de er abstrakte) ... }   // implementasjon som bare er avhengig av at Dice utvider Iterable public int getValueCount(int value) { int count = 0; // Iterable er implementert! for (int dieValue : this
) {
        ???
 
if
 
(dieValue
 
==
 
value)
}
 
{

    
/**
    * Checks that all die values 
count++;
in the provided Dice form a series of consecutive 
}
values.
    * 
} return count;
If this is the case returns a DiceScore object with a Dice with
   
}
 
* the die 
@Override
values contributing to 
public
the 
int
score 
getScore
(necessarily all of them) 
{
and
    * the 
return
corresponding score
;
.
   
}
 */
 
   @Override
    public 
void
DiceScore 
setScore
getScore(
int
Dice 
score
dice) {
      
if
 
(this.score >= 0) {
 ???
    
throw new IllegalStateException("Cannot set score more than once");
}
this.score = score; } }

Vanlige feil/svakheter:

  • Generell beskrivelse av arv og fraser om at arv gir gjenbruk, uten å være spesifikk på hva slags variabler og metoder som er aktuelle å ha i en superklasse i dette tilfellet.
  • Forklare hvordan en kan ha en generell superklasse for poengberegning, med subklasser for spesifikk spill som Yatzy og Farkle. Her var det spesifikt snakk om Dice, og poengberegning er ikke en del av Dice-grensesnittet eller naturlig å ha med i en Dice-klasse.
  • Ikke nevne muligheten til å implementere flere metoder, f.eks. toString, som bruker deklarerte abstrakte metoder.
Expand
titleLF

Her er poenget å skille ut det som har med score å gjøre i en abstrakt superklasse, nemlig feltet score, (den delen av) konstruktøren som tar inn og setter score og getScore og setScore. Dette er viktigst.

Hvis en lar den abstrakte superklassen implementere Dice-grensesnittet evt. deklarer en del av Dice sine metoder som abstrakte metoder, så kan enda flere metoder implementeres, f.eks. trenger toString og getValueCount bare getDieCount og getDieValue. En trenger ikke implementere disse, men forklare at det går an og vil være lurt.

Code Block
Oppgave c) - Delegering (10 poeng)

Dice sin add-metode skal lage en ny Dice-instans (altså instans av en klasse som implementerer Dice) som kombinerer terningverdier fra to andre Dice-instanser (this og argumentet). En kan tenke seg at metoden returnerer en instans av en ny Dice-implementasjon kalt DiceAdder, som bruker delegering. Den vil ha to Dice-felt og en konstruktør som tar inn to Dice-instanser:

DiceAdder(Dice dice1, Dice dice2) { ... feltene settes her ... }

Hver Dice-metode kan da bruke/delegere til disse to Dice-instansene i sin løsning, f.eks. vil getDieCount() returnere summen av getDieCount() for hver av de to Dice-instansene:

public int getDieCount() {

   return dice1.getDieCount() + dice2.getDieCount();

}

Forklar med tekst og/eller kode hvordan delegeringsteknikken vil bli brukt i følgende metoder i en slik DiceAdder-klasse:  getDieValuegetValueCountcontainsadd og remove. Kommenter spesielt hvis delegeringsteknikken ikke passer for en spesifikk metode!

Expand
titleLF

Her ønsker vi kode for de som er greie å implementere med delegering, og tekst som forklarer hvorfor det ikke er greit. Det går også an med en blanding. Vi sa ingenting i oppgaven om håndtering av score, så det dropper vi helt her.

Code Block
public class DiceAdder implements Dice {
 
   private final Dice dice1, dice2;
 
   public DiceAdder(Dice dice1, final Dice dice2) {
      super(score);
      this.dice1 = dice1;
      this.dice2 = dice2;
   }
 
   @Override
   public int getDieCount() {
      // Rett frem bruk av delegering, som vist i oppgaveteksten
      return dice1.getDieCount() + dice2.getDieCount();
   }
 
   @Override
   public int getDieValue(final int dieNum) {
      int dieCount1 = dice1.getDieCount();
      // Vi henter de første verdiene fra dice1,
      // så sjekker om indeksen er lav nok
      if (dieNum < dieCount1) {
         return dice1.getDieValue(dieNum);
      }
      // De neste verdiene hentes fra dice2, med (ned)justert indeks 
      return dice2.getDieValue(dieNum - dieCount1);
   }
 
   @Override
   public int getValueCount(final int value) {
      // Rett frem bruk av delegering, som vist i oppgaveteksten
      return dice1.getValueCount(value) + dice2.getValueCount(value);
   }
 
   @Override
   public Dice add(final Dice dice) {
      // Vi lager en instans av denne klassen,
      // den representerer jo nettopp to Dice slått sammen
      return new DiceAdder(this, dice);
   }
 
   @Override
   public boolean contains(final Dice dice) {
      // Dette er ikke så rett frem, men
      // algoritmen bruker delegering til getValueCount
      for (int dieValue = 1; dieValue <= 6; dieValue++) {
         if (dice.getValueCount(dieValue) > getValueCount(dieValue)) {
            return false;
         }
      }
      return true;
   }
 
   @Override
   public Dice remove(final Dice dice) {
      // Denne er ikke rett frem i det hele tatt, så den utelater vi...
   }
}

Vanlige feil/svakheter:

  • Implementere getDieValue ved å summere terningverdier fra de to Dice-instansene (én fra hver på den angitt indeksen). Det gir ikke mening i det hele tatt.
  • Implementere contains med dice1.contains(dice) || dice1.contains(dice) evt. med &&. Dette vil gi rett resultat i enkelttilfeller, men ikke generelt.
  • Implementere add og remove omtrent på samme måte som i del 1. Det kan virke, men er ikke delegering.
Expand
titleDel 4 - Farkle-regler og FarkleRound-klassen
}


Expand
titleLF

Her kan man velge to fremgangsmåter, enten se på sekvensen av terningverdier (det enkleste) eller sekvensen av verditellere (det mest opplagte). Den første blir enkel iterasjon, hvor man sjekker at differansen til forrige verdi er 1, hvis ikke returneres null. Det er kanskje mer opplagt å se på verditellerne, siden det er de som er eksplisitt representert, men det er i praksis mer kinkig, en må skjønne at en straight er en sammenhengende serie 1-er-tellere, med evt. 0-er-tellere før og etter. En må først spole seg over evt. 0-ere og frem til den første 1-ern og så over 1-erne og frem til en 0. Hvis antall 1-ere man har avgrenset tilsvarer antall terninger i Dice-argumentet, så har man en straight. Det er denne fremgangsmåten vi har brukt under. Vi bruker getValueCount-metoden for å hente ut tellerne, siden valueCounters-feltet ikke nødvendigvis er synlig.

Code Block
 public class Straight implements DiceScorer {

	private final int score;
	public Straight(int score) {
		this.score = score;
	}

	@Override
	public DiceScore getScore(final Dice dice) {
		int dieCount = dice.getDieCount();
		int start = 1;
		// find start of series
		while (start <= 6) {
			int count = dice.getValueCount(start);
			if (count > 1) {
				return null;
			} else if (count == 1) {
				break;
			}
			start = start + 1;
		}

		// find end of series
		int end = start + 1;
		while (end <= 6) {
			int count = dice.getValueCount(end);
			if (count > 1) {
				return null;
			} else if (count == 0) {
				break;
			}
			end = end + 1;
		}
        // check if the number of ones is wrong
		if (end - start != dieCount) {
			return null;
		}
		return new DiceScore(dice, score);
	}
}


Oppgave d) - Nothing-klassen (8 poeng)

Skriv kode for klassen Nothing, som implementerer DiceScorer med følgende logikk:

Et spesifikt antall poeng gis hvis ingen (andre) poengregler gir poeng. De andre reglene representeres av et sett DiceScore-objekter som gis inn når Nothing-objektet opprettes. Dette gjør at Nothing-klassen ikke trenger å duplisere logikk som er spesifikke for andre regler. Bestem selv typen til diceScorers-argumentet som gis inn.

Merk at denne regelen bare gjelder hvis Dice-argumentet til getScore-metoden inneholder minst et visst antall terninger, typisk like mange som brukes i spillet. Dette minsteantallet gis også inn ved opprettelse av Nothing-objektet.

Code Block
/**
* Implementation of DiceScorer that gives a specific score
* when no other DiceScore object applies.
* Requires that a certain number of dice have been thrown.
*/
public class Nothing implements DiceScorer {
 
    ??? fields
 
    /**
    * Initializes this Nothing object with the minimum required number of dice,
     * the specific score given and the other DiceScorer objects.
    * @param numDice the min. number of dice required for this rule to apply
    * @param score the specific score to give
    * @param diceScorers the (other) rules to check
    */
    public Nothing(int numDice, int score, ??? diceScorers) {
        ???
   }

 

 

/** * Represents a round of Farkle, where a player throws and keeps dice, * until s/he either "bank", i.e. save your points, or * farkle, i.e. get no points in a throw and hence looses all gathered points. * During and after a finished round, kept sets of dice and their scores are available. * During a round, the remaining dice are also available. */ public class FarkleRound { private int dieCount; private Collection<Dice> kept = new ArrayList<>(); private Dice currentDice;

 
    /**
    * Checks that 
*
no 
Initializes
(other) 
a
DiceScore 
new
object 
round
apply,
where dieCount number of dice is immediately rolled.

    * in case a specific score is given.
    */
    @Override
 
*
 
Note
 
that
 
the
public 
round
DiceScore 
may be immedielately finished, if the initial roll give no points. * @param dieCount the number of dice rolled * @param scoring the object responsible for computing the score */ public FarkleRound(int dieCount) { this.dieCount = dieCount; roll(dieCount); } private void roll(int dieCount) { this.currentDice = new Dice(Dice.randomDieValues(dieCount), -1); if (computeDiceScores(currentDice).isEmpty()) { this.kept.clear(); this.currentDice = null; } } private Collection<Dice> computeDiceScores(Dice dice) { ... implemented earlier in this exam ... } /** * @return true of the round is finised, false otherwise */ public boolean isFinished() { return this.currentDice == null; } /** * Called when the player decides to stop and secure points. * Finishes the round, by keeping all scoring Dice, as computed by the scoring object. */ public void bank() { if (isFinished()) { throw new ... which exception class ? ... } this.kept.addAll(computeDiceScores(currentDice)); ... put this object in a state that indicates this round is finished ... } /** * Called when the player decides to keep specific dice and roll the rest. * All the dice kept must be scoring ones, but not all scoring dice need to be kept. * @param dice the dice to keep, the rest of the current dice should be thrown. */ public void keepAndRoll(final Dice dice) { if (isFinished()) { throw new ... which exception class ? ... } if (! currentDice.contains(dice)) { throw new ... which exception class ? ... } final Collection<Dice> scores = computeDiceScores(dice); if (scores.stream().mapToInt(Dice::getDieCount).sum() != dice.getDieCount()) { throw new SomeKindOfException("You can only set aside dice that contribute to the score"); } kept.addAll(scores); currentDice = currentDice.remove(dice); // roll remaining dice or all, if there are none left ... what code needs to be here ? ... } }
Expand

Så langt har koden vært relativt uavhengig av typen terningspill. I denne deloppgaven handler det om en runde i Farkle-spillet. I en slik runde så akkumuleres det poeng ved at en gjentatte ganger kaster terninger, og sparer på en eller flere poenggivende terningkombinasjoner.

Reglene for hvilke kombinasjoner som gir poeng varierer noe mellom ulike varianter av Farkle, men en får typisk poeng for tre eller flere like, og for femmere (50 poeng pr. stk) og enere (100 poeng pr. stk). En tenker seg at dette beregnes av en metode tilsvarende computeDiceScores fra deloppgave 8 om totalpoengberegning.

Etter hvert kast har en tre tilfeller/muligheter:

  1. Hvis ingen terningkombinasjoner gir poeng, så er runden over og en mister alle poengene en evt. har akkumulert.
  2. En kan velge å stoppe og får poeng for terningkombinasjonene en har spart så langt og for de gjenværende som en akkurat kastet.
  3. En kan velge å legge til side en eller flere poenggivende terningkombinasjoner, og få poeng for dem, og så kaste resten. Hvis en beholder alle terningene en kastet så det ikke er noen igjen å kaste, så kan en kaste alle på nytt.

For hvert kast så får en de samme tre mulighetene. Avveiningen i spillet er altså om en skal beholde poengene en har fått så langt, eller kaste på nytt for å få flere poeng, men risikere å miste alle. 

Her er noen eksempler som illustrerer reglene:

  1. En kaster 2, 3, 3, 4 og 4. Runden stopper opp av seg selv, med 0 poeng som resultat :-(
  2. En kaster 2, 3, 4, 5 og 5. En legger til side en 5-er for 50 poeng, og kaster de fire gjenværende terningene. En får 1, 3, 5 og 6. En legger til side 1-eren og 5-eren for 150 nye poeng, altså 200 til sammen så langt, og kaster de to terningene som er igjen. En får da 4 og 4, og mister alle poengene :-(
  3. En kaster som i pkt. 3, men stopper før det siste kastet og får altså 200 poeng for runden :-) // Her skulle det stått "som i pkt. 2"
  4. En kaster som over, men er heldig og får to 5-ere for 50+50 poeng i de siste kastet og kan kaste alle på nytt. En får da 1, 1, 1, 4 og 6, sparer de tre 1-erne for 1000 poeng (spesialregel for poenggiving i Farkle) og stopper. Runden gir da 200+50+50+1000 poeng :-)

Et forslag til realisering av en klasse som håndterer en Farkle-runde, er vist under. Noen deler er imidlertid utelatt...

Code Block

Oppgave a) - final (2 poeng)

Dette var en flervalgsoppgave med ett spørsmål og to korrekte alternativer og ett galt et.

Spørsmål: Hvilke, om noen, av feltene øverst i FarkleRound-klassen kan ha modifikatoren final? 

  1. dieCount
  2. kept
  3. currentDice

 

Expand
titleLF

Her er 1 og 2 riktig og 3 er gal, siden 1 og 2 bare settes én gang (i initialiseringsdel av deklarasjon eller i konstruktør). Riktignok endres innholdet i kept-lista, men det er det samme objektet som endres hele tiden.

Oppgave b) - Kode i bank (2 poeng)

Dette er en tekstinnfyllingsoppgave som vurderes automatisk.

Spørsmål: Nederst i bank-metoden er en kodelinje erstattet med "... put this object in a state that indicates this round is finished ...". Skriv inn den korrekte kodelinjen. Koden må være korrekt, komplett og konsis, uten kommentarer, siden den sjekkes automatisk.

Expand
titleLF

Her er poenget å skjønne at isFinished-metoden i FarkleRound sjekker om currentDice er null, så koden må sette dette feltet til null.

Alternativene som ble godkjent var currentDice = null med eller uten this foran og ; bak. Mellomrom rundt = ignoreres.

 

Oppgave c) - Unntakstyper (3 poeng)

Dette er en flervalgsoppgave med tre spørsmål.

keepAndRoll-metoden utløses unntak i tre tilfeller.

Spørsmål 1: Hvilken av disse unntakstypene passer i det første tilfellet?

  1. Exception
  2. RuntimeException
  3. IllegalArgumentException
  4. IllegalStateException

Spørsmål 2: Hvilken av disse unntakstypene passer i det andre tilfellet?

  1. Exception
  2. RuntimeException
  3. IllegalArgumentException
  4. IllegalStateException

Spørsmål 3: I det tredje tilfellet bruker vi unntakstypen SomeKindOfException, som er tenkt som en egendefinert unntakstype. Hvilken unntakstype bør brukes som superklassen til SomeKindOfException?

  1. Exception
  2. RuntimeException
  3. IllegalArgumentException
  4. IllegalStateException

 

Expand
titleLF

Svaret på det første spørsmålet er IllegalStateException, fordi metoden skal ikke kalles (uansett hva argumentet er) når runden er ferdig.

Svaret på de to neste IllegalArgumentException, fordi metoden det (primært) handler om validering av argumentet.

 

Oppgave d) - Kode i keepAndRoll (2 poeng)

Nederst i keepAndRoll-metoden er én eller flere kodelinjer erstattet med "... what code needs to be here ? ...". Skrive linjen(e) som mangler. Koden vil bli lest av sensor.

Expand
titleLF

Her er poenget at vi må avslutte metoden med å kalle roll, og må finne ut om vi skal kaste de som er igjen (hvis det er noen terninger igjen) eller alle.

 

Code Block
int dieCount2 = currentDice.getDieCount();
if (dieCount2 == 0) {
   dieCount2 = this.dieCount;
}
roll(dieCount2);

// Evt. bareint dieCount2 = currentDice.getDieCount();
roll(dieCount2 == 0 ? this.dieCount : dieCount2);

Vanlige feil/mangler:

  • Bruke metoder som ikke finnes, f.eks. Dice.size()
  • Kalle keepAndRoll (rekursivt)

 

Oppgave e) - FarkleRound-objektdiagram (6 poeng)

Underveis i og etter en Farkle-runde vil tilstanden til runden være representert ved hjelp av flere objekter, inkludert et FarkleRound-objekt. Tegn objektdiagram for tilstanden etter runden som er beskrevet i det tredje eksemplet (fremhevet med fet skrift) for Farkle-reglene. 

Bruk blyantikonet til høyre på verktøylinja for tekstfeltet, for å aktivere tegneverktøyet. Det kan være lurt å kladde på forhånd, siden tegneverktøyet er litt begrenset...

 

Expand
titleLF

 

 

PlantUML Macro
object "~#fr: FarkleRound" as fr {
   dieCount = 5
}
object "~#dice1: Dice" as dice1 {
   dieValues = 5
   score = 50
}
object "~#dice2: Dice" as dice2 {
   dieValues = 1, 5
   score = 150
}
fr -> dice1 : kept
fr -> dice2 : kept
Vanlige feil/svakheter:

  • Bruke klassediagramnotasjon blandet med objektdiagramnotasjon
  • Blande inn metoder og evt. ha sekvenser av objekter som iobjekttilstandsdiagrammer
  • Bruke tekstrepresentasjonen til Dice inni FarkleRound-instansen, slik at de ikke blir med som egne objekter.
  • Ikke tegne på pilspisser eller sette navn på streker/linker
  • Tilstand som ikke tilsvarer den en får etter den angitt Farkle-runden, f.eks. currentDice ulik null, tre Dice-objekter i keep-lista, dieCount ulik 5, ...

 

 

 

 

getScore(Dice dice) {
        ???
    }
}


Expand
titleLF

Her må en skjønne hvordan Nothing skal bruke (andre) instanser av DiceScorer til å gjøre sin jobb. Her bruker vi ...-notasjonen, altså varargs som tilsvarer DiceScorer[], men det er greit å bruke andre typer som en kan iterere over, f.eks. Collection eller List.

Code Block
public class Nothing implements DiceScorer {

	private final int score;
	private final int numDice;
	private final DiceScorer[] scorers;

	public NothingScorer(final int numDice, final int score, final DiceScorer... diceScorers) {
		this.score = score;
		this.numDice = numDice;
		this.scorers = diceScorers;
	}

	@Override
	public DiceScore getScore(final Dice dice) {
		if (dice.getDieCount() >= numDice) {
			for (DiceScorer diceScorer : scorers) {
				if (diceScorer.getScore(dice) != null) {
					return null;
				}
			}
		}
		return new DiceScore(dice, this.score);
	}
}


Oppgave e) - Mer om Nothing-klassen (4 poeng)

Hva kalles implementasjonsteknikken som det legges opp til at Nothingskal bruke? Begrunn svaret, f.eks. ved å forklare (kort) hva som er karakteristisk for denne teknikken.

Expand
titleLF

Dette er delegeringsteknikken i praksis, hvor en instans av (en implementasjon av) et grensesnitt bruker en eller flere andre instanser av (implementasjoner av) samme grensesnitt, til å gjøre jobben.

Oppgave f) - DiceScorer-deklarasjon (4 poeng)

Når en lager en instans av et DiceScorer-objekt og lagrer (referansen til) det i en variabel, så kreves det at variablen er deklarert med en egnet type. Anta at en har følgende deklarasjon med initialisering:

??? aSingleValueObject = new SingleValue(1, 100);

Hvilke (tre) mulige typer er det lov å skrive der det står ???, hva er avgjørende for valget og hva vil man typisk bruke?

Expand
titleLF

Regelen er at typen på venstre side på være lik eller en supertype av typen på høyre side. Dermed kan en bruke SingleValue selv, DiceScorer-grensesnittet som den implementerer, og Object, som er superklassen til alle klasser.

Det som avgjør valget er hvilke metoder man ønsker å (kunne) bruke. Dersom man ikke trenger metodene som er spesifikke for en subtype, så velger man supertypen. Her er det naturlig å velge DiceScorer, siden SingleValue uansett ikke har andre metoder som kan være relevante å (ønske å) bruke. Hvis en bare skal bruke Object-metoder, som toString og equals, så kan en velge Object, men det er sjeldent bare Object-metodene er relevante.

Oppgave g) - Arv med AbstractScorer (6 poeng)

De tre implementasjonene av DiceScorer(SingleValueStraight og Nothing) har én ting felles. Forklar med tekst og/eller kode hvordan du vil utforme en felles superklasse (f.eks. kalt AbstractScorer) for disse tre, og hvordan de tre implementasjonene vil måtte endres for å fungere som subklasser.

Expand
titleLF

Det disse tre implementasjonene har felles er score-verdien, så denne kan legges i den abstrakte AbstractScorer-klassen, initialiseres ved å kalle super(score) øverst i subklasse-konstruktørene og leses med getScore():

Code Block
public abstract class AbstractScorer implements DiceScorer {

	private final int score;

	protected AbstractScorer(int score) {
		this.score = score;
	}

	protected int getScore() {
		return score;
	}
}
 
public class SingleValue extends AbstractScorer {

	private final int value;

	public SingleValue(int value, int score) {
		super(score);
		this.value = value;
	}
}




Expand
titleDel 3 - Diverse
Oppgave a) - Iterasjon (6 poeng)

Du ønsker å skrive ut (med System.out.print(...)) alle terningverdiene som et Dice-objekt representerer, men oppdager at den ikke har en egen toString()-metode som kan brukes. Skriv vanlig iterativ løkkekode som skriver ut alle terningverdiene til et Dice-objekt med mellomrom mellom (det gjør ikke noe om det kommer et ekstra mellomrom bakerst):

Dice dice = new Dice(...);

??? Hva skal stå her for å få skrevet ut alle terningverdiene i dice?

 

Skriv kode som har samme effekt, men som utnytter et funksjonelt grensesnitt (se ressurs med Java-API) og Java 8 sin lambda-syntaks:

dice. ??? Hva skal stå her for å få skrevet ut alle terningverdiene i dice?

 

Expand
titleLF

Siden Dice implementerer Iterable kan en bruke en vanlig foreach-løkke:

Code Block
Dice dice = new Dice(...);
for (int dieValue : dice) {
	System.out.println(dieValue + " ");
}

Iterable har også en forEach-metode som tar inn en Consumer, og da kan en gjøre det samme med en én-linjer.

Code Block
Dice dice = new Dice(...);
dice.forEach(dieValue -> { System.out.println(dieValue + " "); });


Oppgave b) - Testing av Dice-klassen (6 poeng)

Skriv en eller flere test-metoder for Dice sin contains-metode. Tenk spesielt på å teste grensetilfeller, som det kan tenkes ikke håndteres riktig! 

Expand
titleLF

 

Det kan være flere typer grensetilfeller, men her er det mest relevant å sjekke med like Dice-objekter og med tomme Dice-objekter, i tillegg til mer "vanlige" tilfeller. Her testes det bl.a. at alle Dice-objekter, inkl. et tomt Dice-objekt, inneholder et likt Dice-objekt og et tomt Dice-objekt.

Code Block
@Test
public void testContains() {
	assertFalse(new Dice(Arrays.asList(1, 2)).contains(new Dice(Arrays.asList(1, 2, 3))));
	assertTrue(new Dice(Arrays.asList(1, 2)).contains(new Dice(Arrays.asList(1, 2))));
	assertTrue(new Dice(Arrays.asList(1, 2)).contains(new Dice(Arrays.asList(1))));
	assertTrue(new Dice(Arrays.asList(1, 2)).contains(new Dice(Arrays.asList())));
 
	assertFalse(new Dice(Arrays.asList(1)).contains(new Dice(Arrays.asList(1, 2))));
	assertTrue(new Dice(Arrays.asList(1)).contains(new Dice(Arrays.asList(1))));
	assertTrue(new Dice(Arrays.asList(1)).contains(new Dice(Arrays.asList())));
	assertTrue(new Dice(Arrays.asList()).contains(new Dice(Arrays.asList())));

	assertFalse(new Dice(Arrays.asList()).contains(new Dice(Arrays.asList(1))));
}

Vanlige feil/svakheter:

 

Oppgave c) - Diagramtyper (4 poeng)

Hva er forskjellen (hensikt og innhold) mellom objektdiagrammer og objekttilstandsdiagrammer?

Expand
titleLF

Et objektdiagram viser oppbygning av og koblinger mellom objekter/instanser, og brukes for å illustrere tilstanden til (eller en mulig tilstand til) et program. Et objekttilstandsdiagram viser hvordan slike strukturer endres over tid, ved kall av metoder (typisk endringsmetoder), og brukes til å illustrere (mulig) objektoppførsel og utviklingen av tilstanden til et program.

Vanlige feil/svakheter:

 

Oppgave d) - Observerbarhet (6 poeng)

Hva er hensikten med observert-observatør-teknikken? Hva vil det si at et objekt er observerbart?

Du blir bedt om å gjøre Dice-objekter observerbare. Forklar med tekst og evt. kode hva du vil gjøre for å møte dette kravet.

Expand
titleLF

Observert-observatør-teknikken brukes til å følge med på hvordan tilstanden til objekter endres over tid, typisk for å sikre konsistens med andre objekter, f.eks. et GUI med "indre" objekter. Et objekt er observerbart dersom en kan lese ut alle relevant tilstand og en kan få beskjed (lytte på hendelser) om når og hvordan denne tilstanden endres.

Det siste spørsmålet er et lurespørsmål: Dice er allerede observerbar! Siden tilstanden ikke kan endres, så trenger en ikke å støtte lyttere. Hvis tilstanden kunne endres, så måtte man 1) definert et lyttergrensesnitt, 2) hatt metoder for å administrere (registrere og avregistrere) lyttere, 3) hatt metode(r) for å sende ut varsler om tilstandsendringer og 4) kalt disse metodene i alle metoder som faktisk endrer tilstanden.

Vanlige feil/svakheter:

 



Dice-klassen har en konstruktør som initialiserer den med tilfeldige terningverdier, og det gjør den vanskelig å teste, fordi en jo ikke vet hvilke verdier random-metoden genererer. Et alternativ er en konstruktør som henter verdier fra en Supplier<Integer>(se nedenfor). Til vanlig kan en bruke en Supplier-implementasjon som leverer tilfeldige tall vha. Math.random(), mens til testing kan en lage en som gir ut bestemte verdier:

Expand
titleDel 4 - JavaFX og FXML

Du skal lage en liten JavaFX-app for å teste DiceScorer-implementasjonene dine, altså SingleValue-, Straight- og Nothing-klassene. Følgende oppførsel skal støttes:

  • Brukeren skal kunne fylle inn et sett terningverdier i et tekstfelt (TextField). Mellomrom brukes som skilletegn.
  • For hver DiceScorer-implementasjon er det en knapp og når den trykkes, kjøres getScore-metoden til en tilsvarende DiceScorer-instans, med de angitt terningverdiene som argument.
  • Innholdet i DiceScorer-objektet som returneres, skal vises som tekst (Label). Hvis null returneres skal begge tekstene settes til en tom String.

Illustrasjonen for oppgaven viser resultatet etter at knappen tilsvarende Straight er trykket.

Image Added

Følgende FXML er skrevet for appen:

Code Block
<VBoxxmlns:fx="http://javafx.com/fxml/1"
    fx:controller="kont2018.farkle.fx.DiceScorerController">
    <HBox>
        <Labeltext="Die values: "/>
        <!--  text field for inputing the die values -->
        <TextFieldfx:id="dieValuesInput"/>
    </HBox>
    <HBox>
        <ButtononAction="#testSingleValue" text="Test SingleValue"/>
        <ButtononAction="#testStraight" text="Test Straight"/>
        <ButtononAction="#testNothing" text="Test Nothing"/>
    </HBox>
    <HBox>
        <Labeltext="Points: "/>
        <!--  label for outputing the score -->
        <Labelfx:id="scoreOutput" text=""/>
    </HBox>
    <HBox>
        <Labeltext="For values: "/>
        <!--  label for outputing the scoring die values -->
        <Labelfx:id="diceOutput" text=""/>
    </HBox>
</VBox>

Skriv en JavaFX-kontroller som implementerer ønsket oppførsel, basert på kodeskjelettet nedenfor. Den skal passe til den oppgitte FXML-en og bruke klasser og metoder beskrevet tidligere i oppgavesettet. Du kan anta at Dice har en passende toString()-metode.

Code Block
public class DiceScorerController {
 
    private DiceScorer singleValue, straight, nothing;
 
    @FXML
    public void initialize() {
        ??? initialize DiceScorer instances
    }
 
    @FXML private TextField dieValuesInput;
    @FXML private Label scoreOutput;
    @FXML private Label diceOutput;
 
    // helper method for parsing die values input
    private Dice getDiceInput() {
        Collection<Integer> dieValues = new ArrayList<>();
        for (String dieValue : dieValuesInput.getText().split(" ")) {
            ??? parse and add die value
        }
        ??? return new Dice instances
    }
 
    // helper method for 
    private void runDiceScorer(DiceScorer scorer) {
        ??? get DiceScore object by calling scorer's getScore method
        if (score != null) {
            ??? show output
        } else {
            ??? show output
        }
    }
 
    @FXML
    public void testSingleValue() {
        ??? use SingleValue implementation
    }
 
    @FXML
    public void testStraight() {
        ??? use Straight implementation
    }
 
    @FXML
    public void testNothing() {
        ??? use Nothing implementation
    }
}

Hvis det er detaljer du er usikker på, så forklar med tekstkommentarer i koden.

Expand
titleLF

Vesentlig poeng her er:

  • opprettelse av de tre typene DiceScorer i initialize-metoden, Nothing med de to andre som argument(er)
  • bruk av getDiceInput for å beregne DiceScore-objektet
  • setting av teksten til Label-objektene som brukes til feedback til brukeren
  • bruk av runDiceScorer i metodene som kalles ved trykke av knappene

 

Code Block
public class DiceScorerController {

	private DiceScorer singleValue, straight, nothing;

	@FXML
	public void initialize() {
		singleValue = new SingleValue(1, 100);
		straight = new Straight(500);
		nothing = new Nothing(5, 1000, singleValue, straight);
	}

	@FXML
	private TextField dieValuesInput;

	@FXML
	private Label scoreOutput;

	@FXML
	private Label diceOutput;

	private Dice getDiceInput() {
		Collection<Integer> dieValues = new ArrayList<>();
		for (String dieValue : dieValuesInput.getText().split(" ")) {
			dieValues.add(Integer.valueOf(dieValue));
		}
		return new Dice(dieValues);
	}

	private void runDiceScorer(final DiceScorer scorer) {
		DiceScore score = scorer.getScore(getDiceInput());
		if (score != null) {
			scoreOutput.setText(String.valueOf(score.getScore()));
			String dieValues = "";
			for (final int dieValue : score.getDice()) {
				dieValues += dieValue + " ";
			}
			diceOutput.setText(dieValues);
		} else {
			scoreOutput.setText("");
			diceOutput.setText("");
		}
	}

	@FXML
	public void testSingleValue() {
		runDiceScorer(singleValue);
	}

	@FXML
	public void testStraight() {
		runDiceScorer(straight);
	}

	@FXML
	public void testNothing() {
		runDiceScorer(nothing);
	}
}
Expand
titleDel 5 - Dice-konstruktør og Supplier-argument
Code Block
/**
 * Initializes this Dice with n die values provided by the supplier argument.
 * @param dieCount the number of dice to "throw"
 * @param supplier provides the die values
 */
public Dice(int dieCount, Supplier<Integer> supplier) { ... }

Supplierer deklarert som følger:

Code Block
public interface Supplier<T> {
    /**
     * Gets a result.
     * @return a result of the type T
     */
    T get();
}

 

Oppgave a) - Funksjonelt grensesnitt (3 poeng)

Er DiceScorer et funksjonelt grensesnitt? Forklar hvorfor/hvorfor ikke!

Expand
titleLF

DiceScorer-grensesnittet er funksjonelt fordi det 1) har kun én abstrakt metode og 2) er ment å være primærtfunksjonen til klassen som implementerer den.

Oppgave b) - Alternativ Dice-konstruktør (3 poeng)

Implementer den alternative Dice-konstruktøren.

 

Expand
titleLF

Her er koden nokså lik den i randomDieValues, vi bare bytter ut uttrykket med Math.random med supplier.get(). Her bryr vi oss heller ikke om poeng (score).

Code Block
public Dice(int dieCount, Supplier<Integer> supplier) { 
   this.dieValues = new ArrayList<>(dieCount);
   while (this.dieValues.size() < dieCount) { // evt. for (int i = 0; i < dieCount; i++) {
      this.dieValues.add(supplier.get());
   }
}
 
Oppgave c) - Testing av Dice-klassen (5 poeng)

Skriv en eller flere test-metoder for Dice sin valueOf-metode. Du kan anta det finnes en metode kalt assertDieValuessom sjekker terningverdiene til en Dice:

assertDieValues(dice, 1, 2, 3) // utløser assert-unntak hvis dice ikke har terningverdiene 1, 2, 3 og bare disse.

Expand
titleLF

Her bør det testes med og uten = score og i hvertfall for én feil syntaks.

Code Block
@Test
public void testValueOf() {
   Dice dice1 = Dice.valueOf("[1, 1, 2]");
   assertDieValues(dice1, 1, 1, 2);
   Assert.assertEquals(-1, dice1.getScore());

   Dice dice2 = Dice.valueOf("[1, 1, 2] = 200");
   assertDieValues(dice2, 1, 1, 2);
   Assert.assertEquals(200, dice2.getScore());

   try {
      Dice.valueOf("1, 5, 2 = 200");
      Assert.fail();
   } catch (final Exception e) {
      Assert.assertTrue(e instanceof IllegalArgumentException);
   }
   try {
      Dice.valueOf("1, x, 2 = 200");
      Assert.fail();
   } catch (final Exception e) {
      Assert.assertTrue(e instanceof IllegalArgumentException);
   }
   try {
      Dice.valueOf("1, 5, 2 = x");
      Assert.fail();
   } catch (final Exception e) {
      Assert.assertTrue(e instanceof IllegalArgumentException);
   }
}

Vanlige feil/mangler:

  • Ikke teste valueOf i det hele tatt, men andre Dice-metoder!
  • Bare teste én av syntaksvariantene (med eller uten score-del)
  • Ikke teste ett eller flere tilfeller med feil syntaks.
  • Ikke sjekke både terningverdiene og poengverdien til Dice-resultatet.
  • Bruke fail() etter assertDieValues for å markere at den skal utløses unntak (altså at det er feil hvis det ikke utløses unntak).

 

Expand
titleDel 6 - JavaFX og FXML (6 poeng)

Du skal lage en liten JavaFX-app for terningspill basert på Dice-klassen. I første omgang skal du støtte terningkast og poengberegning:

  • Brukeren skal kunne fylle inn et tall ni et tekstfelt (TextField)
  • Når en knapp (Button) trykkes, så skal det lages et Dice-objekt med ntilfeldige tall.
  • Poengverdien til Dice-objektet settes vha. metoden void computeFarkleScore(Dice), som du kan anta finnes.
  • Den tekstlige representasjonen til Dice-objektet skal så vises som en tekst (Label)

 

Følgende FXML er skrevet for appen:

Code Block
<HBoxxmlns:fx="http://javafx.com/fxml/1" fx:controller="ord2018.farkle.fx.DiceController">
   <!--  text field for inputing the die count -->
   <TextField fx:id="dieCountInput" promptText="Die count"/>
   <!--  button for throwing the dice, i.e. create a Dice object and computing the score -->
   <Button onAction="#handleThrowDice" text="Throw dice"/>
   <!--  label for outputing the textual representation of the Dice object -->
   <Label fx:id="diceOutput" text="No dice thrown, yet"/>
</HBox>

Skriv en JavaFX-kontroller som implementerer ønsket oppførsel. Den skal passe til den oppgitte FXML-en og bruke klasser og metoder beskrevet tidligere i oppgavesettet.

Hvis det er detaljer du er usikker på, så forklar med tekstkommentarer i koden.

Expand
titleLF

Her er vi mest opptatt av:

  • @FXML-annotasjonene
  • riktig type og navn for variablene og metoden
  • at en henter input fra dieCountInput og setter output med diceOutput

 

Code Block
public class DiceController {
 
   @FXML
   private TextField dieCountInput;
 
   @FXML
   private Label diceOutput;
 
   @FXML
   public void handleThrowDice() {
      Dice dice = new Dice(Integer.valueOf(dieCountInput.getText()));
      computeFarkleScore(dice); // var jo oppgitt
      diceOutput.setText(dice.toString());
   }
}