Weekend Coding Kata Challenge – Yatzy Refactor in Typescript

I had done the Yatzy refactoring kata (https://github.com/emilybache/Yatzy-Refactoring-Kata) a few times before. I think once it was in Kotlin, and once in Java. However, due to time constraint, I never managed to finish the kata. I think I was pair-programming with one of my work colleagues, and we only had around 90 minutes to do the kata.

I wanted to try again the other week, so I decided to re-do the kata but this time in Typescript stack. It managed to complete the whole thing, but it did took me longer than 90 minutes. Here is the final product after a single afternoon of coding

https://github.com/suryast/yatzy-ts

import assert from 'assert';
import Yatzy from '../src/Yatzy';
describe('Chance', () => {
it('scores sum of all dice', () => {
assert.strictEqual(15, new Yatzy(2, 3, 4, 5, 1).chance());
assert.strictEqual(16, new Yatzy(3, 3, 4, 5, 1).chance());
});
});
describe('Ones', () => {
it('score the sum of 1s', () => {
assert.strictEqual(1, new Yatzy(1, 2, 3, 4, 5).ones());
assert.strictEqual(2, new Yatzy(1, 2, 1, 4, 5).ones());
assert.strictEqual(0, new Yatzy(6, 2, 2, 4, 5).ones());
assert.strictEqual(4, new Yatzy(1, 2, 1, 1, 1).ones());
});
});
describe('Twos', () => {
it('score the sum of 2s', () => {
assert.strictEqual(4, new Yatzy(1, 2, 3, 2, 6).twos());
assert.strictEqual(10, new Yatzy(2, 2, 2, 2, 2).twos());
});
});
describe('Threes', () => {
it('score the sum of 3s', () => {
assert.strictEqual(6, new Yatzy(1, 2, 3, 2, 3).threes());
assert.strictEqual(12, new Yatzy(2, 3, 3, 3, 3).threes());
});
});
describe('Fours', () => {
it('score the sum of 4s', () => {
assert.strictEqual(12, new Yatzy(4, 4, 4, 5, 5).fours());
assert.strictEqual(8, new Yatzy(4, 4, 5, 5, 5).fours());
assert.strictEqual(4, new Yatzy(4, 5, 5, 5, 5).fours());
});
});
describe('Fives', () => {
it('score the sum of fives', () => {
assert.strictEqual(10, new Yatzy(4, 4, 4, 5, 5).fives());
assert.strictEqual(15, new Yatzy(4, 4, 5, 5, 5).fives());
assert.strictEqual(20, new Yatzy(4, 5, 5, 5, 5).fives());
});
});
describe('Sixes', () => {
it('score the sum of sixes', () => {
assert.strictEqual(0, new Yatzy(4, 4, 4, 5, 5).sixes());
assert.strictEqual(6, new Yatzy(4, 4, 6, 5, 5).sixes());
assert.strictEqual(18, new Yatzy(6, 5, 6, 6, 5).sixes());
});
});
describe('Yatzy', () => {
it('scores 50', () => {
assert.strictEqual(50, new Yatzy(4, 4, 4, 4, 4).yatzy());
assert.strictEqual(50, new Yatzy(6, 6, 6, 6, 6).yatzy());
assert.strictEqual(0, new Yatzy(6, 6, 6, 6, 3).yatzy());
});
});
describe('Score a pair', () => {
it('scores the sum of the highest pair', () => {
assert.strictEqual(6, new Yatzy(3, 4, 3, 5, 6).onePair());
assert.strictEqual(10, new Yatzy(5, 3, 3, 3, 5).onePair());
assert.strictEqual(12, new Yatzy(5, 3, 6, 6, 5).onePair());
});
});
describe('Two pair', () => {
it('scores the sum of the two pairs', () => {
assert.strictEqual(16, new Yatzy(3, 3, 5, 4, 5).twoPairs());
assert.strictEqual(16, new Yatzy(3, 3, 5, 5, 5).twoPairs());
});
});
describe('Three of a kind', () => {
it('scores the sum of the three of the kind', () => {
assert.strictEqual(9, new Yatzy(3, 3, 3, 4, 5).threeOfAKind());
assert.strictEqual(15, new Yatzy(5, 3, 5, 4, 5).threeOfAKind());
assert.strictEqual(9, new Yatzy(3, 3, 3, 3, 5).threeOfAKind());
});
});
describe('Four of a kind', () => {
it('scores the sum of the four of the kind', () => {
assert.strictEqual(12, new Yatzy(3, 3, 3, 3, 5).fourOfAKind());
assert.strictEqual(20, new Yatzy(5, 5, 5, 4, 5).fourOfAKind());
assert.strictEqual(12, new Yatzy(3, 3, 3, 3, 3).fourOfAKind());
});
});
describe('Small straight', () => {
it('scores 15', () => {
assert.strictEqual(15, new Yatzy(1, 2, 3, 4, 5).smallStraight());
assert.strictEqual(15, new Yatzy(2, 3, 4, 5, 1).smallStraight());
assert.strictEqual(0, new Yatzy(1, 2, 2, 4, 5).smallStraight());
});
});
describe('Large straight', () => {
it('scores 20', () => {
assert.strictEqual(20, new Yatzy(6, 2, 3, 4, 5).largeStraight());
assert.strictEqual(20, new Yatzy(2, 3, 4, 5, 6).largeStraight());
assert.strictEqual(0, new Yatzy(1, 2, 2, 4, 5).largeStraight());
});
});
describe('Full house', () => {
it('scores the sum of the full house', () => {
assert.strictEqual(18, new Yatzy(6, 2, 2, 2, 6).fullHouse());
assert.strictEqual(0, new Yatzy(2, 3, 4, 5, 6).fullHouse());
});
});
export default class Yatzy {
private readonly dice: number[];
constructor(d1: number, d2: number, d3: number, d4: number, d5: number) {
this.dice = [d1, d2, d3, d4, d5];
}
chance(): number {
return this.dice.reduce((prev, next) => prev + next);
}
ones(): number {
return this.singles(1);
}
twos(): number {
return this.singles(2);
}
threes(): number {
return this.singles(3);
}
fours(): number {
return this.singles(4);
}
fives(): number {
return this.singles(5);
}
sixes(): number {
return this.singles(6);
}
yatzy(): number {
return (new Set(this.dice).size === 1) ? 50 : 0;
}
onePair(): number {
const keysArr = this.createDieKeysArr();
const max = Math.max.apply(Math, keysArr);
return this.hasPairs() ? max * 2 : 0;
}
twoPairs(): number {
const keysArr = this.createDieKeysArr();
const sumOfTwoPairs = keysArr.map(item => item * 2)
.reduce((firstPair, secondPair) => firstPair + secondPair)
return this.hasPairs() ? sumOfTwoPairs : 0;
}
threeOfAKind(): number {
let result = this.createDiceObject();
const threeOfAKindDie = this.getDieIndex(result, 3);
return this.hasPairs() ? threeOfAKindDie * 3 : 0;
}
fourOfAKind(): number {
let result = this.createDiceObject();
const fourOfAKindDie = this.getDieIndex(result, 4);
return this.hasPairs() ? fourOfAKindDie * 4 : 0;
}
smallStraight(): number {
const sizeOfDiceSet = new Set(this.dice).size;
const min = Math.min.apply(Math, this.dice);
return (sizeOfDiceSet === 5 && min === 1)? 15:0;
}
largeStraight(): number {
const sizeOfDiceSet = new Set(this.dice).size;
const max = Math.max.apply(Math, this.dice);
return (sizeOfDiceSet === 5 && max === 6)? 20:0;
}
fullHouse(): number {
const sum = this.threeOfAKind() + this.onePair()
return this.hasPairs() ? sum : 0
}
private singles(input: number): number {
return this.dice.filter(die => die === input)
.reduce((prev, next) => prev + next, 0);
}
private hasPairs() {
return this.dice.length > new Set(this.dice).size;
}
private getDieIndex(result: {}, dieIndex: number) {
// @ts-ignore
const fourOfAKindDie: number = Object.keys(result).find((key) =>
// @ts-ignore
(result[key] >= dieIndex) ? +key! : 0
);
return fourOfAKindDie;
}
private createDiceObject() {
let diceObject = {};
this.dice.forEach(function (die) { // @ts-ignore
diceObject[die] = (diceObject[die] || 0) + 1;
});
return diceObject;
}
private createDieKeysArr() {
const result = this.createDiceObject()
// @ts-ignore
let keysArr: number[] = []
Object.keys(result).find((key) => {
// @ts-ignore
(result[key] === 2 || result[key] === 3) ? keysArr.push(+key!) : keysArr.push(null);
});
return keysArr;
}
}

Having the chance to redo this kata again allows me to discover some interesting new features of Typescript. Looking at some of what I wrote a few weeks ago, I no longer remember some of the syntaxes and why I had written things in a certain way. The names of some of the helper names make no sense :P. I guess I will revisit this kata again some other time.

Working with Mocha for doing the unit tests was not bad. Next time, I would prefer to use Jest.

Leave a comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.