implemented dynamic board size

main
Inga 🏳‍🌈 2 days ago
parent e8b145707d
commit 4d13d2b1a4
  1. 10
      src/backend/components/boardgame.tsx
  2. 28
      src/frontend/components/board-game.ts
  3. 116
      src/shared/datatypes/boardgame-state.spec.ts
  4. 16
      src/shared/datatypes/boardgame-state.ts
  5. 4
      src/shared/datatypes/types.ts
  6. 4
      src/shared/display.ts
  7. 6
      src/shared/gameplay/solver.spec.ts
  8. 4
      src/shared/gameplay/solver.ts

@ -110,6 +110,16 @@ export const getBoardgameHtml = (key: string, gameState: BoardgameStateType, gam
<span class="when-game-in-progress">Restart game</span> <span class="when-game-in-progress">Restart game</span>
</button> </button>
</p> </p>
<p>
<button {...getGenericButtonAttributes("add-row", { key, buttonValues })}>Add row</button>
{" / "}
<button {...getGenericButtonAttributes("remove-row", { key, buttonValues })}>Remove row</button>
</p>
<p>
<button {...getGenericButtonAttributes("add-column", { key, buttonValues })}>Add column</button>
{" / "}
<button {...getGenericButtonAttributes("remove-column", { key, buttonValues })}>Remove column</button>
</p>
</form> </form>
</board-game> </board-game>
); );

