implemented tictactoe-all ruleset

feature/modern-browsers
Inga 🏳‍🌈 1 month ago
parent ec1048e603
commit 56216e904f
  1. 24
      src/backend/main/index.tsx
  2. 6
      src/shared/game-variants/index.ts
  3. 194
      src/shared/game-variants/tictactoe-rules.spec.ts
  4. 253
      src/shared/game-variants/tictactoe/tictactoe-all-rules.spec.ts
  5. 66
      src/shared/game-variants/tictactoe/tictactoe-all-rules.ts
  6. 124
      src/shared/game-variants/tictactoe/tictactoe-generic.spec.ts
  7. 18
      src/shared/game-variants/tictactoe/tictactoe-generic.ts
  8. 227
      src/shared/game-variants/tictactoe/tictactoe-three-rules.spec.ts
  9. 22
      src/shared/game-variants/tictactoe/tictactoe-three-rules.ts
  10. 40
      src/shared/integration-tests/test-helpers.ts
  11. 59
      src/shared/integration-tests/tictactoe-generic.test.ts
  12. 90
      src/shared/integration-tests/tictactoe-three.test.ts
  13. 2
      tsconfig.build.json

@ -4,15 +4,15 @@ import { handleBoardgame } from "./boardgame-handler.ts";
import { getCounterHtml } from "../components/counter.tsx"; import { getCounterHtml } from "../components/counter.tsx";
export const mainPageHandler: RequestHandler = (req, res) => { export const mainPageHandler: RequestHandler = (req, res) => {
const board1 = handleBoardgame(req, res, "tictactoe1", "tictactoe"); const boardThree = handleBoardgame(req, res, "tictactoe-three-1", "tictactoe-three");
if (!board1) { if (!boardThree) {
// No return value from handleBoardgame means that it redirected user to another URL, // No return value from handleBoardgame means that it redirected user to another URL,
// and we no longer need to render anything. // and we no longer need to render anything.
return; return;
} }
const board2 = handleBoardgame(req, res, "tictactoe2", "tictactoe"); const boardAll = handleBoardgame(req, res, "tictactoe-all-2", "tictactoe-all");
if (!board2) { if (!boardAll) {
return; return;
} }
@ -42,8 +42,20 @@ export const mainPageHandler: RequestHandler = (req, res) => {
<li>{getCounterHtml(req, "b")}</li> <li>{getCounterHtml(req, "b")}</li>
</ul> </ul>
</section> </section>
<section class="game1">{board1}</section> <section class="game1">
<section class="game2">{board2}</section> <p>
"Three in a row" generalized variant of tictactoe (it is enough to get three adjacent cells on the same row,
on the same column, or on the same 45°/135° line)
</p>
{boardThree}
</section>
<section class="game2">
<p>
"All in a row" generalized variant of tictactoe (you need to get complete row, complete column, or, on
square boards, complete diagonal)
</p>
{boardAll}
</section>
</body> </body>
</html>, </html>,
); );

@ -1,7 +1,9 @@
import { rules as tictactoeRules } from "./tictactoe-rules.ts"; import { tictactoeAllRules } from "./tictactoe/tictactoe-all-rules.ts";
import { tictactoeThreeRules } from "./tictactoe/tictactoe-three-rules.ts";
export const gamesRules = { export const gamesRules = {
tictactoe: tictactoeRules, "tictactoe-three": tictactoeThreeRules,
"tictactoe-all": tictactoeAllRules,
}; };
export type GameVariantName = keyof typeof gamesRules; export type GameVariantName = keyof typeof gamesRules;

@ -1,194 +0,0 @@
import t from "tap";
import { Board } from "../datatypes/board.ts";
import { CurrentOutcome, SquareState } from "../datatypes/types.ts";
import { getBoardOutcome, getSequenceOutcome } from "./tictactoe-rules.ts";
void t.test("getSequenceOutcome", async (t) => {
void t.test("empty sequence", async (t) => {
t.equal(getSequenceOutcome([]), null);
});
void t.test("all sequences of length 1", async (t) => {
t.equal(getSequenceOutcome([SquareState.Unoccupied]), null);
t.equal(getSequenceOutcome([SquareState.X]), CurrentOutcome.WinX);
t.equal(getSequenceOutcome([SquareState.O]), CurrentOutcome.WinO);
});
void t.test("all sequences of length 2", async (t) => {
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.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);
});
void t.test("sequences of length 7", async (t) => {
void t.test("all X except for the first element", async (t) => {
t.equal(
getSequenceOutcome([
SquareState.Unoccupied,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
t.equal(
getSequenceOutcome([
SquareState.O,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
});
void t.test("all X except for the last element", async (t) => {
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.Unoccupied,
]),
null,
);
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.O,
]),
null,
);
});
void t.test("all X except for the middle element", async (t) => {
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.Unoccupied,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.O,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
});
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
]),
CurrentOutcome.WinX,
);
});
});
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("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);
});
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);
});
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);
});
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);
});
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);
});
});

