diff --git a/src/12_RefactoringGolf/hole1/README.md b/src/12_RefactoringGolf/hole1/README.md new file mode 100644 index 0000000..19ac25d --- /dev/null +++ b/src/12_RefactoringGolf/hole1/README.md @@ -0,0 +1,28 @@ +# Exo 6 to Exo 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 diff --git a/src/12_RefactoringGolf/hole1/kata.ts b/src/12_RefactoringGolf/hole1/kata.ts index c750e5e..78ee1d0 100644 --- a/src/12_RefactoringGolf/hole1/kata.ts +++ b/src/12_RefactoringGolf/hole1/kata.ts @@ -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 emptyPlay = ' '; + export class Game { - private _lastSymbol = ' '; - private _toto: Board = new Board(); + private _lastSymbol = emptyPlay; + private _board: 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') { + this.validateFirstMove(symbol); + this.validatePlayer(symbol); + this.validatePositionIsEmpty(x, y); + + this.updateLastPlayer(symbol); + this.updateBoard(symbol, x, y); + } + + private validateFirstMove(player: string) { + if (this._lastSymbol == emptyPlay) { + 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._lastSymbol) { 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._lastSymbol = 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 symbol: string = ' '; - //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, symbol: string) { + this.x = x; + this.y = y; + this.symbol = symbol; + } - return ' '; + get Symbol() { + return this.symbol; + } + + get isNotEmpty() { + return this.Symbol !== emptyPlay; + } + + hasSameSymbolAs(other: Tile) { + return this.Symbol === other.Symbol; + } + + hasSameCoordinatesAs(other: Tile) { + return this.x == other.x && this.y == other.y; } -} -interface Tile { - X: number; - Y: number; - Symbol: string; + updateSymbol(newSymbol: string) { + this.symbol = newSymbol; + } } 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, emptyPlay)); } } } 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, emptyPlay)))!; } public AddTileAt(symbol: string, x: number, y: number): void { - //@ts-ignore - const tile: Tile = { X: x, Y: y, Symbol: symbol }; + this._plays + .find((t: Tile) => t.hasSameCoordinatesAs(new Tile(x, y, symbol)))! + .updateSymbol(symbol); + } + + public findRowFullWithSamePlayer(): string { + if (this.isRowFull(firstRow) && this.isRowFullWithSameSymbol(firstRow)) { + return this.TileAt(firstRow, firstColumn)!.Symbol; + } + + if (this.isRowFull(secondRow) && this.isRowFullWithSameSymbol(secondRow)) { + return this.TileAt(secondRow, firstColumn)!.Symbol; + } + + if (this.isRowFull(thirdRow) && this.isRowFullWithSameSymbol(thirdRow)) { + return this.TileAt(thirdRow, firstColumn)!.Symbol; + } + + return emptyPlay; + } + + 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 isRowFullWithSameSymbol(row: number) { + return ( + this.TileAt(row, firstColumn)!.hasSameSymbolAs(this.TileAt(row, secondColumn)!) && + this.TileAt(row, thirdColumn)!.hasSameSymbolAs(this.TileAt(row, secondColumn)!) + ); } } diff --git a/src/12_RefactoringGolf/hole1/test.ts b/src/12_RefactoringGolf/hole1/test.ts index 44d6c2a..9aa2e2e 100644 --- a/src/12_RefactoringGolf/hole1/test.ts +++ b/src/12_RefactoringGolf/hole1/test.ts @@ -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', () => { diff --git a/src/README.md b/src/README.md deleted file mode 100644 index 4cfc9a0..0000000 --- a/src/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Hole 1 to Hole 2 - -Change the code in hole 1 to be identical to the code in hole 2; implementation and tests can change. - -## Refactorings - -- Tackle code comments, long method and large class - - Extract method - -## 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; no failing tests - - 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 compilation errors or test fails, use `git reset` to go back to green