Versions Compared

Key

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

...

Expand
titleDel 4 - Farkle-regler og FarkleRound-klassen

 

 

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 computeDiceScoresfra 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
/**
 * 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;
 
    /**
     * Initializes a new round, where dieCount number of dice is immediately rolled.
     * Note that the round 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 ? ...
    }
}

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);

 

 

 

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
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

 

 

 


Expand
titleDel 5 - Dice-konstruktør og Supplier-argument

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:

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 om 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) {
      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);
   }
}

 


Expand
titleDel 6 - JavaFX og FXML

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());
   }
}