Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@
"rimraf": "^2.6.1",
"ts-jest": "^20.0.4",
"tslint": "^5.3.2",
"typescript": "^2.3.3"
"typescript": "2.4.1"
}
}
10 changes: 2 additions & 8 deletions src/DiceCup.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
export default class DiceCup {
private numberOfDices: number;

public constructor(dices: number) {
this.numberOfDices = dices;
}

public cast(): Array<number> {
public cast(numberOfDice: number): Array<number> {
const pips: Array<number> = [];
for (let i = 0; i < this.numberOfDices; i++) {
for (let i = 0; i < numberOfDice; i++) {
pips.push(Math.floor(Math.random() * 6) + 1);
}
return pips;
Expand Down
229 changes: 229 additions & 0 deletions src/Game.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import DiceCup from './DiceCup';
import Score from './Score';
import ScoreAnalyzer from './ScoreAnalyzer';
import Scorecard from './Scorecard';

import { Category } from './categories';

import {
ACES, TWOS, THREES, FOURS, FIVES, SIXES,
THREE_OF_A_KIND, FOUR_OF_A_KIND, FULL_HOUSE,
SMALL_STRAIGHT, LARGE_STRAIGHT, YAHTZEE, CHANCE
} from './categories';

class Game {
private _players: Game.Players;
private _running = false;
private _currentPlayer: Game.Player;
private numberOfCasts: number;
private scorecard: Scorecard[];
private playerId: number;
private lastDiceCast: Game.DiceCast;

public constructor(private diceCup: DiceCup, private scoreAnalyzer: ScoreAnalyzer) {
this._players = [];
this.playerId = 1;
this.scorecard = [];
this.lastDiceCast = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
}

public player(name: string): Game.Player {
if (this.running) {
throw new Game.GameAlreadyRunningError;
}
return this.newPlayer(name);
}

public start() {
if (this.running) {
throw new Game.GameAlreadyRunningError;
}
if (this.players.length === 0) {
throw new Game.NoPlayersAdded();
}
if (this.players.length < 2) {
throw new Game.MinimumPlayerRequirementsError();
}

this._currentPlayer = this.players.shift() as Game.Player;
this._running = true;
this.numberOfCasts = 0;
}

public cast(dice: number[] = []): Game.Result {
if (!this.running) {
throw new Game.GameNotRunningError;
}

if (this.numberOfCasts === 3) {
throw new Game.DiceCastingExceededError();
}

let numberOfDice = dice.length;
if (numberOfDice === 0) {
numberOfDice = 5;
}

let diceRecastMap: Game.DiceCast = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
dice.forEach((pip) => {
diceRecastMap[pip]++;
});

if (dice.length > 0) {
for (let recastDice in diceRecastMap) {
if ((diceRecastMap[recastDice] !== 0 && this.lastDiceCast[recastDice] === 0)
|| this.lastDiceCast[recastDice] < diceRecastMap[recastDice]) {
throw new Game.NonAvailableDiceError();
}
}
}

this.lastDiceCast = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
const diceCast = this.diceCup.cast(numberOfDice);
diceCast.forEach((pip) => {
this.lastDiceCast[pip]++;
});

const scores = this.scoreAnalyzer.scores(diceCast);
this.numberOfCasts++;

return {
dice: diceCast,
scores: scores,
};
}

public score(score: Score) {
this.scorecard[this.currentPlayer.id].add(score);

let nextPlayerId = this.currentPlayer.id + 1;
if (nextPlayerId > this._players.length) {
nextPlayerId = 1;
}

const nextPlayer = this._players.filter((player) => {
return player.id === nextPlayerId;
});

this._currentPlayer = nextPlayer.pop() as Game.Player;
}

private newPlayer(name: string): Game.Player {
this._players.filter((player) => {
if (player.name === name) {
throw new Game.PlayerNameAlreadyExistsError();
}
});
const player = { id: this.playerId, name };
this.playerId++;
this._players.push(player);
this.scorecard[player.id] = new Scorecard();
return player;
}

public get players(): Game.Player[] {
return [...this._players];
}

public get running(): boolean {
return this._running;
}

public get currentPlayer(): Game.Player {
return this._currentPlayer;
}

public get scores(): number {
return this.scorecard[this.currentPlayer.id].score;
}

public get usedCategories(): Category[] {
return this.scorecard[this.currentPlayer.id].categories.map((score) => score.category);
}

public get unusedCategories(): Category[] {
const unusedCategories: Category[] = [
ACES, TWOS, THREES, FOURS, FIVES, SIXES,
THREE_OF_A_KIND, FOUR_OF_A_KIND, FULL_HOUSE,
SMALL_STRAIGHT, LARGE_STRAIGHT, YAHTZEE, CHANCE
];

this.usedCategories.forEach((category) => unusedCategories.splice(
unusedCategories.indexOf(category), 1
));

return unusedCategories;
}
}

namespace Game {

export type Result = {
dice: number[];
scores: Score[];
};

export type Player = {
id: number;
name: string;
};

export type Players = Player[];

export type DiceCast = {
[n: number]: number;
};

export class GameAlreadyRunningError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, GameAlreadyRunningError.prototype);
}

}

export class GameNotRunningError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, GameNotRunningError.prototype);
}
}

export class DiceCastingExceededError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, DiceCastingExceededError.prototype);
}
}

export class NoPlayersAdded extends Error {
constructor() {
super();
Object.setPrototypeOf(this, NoPlayersAdded.prototype);
}
}

export class PlayerNameAlreadyExistsError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, PlayerNameAlreadyExistsError.prototype);
}
}

export class MinimumPlayerRequirementsError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, MinimumPlayerRequirementsError.prototype);
}
}

export class NonAvailableDiceError extends Error {
constructor() {
super();
Object.setPrototypeOf(this, NonAvailableDiceError.prototype);
}
}

}

export default Game;
3 changes: 3 additions & 0 deletions src/Player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default class Player {
public constructor(public readonly name: string, public readonly id: number) { }
}
16 changes: 8 additions & 8 deletions src/__tests__/DiceCup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import DiceCup from '../DiceCup';

describe('DiceCup', () => {
it('casts five numbers between 1 and 6', () => {
const numberOfDices = 5;
const cup = new DiceCup(numberOfDices);
const cast = cup.cast();
const numberOfDice = 5;
const cup = new DiceCup();
const cast = cup.cast(numberOfDice);

expect(cast).toHaveLength(numberOfDices);
expect(cast).toHaveLength(numberOfDice);
cast.forEach((pip) => {
expect(pip).toBeGreaterThanOrEqual(1);
expect(pip).toBeLessThanOrEqual(6);
});
});

it('casts exactly as many dices as are in the cup', () => {
const numberOfDices = 10;
const cup = new DiceCup(numberOfDices);
expect(cup.cast()).toHaveLength(numberOfDices);
it('casts exactly as many dice as are in the cup', () => {
const numberOfDice = 10;
const cup = new DiceCup();
expect(cup.cast(numberOfDice)).toHaveLength(numberOfDice);
});
});
Loading