Versions Compared

Key

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

...

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
 
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) {
        ???
   }
 
    /**
    * Checks that no (other) DiceScore object apply,
    * in case a specific score is given.
    */
    @Override
    public DiceScore getScore(Dice dice) {
        ???
    }
}
Expand
titleLF
Code Block
 
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

 

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

 

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

 


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.  

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
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) {
            count++;
         }
      }
      return count;
   }
 
   @Override
   public int getScore() {
      return score;
   }
 
   @Override
   public void setScore(int score) {
      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.
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.

...