@ -0,0 +1,253 @@
import t from "tap";
import { Board } from "../../datatypes/board.ts";
import { CurrentOutcome } from "../../datatypes/types.ts";
import { getBoardOutcome } from "./tictactoe-all-rules.ts";
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("X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("O")), CurrentOutcome.WinO);
});
void t.test("2x2 boards", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("__|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("OO|OO")), CurrentOutcome.WinO);
// Could be WinO as well, but we consider right-down diagonal before right-up one
t.equal(getBoardOutcome(Board.fromSerialized("XO|OX")), CurrentOutcome.WinX);
});
void t.test("Enumerate all 2x2 boards without O", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("__|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX")), CurrentOutcome.WinX);
});
void t.test("Enumerate all 2x3 boards without O", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("___|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__X|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|__X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__X|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__X|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__X|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|_X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|__X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|_X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("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__|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X__|X__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X__|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X__|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X__|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|__X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|X__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|_X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|X__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|___")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|__X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|_X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|X__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|XXX")), CurrentOutcome.WinX);
});
void t.test("Enumerate all 3x2 boards without O", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("__|__|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|__|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|__|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|__|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__|_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|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__|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_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_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|__|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("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_|__|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|XX")), CurrentOutcome.WinX);
});
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);
});
void t.test("5x5 boards", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XXXXX|_____|_____|_____|_____")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|XXXXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X____|X____|X____|X____|X____")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("____X|____X|____X|____X|____X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X____|_X___|__X__|___X_|____X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("____X|___X_|__X__|_X___|X____")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXXX_|_____|_____|_____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_XXXX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X____|X____|X____|X____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|____X|____X|____X|____X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X____|_X___|__X__|___X_|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|___X_|__X__|_X___|X____")), CurrentOutcome.Undecided);
});
void t.test("6x5 boards", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XXXXX|_____|_____|_____|_____|_____")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____|XXXXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X____|X____|X____|X____|X____|X____")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("____X|____X|____X|____X|____X|____X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X____|_X___|__X__|___X_|____X|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("____X|___X_|__X__|_X___|X____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|X____|_X___|__X__|___X_|____X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|____X|___X_|__X__|_X___|X____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XXXX_|_____|_____|_____|_____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|_____|_____|_____|_____|_XXXX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X____|X____|X____|X____|X____|_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____|____X|____X|____X|____X|____X")), CurrentOutcome.Undecided);
});
void t.test("5x6 boards", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("______|______|______|______|______")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XXXXXX|______|______|______|______")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("______|______|______|______|XXXXXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_____|X_____|X_____|X_____|X_____")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_____X|_____X|_____X|_____X|_____X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_____|_X____|__X___|___X__|____X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X____|__X___|___X__|____X_|_____X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("____X_|___X__|__X___|_X____|X_____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_____X|____X_|___X__|__X___|_X____")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XXXXX_|______|______|______|______")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("______|______|______|______|_XXXXX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_____|X_____|X_____|X_____|______")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("______|_____X|_____X|_____X|_____X")), CurrentOutcome.Undecided);
});
});

