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
28 changes: 28 additions & 0 deletions src/12_RefactoringGolf/hole1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Hole 6 to Hole 7


## Refactorings

- Tackle domain language as result of new abstractions
- Rename constants, variables, arguments, methods to better express domain language

Le code parle du Jeu TIC TAC TOE, on ne devrait employer des termes qui ont du sens dans ce contexte là
https://en.wikipedia.org/wiki/Tic-tac-toe

Tic-tac-toe is played on a three-by-three grid by two players, who alternately place the marks X and O in one of the nine spaces in the grid.

The player who succeeds in placing three of their marks in a horizontal, vertical, or diagonal row is the winner.

## Tips

- Use a diff tool to identify the code changes you need to perform
- Check the code coverage is enough to detect any unintended behaviour changes

### While refactoring

- Stay in the green while refactoring
- Run the tests after each refactor
- Check all tests still pass
- Check code coverage has not dropped
- Commit after each refactor
- In case of persistent test fails, use `git reset` to go back to green
179 changes: 107 additions & 72 deletions src/12_RefactoringGolf/hole1/kata.ts
Original file line number Diff line number Diff line change
@@ -1,107 +1,142 @@
/* eslint-disable */

const firstRow = 0;
const secondRow = 1;
const thirdRow = 2;
const firstColumn = 0;
const secondColumn = 1;
const thirdColumn = 2;

const playerO = 'O';
const noPlayer = ' ';

