unified button logic between backend and frontend

main
Inga 🏳‍🌈 4 days ago
parent 15b63eefcf
commit de52499d6f
  1. 27
      src/backend/components/boardgame.tsx
  2. 6
      src/backend/main/index.tsx
  3. 33
      src/frontend/components/board-game.ts
  4. 10
      src/shared/display.ts
  5. 2
      tsconfig.json

@ -1,6 +1,6 @@
import { sequence } from "../../shared/array-utils.ts"; import { sequence } from "../../shared/array-utils.ts";
import { BoardgameStateType, CurrentOutcome, GameRules, Player } from "../../shared/datatypes.ts"; import { BoardgameStateType, CurrentOutcome, GameRules } from "../../shared/datatypes.ts";
import { getCellDisplayData, getDisplayStates } from "../../shared/display.ts"; import { ButtonValues, getButtonValues, getCellDisplayData, getDisplayStates } from "../../shared/display.ts";
const getSubmitAttributes = (key: string, targetState: BoardgameStateType) => ({ const getSubmitAttributes = (key: string, targetState: BoardgameStateType) => ({
type: "submit" as const, type: "submit" as const,
@ -30,15 +30,26 @@ const getCellHtml = ({
); );
}; };
const getGenericButtonAttributes = (
kind: keyof ButtonValues,
{ key, buttonValues }: { key: string; buttonValues: ButtonValues },
) => ({
...getSubmitAttributes(key, buttonValues[kind]),
className: kind,
});
export const getBoardgameHtml = (key: string, gameState: BoardgameStateType, rules: GameRules) => { export const getBoardgameHtml = (key: string, gameState: BoardgameStateType, rules: GameRules) => {
const currentOutcome = gameState.board ? rules.getBoardOutcome(gameState.board) : CurrentOutcome.Undecided; const currentOutcome = gameState.board ? rules.getBoardOutcome(gameState.board) : CurrentOutcome.Undecided;
const buttonValues = getButtonValues(gameState);
const gameClasses = getDisplayStates(gameState, currentOutcome); const gameClasses = getDisplayStates(gameState, currentOutcome);
const gameActiveClassNames = Object.entries(gameClasses) const gameActiveClassNames = Object.entries(gameClasses)
.filter(([, value]) => value) .filter(([, value]) => value)
.map(([name]) => name); .map(([name]) => name);
return ( return (
<board-game track={key} class={gameActiveClassNames.join(" ")}> <board-game track={key} className={gameActiveClassNames.join(" ")}>
<form method="post" is="progressive-form"> <form method="post" is="progressive-form">
<p> <p>
Current player: <span class="current-player-name">{gameState.currentPlayerName}</span> Current player: <span class="current-player-name">{gameState.currentPlayerName}</span>
@ -64,34 +75,34 @@ export const getBoardgameHtml = (key: string, gameState: BoardgameStateType, rul
<p class="when-autoplayer-x-disabled"> <p class="when-autoplayer-x-disabled">
Currently X moves are made manually.{" "} Currently X moves are made manually.{" "}
<button class="autoplayer-x-enable" {...getSubmitAttributes(key, gameState.withAutoPlayer(Player.X))}> <button {...getGenericButtonAttributes("autoplayer-x-enable", { key, buttonValues })}>
Make computer play for X. Make computer play for X.
</button> </button>
</p> </p>
<p class="when-autoplayer-x-enabled"> <p class="when-autoplayer-x-enabled">
Currently X moves are made by computer.{" "} Currently X moves are made by computer.{" "}
<button class="autoplayer-x-disable" {...getSubmitAttributes(key, gameState.withoutAutoPlayer(Player.X))}> <button {...getGenericButtonAttributes("autoplayer-x-disable", { key, buttonValues })}>
Make them manually. Make them manually.
</button> </button>
</p> </p>
<p class="when-autoplayer-o-disabled"> <p class="when-autoplayer-o-disabled">
Currently O moves are made manually.{" "} Currently O moves are made manually.{" "}
<button class="autoplayer-o-enable" {...getSubmitAttributes(key, gameState.withAutoPlayer(Player.O))}> <button {...getGenericButtonAttributes("autoplayer-o-enable", { key, buttonValues })}>
Make computer play for O. Make computer play for O.
</button> </button>
</p> </p>
<p class="when-autoplayer-o-enabled"> <p class="when-autoplayer-o-enabled">
Currently O moves are made by computer.{" "} Currently O moves are made by computer.{" "}
<button class="autoplayer-o-disable" {...getSubmitAttributes(key, gameState.withoutAutoPlayer(Player.O))}> <button {...getGenericButtonAttributes("autoplayer-o-disable", { key, buttonValues })}>
Make them manually. Make them manually.
</button> </button>
</p> </p>
<p> <p>
<button type="submit" class="game-start" name={key} value={gameState.withEmptyBoard().serialize()}> <button {...getGenericButtonAttributes("game-start", { key, buttonValues })}>
<span class="when-game-not-in-progress">Start game</span> <span class="when-game-not-in-progress">Start game</span>
<span class="when-game-in-progress">Restart game</span> <span class="when-game-in-progress">Restart game</span>
</button> </button>

@ -13,6 +13,11 @@ export const mainPageHandler: RequestHandler = (req, res) => {
return; return;
} }
const board2 = handleBoardgame(req, res, "tictactoe2");
if (!board2) {
return;
}
sendHtml( sendHtml(
res, res,
<html> <html>
@ -68,6 +73,7 @@ export const mainPageHandler: RequestHandler = (req, res) => {
</ul> </ul>
</section> </section>
<section>{board1}</section> <section>{board1}</section>
<section>{board2}</section>
</body> </body>
</html>, </html>,
); );

@ -1,7 +1,7 @@
import { BoardgameState } from "../../shared/boardgame-state.ts"; import { BoardgameState } from "../../shared/boardgame-state.ts";
import { getTargetGameState } from "../../shared/boardgame.ts"; import { getTargetGameState } from "../../shared/boardgame.ts";
import { CurrentOutcome, Player } from "../../shared/datatypes.ts"; import { CurrentOutcome } from "../../shared/datatypes.ts";
import { getCellDisplayData, getDisplayStates } from "../../shared/display.ts"; import { ButtonValues, getButtonValues, getCellDisplayData, getDisplayStates } from "../../shared/display.ts";
import { gamesRules } from "../../shared/rules.ts"; import { gamesRules } from "../../shared/rules.ts";
import { replaceLocation } from "../lib/navigation-utils.ts"; import { replaceLocation } from "../lib/navigation-utils.ts";
import { TrackingTools } from "../lib/query-tracking-utils.ts"; import { TrackingTools } from "../lib/query-tracking-utils.ts";
@ -24,37 +24,26 @@ export class BoardGameComponent extends HTMLElement {
return; return;
} }
const currentOutcome = gameState.board ? rules.getBoardOutcome(gameState.board) : CurrentOutcome.Undecided;
const displayStates = getDisplayStates(gameState, currentOutcome);
for (const [className, shouldEnable] of Object.entries(displayStates)) {
this.classList.toggle(className, shouldEnable);
}
for (const playerNameElement of this.querySelectorAll(".current-player-name")) { for (const playerNameElement of this.querySelectorAll(".current-player-name")) {
if ((playerNameElement as HTMLElement).innerText !== gameState.currentPlayerName) { if ((playerNameElement as HTMLElement).innerText !== gameState.currentPlayerName) {
(playerNameElement as HTMLElement).innerText = gameState.currentPlayerName; (playerNameElement as HTMLElement).innerText = gameState.currentPlayerName;
} }
} }
for (const button of this.querySelectorAll("button.autoplayer-x-enable")) { const buttonValues = getButtonValues(gameState);
(button as HTMLButtonElement).value = gameState.withAutoPlayer(Player.X).serialize(); for (const button of this.querySelectorAll("button")) {
for (const className of button.classList) {
if (Object.hasOwn(buttonValues, className)) {
button.value = buttonValues[className as keyof ButtonValues].serialize();
} }
for (const button of this.querySelectorAll("button.autoplayer-x-disable")) {
(button as HTMLButtonElement).value = gameState.withoutAutoPlayer(Player.X).serialize();
} }
for (const button of this.querySelectorAll("button.autoplayer-o-enable")) {
(button as HTMLButtonElement).value = gameState.withAutoPlayer(Player.O).serialize();
} }
for (const button of this.querySelectorAll("button.autoplayer-o-disable")) { const currentOutcome = gameState.board ? rules.getBoardOutcome(gameState.board) : CurrentOutcome.Undecided;
(button as HTMLButtonElement).value = gameState.withoutAutoPlayer(Player.O).serialize();
}
for (const button of this.querySelectorAll("button.game-start")) { const displayStates = getDisplayStates(gameState, currentOutcome);
(button as HTMLButtonElement).value = gameState.withEmptyBoard().serialize(); for (const [className, shouldEnable] of Object.entries(displayStates)) {
this.classList.toggle(className, shouldEnable);
} }
for (const tbodyUntyped of this.querySelectorAll("tbody.game-board")) { for (const tbodyUntyped of this.querySelectorAll("tbody.game-board")) {

@ -43,3 +43,13 @@ export const getCellDisplayData = ({
text: formatSquareState(squareState), text: formatSquareState(squareState),
}; };
}; };
export const getButtonValues = (gameState: BoardgameStateType) => ({
"autoplayer-x-enable": gameState.withAutoPlayer(Player.X),
"autoplayer-x-disable": gameState.withoutAutoPlayer(Player.X),
"autoplayer-o-enable": gameState.withAutoPlayer(Player.O),
"autoplayer-o-disable": gameState.withoutAutoPlayer(Player.O),
"game-start": gameState.withEmptyBoard(),
});
export type ButtonValues = ReturnType<typeof getButtonValues>;

@ -8,6 +8,6 @@
"jsxImportSource": "preact", "jsxImportSource": "preact",
"module": "NodeNext", "module": "NodeNext",
"noEmit": true, "noEmit": true,
"target": "ES2020" "target": "ES2022"
} }
} }

Loading…
Cancel
Save