diff --git a/.prettierrc b/.prettierrc index 222861c..88f4ae1 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { + "printWidth": 120, "tabWidth": 2, "useTabs": false } diff --git a/eslint.config.js b/eslint.config.js index aa267e0..0f337f7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -18,10 +18,7 @@ export default tsEslint.config( { rules: { "@typescript-eslint/consistent-type-definitions": ["error", "type"], - "@typescript-eslint/restrict-template-expressions": [ - "error", - { allowNumber: true, allowNever: true }, - ], + "@typescript-eslint/restrict-template-expressions": ["error", { allowNumber: true, allowNever: true }], }, }, { diff --git a/src/lib/board.ts b/src/lib/board.ts index 9077599..eff0082 100644 --- a/src/lib/board.ts +++ b/src/lib/board.ts @@ -36,9 +36,7 @@ export class Board { } static createEmpty(rows: number, columns: number) { - const row = [...(new Array(columns) as unknown[])].map( - () => SquareState.Unoccupied, - ); + const row = [...(new Array(columns) as unknown[])].map(() => SquareState.Unoccupied); const boardState = [...(new Array(rows) as unknown[])].map(() => row); return new Board(boardState); } @@ -75,9 +73,7 @@ export class Board { case SquareState.X: return "X"; default: - throw new Error( - `Unsupported square state: ${unreachable(squareState)}`, - ); + throw new Error(`Unsupported square state: ${unreachable(squareState)}`); } }) .join(""), diff --git a/src/lib/datatypes.spec.ts b/src/lib/datatypes.spec.ts index 5f5e28b..f2c70c5 100644 --- a/src/lib/datatypes.spec.ts +++ b/src/lib/datatypes.spec.ts @@ -31,12 +31,9 @@ void t.test("getExpectedOutcomeByCurrentOutcome", async (t) => { movesLeft: 0, }); - t.throws( - () => getExpectedOutcomeByCurrentOutcome(CurrentOutcome.Undecided as any), - { - message: "Unsupported current outcome: 201", - }, - ); + t.throws(() => getExpectedOutcomeByCurrentOutcome(CurrentOutcome.Undecided as any), { + message: "Unsupported current outcome: 201", + }); t.throws(() => getExpectedOutcomeByCurrentOutcome("test" as any), { message: "Unsupported current outcome: test", diff --git a/src/lib/opponent.spec.ts b/src/lib/opponent.spec.ts index 22a8a7a..4fd5ee2 100644 --- a/src/lib/opponent.spec.ts +++ b/src/lib/opponent.spec.ts @@ -35,12 +35,7 @@ void t.test("createOpponent", async (t) => { expectedNextBoardSerialized: string, ) => { t.equal( - opponent - .getNextMove( - Board.fromSerialized(currentBoardSerialized), - currentPlayer, - ) - .serialize(), + opponent.getNextMove(Board.fromSerialized(currentBoardSerialized), currentPlayer).serialize(), expectedNextBoardSerialized, ); }; @@ -48,20 +43,16 @@ void t.test("createOpponent", async (t) => { checkNextMove("X_|__|__", Player.O, "X_|__|_O"); checkNextMove("X_|__|__", Player.X, "XX|__|__"); - t.throws( - () => opponent.getNextMove(Board.fromSerialized("XO|XO|XO"), Player.X), - { message: "There are no possible moves left: XO|XO|XO" }, - ); - t.throws( - () => opponent.getNextMove(Board.fromSerialized("XX|XX|XX"), Player.X), - { message: "Board is not solved: XX|XX|XX" }, - ); - t.throws( - () => opponent.getNextMove(Board.fromSerialized("XX|XX|X_"), Player.X), - { message: "Next board is not solved: XX|XX|XX" }, - ); - t.throws( - () => opponent.getNextMove(Board.fromSerialized("OO|OO|__"), Player.O), - { message: "Cannot find appropriate next move: OO|OO|__" }, - ); + t.throws(() => opponent.getNextMove(Board.fromSerialized("XO|XO|XO"), Player.X), { + message: "There are no possible moves left: XO|XO|XO", + }); + t.throws(() => opponent.getNextMove(Board.fromSerialized("XX|XX|XX"), Player.X), { + message: "Board is not solved: XX|XX|XX", + }); + t.throws(() => opponent.getNextMove(Board.fromSerialized("XX|XX|X_"), Player.X), { + message: "Next board is not solved: XX|XX|XX", + }); + t.throws(() => opponent.getNextMove(Board.fromSerialized("OO|OO|__"), Player.O), { + message: "Cannot find appropriate next move: OO|OO|__", + }); }); diff --git a/src/lib/opponent.ts b/src/lib/opponent.ts index a0c644d..e278636 100644 --- a/src/lib/opponent.ts +++ b/src/lib/opponent.ts @@ -1,14 +1,6 @@ -import { - BoardType, - ExpectedOutcome, - Player, - SquareState, - getOccupiedStateByPlayer, -} from "./datatypes.ts"; +import { BoardType, ExpectedOutcome, Player, SquareState, getOccupiedStateByPlayer } from "./datatypes.ts"; -export const createOpponent = ( - outcomesByBoard: Map, -) => { +export const createOpponent = (outcomesByBoard: Map) => { const getNextMove = (board: BoardType, currentPlayer: Player) => { const currentExpectedOutcome = outcomesByBoard.get(board.serialize()); if (!currentExpectedOutcome) { @@ -24,20 +16,14 @@ export const createOpponent = ( for (let column = 0; board.hasSquare(row, column); column++) { if (board.get(row, column) === SquareState.Unoccupied) { const nextBoard = board.with(row, column, occupiedState); - const nextExpectedOutcome = outcomesByBoard.get( - nextBoard.serialize(), - ); + const nextExpectedOutcome = outcomesByBoard.get(nextBoard.serialize()); if (!nextExpectedOutcome) { - throw new Error( - `Next board is not solved: ${nextBoard.serialize()}`, - ); + throw new Error(`Next board is not solved: ${nextBoard.serialize()}`); } if ( - nextExpectedOutcome.finalOutcome === - currentExpectedOutcome.finalOutcome && - nextExpectedOutcome.movesLeft === - currentExpectedOutcome.movesLeft - 1 + nextExpectedOutcome.finalOutcome === currentExpectedOutcome.finalOutcome && + nextExpectedOutcome.movesLeft === currentExpectedOutcome.movesLeft - 1 ) { return nextBoard; } diff --git a/src/lib/solver.spec.ts b/src/lib/solver.spec.ts index 234b94c..87a6a83 100644 --- a/src/lib/solver.spec.ts +++ b/src/lib/solver.spec.ts @@ -60,35 +60,23 @@ void t.test("getPreferredNextOutcome", async (t) => { movesLeft: 140, }); - t.matchOnlyStrict( - getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.X), - { - finalOutcome: FinalOutcome.WinX, - movesLeft: 60, - }, - ); - t.matchOnlyStrict( - getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.O), - { - finalOutcome: FinalOutcome.Draw, - movesLeft: 150, - }, - ); - - t.matchOnlyStrict( - getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.X), - { - finalOutcome: FinalOutcome.Draw, - movesLeft: 150, - }, - ); - t.matchOnlyStrict( - getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.O), - { - finalOutcome: FinalOutcome.Draw, - movesLeft: 150, - }, - ); + t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.X), { + finalOutcome: FinalOutcome.WinX, + movesLeft: 60, + }); + t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.O), { + finalOutcome: FinalOutcome.Draw, + movesLeft: 150, + }); + + t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.X), { + finalOutcome: FinalOutcome.Draw, + movesLeft: 150, + }); + t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.O), { + finalOutcome: FinalOutcome.Draw, + movesLeft: 150, + }); t.throws(() => getPreferredNextOutcome([], Player.X), { message: "Could not find the best outcome out of 0 possible next outcomes", @@ -101,10 +89,7 @@ void t.test("computeAllSolutions", async (t) => { columns: number, expectedSolutions: Record, ) => { - t.matchOnlyStrict( - Object.fromEntries(computeAllSolutions(rows, columns, rules).entries()), - expectedSolutions, - ); + t.matchOnlyStrict(Object.fromEntries(computeAllSolutions(rows, columns, rules).entries()), expectedSolutions); }; const checkSolutionsIncomplete = ( @@ -115,10 +100,7 @@ void t.test("computeAllSolutions", async (t) => { ) => { const allSolutions = computeAllSolutions(rows, columns, rules); t.equal(allSolutions.size, expectedSolutionsCount); - t.matchStrict( - Object.fromEntries(allSolutions.entries()), - expectedSolutionsIncomplete, - ); + t.matchStrict(Object.fromEntries(allSolutions.entries()), expectedSolutionsIncomplete); }; checkSolutionsComplete(0, 0, { diff --git a/src/lib/solver.ts b/src/lib/solver.ts index c404e57..d1f972c 100644 --- a/src/lib/solver.ts +++ b/src/lib/solver.ts @@ -24,10 +24,7 @@ export const getPreferredNextOutcome = ( for (const nextOutcome of possibleNextOutcomes) { if (nextOutcome.finalOutcome === desiredFinalOutcome) { // If there are multiple winning options, give preference to the one that wins in less moves - if ( - !bestDesiredNextOutcome || - bestDesiredNextOutcome.movesLeft > nextOutcome.movesLeft - ) { + if (!bestDesiredNextOutcome || bestDesiredNextOutcome.movesLeft > nextOutcome.movesLeft) { bestDesiredNextOutcome = nextOutcome; } } @@ -39,21 +36,14 @@ export const getPreferredNextOutcome = ( } { - const leastDesiredFinalOutcome = - getUndesiredFinalOutcomeByPlayer(currentPlayer); - for (const undesiredFinalOutcome of [ - FinalOutcome.Draw, - leastDesiredFinalOutcome, - ]) { + const leastDesiredFinalOutcome = getUndesiredFinalOutcomeByPlayer(currentPlayer); + for (const undesiredFinalOutcome of [FinalOutcome.Draw, leastDesiredFinalOutcome]) { let leastBadUndesiredNextOutcome: ExpectedOutcome | null = null; for (const nextOutcome of possibleNextOutcomes) { if (nextOutcome.finalOutcome === undesiredFinalOutcome) { // If there are multiple draw options, or multiple losing options, // give preference to the one that protracts for most moves - if ( - !leastBadUndesiredNextOutcome || - leastBadUndesiredNextOutcome.movesLeft < nextOutcome.movesLeft - ) { + if (!leastBadUndesiredNextOutcome || leastBadUndesiredNextOutcome.movesLeft < nextOutcome.movesLeft) { leastBadUndesiredNextOutcome = nextOutcome; } } @@ -65,22 +55,13 @@ export const getPreferredNextOutcome = ( } } - throw new Error( - `Could not find the best outcome out of ${possibleNextOutcomes.length} possible next outcomes`, - ); + throw new Error(`Could not find the best outcome out of ${possibleNextOutcomes.length} possible next outcomes`); }; -export const computeAllSolutions = ( - rows: number, - columns: number, - rules: GameRules, -) => { +export const computeAllSolutions = (rows: number, columns: number, rules: GameRules) => { const expectedOutcomesByBoard = new Map(); - const getExpectedOutcomeForBoard = ( - board: BoardType, - currentPlayer: Player, - ): ExpectedOutcome => { + const getExpectedOutcomeForBoard = (board: BoardType, currentPlayer: Player): ExpectedOutcome => { // assuming that currentPlayer is always the same for the same board // (which is true, because for correct board states, currentPlayer is determined by // parity of the number of occupied cells) @@ -98,8 +79,7 @@ export const computeAllSolutions = ( { const currentOutcome = rules.getBoardOutcome(board); if (currentOutcome !== CurrentOutcome.Undecided) { - const expectedOutcome = - getExpectedOutcomeByCurrentOutcome(currentOutcome); + const expectedOutcome = getExpectedOutcomeByCurrentOutcome(currentOutcome); expectedOutcomesByBoard.set(key, expectedOutcome); return expectedOutcome; } @@ -114,20 +94,14 @@ export const computeAllSolutions = ( if (board.get(row, column) === SquareState.Unoccupied) { // Recursion is not a problem here, because every time the number of occupied squares increases, // therefore the depth of recursion is limited by the number of originally free squares - const nextOutcome = getExpectedOutcomeForBoard( - board.with(row, column, occupiedState), - nextPlayer, - ); + const nextOutcome = getExpectedOutcomeForBoard(board.with(row, column, occupiedState), nextPlayer); possibleNextOutcomes.push(nextOutcome); } } } } - const preferredNextOutcome = getPreferredNextOutcome( - possibleNextOutcomes, - currentPlayer, - ); + const preferredNextOutcome = getPreferredNextOutcome(possibleNextOutcomes, currentPlayer); const expectedOutcome: ExpectedOutcome = { finalOutcome: preferredNextOutcome.finalOutcome, movesLeft: preferredNextOutcome.movesLeft + 1, diff --git a/src/lib/tictactoe-rules.spec.ts b/src/lib/tictactoe-rules.spec.ts index 7e6f15a..5b092cf 100644 --- a/src/lib/tictactoe-rules.spec.ts +++ b/src/lib/tictactoe-rules.spec.ts @@ -16,24 +16,15 @@ void t.test("getSequenceOutcome", async (t) => { }); void t.test("all sequences of length 2", async (t) => { - t.equal( - getSequenceOutcome([SquareState.Unoccupied, SquareState.Unoccupied]), - null, - ); + t.equal(getSequenceOutcome([SquareState.Unoccupied, SquareState.Unoccupied]), null); t.equal(getSequenceOutcome([SquareState.Unoccupied, SquareState.X]), null); t.equal(getSequenceOutcome([SquareState.Unoccupied, SquareState.O]), null); t.equal(getSequenceOutcome([SquareState.X, SquareState.Unoccupied]), null); - t.equal( - getSequenceOutcome([SquareState.X, SquareState.X]), - CurrentOutcome.WinX, - ); + t.equal(getSequenceOutcome([SquareState.X, SquareState.X]), CurrentOutcome.WinX); t.equal(getSequenceOutcome([SquareState.X, SquareState.O]), null); t.equal(getSequenceOutcome([SquareState.O, SquareState.Unoccupied]), null); t.equal(getSequenceOutcome([SquareState.O, SquareState.X]), null); - t.equal( - getSequenceOutcome([SquareState.O, SquareState.O]), - CurrentOutcome.WinO, - ); + t.equal(getSequenceOutcome([SquareState.O, SquareState.O]), CurrentOutcome.WinO); }); void t.test("sequences of length 7", async (t) => { @@ -135,211 +126,69 @@ void t.test("getSequenceOutcome", async (t) => { void t.test("getBoardOutcome", async (t) => { void t.test("1x1 boards", async (t) => { - t.equal( - getBoardOutcome(Board.fromSerialized("_")), - CurrentOutcome.Undecided, - ); + t.equal(getBoardOutcome(Board.fromSerialized("_")), CurrentOutcome.Undecided); t.equal(getBoardOutcome(Board.fromSerialized("X")), CurrentOutcome.Draw); t.equal(getBoardOutcome(Board.fromSerialized("O")), CurrentOutcome.Draw); }); void t.test("2x2 boards", async (t) => { - t.equal( - getBoardOutcome(Board.fromSerialized("__|__")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XX|X_")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XX|XX")), - CurrentOutcome.Draw, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("OO|OO")), - CurrentOutcome.Draw, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XO|OX")), - CurrentOutcome.Draw, - ); + t.equal(getBoardOutcome(Board.fromSerialized("__|__")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("XX|X_")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("XX|XX")), CurrentOutcome.Draw); + t.equal(getBoardOutcome(Board.fromSerialized("OO|OO")), CurrentOutcome.Draw); + t.equal(getBoardOutcome(Board.fromSerialized("XO|OX")), CurrentOutcome.Draw); }); void t.test("3x3 boards", async (t) => { - t.equal( - getBoardOutcome(Board.fromSerialized("___|___|___")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("___|_X_|___")), - CurrentOutcome.Undecided, - ); - - t.equal( - getBoardOutcome(Board.fromSerialized("XX_|___|___")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("___|___|_XX")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("X__|X__|___")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("___|__X|__X")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("X__|_X_|___")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("___|_X_|__X")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("__X|_X_|___")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("___|_X_|X__")), - CurrentOutcome.Undecided, - ); - - t.equal( - getBoardOutcome(Board.fromSerialized("XXX|O_O|O_O")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("O_O|O_O|XXX")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XOO|X__|XOO")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("OOX|__X|OOX")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XOO|OXO|OOX")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("OOX|OXO|XOO")), - CurrentOutcome.WinX, - ); - - t.equal( - getBoardOutcome(Board.fromSerialized("XXO|XOX|OXX")), - CurrentOutcome.WinO, - ); - - t.equal( - getBoardOutcome(Board.fromSerialized("XOX|XXO|OXO")), - CurrentOutcome.Draw, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XOX|XXO|OX_")), - CurrentOutcome.Undecided, - ); + t.equal(getBoardOutcome(Board.fromSerialized("___|___|___")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("___|_X_|___")), CurrentOutcome.Undecided); + + t.equal(getBoardOutcome(Board.fromSerialized("XX_|___|___")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("___|___|_XX")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("X__|X__|___")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("___|__X|__X")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("X__|_X_|___")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("___|_X_|__X")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("__X|_X_|___")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("___|_X_|X__")), CurrentOutcome.Undecided); + + t.equal(getBoardOutcome(Board.fromSerialized("XXX|O_O|O_O")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("O_O|O_O|XXX")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("XOO|X__|XOO")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("OOX|__X|OOX")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("XOO|OXO|OOX")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("OOX|OXO|XOO")), CurrentOutcome.WinX); + + t.equal(getBoardOutcome(Board.fromSerialized("XXO|XOX|OXX")), CurrentOutcome.WinO); + + t.equal(getBoardOutcome(Board.fromSerialized("XOX|XXO|OXO")), CurrentOutcome.Draw); + t.equal(getBoardOutcome(Board.fromSerialized("XOX|XXO|OX_")), CurrentOutcome.Undecided); }); void t.test("5x5 boards", async (t) => { - t.equal( - getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____")), - CurrentOutcome.Undecided, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("XXX__|_____|_____|_____|_____")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("X____|X____|X____|_____|_____")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("X____|_X___|__X__|_____|_____")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("__X__|_X___|X____|_____|_____")), - CurrentOutcome.WinX, - ); - - t.equal( - getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|__XXX")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("_____|_____|____X|____X|____X")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("_____|_____|__X__|___X_|____X")), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome(Board.fromSerialized("_____|_____|____X|___X_|__X__")), - CurrentOutcome.WinX, - ); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____")), CurrentOutcome.Undecided); + t.equal(getBoardOutcome(Board.fromSerialized("XXX__|_____|_____|_____|_____")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("X____|X____|X____|_____|_____")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("X____|_X___|__X__|_____|_____")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("__X__|_X___|X____|_____|_____")), CurrentOutcome.WinX); + + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|__XXX")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|____X|____X|____X")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|__X__|___X_|____X")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|____X|___X_|__X__")), CurrentOutcome.WinX); }); void t.test("6x5 boards", async (t) => { - t.equal( - getBoardOutcome( - Board.fromSerialized("_____|_____|_____|_____|_____|__XXX"), - ), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome( - Board.fromSerialized("_____|_____|_____|____X|____X|____X"), - ), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome( - Board.fromSerialized("_____|_____|_____|__X__|___X_|____X"), - ), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome( - Board.fromSerialized("_____|_____|_____|____X|___X_|__X__"), - ), - CurrentOutcome.WinX, - ); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____|__XXX")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|____X|____X|____X")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|__X__|___X_|____X")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|____X|___X_|__X__")), CurrentOutcome.WinX); }); void t.test("5x6 boards", async (t) => { - t.equal( - getBoardOutcome( - Board.fromSerialized("______|______|______|______|___XXX"), - ), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome( - Board.fromSerialized("______|______|_____X|_____X|_____X"), - ), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome( - Board.fromSerialized("______|______|___X__|____X_|_____X"), - ), - CurrentOutcome.WinX, - ); - t.equal( - getBoardOutcome( - Board.fromSerialized("______|______|_____X|____X_|___X__"), - ), - CurrentOutcome.WinX, - ); + t.equal(getBoardOutcome(Board.fromSerialized("______|______|______|______|___XXX")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("______|______|_____X|_____X|_____X")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("______|______|___X__|____X_|_____X")), CurrentOutcome.WinX); + t.equal(getBoardOutcome(Board.fromSerialized("______|______|_____X|____X_|___X__")), CurrentOutcome.WinX); }); }); diff --git a/src/lib/tictactoe-rules.ts b/src/lib/tictactoe-rules.ts index 9691de9..5d78975 100644 --- a/src/lib/tictactoe-rules.ts +++ b/src/lib/tictactoe-rules.ts @@ -20,11 +20,7 @@ export const getSequenceOutcome = (sequence: SquareState[]) => { export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => { for (let row = 0; board.hasRow(row); row++) { for (let column = 2; board.hasSquare(row, column); column++) { - const tripleLeft = [ - board.get(row, column - 2), - board.get(row, column - 1), - board.get(row, column - 0), - ]; + const tripleLeft = [board.get(row, column - 2), board.get(row, column - 1), board.get(row, column - 0)]; const outcome = getSequenceOutcome(tripleLeft); if (outcome) { @@ -35,11 +31,7 @@ export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => { for (let row = 2; board.hasRow(row); row++) { for (let column = 0; board.hasSquare(row, column); column++) { - const tripleUp = [ - board.get(row - 2, column), - board.get(row - 1, column), - board.get(row - 0, column), - ]; + const tripleUp = [board.get(row - 2, column), board.get(row - 1, column), board.get(row - 0, column)]; const outcome = getSequenceOutcome(tripleUp); if (outcome) {