@ -0,0 +1,66 @@
import { CurrentOutcome, GameRules, SquareState } from "../../datatypes/types.ts";
import { getSequenceOutcome } from "./tictactoe-generic.ts";
export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => {
for (let row = 0; board.hasRow(row); row++) {
const rowValues = [];
for (let column = 0; board.hasSquare(row, column); column++) {
rowValues.push(board.get(row, column));
}
const outcome = getSequenceOutcome(rowValues);
if (outcome) {
return outcome;
}
}
for (let column = 0; board.hasSquare(0, column); column++) {
const columnValues = [];
for (let row = 0; board.hasSquare(row, column); row++) {
columnValues.push(board.get(row, column));
}
const outcome = getSequenceOutcome(columnValues);
if (outcome) {
return outcome;
}
}
{
const rightDownValues = [];
for (let index = 0; board.hasSquare(index, index); index++) {
rightDownValues.push(board.get(index, index));
}
if (!board.hasRow(rightDownValues.length) && !board.hasSquare(rightDownValues.length - 1, rightDownValues.length)) {
// It only makes sense to check diagonals on square boards
const rightDownOutcome = getSequenceOutcome(rightDownValues);
if (rightDownOutcome) {
return rightDownOutcome;
}
const rightUpValues = [];
for (let index = 0; board.hasSquare(rightDownValues.length - 1 - index, index); index++) {
rightUpValues.push(board.get(rightDownValues.length - 1 - index, index));
}
const rightUpOutcome = getSequenceOutcome(rightUpValues);
if (rightUpOutcome) {
return rightUpOutcome;
}
}
}
for (let row = 0; board.hasRow(row); row++) {
for (let column = 0; board.hasSquare(row, column); column++) {
if (board.get(row, column) === SquareState.Unoccupied) {
return CurrentOutcome.Undecided;
}
}
}
return CurrentOutcome.Draw;
};
export const tictactoeAllRules: GameRules = {
getBoardOutcome,
};

@ -0,0 +1,124 @@
import t from "tap";
import { CurrentOutcome, SquareState } from "../../datatypes/types.ts";
import { getSequenceOutcome } from "./tictactoe-generic.ts";
void t.test("getSequenceOutcome", async (t) => {
void t.test("empty sequence", async (t) => {
t.equal(getSequenceOutcome([]), null);
});
void t.test("all sequences of length 1", async (t) => {
t.equal(getSequenceOutcome([SquareState.Unoccupied]), null);
t.equal(getSequenceOutcome([SquareState.X]), CurrentOutcome.WinX);
t.equal(getSequenceOutcome([SquareState.O]), CurrentOutcome.WinO);
});
void t.test("all sequences of length 2", async (t) => {
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.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);
});
void t.test("sequences of length 7", async (t) => {
void t.test("all X except for the first element", async (t) => {
t.equal(
getSequenceOutcome([
SquareState.Unoccupied,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
t.equal(
getSequenceOutcome([
SquareState.O,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
});
void t.test("all X except for the last element", async (t) => {
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.Unoccupied,
]),
null,
);
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.O,
]),
null,
);
});
void t.test("all X except for the middle element", async (t) => {
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.Unoccupied,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.O,
SquareState.X,
SquareState.X,
SquareState.X,
]),
null,
);
});
t.equal(
getSequenceOutcome([
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
SquareState.X,
]),
CurrentOutcome.WinX,
);
});
});

