improved formatting (line width set to 120)

main
Inga 🏳‍🌈 4 days ago
parent d823eab61f
commit ca311400ea
  1. 1
      .prettierrc
  2. 5
      eslint.config.js
  3. 8
      src/lib/board.ts
  4. 7
      src/lib/datatypes.spec.ts
  5. 35
      src/lib/opponent.spec.ts
  6. 26
      src/lib/opponent.ts
  7. 38
      src/lib/solver.spec.ts
  8. 46
      src/lib/solver.ts
  9. 251
      src/lib/tictactoe-rules.spec.ts
  10. 12
      src/lib/tictactoe-rules.ts

@ -1,4 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false
}

@ -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 }],
},
},
{

@ -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(""),

@ -31,12 +31,9 @@ void t.test("getExpectedOutcomeByCurrentOutcome", async (t) => {
movesLeft: 0,
});
t.throws(
() => getExpectedOutcomeByCurrentOutcome(CurrentOutcome.Undecided as any),
{
t.throws(() => getExpectedOutcomeByCurrentOutcome(CurrentOutcome.Undecided as any), {
message: "Unsupported current outcome: 201",
},
);
});
t.throws(() => getExpectedOutcomeByCurrentOutcome("test" as any), {
message: "Unsupported current outcome: test",

@ -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|__",
});
});

@ -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<string, ExpectedOutcome>,
) => {
export const createOpponent = (outcomesByBoard: Map<string, ExpectedOutcome>) => {
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;
}

@ -60,35 +60,23 @@ void t.test("getPreferredNextOutcome", async (t) => {
movesLeft: 140,
});
t.matchOnlyStrict(
getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.X),
{
t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.X), {
finalOutcome: FinalOutcome.WinX,
movesLeft: 60,
},
);
t.matchOnlyStrict(
getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.O),
{
});
t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 5), Player.O), {
finalOutcome: FinalOutcome.Draw,
movesLeft: 150,
},
);
});
t.matchOnlyStrict(
getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.X),
{
t.matchOnlyStrict(getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.X), {
finalOutcome: FinalOutcome.Draw,
movesLeft: 150,
},
);
t.matchOnlyStrict(
getPreferredNextOutcome(nextOutcomes.slice(0, 3), Player.O),
{
});
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<string, ExpectedOutcome>,
) => {
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, {

@ -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<string, ExpectedOutcome>();
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,

@ -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);
});
});

@ -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) {

Loading…
Cancel
Save