Skip to content

Instantly share code, notes, and snippets.

@DavidIAm
Created February 18, 2016 19:22
Show Gist options
  • Save DavidIAm/e535b2795e0cf7400cf1 to your computer and use it in GitHub Desktop.
Save DavidIAm/e535b2795e0cf7400cf1 to your computer and use it in GitHub Desktop.
Yatzy Kata for your review
package testpackage;
// This enum represents the sides on a six sided die.
// A die face can only be one of these six values.
// Not five.
// Not seven.
// Eight is right out.
public enum Face {
ONE, TWO, THREE, FOUR, FIVE, SIX;
public Integer value() {
return this.ordinal() + 1;
}
}
package testpackage;
// This is a list of faces. It is very simple
// It is probably overkill but I was exploring how to create a type that looks externally less parameterized.
// This looks nicer from outside the class with less parameterizations
import java.util.ArrayList;
import java.util.List;
public class FaceList {
private List<Face> faceList = new ArrayList<>();
public Face get(Integer index) {
return faceList.get(index);
}
public void add(Face index) {
faceList.add(index);
}
public Integer size() {
return faceList.size();
}
}
package testpackage;
// This enum is the limit of the types of scoring we are doing in this game.
// If its not on this list, you can't score it that way.
public enum Scoring {
YATZY, PAIR, TWOPAIR, THREEOFAKIND, FOUROFAKIND, SMALLSTRAIGHT, LARGESTRAIGHT, FULLHOUSE;
}
package testpackage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// This represents a throw of five dice
// You must construct it with five <Face> parameters.
// Their order doesn't matter.
public class Throw {
public List<Face> faces = new ArrayList<>();
Throw(Face a, Face b, Face c, Face d, Face e) {
this.faces.add(a);
this.faces.add(b);
this.faces.add(c);
this.faces.add(d);
this.faces.add(e);
}
// This provides a map of Face => count
// for the throw represented by this object
// as it is rather important to figure out the groupings and how large they are
public Map<Face, Integer> mapCounts() {
Map<Face, Integer> faceCounts = new HashMap<>();// <Face, Integer>();
for (Face thisFace : this.faces) {
int count;
count = faceCounts.getOrDefault(thisFace, 0) + 1;
faceCounts.put(thisFace, count);
}
return faceCounts;
}
}
package testpackage;
import java.util.Map;
// The Yatzy class provides the answer method which scores the result.
public class Yatzy {
// This uses a case swi]tch to select the various elements of the throw
public static int answer(Throw dice, Scoring scoreType) {
Map<Face, Integer> faceCounts = dice.mapCounts();
switch (scoreType) {
case YATZY:
if (faceCounts.keySet().size() == 1) {
return 50;
}
break;
case PAIR:
Face maxDie;
maxDie = Face.ONE;
boolean paired;
paired = false;
// I think the line below looks as bad as perl with all the < > and such.
for (Map.Entry<Face, Integer> countEntry : faceCounts.entrySet()) {
if (countEntry.getValue() == 2) {
paired = true;
if (countEntry.getKey().value() >= maxDie.value()) {
maxDie = countEntry.getKey();
}
}
}
if (paired) {
return (maxDie.value()) * 2; // twice the plus one of the
// value? bleh.
}
break;
case TWOPAIR:
FaceList faceList = new FaceList();
for (Map.Entry<Face, Integer> countEntry : faceCounts.entrySet()) {
if (countEntry.getValue() == 2) {
faceList.add(countEntry.getKey());
}
}
if (faceList.size() == 2) {
return ((faceList.get(0).value()) * 2 + (faceList.get(1).value()) * 2);
}
break;
case THREEOFAKIND:
for (Map.Entry<Face, Integer> countEntry : faceCounts.entrySet()) {
if (countEntry.getValue() == 3) {
return ((countEntry.getKey().value()) * 3);
}
}
break;
case FOUROFAKIND:
for (Map.Entry<Face, Integer> countEntry : faceCounts.entrySet()) {
if (countEntry.getValue() == 4) {
return ((countEntry.getKey().value()) * 4);
}
}
break;
case SMALLSTRAIGHT:
if (faceCounts.containsKey(Face.TWO) && faceCounts.containsKey(Face.THREE) && faceCounts.containsKey(
Face.FOUR)) {
if (faceCounts.containsKey(Face.ONE)) {
return 15;
} else {
return 0;
}
}
break;
case LARGESTRAIGHT:
if (faceCounts.containsKey(Face.TWO) && faceCounts.containsKey(Face.THREE) && faceCounts.containsKey(
Face.FOUR)) {
if (faceCounts.containsKey(Face.SIX)) {
return 20;
} else {
return 0;
}
}
break;
case FULLHOUSE:
if (faceCounts.entrySet().size() == 2) {
for (Integer oneofthem : faceCounts.values()) {
if (oneofthem == 1 || oneofthem == 4) {
return 0; // shortcut! Must have been a 4/1 split.
}
}
// Streams are fun. It lets me map like perl.
// map-reduce is fun. sum is short for reduce(a+b)
return dice.faces.stream().mapToInt(b -> b.value()).sum();
}
break;
}
return 0;
}
}
package testpackage;
import org.junit.*;
import static org.junit.Assert.*;
public class YatzyTest {
@Test
public void yatzy_scoring_ones() {
assertEquals(50, Yatzy.answer(new Throw(Face.ONE, Face.ONE, Face.ONE, Face.ONE, Face.ONE), Scoring.YATZY));
// 1,1,1,1,1 scores 50 as yatzy
}
@Test
public void yatzy_scoring_fives() {
assertEquals(50, Yatzy.answer(new Throw(Face.FIVE, Face.FIVE, Face.FIVE, Face.FIVE, Face.FIVE), Scoring.YATZY));
// 5,5,5,5,5 scores 50 as yatzy
}
@Test
public void yatzy_scoring_four() {
assertEquals(0, Yatzy.answer(new Throw(Face.FIVE, Face.FIVE, Face.FIVE, Face.FIVE, Face.ONE), Scoring.YATZY));
// 5,5,5,5,1 scores 0 as yatzy
}
@Test
public void yatzy_scoring_full_house() {
assertEquals(0, Yatzy.answer(new Throw(Face.THREE, Face.THREE, Face.THREE, Face.FOUR, Face.FOUR),
Scoring.YATZY));
// 3,3,3,4,4 scores 8 (4+4) as yatzy
}
@Test
public void pair_scoring_two_pair() {
assertEquals(12, Yatzy.answer(new Throw(Face.ONE, Face.ONE, Face.SIX, Face.TWO, Face.SIX), Scoring.PAIR));
// 1,1,6,2,6 scores 12 (6+6) as pair
}
@Test
public void pair_scoring_three() {
assertEquals(0, Yatzy.answer(new Throw(Face.THREE, Face.THREE, Face.THREE, Face.FOUR, Face.ONE), Scoring.PAIR));
// 3,3,3,4,1 scores 0 as pair
}
@Test
public void pair_scoring_four() {
assertEquals(0, Yatzy.answer(new Throw(Face.THREE, Face.THREE, Face.THREE, Face.THREE, Face.ONE),
Scoring.PAIR));
// 3,3,3,3,1 scores 0 as pair
}
@Test
public void two_pair_scoring_two_pair() {
assertEquals(8, Yatzy.answer(new Throw(Face.ONE, Face.ONE, Face.TWO, Face.THREE, Face.THREE), Scoring.TWOPAIR));
// 1,1,2,3,3 scores 8 (1+1+3+3) as two pair
}
@Test
public void two_pair_scoring_one_pair() {
assertEquals(0, Yatzy.answer(new Throw(Face.ONE, Face.ONE, Face.TWO, Face.THREE, Face.FOUR), Scoring.TWOPAIR));
// 1,1,2,3,4 scores 0 as two pair
}
@Test
public void two_pair_scoring_full_hosue() {
assertEquals(0, Yatzy.answer(new Throw(Face.ONE, Face.ONE, Face.TWO, Face.TWO, Face.TWO), Scoring.TWOPAIR));
// 1,1,2,2,2 scores 0 as two pair
}
@Test
public void three_of_kind_scoring_three() {
assertEquals(9, Yatzy.answer(new Throw(Face.THREE, Face.THREE, Face.THREE, Face.FOUR, Face.FIVE),
Scoring.THREEOFAKIND));
// 3,3,3,4,5 scores 9 (3+3+3) as three of a kind
}
@Test
public void three_of_kind_scoring_pair() {
assertEquals(0, Yatzy.answer(new Throw(Face.THREE, Face.THREE, Face.FOUR, Face.FIVE, Face.SIX),
Scoring.THREEOFAKIND));
// 3,3,4,5,6 scores 0 as three of a kind
}
@Test
public void three_of_kind_scoring_four() {
assertEquals(0, Yatzy.answer(new Throw(Face.THREE, Face.THREE, Face.THREE, Face.THREE, Face.ONE),
Scoring.THREEOFAKIND));
// 3,3,3,3,1 scores 0 as three of a kind
}
@Test
public void four_of_kind_scoring_four_twos() {
assertEquals(8, Yatzy.answer(new Throw(Face.TWO, Face.TWO, Face.TWO, Face.TWO, Face.FIVE),
Scoring.FOUROFAKIND));
// 2,2,2,2,5 scores 8 (2+2+2+2) as four of a kind
}
@Test
public void four_of_kind_scoring_full_house() {
assertEquals(0, Yatzy.answer(new Throw(Face.TWO, Face.TWO, Face.TWO, Face.FIVE, Face.FIVE),
Scoring.FOUROFAKIND));
// 2,2,2,5,5 scores 0 as four of a kind
}
@Test
public void four_of_kind_scoring_five_twos() {
assertEquals(0, Yatzy.answer(new Throw(Face.TWO, Face.TWO, Face.TWO, Face.TWO, Face.TWO), Scoring.FOUROFAKIND));
// 2,2,2,2,2 scores 0 as four of a kind
}
@Test
public void small_straight_scoring_small() {
assertEquals(15, Yatzy.answer(new Throw(Face.ONE, Face.TWO, Face.THREE, Face.FOUR, Face.FIVE),
Scoring.SMALLSTRAIGHT));
// 1,2,3,4,5 scores 15 as small straight
}
@Test
public void large_straight_scoring_large() {
assertEquals(20, Yatzy.answer(new Throw(Face.TWO, Face.THREE, Face.FOUR, Face.FIVE, Face.SIX),
Scoring.LARGESTRAIGHT));
// 2,3,4,5,6 scores 20 as a large straight
}
@Test
public void full_house_scoring_full_house() {
assertEquals(8, Yatzy.answer(new Throw(Face.ONE, Face.ONE, Face.TWO, Face.TWO, Face.TWO), Scoring.FULLHOUSE));
// 1,1,2,2,2 scores 8 (1+1+2+2+2) as a full house
}
@Test
public void full_house_scoring_two_pair() {
assertEquals(0, Yatzy.answer(new Throw(Face.TWO, Face.TWO, Face.THREE, Face.THREE, Face.FOUR),
Scoring.FULLHOUSE));
// 2,2,3,3,4 scores 0 as a full house
}
@Test
public void full_house_scoring_yatzy() {
assertEquals(0, Yatzy.answer(new Throw(Face.FOUR, Face.FOUR, Face.FOUR, Face.FOUR, Face.FOUR),
Scoring.FULLHOUSE));
// 4,4,4,4,4 scores 0 as a full house
}
}
Copy link