@ -0,0 +1,18 @@
import { CurrentOutcome, SquareState } from "../../datatypes/types.ts";
export const getSequenceOutcome = (sequence: SquareState[]) => {
for (let i = 1; i < sequence.length; i++) {
if (sequence[i - 1] != sequence[i]) {
return null;
}
}
switch (sequence[0]) {
case SquareState.X:
return CurrentOutcome.WinX;
case SquareState.O:
return CurrentOutcome.WinO;
default:
return null;
}
};

@ -0,0 +1,227 @@
import t from "tap";
import { Board } from "../../datatypes/board.ts";
import { CurrentOutcome } from "../../datatypes/types.ts";
import { getBoardOutcome } from "./tictactoe-three-rules.ts";
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("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);
});
void t.test("Enumerate all 2x2 boards without O", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("__|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_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|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("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_|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX")), CurrentOutcome.Draw);
});
void t.test("Enumerate all 2x3 boards without O", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("___|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("___|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("__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|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__X|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_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_|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X_|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_XX|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("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__|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X__|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X__|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X__|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X__|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_X|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|___")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|__X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|_X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|_XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|X__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|X_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|XX_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX_|XXX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|___")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|__X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|_X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|_XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|X__")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|X_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|XX_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XXX|XXX")), CurrentOutcome.WinX);
});
void t.test("Enumerate all 3x2 boards without O", async (t) => {
t.equal(getBoardOutcome(Board.fromSerialized("__|__|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|__|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|__|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|__|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|_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|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|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_|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("__|XX|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_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|__|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|_X|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|X_|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("_X|XX|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("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_|__|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|_X|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|X_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("X_|XX|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|__|XX")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|X_")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|_X|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|_X")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|X_|XX")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|__")), CurrentOutcome.Undecided);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|_X")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|X_")), CurrentOutcome.WinX);
t.equal(getBoardOutcome(Board.fromSerialized("XX|XX|XX")), CurrentOutcome.WinX);
});
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);
});
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);
});
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);
});
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);
});
});

