implemented integration tests for opponent

main
Inga 🏳‍🌈 4 days ago
parent 4ab4f19070
commit fcc87648be
  1. 2
      README.md
  2. 4
      src/lib/board.ts
  3. 4
      src/lib/datatypes.ts
  4. 2
      src/lib/opponent.spec.ts
  5. 6
      src/lib/opponent.ts
  6. 73
      src/lib/tictactoe.test.ts

@ -29,5 +29,5 @@ Source: https://www.programmfabrik.de/en/assignment-frontend-backend-developer-j
## Time spent
* ~0.5 hours to set up the project;
* ~4.5 hours to implement game board serialization, tic-tac-toe rules and game solver (with tests);
* ~5 hours to implement game board serialization, tic-tac-toe rules and game solver (with tests);
* ...

@ -1,7 +1,7 @@
import { SquareState } from "./datatypes.ts";
import { BoardType, SquareState } from "./datatypes.ts";
import { unreachable } from "./utils.ts";
export class Board {
export class Board implements BoardType {
// State should be immutable
constructor(private readonly state: SquareState[][]) {}

@ -39,6 +39,10 @@ export type GameRules = {
getBoardOutcome(board: BoardType): CurrentOutcome;
};
export type Opponent = {
getNextMove(board: BoardType, currentPlayer: Player): BoardType;
};
export const getExpectedOutcomeByCurrentOutcome = (
currentOutcome: Exclude<CurrentOutcome, CurrentOutcome.Undecided>,
): ExpectedOutcome => {

@ -1,7 +1,7 @@
import t from "tap";
import { Board } from "./board.ts";
import { ExpectedOutcome, FinalOutcome, Player } from "./datatypes.ts";
import { createOpponent } from "./opponent.ts";
import { Board } from "./board.ts";
void t.test("createOpponent", async (t) => {
const outcomesByBoard: Record<string, ExpectedOutcome> = {

@ -1,7 +1,7 @@
import { BoardType, ExpectedOutcome, Player, SquareState, getOccupiedStateByPlayer } from "./datatypes.ts";
import { ExpectedOutcome, Opponent, SquareState, getOccupiedStateByPlayer } from "./datatypes.ts";
export const createOpponent = (outcomesByBoard: Map<string, ExpectedOutcome>) => {
const getNextMove = (board: BoardType, currentPlayer: Player) => {
export const createOpponent = (outcomesByBoard: Map<string, ExpectedOutcome>): Opponent => {
const getNextMove: Opponent["getNextMove"] = (board, currentPlayer) => {
const currentExpectedOutcome = outcomesByBoard.get(board.serialize());
if (!currentExpectedOutcome) {
throw new Error(`Board is not solved: ${board.serialize()}`);

@ -1,6 +1,8 @@
import t from "tap";
import t, { Test } from "tap";
import { ExpectedOutcome, FinalOutcome } from "./datatypes.ts";
import { Board } from "./board.ts";
import { ExpectedOutcome, FinalOutcome, Opponent, Player } from "./datatypes.ts";
import { createOpponent } from "./opponent.ts";
import { computeAllSolutions } from "./solver.ts";
import { rules } from "./tictactoe-rules.ts";
@ -142,3 +144,70 @@ void t.test("computeAllSolutions", async (t) => {
"OO_|__X|_XX": { finalOutcome: FinalOutcome.WinO, movesLeft: 1 },
});
});
void t.test("createOpponent", async (t) => {
const checkNextMove = (
t: Test,
opponent: Opponent,
currentBoardSerialized: string,
currentPlayer: Player,
expectedNextBoardSerialized: string,
) => {
t.equal(
opponent.getNextMove(Board.fromSerialized(currentBoardSerialized), currentPlayer).serialize(),
expectedNextBoardSerialized,
);
};
void t.test("1x5 board", async (t) => {
const outcomes = computeAllSolutions(1, 5, rules);
const opponent = createOpponent(outcomes);
checkNextMove(t, opponent, "_____", Player.X, "X____");
checkNextMove(t, opponent, "X____", Player.O, "XO___");
checkNextMove(t, opponent, "XO___", Player.X, "XOX__");
checkNextMove(t, opponent, "XOX__", Player.O, "XOXO_");
checkNextMove(t, opponent, "XOXO_", Player.X, "XOXOX");
checkNextMove(t, opponent, "__X__", Player.O, "_OX__");
checkNextMove(t, opponent, "_OX__", Player.X, "XOX__");
checkNextMove(t, opponent, "XOX__", Player.O, "XOXO_");
checkNextMove(t, opponent, "XOXO_", Player.X, "XOXOX");
checkNextMove(t, opponent, "O_X__", Player.X, "O_XX_");
checkNextMove(t, opponent, "O_XX_", Player.O, "OOXX_");
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");
});
});

Loading…
Cancel
Save