Fix commit and reset actions crossing pointers by creating an action stack (will be used to send data to backend)

pull/14/head
MiguelMLorente 2024-12-05 10:46:38 +01:00
parent 0a0ca65e3b
commit 7c4a6f5603
8 changed files with 116 additions and 16 deletions

View File

@ -105,4 +105,8 @@ export class Cell {
throw Error("No adjacent exit or piece available to connect to");
}
}
public removePiece() {
this.placedPiece = undefined;
}
}

8
web/package-lock.json generated
View File

@ -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",
@ -4202,9 +4202,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": {

View File

@ -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",

View File

@ -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}
/>
<div className="right-panel">
<DiceSet

View File

@ -1,4 +1,4 @@
import { Cell, CellType, directions, Exit } from "interface";
import { Cell, CellType, directions, Exit, PieceId } from "interface";
import "./BoardCell.scss";
import { useState } from "react";
import { DieViewProps } from "../types/DieViewProps";
@ -14,6 +14,7 @@ export interface BoardCellProps {
matcher: (die: DieViewProps) => boolean,
) => DieViewProps | undefined;
setSpecialDieUsedInRound: React.Dispatch<React.SetStateAction<boolean>>;
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;

View File

@ -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<React.SetStateAction<boolean>>;
refreshBoardRender: () => void;
board: Cell[][];
placePieceActionStack: PlacePieceActionStack;
setPlacePieceActionStack: React.Dispatch<
React.SetStateAction<PlacePieceActionStack>
>;
}
const GameBoard = (props: GameBoardProps) => {
const { board } = props;
const { board, placePieceActionStack, setPlacePieceActionStack } = props;
return (
<div className="game-board">
{board.flatMap((row) =>
row.map((cell) => <BoardCell {...props} cell={cell} />),
{board.flatMap((row, rowIndex) =>
row.map((cell, colIndex) => (
<BoardCell
{...props}
cell={cell}
placePieceActionHandler={(pieceId, rotation) => {
placePieceActionStack.executeAction(
new PlacePieceAction(pieceId, rotation, rowIndex, colIndex),
board,
);
setPlacePieceActionStack(placePieceActionStack);
}}
/>
)),
)}
</div>
);

View File

@ -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();
}
}

View File

@ -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 = [];
}
}