@ -1,21 +1,5 @@
import { CurrentOutcome, GameRules, SquareState } from "../datatypes/types.ts"; import { CurrentOutcome, GameRules, SquareState } from "../../datatypes/types.ts";
import { getSequenceOutcome } from "./tictactoe-generic.ts";
export const getSequenceOutcome = (sequence: SquareState[]) => {
for (let i = 1; i < sequence.length; i++) {
if (sequence[i - 1] != sequence[i]) {
return null;
}
}
switch (sequence[0]) {
case SquareState.X:
return CurrentOutcome.WinX;
case SquareState.O:
return CurrentOutcome.WinO;
default:
return null;
}
};
export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => { export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => {
for (let row = 0; board.hasRow(row); row++) { for (let row = 0; board.hasRow(row); row++) {
@ -80,6 +64,6 @@ export const getBoardOutcome: GameRules["getBoardOutcome"] = (board) => {
return CurrentOutcome.Draw; return CurrentOutcome.Draw;
}; };
export const rules: GameRules = { export const tictactoeThreeRules: GameRules = {
getBoardOutcome, getBoardOutcome,
}; };

@ -0,0 +1,40 @@
import { Test } from "tap";
import { ExpectedOutcome, GameRules, Opponent, Player, getOccupiedStateByPlayer } from "../datatypes/types.ts";
import { computeAllSolutions } from "../gameplay/solver.ts";
import { Board } from "../datatypes/board.ts";
export const checkSolutionsComplete = (
t: Test,
rules: GameRules,
rows: number,
columns: number,
expectedSolutions: Record<string, ExpectedOutcome>,
) => {
t.matchOnlyStrict(Object.fromEntries(computeAllSolutions(rows, columns, rules).entries()), expectedSolutions);
};
export const checkSolutionsIncomplete = (
t: Test,
rules: GameRules,
rows: number,
columns: number,
expectedSolutionsCount: number,
expectedSolutionsIncomplete: Record<string, ExpectedOutcome>,
) => {
const allSolutions = computeAllSolutions(rows, columns, rules);
t.equal(allSolutions.size, expectedSolutionsCount);
t.matchStrict(Object.fromEntries(allSolutions.entries()), expectedSolutionsIncomplete);
};
export const checkNextMove = (
t: Test,
opponent: Opponent,
currentBoardSerialized: string,
currentPlayer: Player,
expectedNextBoardSerialized: string,
) => {
const currentBoard = Board.fromSerialized(currentBoardSerialized);
const nextMove = opponent.getNextMove(currentBoard, currentPlayer);
const nextBoard = currentBoard.with(nextMove.row, nextMove.column, getOccupiedStateByPlayer(currentPlayer));
t.equal(nextBoard.serialize(), expectedNextBoardSerialized);
};

@ -0,0 +1,59 @@
import t from "tap";
import { FinalOutcome, Player } from "../datatypes/types.ts";
import { createOpponent } from "../gameplay/opponent.ts";
import { computeAllSolutions } from "../gameplay/solver.ts";
import { checkNextMove, checkSolutionsIncomplete } from "./test-helpers.ts";
import { gamesRules } from "../game-variants/index.ts";
for (const [gameVariant, rules] of Object.entries(gamesRules)) {
void t.test(`Checking 3x3 game for ${gameVariant}`, async (t) => {
void t.test("computeAllSolutions", async (t) => {
// number 5478 taken from https://math.stackexchange.com/a/613505
checkSolutionsIncomplete(t, rules, 3, 3, 5478, {
"___|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 9 },
"X__|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 },
"_X_|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 },
"___|_X_|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 },
"XO_|___|___": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"X__|___|_O_": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"X_O|___|___": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"X__|___|__O": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"OO_|___|_XX": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
"OO_|__X|_XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 },
});
void t.test("createOpponent", async (t) => {
const outcomes = computeAllSolutions(3, 3, rules);
const opponent = createOpponent(outcomes);
checkNextMove(t, opponent, "___|___|___", Player.X, "X__|___|___");
checkNextMove(t, opponent, "X__|___|___", Player.O, "X__|_O_|___");
checkNextMove(t, opponent, "X__|_O_|___", Player.X, "XX_|_O_|___");
checkNextMove(t, opponent, "XX_|_O_|___", Player.O, "XXO|_O_|___");
checkNextMove(t, opponent, "XXO|_O_|___", Player.X, "XXO|_O_|X__");
checkNextMove(t, opponent, "XXO|_O_|X__", Player.O, "XXO|OO_|X__");
checkNextMove(t, opponent, "XXO|OO_|X__", Player.X, "XXO|OOX|X__");
checkNextMove(t, opponent, "XXO|OOX|X__", Player.O, "XXO|OOX|XO_");
checkNextMove(t, opponent, "XXO|OOX|XO_", Player.X, "XXO|OOX|XOX");
checkNextMove(t, opponent, "___|___|__X", Player.O, "___|_O_|__X");
checkNextMove(t, opponent, "___|_O_|__X", Player.X, "X__|_O_|__X");
checkNextMove(t, opponent, "X__|_O_|__X", Player.O, "XO_|_O_|__X");
checkNextMove(t, opponent, "XO_|_O_|__X", Player.X, "XO_|_O_|_XX");
checkNextMove(t, opponent, "XO_|_O_|_XX", Player.O, "XO_|_O_|OXX");
checkNextMove(t, opponent, "XO_|_O_|OXX", Player.X, "XOX|_O_|OXX");
checkNextMove(t, opponent, "XOX|_O_|OXX", Player.O, "XOX|_OO|OXX");
checkNextMove(t, opponent, "XOX|_OO|OXX", Player.X, "XOX|XOO|OXX");
checkNextMove(t, opponent, "XO_|___|___", Player.X, "XO_|X__|___");
checkNextMove(t, opponent, "XO_|X__|___", Player.O, "XO_|X__|O__");
checkNextMove(t, opponent, "XO_|X__|O__", Player.X, "XO_|XX_|O__");
checkNextMove(t, opponent, "XO_|XX_|O__", Player.O, "XOO|XX_|O__");
checkNextMove(t, opponent, "XOO|XX_|O__", Player.X, "XOO|XXX|O__");
checkNextMove(t, opponent, "XO_|XXO|O__", Player.X, "XO_|XXO|O_X");
});
});
});
}

@ -1,33 +1,16 @@
import t, { Test } from "tap"; import t from "tap";
import { Board } from "../datatypes/board.ts"; import { FinalOutcome, Player } from "../datatypes/types.ts";
import { ExpectedOutcome, FinalOutcome, Opponent, Player, getOccupiedStateByPlayer } from "../datatypes/types.ts";
import { createOpponent } from "../gameplay/opponent.ts"; import { createOpponent } from "../gameplay/opponent.ts";
import { computeAllSolutions } from "../gameplay/solver.ts"; import { computeAllSolutions } from "../gameplay/solver.ts";
import { rules } from "./tictactoe-rules.ts"; import { tictactoeThreeRules } from "../game-variants/tictactoe/tictactoe-three-rules.ts";
import { checkNextMove, checkSolutionsComplete } from "./test-helpers.ts";
void t.test("computeAllSolutions", async (t) => { const rules = tictactoeThreeRules;
const checkSolutionsComplete = (
rows: number,
columns: number,
expectedSolutions: Record<string, ExpectedOutcome>,
) => {
t.matchOnlyStrict(Object.fromEntries(computeAllSolutions(rows, columns, rules).entries()), expectedSolutions);
};
const checkSolutionsIncomplete = (
rows: number,
columns: number,
expectedSolutionsCount: number,
expectedSolutionsIncomplete: Record<string, ExpectedOutcome>,
) => {
const allSolutions = computeAllSolutions(rows, columns, rules);
t.equal(allSolutions.size, expectedSolutionsCount);
t.matchStrict(Object.fromEntries(allSolutions.entries()), expectedSolutionsIncomplete);
};
void t.test("computeAllSolutions", async (t) => {
// smallest possible board where X can win // smallest possible board where X can win
checkSolutionsComplete(1, 5, { checkSolutionsComplete(t, rules, 1, 5, {
_____: { finalOutcome: FinalOutcome.Draw, movesLeft: 5 }, _____: { finalOutcome: FinalOutcome.Draw, movesLeft: 5 },
X____: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 }, X____: { finalOutcome: FinalOutcome.Draw, movesLeft: 4 },
@ -129,36 +112,9 @@ void t.test("computeAllSolutions", async (t) => {
OXOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 }, OXOXX: { finalOutcome: FinalOutcome.Draw, movesLeft: 0 },
OOXXX: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 }, OOXXX: { finalOutcome: FinalOutcome.WinX, movesLeft: 0 },
}); });
// number 5478 taken from https://math.stackexchange.com/a/613505
checkSolutionsIncomplete(3, 3, 5478, {
"___|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 9 },
"X__|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 },
"_X_|___|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 },
"___|_X_|___": { finalOutcome: FinalOutcome.Draw, movesLeft: 8 },
"XO_|___|___": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"X__|___|_O_": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"X_O|___|___": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"X__|___|__O": { finalOutcome: FinalOutcome.WinX, movesLeft: 5 },
"OO_|___|_XX": { finalOutcome: FinalOutcome.WinX, movesLeft: 1 },
"OO_|__X|_XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 },
});
}); });
void t.test("createOpponent", async (t) => { void t.test("createOpponent", async (t) => {
const checkNextMove = (
t: Test,
opponent: Opponent,
currentBoardSerialized: string,
currentPlayer: Player,
expectedNextBoardSerialized: string,
) => {
const currentBoard = Board.fromSerialized(currentBoardSerialized);
const nextMove = opponent.getNextMove(currentBoard, currentPlayer);
const nextBoard = currentBoard.with(nextMove.row, nextMove.column, getOccupiedStateByPlayer(currentPlayer));
t.equal(nextBoard.serialize(), expectedNextBoardSerialized);
};
void t.test("1x5 board", async (t) => { void t.test("1x5 board", async (t) => {
const outcomes = computeAllSolutions(1, 5, rules); const outcomes = computeAllSolutions(1, 5, rules);
const opponent = createOpponent(outcomes); const opponent = createOpponent(outcomes);
@ -178,36 +134,4 @@ void t.test("createOpponent", async (t) => {
checkNextMove(t, opponent, "O_XX_", Player.O, "OOXX_"); checkNextMove(t, opponent, "O_XX_", Player.O, "OOXX_");
checkNextMove(t, opponent, "OOXX_", Player.X, "OOXXX"); checkNextMove(t, opponent, "OOXX_", Player.X, "OOXXX");
}); });
void t.test("3x3 board", async (t) => {
const outcomes = computeAllSolutions(3, 3, rules);
const opponent = createOpponent(outcomes);
checkNextMove(t, opponent, "___|___|___", Player.X, "X__|___|___");
checkNextMove(t, opponent, "X__|___|___", Player.O, "X__|_O_|___");
checkNextMove(t, opponent, "X__|_O_|___", Player.X, "XX_|_O_|___");
checkNextMove(t, opponent, "XX_|_O_|___", Player.O, "XXO|_O_|___");
checkNextMove(t, opponent, "XXO|_O_|___", Player.X, "XXO|_O_|X__");
checkNextMove(t, opponent, "XXO|_O_|X__", Player.O, "XXO|OO_|X__");
checkNextMove(t, opponent, "XXO|OO_|X__", Player.X, "XXO|OOX|X__");
checkNextMove(t, opponent, "XXO|OOX|X__", Player.O, "XXO|OOX|XO_");
checkNextMove(t, opponent, "XXO|OOX|XO_", Player.X, "XXO|OOX|XOX");
checkNextMove(t, opponent, "___|___|__X", Player.O, "___|_O_|__X");
checkNextMove(t, opponent, "___|_O_|__X", Player.X, "X__|_O_|__X");
checkNextMove(t, opponent, "X__|_O_|__X", Player.O, "XO_|_O_|__X");
checkNextMove(t, opponent, "XO_|_O_|__X", Player.X, "XO_|_O_|_XX");
checkNextMove(t, opponent, "XO_|_O_|_XX", Player.O, "XO_|_O_|OXX");
checkNextMove(t, opponent, "XO_|_O_|OXX", Player.X, "XOX|_O_|OXX");
checkNextMove(t, opponent, "XOX|_O_|OXX", Player.O, "XOX|_OO|OXX");
checkNextMove(t, opponent, "XOX|_OO|OXX", Player.X, "XOX|XOO|OXX");
checkNextMove(t, opponent, "XO_|___|___", Player.X, "XO_|X__|___");
checkNextMove(t, opponent, "XO_|X__|___", Player.O, "XO_|X__|O__");
checkNextMove(t, opponent, "XO_|X__|O__", Player.X, "XO_|XX_|O__");
checkNextMove(t, opponent, "XO_|XX_|O__", Player.O, "XOO|XX_|O__");
checkNextMove(t, opponent, "XOO|XX_|O__", Player.X, "XOO|XXX|O__");
checkNextMove(t, opponent, "XO_|XXO|O__", Player.X, "XO_|XXO|O_X");
});
}); });

@ -1,6 +1,6 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": ["src/**/*.spec.*"], "exclude": ["src/**/*.spec.*", "src/**/*.test.*", "src/**/test-helpers.*"],
"compilerOptions": { "compilerOptions": {
"module": "Preserve", "module": "Preserve",
"noCheck": true, "noCheck": true,

Loading…
Cancel
Save