diff --git a/src/backend/components/boardgame.tsx b/src/backend/components/boardgame.tsx
index 7392f12..8e15226 100644
--- a/src/backend/components/boardgame.tsx
+++ b/src/backend/components/boardgame.tsx
@@ -1,7 +1,7 @@
-import { sequence } from "../../shared/array-utils.ts";
-import { BoardgameStateType, CurrentOutcome } from "../../shared/datatypes.ts";
+import { sequence } from "../../shared/utils/array-utils.ts";
+import { BoardgameStateType, CurrentOutcome } from "../../shared/datatypes/types.ts";
import { ButtonValues, getButtonValues, getCellDisplayData, getDisplayStates } from "../../shared/display.ts";
-import { GameVariantName, gamesRules } from "../../shared/rules.ts";
+import { GameVariantName, gamesRules } from "../../shared/game-variants/index.ts";
const getSubmitAttributes = (key: string, targetState: BoardgameStateType) => ({
type: "submit" as const,
diff --git a/src/backend/components/counter.tsx b/src/backend/components/counter.tsx
new file mode 100644
index 0000000..f1e8097
--- /dev/null
+++ b/src/backend/components/counter.tsx
@@ -0,0 +1,22 @@
+import type { Request } from "express";
+import { safeGetQueryValue } from "../utils.ts";
+
+export const getCounterHtml = (req: Request, key: string) => {
+ const counter = parseInt(safeGetQueryValue(req, "a") ?? "0", 10);
+ return (
+ <>
+
{" "}
+ Value of "{key}":{" "}
+
+ {counter}
+
+ >
+ );
+};
diff --git a/src/backend/main/boardgame-handler.ts b/src/backend/main/boardgame-handler.ts
index d78397e..6d70453 100644
--- a/src/backend/main/boardgame-handler.ts
+++ b/src/backend/main/boardgame-handler.ts
@@ -1,9 +1,9 @@
import type { Request, Response } from "express";
import { rewriteQueryParamsWith, safeGetQueryValue } from "../utils.ts";
-import { BoardgameState } from "../../shared/boardgame-state.ts";
+import { BoardgameState } from "../../shared/datatypes/boardgame-state.ts";
import { getBoardgameHtml } from "../components/boardgame.tsx";
-import { GameVariantName, gamesRules } from "../../shared/rules.ts";
-import { getTargetGameState } from "../../shared/boardgame.ts";
+import { GameVariantName, gamesRules } from "../../shared/game-variants/index.ts";
+import { getTargetGameState } from "../../shared/gameplay/boardgame.ts";
// Returns nothing if query parameter is uninitialized and a redirect was made,
// or component if query parameter is initialized.
diff --git a/src/backend/main/index.tsx b/src/backend/main/index.tsx
index 3fe1f92..bc7bd3f 100644
--- a/src/backend/main/index.tsx
+++ b/src/backend/main/index.tsx
@@ -1,15 +1,13 @@
import type { RequestHandler } from "express";
-import { safeGetQueryValue, sendHtml } from "../utils.ts";
+import { sendHtml } from "../utils.ts";
import { handleBoardgame } from "./boardgame-handler.ts";
+import { getCounterHtml } from "../components/counter.tsx";
export const mainPageHandler: RequestHandler = (req, res) => {
- const counters = {
- a: parseInt(safeGetQueryValue(req, "a") ?? "0", 10),
- b: parseInt(safeGetQueryValue(req, "b") ?? "0", 10),
- };
-
const board1 = handleBoardgame(req, res, "tictactoe1", "tictactoe");
if (!board1) {
+ // No return value from handleBoardgame means that it redirected user to another URL,
+ // and we no longer need to render anything.
return;
}
@@ -40,36 +38,8 @@ export const mainPageHandler: RequestHandler = (req, res) => {
supports history navigation (you can go back & forward and see how the state changes).
- {(["a", "b"] as const).map((key) => (
- -
- {" "}
- Value of "{key}":{" "}
-
- {counters[key]}
-
-
- ))}
+ - {getCounterHtml(req, "a")}
+ - {getCounterHtml(req, "b")}
diff --git a/src/frontend/components/board-game.ts b/src/frontend/components/board-game.ts
index cbf553d..4a4eeff 100644
--- a/src/frontend/components/board-game.ts
+++ b/src/frontend/components/board-game.ts
@@ -1,11 +1,11 @@
-import { BoardgameState } from "../../shared/boardgame-state.ts";
-import { getTargetGameState } from "../../shared/boardgame.ts";
-import { CurrentOutcome } from "../../shared/datatypes.ts";
+import { BoardgameState } from "../../shared/datatypes/boardgame-state.ts";
+import { getTargetGameState } from "../../shared/gameplay/boardgame.ts";
+import { CurrentOutcome } from "../../shared/datatypes/types.ts";
import { ButtonValues, getButtonValues, getCellDisplayData, getDisplayStates } from "../../shared/display.ts";
-import { GameVariantName, gamesRules } from "../../shared/rules.ts";
-import { replaceLocation } from "../lib/navigation-utils.ts";
-import { TrackingTools } from "../lib/query-tracking-utils.ts";
-import { updateWithQueryParams } from "../lib/url-utils.ts";
+import { GameVariantName, gamesRules } from "../../shared/game-variants/index.ts";
+import { replaceLocation } from "../utils/navigation-utils.ts";
+import { TrackingTools } from "../utils/query-tracking-utils.ts";
+import { updateWithQueryParams } from "../utils/url-utils.ts";
export class BoardGameComponent extends HTMLElement {
private readonly trackingTools = new TrackingTools(this);
diff --git a/src/frontend/components/progressive-form.ts b/src/frontend/components/progressive-form.ts
index dce7a65..e7f998f 100644
--- a/src/frontend/components/progressive-form.ts
+++ b/src/frontend/components/progressive-form.ts
@@ -1,5 +1,5 @@
-import { goToLocation } from "../lib/navigation-utils.ts";
-import { updateWithQueryParams } from "../lib/url-utils.ts";
+import { goToLocation } from "../utils/navigation-utils.ts";
+import { updateWithQueryParams } from "../utils/url-utils.ts";
const submitListener = function (this: HTMLFormElement, e: SubmitEvent) {
goToLocation(updateWithQueryParams(new URL(this.action), new FormData(this, e.submitter)));
diff --git a/src/frontend/components/reactive-button.ts b/src/frontend/components/reactive-button.ts
index da1f59b..faed76c 100644
--- a/src/frontend/components/reactive-button.ts
+++ b/src/frontend/components/reactive-button.ts
@@ -1,4 +1,4 @@
-import { TrackingTools } from "../lib/query-tracking-utils.ts";
+import { TrackingTools } from "../utils/query-tracking-utils.ts";
export class ReactiveButton extends HTMLButtonElement {
private readonly trackingTools = new TrackingTools(this);
diff --git a/src/frontend/components/reactive-span.ts b/src/frontend/components/reactive-span.ts
index 8f68cb3..f930971 100644
--- a/src/frontend/components/reactive-span.ts
+++ b/src/frontend/components/reactive-span.ts
@@ -1,4 +1,4 @@
-import { TrackingTools } from "../lib/query-tracking-utils.ts";
+import { TrackingTools } from "../utils/query-tracking-utils.ts";
export class ReactiveSpan extends HTMLSpanElement {
private readonly trackingTools = new TrackingTools(this);
diff --git a/src/frontend/lib/navigation-utils.ts b/src/frontend/utils/navigation-utils.ts
similarity index 100%
rename from src/frontend/lib/navigation-utils.ts
rename to src/frontend/utils/navigation-utils.ts
diff --git a/src/frontend/lib/query-tracking-utils.ts b/src/frontend/utils/query-tracking-utils.ts
similarity index 100%
rename from src/frontend/lib/query-tracking-utils.ts
rename to src/frontend/utils/query-tracking-utils.ts
diff --git a/src/frontend/lib/url-utils.ts b/src/frontend/utils/url-utils.ts
similarity index 100%
rename from src/frontend/lib/url-utils.ts
rename to src/frontend/utils/url-utils.ts
diff --git a/src/shared/board.spec.ts b/src/shared/datatypes/board.spec.ts
similarity index 99%
rename from src/shared/board.spec.ts
rename to src/shared/datatypes/board.spec.ts
index 2b027ef..f754f9e 100644
--- a/src/shared/board.spec.ts
+++ b/src/shared/datatypes/board.spec.ts
@@ -1,7 +1,7 @@
import t, { type Test } from "tap";
import { Board } from "./board.ts";
-import { SquareState } from "./datatypes.ts";
+import { SquareState } from "./types.ts";
void t.test("Serialize / deserialize", async (t) => {
const createAndCheckBoard = (t: Test, serialized: string) => {
diff --git a/src/shared/board.ts b/src/shared/datatypes/board.ts
similarity index 96%
rename from src/shared/board.ts
rename to src/shared/datatypes/board.ts
index e7e6327..40885af 100644
--- a/src/shared/board.ts
+++ b/src/shared/datatypes/board.ts
@@ -1,5 +1,5 @@
-import { repeat } from "./array-utils.ts";
-import { BoardType, SquareState, formatSquareState, parseSquareState } from "./datatypes.ts";
+import { repeat } from "../utils/array-utils.ts";
+import { BoardType, SquareState, formatSquareState, parseSquareState } from "./types.ts";
export class Board implements BoardType {
// State should be immutable
diff --git a/src/shared/boardgame-state.spec.ts b/src/shared/datatypes/boardgame-state.spec.ts
similarity index 99%
rename from src/shared/boardgame-state.spec.ts
rename to src/shared/datatypes/boardgame-state.spec.ts
index b056cee..a516120 100644
--- a/src/shared/boardgame-state.spec.ts
+++ b/src/shared/datatypes/boardgame-state.spec.ts
@@ -1,7 +1,7 @@
import t, { type Test } from "tap";
import { BoardgameState } from "./boardgame-state.ts";
-import { Player, SquareState } from "./datatypes.ts";
+import { Player, SquareState } from "./types.ts";
const createAndCheckBoardgameState = (t: Test, serialized: string) => {
const boardgameState = BoardgameState.fromSerialized(serialized);
diff --git a/src/shared/boardgame-state.ts b/src/shared/datatypes/boardgame-state.ts
similarity index 98%
rename from src/shared/boardgame-state.ts
rename to src/shared/datatypes/boardgame-state.ts
index 13c5553..71ca590 100644
--- a/src/shared/boardgame-state.ts
+++ b/src/shared/datatypes/boardgame-state.ts
@@ -1,4 +1,4 @@
-import { compact } from "./array-utils.ts";
+import { compact } from "../utils/array-utils.ts";
import { Board } from "./board.ts";
import {
BoardType,
@@ -9,7 +9,7 @@ import {
getNextPlayer,
getOccupiedStateByPlayer,
parsePlayer,
-} from "./datatypes.ts";
+} from "./types.ts";
const parseDimensions = (dimensionsSerialized: string | undefined) => {
if (!dimensionsSerialized) {
diff --git a/src/shared/datatypes.spec.ts b/src/shared/datatypes/datatypes.spec.ts
similarity index 99%
rename from src/shared/datatypes.spec.ts
rename to src/shared/datatypes/datatypes.spec.ts
index a506cad..e65bbd3 100644
--- a/src/shared/datatypes.spec.ts
+++ b/src/shared/datatypes/datatypes.spec.ts
@@ -13,7 +13,7 @@ import {
getUndesiredFinalOutcomeByPlayer,
parsePlayer,
parseSquareState,
-} from "./datatypes.ts";
+} from "./types.ts";
// These unit tests are mostly useless because they're basically a tautology,
// a rephrasing of implementation in slightly different words.
diff --git a/src/shared/datatypes.ts b/src/shared/datatypes/types.ts
similarity index 98%
rename from src/shared/datatypes.ts
rename to src/shared/datatypes/types.ts
index 78120d2..36c0953 100644
--- a/src/shared/datatypes.ts
+++ b/src/shared/datatypes/types.ts
@@ -1,4 +1,4 @@
-import { unreachableString } from "./utils.ts";
+import { unreachableString } from "../utils/utils.ts";
export enum SquareState {
Unoccupied = 1, // so that all SquareState values are truthy
diff --git a/src/shared/display.ts b/src/shared/display.ts
index 1729941..a75028d 100644
--- a/src/shared/display.ts
+++ b/src/shared/display.ts
@@ -1,4 +1,4 @@
-import { BoardgameStateType, CurrentOutcome, Player, SquareState, formatSquareState } from "./datatypes.ts";
+import { BoardgameStateType, CurrentOutcome, Player, SquareState, formatSquareState } from "./datatypes/types.ts";
export const getDisplayStates = (gameState: BoardgameStateType, currentOutcome: CurrentOutcome) => ({
"outcome-winx": currentOutcome === CurrentOutcome.WinX,
diff --git a/src/shared/rules.ts b/src/shared/game-variants/index.ts
similarity index 100%
rename from src/shared/rules.ts
rename to src/shared/game-variants/index.ts
diff --git a/src/shared/tictactoe-rules.spec.ts b/src/shared/game-variants/tictactoe-rules.spec.ts
similarity index 98%
rename from src/shared/tictactoe-rules.spec.ts
rename to src/shared/game-variants/tictactoe-rules.spec.ts
index 5b092cf..0caded1 100644
--- a/src/shared/tictactoe-rules.spec.ts
+++ b/src/shared/game-variants/tictactoe-rules.spec.ts
@@ -1,7 +1,7 @@
import t from "tap";
-import { Board } from "./board.ts";
-import { CurrentOutcome, SquareState } from "./datatypes.ts";
+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) => {
diff --git a/src/shared/tictactoe-rules.ts b/src/shared/game-variants/tictactoe-rules.ts
similarity index 96%
rename from src/shared/tictactoe-rules.ts
rename to src/shared/game-variants/tictactoe-rules.ts
index 5d78975..ed3f108 100644
--- a/src/shared/tictactoe-rules.ts
+++ b/src/shared/game-variants/tictactoe-rules.ts
@@ -1,4 +1,4 @@
-import { CurrentOutcome, GameRules, SquareState } from "./datatypes.ts";
+import { CurrentOutcome, GameRules, SquareState } from "../datatypes/types.ts";
export const getSequenceOutcome = (sequence: SquareState[]) => {
for (let i = 1; i < sequence.length; i++) {
diff --git a/src/shared/tictactoe.test.ts b/src/shared/game-variants/tictactoe.test.ts
similarity index 98%
rename from src/shared/tictactoe.test.ts
rename to src/shared/game-variants/tictactoe.test.ts
index 546f9e2..dc23aa1 100644
--- a/src/shared/tictactoe.test.ts
+++ b/src/shared/game-variants/tictactoe.test.ts
@@ -1,9 +1,9 @@
import t, { Test } from "tap";
-import { Board } from "./board.ts";
-import { ExpectedOutcome, FinalOutcome, Opponent, Player, getOccupiedStateByPlayer } from "./datatypes.ts";
-import { createOpponent } from "./opponent.ts";
-import { computeAllSolutions } from "./solver.ts";
+import { Board } from "../datatypes/board.ts";
+import { ExpectedOutcome, FinalOutcome, Opponent, Player, getOccupiedStateByPlayer } from "../datatypes/types.ts";
+import { createOpponent } from "../gameplay/opponent.ts";
+import { computeAllSolutions } from "../gameplay/solver.ts";
import { rules } from "./tictactoe-rules.ts";
void t.test("computeAllSolutions", async (t) => {
diff --git a/src/shared/boardgame.ts b/src/shared/gameplay/boardgame.ts
similarity index 84%
rename from src/shared/boardgame.ts
rename to src/shared/gameplay/boardgame.ts
index 802d1be..9655ca0 100644
--- a/src/shared/boardgame.ts
+++ b/src/shared/gameplay/boardgame.ts
@@ -1,5 +1,5 @@
-import { BoardgameState } from "./boardgame-state.ts";
-import { CurrentOutcome, GameRules } from "./datatypes.ts";
+import { BoardgameState } from "../datatypes/boardgame-state.ts";
+import { CurrentOutcome, GameRules } from "../datatypes/types.ts";
import { createOpponent } from "./opponent.ts";
import { getAllSolutions } from "./solver-cache.ts";
diff --git a/src/shared/opponent.spec.ts b/src/shared/gameplay/opponent.spec.ts
similarity index 96%
rename from src/shared/opponent.spec.ts
rename to src/shared/gameplay/opponent.spec.ts
index 10891a7..0e1c93c 100644
--- a/src/shared/opponent.spec.ts
+++ b/src/shared/gameplay/opponent.spec.ts
@@ -1,6 +1,6 @@
import t from "tap";
-import { Board } from "./board.ts";
-import { ExpectedOutcome, FinalOutcome, Player, getOccupiedStateByPlayer } from "./datatypes.ts";
+import { Board } from "../datatypes/board.ts";
+import { ExpectedOutcome, FinalOutcome, Player, getOccupiedStateByPlayer } from "../datatypes/types.ts";
import { createOpponent } from "./opponent.ts";
void t.test("createOpponent", async (t) => {
diff --git a/src/shared/opponent.ts b/src/shared/gameplay/opponent.ts
similarity index 97%
rename from src/shared/opponent.ts
rename to src/shared/gameplay/opponent.ts
index be7601d..b27007a 100644
--- a/src/shared/opponent.ts
+++ b/src/shared/gameplay/opponent.ts
@@ -1,4 +1,4 @@
-import { ExpectedOutcome, Opponent, SquareState, getOccupiedStateByPlayer } from "./datatypes.ts";
+import { ExpectedOutcome, Opponent, SquareState, getOccupiedStateByPlayer } from "../datatypes/types.ts";
export const createOpponent = (outcomesByBoard: Map): Opponent => {
const getNextMove: Opponent["getNextMove"] = (board, currentPlayer) => {
diff --git a/src/shared/solver-cache.ts b/src/shared/gameplay/solver-cache.ts
similarity index 92%
rename from src/shared/solver-cache.ts
rename to src/shared/gameplay/solver-cache.ts
index 2f613be..6fa1467 100644
--- a/src/shared/solver-cache.ts
+++ b/src/shared/gameplay/solver-cache.ts
@@ -1,4 +1,4 @@
-import { ExpectedOutcome, GameRules } from "./datatypes.ts";
+import { ExpectedOutcome, GameRules } from "../datatypes/types.ts";
import { computeAllSolutions } from "./solver.ts";
const solverCache = new Map>>();
diff --git a/src/shared/solver.spec.ts b/src/shared/gameplay/solver.spec.ts
similarity index 99%
rename from src/shared/solver.spec.ts
rename to src/shared/gameplay/solver.spec.ts
index 78c8715..47df26e 100644
--- a/src/shared/solver.spec.ts
+++ b/src/shared/gameplay/solver.spec.ts
@@ -1,6 +1,6 @@
import t, { type Test } from "tap";
-import { CurrentOutcome, ExpectedOutcome, FinalOutcome, GameRules, Player, SquareState } from "./datatypes.ts";
+import { CurrentOutcome, ExpectedOutcome, FinalOutcome, GameRules, Player, SquareState } from "../datatypes/types.ts";
import { computeAllSolutions, getPreferredNextOutcome } from "./solver.ts";
void t.test("getPreferredNextOutcome", async (t) => {
diff --git a/src/shared/solver.ts b/src/shared/gameplay/solver.ts
similarity index 98%
rename from src/shared/solver.ts
rename to src/shared/gameplay/solver.ts
index 8857c22..33e8c4e 100644
--- a/src/shared/solver.ts
+++ b/src/shared/gameplay/solver.ts
@@ -1,4 +1,4 @@
-import { Board } from "./board.ts";
+import { Board } from "../datatypes/board.ts";
import {
BoardType,
CurrentOutcome,
@@ -13,7 +13,7 @@ import {
getNextPlayer,
getOccupiedStateByPlayer,
getUndesiredFinalOutcomeByPlayer,
-} from "./datatypes.ts";
+} from "../datatypes/types.ts";
export const getPreferredNextOutcome = (
possibleNextOutcomes: ExpectedOutcome[],
diff --git a/src/shared/array-utils.spec.ts b/src/shared/utils/array-utils.spec.ts
similarity index 100%
rename from src/shared/array-utils.spec.ts
rename to src/shared/utils/array-utils.spec.ts
diff --git a/src/shared/array-utils.ts b/src/shared/utils/array-utils.ts
similarity index 100%
rename from src/shared/array-utils.ts
rename to src/shared/utils/array-utils.ts
diff --git a/src/shared/utils.ts b/src/shared/utils/utils.ts
similarity index 100%
rename from src/shared/utils.ts
rename to src/shared/utils/utils.ts