@ -48,18 +48,46 @@ export class BoardGameComponent extends HTMLElement {
for (const tbodyUntyped of this.querySelectorAll("tbody.game-board")) { for (const tbodyUntyped of this.querySelectorAll("tbody.game-board")) {
const tbody = tbodyUntyped as HTMLTableSectionElement; const tbody = tbodyUntyped as HTMLTableSectionElement;
for (let rowNumberToRemove = gameState.rows; rowNumberToRemove < tbody.rows.length; rowNumberToRemove++) {
tbody.rows[rowNumberToRemove]?.remove();
}
for (let rowNumberToAdd = tbody.rows.length; rowNumberToAdd < gameState.rows; rowNumberToAdd++) {
tbody.insertRow();
}
for (let rowNumber = 0; rowNumber < tbody.rows.length; rowNumber++) { for (let rowNumber = 0; rowNumber < tbody.rows.length; rowNumber++) {
const row = tbody.rows[rowNumber]; const row = tbody.rows[rowNumber];
if (!row) { if (!row) {
continue; continue;
} }
for (
let columnNumberToRemove = gameState.columns;
columnNumberToRemove < row.cells.length;
columnNumberToRemove++
) {
row.cells[columnNumberToRemove]?.remove();
}
for (let columnNumberToAdd = row.cells.length; columnNumberToAdd < gameState.columns; columnNumberToAdd++) {
row.insertCell();
}
for (let columnNumber = 0; columnNumber < row.cells.length; columnNumber++) { for (let columnNumber = 0; columnNumber < row.cells.length; columnNumber++) {
const cell = row.cells[columnNumber]; const cell = row.cells[columnNumber];
if (!cell) { if (!cell) {
continue; continue;
} }
if (!cell.childNodes.length) {
const button = document.createElement("button");
button.type = "submit";
button.name = key;
cell.append(button);
}
const { isDisabled, nextGameState, text } = getCellDisplayData({ const { isDisabled, nextGameState, text } = getCellDisplayData({
gameState, gameState,
currentOutcome, currentOutcome,

@ -236,6 +236,122 @@ void t.test("withEmptyBoard", async (t) => {
}); });
}); });
void t.test("withAdditionalRow", async (t) => {
void t.test("On uninitialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20...");
const newState = oldState?.withAdditionalRow();
t.equal(newState?.serialize(), "11x20...");
t.equal(newState?.rows, 11);
t.equal(newState?.columns, 20);
});
void t.test("On initialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20.XO.X.___");
const newState = oldState?.withAdditionalRow();
t.equal(newState?.serialize(), "11x20.XO..");
t.equal(newState?.rows, 11);
t.equal(newState?.columns, 20);
});
});
void t.test("withoutAdditionalRow", async (t) => {
void t.test("On uninitialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20...");
const newState = oldState?.withoutAdditionalRow();
t.equal(newState?.serialize(), "9x20...");
t.equal(newState?.rows, 9);
t.equal(newState?.columns, 20);
});
void t.test("On initialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20.XO.X.___");
const newState = oldState?.withoutAdditionalRow();
t.equal(newState?.serialize(), "9x20.XO..");
t.equal(newState?.rows, 9);
t.equal(newState?.columns, 20);
});
void t.test("On initialized board with 1 row", async (t) => {
const oldState = createAndCheckBoardgameState(t, "0x20.XO.X.___");
const newState = oldState?.withoutAdditionalRow();
t.equal(newState?.serialize(), "0x20.XO..");
t.equal(newState?.rows, 0);
t.equal(newState?.columns, 20);
});
void t.test("On initialized board with 0 rows", async (t) => {
const oldState = createAndCheckBoardgameState(t, "0x20.XO.X.___");
const newState = oldState?.withoutAdditionalRow();
t.equal(newState?.serialize(), "0x20.XO..");
t.equal(newState?.rows, 0);
t.equal(newState?.columns, 20);
});
});
void t.test("withAdditionalColumn", async (t) => {
void t.test("On uninitialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20...");
const newState = oldState?.withAdditionalColumn();
t.equal(newState?.serialize(), "10x21...");
t.equal(newState?.rows, 10);
t.equal(newState?.columns, 21);
});
void t.test("On initialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20.XO.X.___");
const newState = oldState?.withAdditionalColumn();
t.equal(newState?.serialize(), "10x21.XO..");
t.equal(newState?.rows, 10);
t.equal(newState?.columns, 21);
});
});
void t.test("withoutAdditionalColumn", async (t) => {
void t.test("On uninitialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20...");
const newState = oldState?.withoutAdditionalColumn();
t.equal(newState?.serialize(), "10x19...");
t.equal(newState?.rows, 10);
t.equal(newState?.columns, 19);
});
void t.test("On initialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x20.XO.X.___");
const newState = oldState?.withoutAdditionalColumn();
t.equal(newState?.serialize(), "10x19.XO..");
t.equal(newState?.rows, 10);
t.equal(newState?.columns, 19);
});
void t.test("On initialized board with 1 columns", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x1.XO.X.___");
const newState = oldState?.withoutAdditionalColumn();
t.equal(newState?.serialize(), "10x0.XO..");
t.equal(newState?.rows, 10);
t.equal(newState?.columns, 0);
});
void t.test("On initialized board with 0 columns", async (t) => {
const oldState = createAndCheckBoardgameState(t, "10x0.XO.X.___");
const newState = oldState?.withoutAdditionalColumn();
t.equal(newState?.serialize(), "10x0.XO..");
t.equal(newState?.rows, 10);
t.equal(newState?.columns, 0);
});
});
void t.test("withAutoPlayer", async (t) => { void t.test("withAutoPlayer", async (t) => {
void t.test("On uninitialized board", async (t) => { void t.test("On uninitialized board", async (t) => {
const oldState = createAndCheckBoardgameState(t, "2x3..."); const oldState = createAndCheckBoardgameState(t, "2x3...");

@ -79,6 +79,22 @@ export class BoardgameState implements BoardgameStateType {
); );
} }
withAdditionalRow() {
return new BoardgameState(this.rows + 1, this.columns, this.autoPlayers, null, null);
}
withoutAdditionalRow() {
return new BoardgameState(Math.max(0, this.rows - 1), this.columns, this.autoPlayers, null, null);
}
withAdditionalColumn() {
return new BoardgameState(this.rows, this.columns + 1, this.autoPlayers, null, null);
}
withoutAdditionalColumn() {
return new BoardgameState(this.rows, Math.max(0, this.columns - 1), this.autoPlayers, null, null);
}
withAutoPlayer(player: Player) { withAutoPlayer(player: Player) {
const autoPlayers = new Set(this.autoPlayers); const autoPlayers = new Set(this.autoPlayers);
autoPlayers.add(player); autoPlayers.add(player);

@ -49,6 +49,10 @@ export type BoardgameStateType = {
readonly currentPlayerName: string; readonly currentPlayerName: string;
withEmptyBoard(): BoardgameStateType; withEmptyBoard(): BoardgameStateType;
withAdditionalRow(): BoardgameStateType;
withoutAdditionalRow(): BoardgameStateType;
withAdditionalColumn(): BoardgameStateType;
withoutAdditionalColumn(): BoardgameStateType;
withAutoPlayer(player: Player): BoardgameStateType; withAutoPlayer(player: Player): BoardgameStateType;
withoutAutoPlayer(player: Player): BoardgameStateType; withoutAutoPlayer(player: Player): BoardgameStateType;
withMove(row: number, column: number): BoardgameStateType; withMove(row: number, column: number): BoardgameStateType;

@ -50,6 +50,10 @@ export const getButtonValues = (gameState: BoardgameStateType) => ({
"autoplayer-o-enable": gameState.withAutoPlayer(Player.O), "autoplayer-o-enable": gameState.withAutoPlayer(Player.O),
"autoplayer-o-disable": gameState.withoutAutoPlayer(Player.O), "autoplayer-o-disable": gameState.withoutAutoPlayer(Player.O),
"game-start": gameState.withEmptyBoard(), "game-start": gameState.withEmptyBoard(),
"add-row": gameState.withAdditionalRow(),
"remove-row": gameState.withoutAdditionalRow(),
"add-column": gameState.withAdditionalColumn(),
"remove-column": gameState.withoutAdditionalColumn(),
}); });
export type ButtonValues = ReturnType<typeof getButtonValues>; export type ButtonValues = ReturnType<typeof getButtonValues>;

@ -96,6 +96,12 @@ void t.test("computeAllSolutions", async (t) => {
t.matchOnlyStrict(Object.fromEntries(solutions.entries()), expectedSolutions); t.matchOnlyStrict(Object.fromEntries(solutions.entries()), expectedSolutions);
}; };
void t.test("too large boards", async (t) => {
t.throws(() => computeAllSolutions(4, 5, createRulesForSquares()), {
message: "Board is too large, solving requires too many computational resources",
});
});
void t.test("empty boards", async (t) => { void t.test("empty boards", async (t) => {
void t.test("0x0 board", async (t) => { void t.test("0x0 board", async (t) => {
checkSolutionsComplete(t, 0, 0, [], { checkSolutionsComplete(t, 0, 0, [], {

@ -60,6 +60,10 @@ export const getPreferredNextOutcome = (
}; };
export const computeAllSolutions = (rows: number, columns: number, rules: GameRules) => { export const computeAllSolutions = (rows: number, columns: number, rules: GameRules) => {
if (rows * columns > 9) {
throw new Error("Board is too large, solving requires too many computational resources");
}
const expectedOutcomesByBoard = new Map<string, ExpectedOutcome>(); const expectedOutcomesByBoard = new Map<string, ExpectedOutcome>();
const getExpectedOutcomeForBoard = (board: BoardType, currentPlayer: Player): ExpectedOutcome => { const getExpectedOutcomeForBoard = (board: BoardType, currentPlayer: Player): ExpectedOutcome => {

Loading…
Cancel
Save