export class Game {
private _lastSymbol = ' ';
private _toto: Board = new Board();

public Play(symbol: string, x: number, y: number): void {
//if first move
if (this._lastSymbol == ' ') {
//if player is X
if (symbol == 'O') {
private _lastPlayer = noPlayer;
private _board: Board = new Board();

public Play(player: string, x: number, y: number): void {
this.validateFirstMove(player);
this.validatePlayer(player);
this.validatePositionIsEmpty(x, y);

this.updateLastPlayer(player);
this.updateBoard(player, x, y);
}

private validateFirstMove(player: string) {
if (this._lastPlayer == noPlayer) {
if (player == playerO) {
throw new Error('Invalid first player');
}
}
//if not first move but player repeated
else if (symbol == this._lastSymbol) {
}

private validatePlayer(player: string) {
if (player == this._lastPlayer) {
throw new Error('Invalid next player');
}
//if not first move but play on an already played tile
else if (this._toto.TileAt(x, y).Symbol != ' ') {
}

private validatePositionIsEmpty(x: number, y: number) {
if (this._board.TileAt(x, y).isNotEmpty) {
throw new Error('Invalid position');
}
}

// update game state
this._lastSymbol = symbol;
this._toto.AddTileAt(symbol, x, y);
private updateLastPlayer(player: string) {
this._lastPlayer = player;
}

private updateBoard(player: string, x: number, y: number) {
this._board.AddTileAt(player, x, y);
}

public Winner(): string {
//if the positions in first row are taken
if (
this._toto.TileAt(0, 0)!.Symbol != ' ' &&
this._toto.TileAt(0, 1)!.Symbol != ' ' &&
this._toto.TileAt(0, 2)!.Symbol != ' '
) {
//if first row is full with same symbol
if (
this._toto.TileAt(0, 0)!.Symbol == this._toto.TileAt(0, 1)!.Symbol &&
this._toto.TileAt(0, 2)!.Symbol == this._toto.TileAt(0, 1)!.Symbol
) {
return this._toto.TileAt(0, 0)!.Symbol;
}
}
return this._board.findRowFullWithSamePlayer();
}
}

//if the positions in first row are taken
if (
this._toto.TileAt(1, 0)!.Symbol != ' ' &&
this._toto.TileAt(1, 1)!.Symbol != ' ' &&
this._toto.TileAt(1, 2)!.Symbol != ' '
) {
//if middle row is full with same symbol
if (
this._toto.TileAt(1, 0)!.Symbol == this._toto.TileAt(1, 1)!.Symbol &&
this._toto.TileAt(1, 2)!.Symbol == this._toto.TileAt(1, 1)!.Symbol
) {
return this._toto.TileAt(1, 0)!.Symbol;
}
}
class Tile {
private x: number = 0;
private y: number = 0;
private player: string = noPlayer;

//if the positions in first row are taken
if (
this._toto.TileAt(2, 0)!.Symbol != ' ' &&
this._toto.TileAt(2, 1)!.Symbol != ' ' &&
this._toto.TileAt(2, 2)!.Symbol != ' '
) {
//if middle row is full with same symbol
if (
this._toto.TileAt(2, 0)!.Symbol == this._toto.TileAt(2, 1)!.Symbol &&
this._toto.TileAt(2, 2)!.Symbol == this._toto.TileAt(2, 1)!.Symbol
) {
return this._toto.TileAt(2, 0)!.Symbol;
}
}
constructor(x: number, y: number, player: string) {
this.x = x;
this.y = y;
this.player = player;
}

return ' ';
get Player() {
return this.player;
}

get isNotEmpty() {
return this.player !== noPlayer;
}

hasSamePlayerAs(other: Tile) {
return this.player === other.player;
}

hasSameCoordinatesAs(other: Tile) {
return this.x == other.x && this.y == other.y;
}
}

interface Tile {
X: number;
Y: number;
Symbol: string;
updatePlayer(newPlayer: string) {
this.player = newPlayer;
}
}

class Board {
private _plays: Tile[] = [];

constructor() {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
const tile: Tile = { X: i, Y: j, Symbol: ' ' };
this._plays.push(tile);
for (let x = firstRow; x <= thirdRow; x++) {
for (let y = firstColumn; y <= thirdColumn; y++) {
this._plays.push(new Tile(x, y, noPlayer));
}
}
}

public TileAt(x: number, y: number): Tile {
return this._plays.find((t: Tile) => t.X == x && t.Y == y)!;
return this._plays.find((t: Tile) => t.hasSameCoordinatesAs(new Tile(x, y, noPlayer)))!;
}

public AddTileAt(player: string, x: number, y: number): void {
this._plays
.find((t: Tile) => t.hasSameCoordinatesAs(new Tile(x, y, player)))!
.updatePlayer(player);
}

public findRowFullWithSamePlayer(): string {
if (this.isRowFull(firstRow) && this.isRowFullWithSamePlayer(firstRow)) {
return this.TileAt(firstRow, firstColumn)!.Player;
}

if (this.isRowFull(secondRow) && this.isRowFullWithSamePlayer(secondRow)) {
return this.TileAt(secondRow, firstColumn)!.Player;
}

if (this.isRowFull(thirdRow) && this.isRowFullWithSamePlayer(thirdRow)) {
return this.TileAt(thirdRow, firstColumn)!.Player;
}

return noPlayer;
}

public AddTileAt(symbol: string, x: number, y: number): void {
//@ts-ignore
const tile: Tile = { X: x, Y: y, Symbol: symbol };
private isRowFull(row: number) {
return (
this.TileAt(row, firstColumn)!.isNotEmpty &&
this.TileAt(row, secondColumn)!.isNotEmpty &&
this.TileAt(row, thirdColumn)!.isNotEmpty
);
}

this._plays.find((t: Tile) => t.X == x && t.Y == y)!.Symbol = symbol;
private isRowFullWithSamePlayer(row: number) {
return (
this.TileAt(row, firstColumn)!.hasSamePlayerAs(this.TileAt(row, secondColumn)!) &&
this.TileAt(row, thirdColumn)!.hasSamePlayerAs(this.TileAt(row, secondColumn)!)
);
}
}
16 changes: 12 additions & 4 deletions src/12_RefactoringGolf/hole1/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,31 @@ describe('TicTacToe game', () => {
});

test('should not allow player O to play first', () => {
expect(() => game.Play('O', 0, 0)).toThrow();
expect(() => {
game.Play('O', 0, 0);
}).toThrow();
});

it('should not allow player x to play twice in a row', () => {
game.Play('X', 0, 0);
expect(() => game.Play('X', 1, 0)).toThrow();
expect(() => {
game.Play('X', 1, 0);
}).toThrow();
});

it('should not allow a player to play in last played position', () => {
game.Play('X', 0, 0);
expect(() => game.Play('O', 0, 0)).toThrow();
expect(() => {
game.Play('O', 0, 0);
}).toThrow();
});

it('should not allow a player to play in any played position', () => {
game.Play('X', 0, 0);
game.Play('O', 1, 0);
expect(() => game.Play('X', 0, 0)).toThrow();
expect(() => {
game.Play('X', 0, 0);
}).toThrow();
});

it('should declare player X as winner if it plays three in top row', () => {
Expand Down
22 changes: 0 additions & 22 deletions src/README.md

This file was deleted.