From 10269ec448d5d82cda158a468576f543def7d8ff Mon Sep 17 00:00:00 2001 From: MiguelMLorente Date: Thu, 5 Dec 2024 10:46:38 +0100 Subject: [PATCH] Fix commit and reset actions crossing pointers by creating an action stack (will be used to send data to backend) --- interface/types/Cell.ts | 4 ++ web/package-lock.json | 8 ++-- web/package.json | 2 +- web/src/pages/game/GamePage.tsx | 13 ++++-- web/src/pages/game/components/BoardCell.tsx | 9 ++--- web/src/pages/game/components/GameBoard.tsx | 24 +++++++++-- web/src/pages/game/types/PlacePieceAction.ts | 32 +++++++++++++++ .../pages/game/types/PlacePieceActionStack.ts | 40 +++++++++++++++++++ 8 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 web/src/pages/game/types/PlacePieceAction.ts create mode 100644 web/src/pages/game/types/PlacePieceActionStack.ts diff --git a/interface/types/Cell.ts b/interface/types/Cell.ts index c71bb2b..cd12587 100644 --- a/interface/types/Cell.ts +++ b/interface/types/Cell.ts @@ -105,4 +105,8 @@ export class Cell { throw Error("No adjacent exit or piece available to connect to"); } } + + public removePiece() { + this.placedPiece = undefined; + } } diff --git a/web/package-lock.json b/web/package-lock.json index ec0ff54..701bdeb 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -13,7 +13,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", - "@types/node": "^16.18.119", + "@types/node": "^17.0.29", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "interface": "file:../interface", @@ -4201,9 +4201,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "16.18.119", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.119.tgz", - "integrity": "sha512-ia7V9a2FnhUFfetng4/sRPBMTwHZUkPFY736rb1cg9AgG7MZdR97q7/nLR9om+sq5f1la9C857E0l/nrI0RiFQ==", + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", + "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", "license": "MIT" }, "node_modules/@types/node-forge": { diff --git a/web/package.json b/web/package.json index f240e25..b5e64b2 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", - "@types/node": "^16.18.119", + "@types/node": "^17.0.29", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "interface": "file:../interface", diff --git a/web/src/pages/game/GamePage.tsx b/web/src/pages/game/GamePage.tsx index 4ad8707..529308b 100644 --- a/web/src/pages/game/GamePage.tsx +++ b/web/src/pages/game/GamePage.tsx @@ -4,6 +4,7 @@ import DiceSet from "./components/DiceSet"; import "./GamePage.scss"; import { buildBoard, PieceId } from "interface"; import { DieViewProps } from "./types/DieViewProps"; +import { PlacePieceActionStack } from "./types/PlacePieceActionStack"; const GamePage = () => { const getRandomPieceId = () => { @@ -71,15 +72,19 @@ const GamePage = () => { return dice.concat(specialDice).find((die) => matcher(die)); }; - const [storedBoard, setStoredBoard] = useState(buildBoard()); const [board, setBoard] = useState(buildBoard()); const [id, setId] = useState(1); const refreshBoardRender = () => { setBoard(board); setId(id + 1); }; + + const [placePieceActionStack, setPlacePieceActionStack] = useState( + new PlacePieceActionStack(), + ); const resetBoard = () => { - setBoard(storedBoard); + placePieceActionStack.resetActions(board); + setBoard(board); modifyDieState( () => true, () => { @@ -90,7 +95,7 @@ const GamePage = () => { }; const commitBoard = () => { if (dice.some((die) => !die.isDisabled)) return; - setStoredBoard(board); + placePieceActionStack.commitActions(); setDice(getRandomDiceSet()); setSpecialDieUsedInRound(false); }; @@ -105,6 +110,8 @@ const GamePage = () => { setSpecialDieUsedInRound={setSpecialDieUsedInRound} refreshBoardRender={refreshBoardRender} board={board} + placePieceActionStack={placePieceActionStack} + setPlacePieceActionStack={setPlacePieceActionStack} />
boolean, ) => DieViewProps | undefined; setSpecialDieUsedInRound: React.Dispatch>; + placePieceActionHandler: (pieceId: PieceId, rotation: number) => void; } const BoardCell = (props: BoardCellProps) => { @@ -23,16 +24,14 @@ const BoardCell = (props: BoardCellProps) => { modifyDieState, fetchDie, setSpecialDieUsedInRound, + placePieceActionHandler, } = props; const [pieceRotationAngle, setPieceRotationAngle] = useState(0); const handleBoardCellClick = () => { const selectedDie = fetchDie((die) => die.isSelected); if (!selectedDie) return; try { - cell.placePiece( - selectedDie.pieceId, - selectedDie.rotation as 0 | 90 | 180 | 270, - ); + placePieceActionHandler(selectedDie.pieceId, selectedDie.rotation); } catch (error) { console.log(error); return; diff --git a/web/src/pages/game/components/GameBoard.tsx b/web/src/pages/game/components/GameBoard.tsx index 2ddfb81..94a94f2 100644 --- a/web/src/pages/game/components/GameBoard.tsx +++ b/web/src/pages/game/components/GameBoard.tsx @@ -2,6 +2,8 @@ import { Cell } from "interface"; import "./GameBoard.scss"; import BoardCell from "./BoardCell"; import { DieViewProps } from "../types/DieViewProps"; +import { PlacePieceActionStack } from "../types/PlacePieceActionStack"; +import { PlacePieceAction } from "../types/PlacePieceAction"; export interface GameBoardProps { modifyDieState: ( @@ -14,15 +16,31 @@ export interface GameBoardProps { setSpecialDieUsedInRound: React.Dispatch>; refreshBoardRender: () => void; board: Cell[][]; + placePieceActionStack: PlacePieceActionStack; + setPlacePieceActionStack: React.Dispatch< + React.SetStateAction + >; } const GameBoard = (props: GameBoardProps) => { - const { board } = props; + const { board, placePieceActionStack, setPlacePieceActionStack } = props; return (
- {board.flatMap((row) => - row.map((cell) => ), + {board.flatMap((row, rowIndex) => + row.map((cell, colIndex) => ( + { + placePieceActionStack.executeAction( + new PlacePieceAction(pieceId, rotation, rowIndex, colIndex), + board, + ); + setPlacePieceActionStack(placePieceActionStack); + }} + /> + )), )}
); diff --git a/web/src/pages/game/types/PlacePieceAction.ts b/web/src/pages/game/types/PlacePieceAction.ts new file mode 100644 index 0000000..6fe46d3 --- /dev/null +++ b/web/src/pages/game/types/PlacePieceAction.ts @@ -0,0 +1,32 @@ +import { Cell, PieceId } from "interface"; + +export class PlacePieceAction { + pieceId: PieceId; + rotation: number; + cell: { + row: number; + col: number; + }; + + constructor(pieceId: PieceId, rotation: number, row: number, col: number) { + this.pieceId = pieceId; + this.rotation = rotation; + this.cell = { + row: row, + col: col, + }; + } + + do(board: Cell[][]) { + const cell = board[this.cell.row][this.cell.col]; + cell.placePiece(this.pieceId, this.rotation as 0 | 90 | 180 | 270); + } + + undo(board: Cell[][]) { + const cell = board[this.cell.row][this.cell.col]; + if (!cell.placedPiece || cell.placedPiece.id !== this.pieceId) { + throw Error("Un-doing action error"); + } + cell.removePiece(); + } +} diff --git a/web/src/pages/game/types/PlacePieceActionStack.ts b/web/src/pages/game/types/PlacePieceActionStack.ts new file mode 100644 index 0000000..4a6841f --- /dev/null +++ b/web/src/pages/game/types/PlacePieceActionStack.ts @@ -0,0 +1,40 @@ +import { Cell } from "interface"; +import { PlacePieceAction } from "./PlacePieceAction"; + +export class PlacePieceActionStack { + readonly committedActions: PlacePieceAction[] = []; + inProgressActions: PlacePieceAction[] = []; + + executeAction(action: PlacePieceAction, board: Cell[][]) { + action.do(board); + this.inProgressActions.push(action); + } + + undoLastAction(board: Cell[][]) { + const lastAction = this.inProgressActions.pop(); + if (!lastAction) { + throw Error("No in progress action to undo"); + } + lastAction.undo(board); + } + + resetActions(board: Cell[][]) { + try { + while (true) { + this.undoLastAction(board); + } + } catch (error) { + if ( + !(error instanceof Error) || + error.message !== "No in progress action to undo" + ) { + throw error; + } + } + } + + commitActions() { + this.committedActions.push(...this.inProgressActions); + this.inProgressActions = []; + } +}