ghost commented Feb 18, 2016

It won't let me do comments by line, so I'll stick them here and preface them with a file:lineNumber.

Copy link

ghost commented Feb 18, 2016

FaceList:all : If you're not putting logic endemic to the specific list of faces in here, there's no real reason to have this class. You've effectively just made a proxy for your list which has no logic.

Copy link

ghost commented Feb 18, 2016

Throw:26 : 'this' is generally only used when there is a potential ambiguity in the variable names. E.g:

class Foo() {
    int integer;
    Foo(Int integer) {
        this.integer = integer;
    }
}

Copy link

ghost commented Feb 18, 2016

Throw:26-29 : Check out the Java 8 Streaming API to get an idea of how this could be a bit more clean.

Copy link

ghost commented Feb 18, 2016

Yatzy:all : Switch statements are kind of an ugly wart of legacy Java and generally avoided.

This looks like a good opportunity to use types effectively with the power of Polymorphism.
Your Scoring class shouldn't be an enum. Consider a class hierarchy where you have an interface or abstract class (depending on how you can reuse your code) of Scoring and then the various kinds of scoring are subclasses for this. Implement the logic for scoring in those subclasses and in your Yatzy class just say:

answer(Throw dice, Scoring scoring) {
    scoring.score(dice);
}

Where you'll be pulling the score implementations out of the switch statement.

@btforsythe
Copy link

To add on to Chris's polymorphism comments, you could have something like a ThrowFactory, that looks something like this (I'm leaving out visibility modifiers, etc):

class ThrowFactory {
  Throw create(Face.... faces) {
    Throw created; 
    // = ... (your implementation -- figure out type to return based on faces)
    return created;
  }
}
interface Throw {
  Score score();
}

Then implementations of Throw for each of your combinations: Pair, TwoPair, ThreeOfAKind, etc. Guessing Pair would look something like this:

class Pair implements Throw {
  private final Face of; 
  Pair(Face of) {
    this.of = of; // 'this' necessary here
  }
  Score score() {
    return new Score(of.value * 2);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment