diff --git a/src/shared/game-variants/capture-the-castle/capture-the-castle.ts b/src/shared/game-variants/capture-the-castle/capture-the-castle.ts new file mode 100644 index 0000000..c1bff49 --- /dev/null +++ b/src/shared/game-variants/capture-the-castle/capture-the-castle.ts @@ -0,0 +1,52 @@ +import { CurrentOutcome, GameRules, SquareState } from "../../datatypes/types.ts"; + +// Simple rules: whoever occupies all the squares passed as a parameter, wins +// This ruleset is only used in tests currently +export const createRulesForSquares = (...squares: [number, number][]): GameRules => ({ + getBoardOutcome: (board) => { + let hasX = false; + let hasO = false; + let hasUnoccupied = false; + + for (const [row, column] of squares) { + const state = board.get(row, column); + switch (state) { + case SquareState.X: + hasX = true; + break; + case SquareState.O: + hasO = true; + break; + case SquareState.Unoccupied: + hasUnoccupied = true; + break; + } + } + + if (hasX && hasO) { + return CurrentOutcome.Draw; + } + + if (hasUnoccupied) { + return CurrentOutcome.Undecided; + } + + if (hasX) { + return CurrentOutcome.WinX; + } + + if (hasO) { + return CurrentOutcome.WinO; + } + + // Special case: if there are no X, O or unoccupied squares, + // this means that the squares list is empty, + // and in this case we only want to decide the board as Draw when it's full + // and no moves can be made. + if (board.serialize().includes("_")) { + return CurrentOutcome.Undecided; + } + + return CurrentOutcome.Draw; + }, +}); diff --git a/src/shared/gameplay/solver.spec.ts b/src/shared/gameplay/solver.spec.ts index 47df26e..4f907d6 100644 --- a/src/shared/gameplay/solver.spec.ts +++ b/src/shared/gameplay/solver.spec.ts @@ -1,7 +1,8 @@ import t, { type Test } from "tap"; -import { CurrentOutcome, ExpectedOutcome, FinalOutcome, GameRules, Player, SquareState } from "../datatypes/types.ts"; +import { ExpectedOutcome, FinalOutcome, Player } from "../datatypes/types.ts"; import { computeAllSolutions, getPreferredNextOutcome } from "./solver.ts"; +import { createRulesForSquares } from "../game-variants/capture-the-castle/capture-the-castle.ts"; void t.test("getPreferredNextOutcome", async (t) => { const nextOutcomes: ExpectedOutcome[] = [ @@ -83,56 +84,6 @@ void t.test("getPreferredNextOutcome", async (t) => { }); void t.test("computeAllSolutions", async (t) => { - // Simple rules: whoever occupies all the squares passed as a parameter, wins - const createRulesForSquares = (...squares: [number, number][]): GameRules => ({ - getBoardOutcome: (board) => { - let hasX = false; - let hasO = false; - let hasUnoccupied = false; - - for (const [row, column] of squares) { - const state = board.get(row, column); - switch (state) { - case SquareState.X: - hasX = true; - break; - case SquareState.O: - hasO = true; - break; - case SquareState.Unoccupied: - hasUnoccupied = true; - break; - } - } - - if (hasX && hasO) { - return CurrentOutcome.Draw; - } - - if (hasUnoccupied) { - return CurrentOutcome.Undecided; - } - - if (hasX) { - return CurrentOutcome.WinX; - } - - if (hasO) { - return CurrentOutcome.WinO; - } - - // Special case: if there are no X, O or unoccupied squares, - // this means that the squares list is empty, - // and in this case we only want to decide the board as Draw when it's full - // and no moves can be made. - if (board.serialize().includes("_")) { - return CurrentOutcome.Undecided; - } - - return CurrentOutcome.Draw; - }, - }); - const checkSolutionsComplete = ( t: Test, rows: number,