From b7561d804cfd5e8d62a06f689a4e4fc918229ad3 Mon Sep 17 00:00:00 2001 From: MiguelMLorente Date: Tue, 3 Dec 2024 23:03:28 +0100 Subject: [PATCH] Enable pieces rotation in dice set and board --- interface/constants/Direction.ts | 17 ++++++++ interface/constants/Pieces.ts | 6 +-- interface/types/Cell.ts | 10 +++-- interface/types/Piece.ts | 29 +++++++++++++- web/public/rotate-icon.png | Bin 0 -> 14454 bytes web/src/pages/game/GamePage.tsx | 1 + web/src/pages/game/components/BoardCell.scss | 12 ++++++ web/src/pages/game/components/BoardCell.tsx | 17 ++++++-- web/src/pages/game/components/DiceSet.scss | 22 +++++++++++ web/src/pages/game/components/DiceSet.tsx | 39 ++++++++++++++++--- web/src/pages/game/components/Die.scss | 14 +++++++ web/src/pages/game/components/Die.tsx | 20 +++++++--- web/src/pages/game/components/GameBoard.tsx | 2 + 13 files changed, 166 insertions(+), 23 deletions(-) create mode 100644 web/public/rotate-icon.png diff --git a/interface/constants/Direction.ts b/interface/constants/Direction.ts index 6bc4347..d44deda 100644 --- a/interface/constants/Direction.ts +++ b/interface/constants/Direction.ts @@ -11,3 +11,20 @@ export const directions: Direction[] = [ Direction.EAST, Direction.WEST, ]; + +export function rotateDirection( + initialDirection: Direction, + rotationAngle: 0 | 90 | 180 | 270, +): Direction { + const angleToDirectionMap: Record = { + [Direction.NORTH]: 0, + [Direction.EAST]: 90, + [Direction.SOUTH]: 180, + [Direction.WEST]: 270, + }; + const finalAngle = + (360 + angleToDirectionMap[initialDirection] - rotationAngle) % 360; + return (Object.keys(angleToDirectionMap) as Direction[]).find( + (direction) => angleToDirectionMap[direction] === finalAngle, + )!; +} diff --git a/interface/constants/Pieces.ts b/interface/constants/Pieces.ts index e71f635..e4bddc3 100644 --- a/interface/constants/Pieces.ts +++ b/interface/constants/Pieces.ts @@ -13,7 +13,6 @@ const STRAIGHT_RAIL: Piece = new Piece({ }, ], }); - const TURN_RAIL: Piece = new Piece({ useInternalTracks: false, trackDefinitions: [ @@ -166,7 +165,7 @@ const DEAD_END_STATION_ROAD: Piece = new Piece({ trackDefinitions: [ { startPoint: Direction.EAST, - type: TrackType.RAIL, + type: TrackType.ROAD, }, ], }); @@ -232,7 +231,6 @@ const FOUR_WAY_WITH_ONE_RAIL: Piece = new Piece({ }, ], }); - const FOUR_WAY_TURNING_CROSSING: Piece = new Piece({ useInternalTracks: true, internalNodeType: InternalNodeType.STATION, @@ -316,7 +314,7 @@ const FOUR_WAY_CROSS_ROAD: Piece = new Piece({ }, { startPoint: Direction.WEST, - type: TrackType.RAIL, + type: TrackType.ROAD, }, ], }); diff --git a/interface/types/Cell.ts b/interface/types/Cell.ts index 9bac0ba..6412cc5 100644 --- a/interface/types/Cell.ts +++ b/interface/types/Cell.ts @@ -1,8 +1,7 @@ import { CellType } from "../constants/CellType"; -import { Direction, directions } from "../constants/Direction"; +import { Direction } from "../constants/Direction"; import { ExitType } from "../constants/ExitType"; import { PieceId, pieceMap } from "../constants/Pieces"; -import { TrackType } from "../constants/TrackType"; import { Exit } from "./Exit"; import { ExternalNode } from "./ExternalNode"; import { InternalNode } from "./InternalNode"; @@ -33,9 +32,12 @@ export class Cell { return node!; } - public placePiece(pieceId: PieceId) { + /** + * @param rotation in degrees counter-clockwise + */ + public placePiece(pieceId: PieceId, rotation: 0 | 90 | 180 | 270) { if (this.placedPiece !== undefined) return; - const piece: Piece = pieceMap[pieceId]; + const piece: Piece = pieceMap[pieceId].rotate(rotation); this.validatePiecePlacement(piece); diff --git a/interface/types/Piece.ts b/interface/types/Piece.ts index f684cf4..953c04d 100644 --- a/interface/types/Piece.ts +++ b/interface/types/Piece.ts @@ -1,4 +1,4 @@ -import { Direction } from "../constants/Direction"; +import { Direction, rotateDirection } from "../constants/Direction"; import { TrackType } from "../constants/TrackType"; import { Cell } from "./Cell"; import { InternalNode } from "./InternalNode"; @@ -65,6 +65,33 @@ export class Piece { } } + rotate(rotation: 0 | 90 | 180 | 270): Piece { + return new Piece({ + useInternalTracks: this.internalNode !== undefined, + internalNodeType: this.internalNode?.type, + trackDefinitions: Array.from(this.tracks).map((track) => { + if (track.joinedPoints.secondPoint instanceof InternalNode) { + return { + type: track.type, + startPoint: rotateDirection( + track.joinedPoints.firstPoint, + rotation, + ), + }; + } else { + return { + type: track.type, + startPoint: rotateDirection( + track.joinedPoints.firstPoint, + rotation, + ), + endPoint: rotateDirection(track.joinedPoints.secondPoint, rotation), + }; + } + }), + }); + } + toPlacedPiece(cell: Cell) { return new PlacedPiece( new Set( diff --git a/web/public/rotate-icon.png b/web/public/rotate-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5af53cacb2d1bb2d8102f5023eb9da43174c7e32 GIT binary patch literal 14454 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 literal 0 HcmV?d00001 diff --git a/web/src/pages/game/GamePage.tsx b/web/src/pages/game/GamePage.tsx index 499bd51..4b06b3d 100644 --- a/web/src/pages/game/GamePage.tsx +++ b/web/src/pages/game/GamePage.tsx @@ -16,6 +16,7 @@ const GamePage = () => { pieceId: getRandomPieceId(), isSelected: false, isDisabled: false, + rotation: 0, }; }), ); diff --git a/web/src/pages/game/components/BoardCell.scss b/web/src/pages/game/components/BoardCell.scss index a7822ed..1250a30 100644 --- a/web/src/pages/game/components/BoardCell.scss +++ b/web/src/pages/game/components/BoardCell.scss @@ -15,6 +15,18 @@ &:nth-child(3) { top: -40px; } + + &.rotate-90 { + transform: rotate(-90deg); + } + + &.rotate-180 { + transform: rotate(180deg); + } + + &.rotate-270 { + transform: rotate(90deg); + } } &.house { diff --git a/web/src/pages/game/components/BoardCell.tsx b/web/src/pages/game/components/BoardCell.tsx index 220e7f6..3170438 100644 --- a/web/src/pages/game/components/BoardCell.tsx +++ b/web/src/pages/game/components/BoardCell.tsx @@ -1,5 +1,6 @@ import { Cell, directions, Exit, PieceId } from "interface"; import "./BoardCell.scss"; +import { useState } from "react"; export interface BoardCellProps { cell: Cell; @@ -7,6 +8,7 @@ export interface BoardCellProps { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }[]; setDice: React.Dispatch< React.SetStateAction< @@ -14,6 +16,7 @@ export interface BoardCellProps { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }[] > >; @@ -22,11 +25,15 @@ export interface BoardCellProps { const BoardCell = (props: BoardCellProps) => { const { cell, dice, setDice, refreshBoardRender } = props; + const [pieceRotationAngle, setPieceRotationAngle] = useState(0); const handleBoardCellClick = () => { const selectedDie = dice.find((die) => die.isSelected); if (!selectedDie) return; try { - cell.placePiece(selectedDie.pieceId); + cell.placePiece( + selectedDie.pieceId, + selectedDie.rotation as 0 | 90 | 180 | 270, + ); } catch (error) { console.log(error); return; @@ -36,12 +43,13 @@ const BoardCell = (props: BoardCellProps) => { return die !== selectedDie ? die : { - pieceId: selectedDie.pieceId, + ...selectedDie, isSelected: false, isDisabled: true, }; }), ); + setPieceRotationAngle(selectedDie.rotation); refreshBoardRender(); }; @@ -64,7 +72,10 @@ const BoardCell = (props: BoardCellProps) => { ); })} {cell.placedPiece && ( - + )} ); diff --git a/web/src/pages/game/components/DiceSet.scss b/web/src/pages/game/components/DiceSet.scss index 1152b08..87776c6 100644 --- a/web/src/pages/game/components/DiceSet.scss +++ b/web/src/pages/game/components/DiceSet.scss @@ -1,3 +1,25 @@ +.dice-set-actions { + display: flex; + flex-direction: row; + padding: 50px 50px 0; + width: max-content; + margin: auto; + + img { + border: 3px solid green; + box-shadow: 0 0 10px green; + border-radius: 20%; + padding: 5px; + width: 80px; + height: 80px; + margin-right: 10px; + + &.icon-inverted { + transform: scaleX(-1); + } + } +} + .dice-set { display: grid; grid-template-columns: repeat(2, auto); diff --git a/web/src/pages/game/components/DiceSet.tsx b/web/src/pages/game/components/DiceSet.tsx index 8e10fe0..1779190 100644 --- a/web/src/pages/game/components/DiceSet.tsx +++ b/web/src/pages/game/components/DiceSet.tsx @@ -1,12 +1,14 @@ import { PieceId } from "interface"; import "./DiceSet.scss"; import Die from "./Die"; +import React from "react"; export interface DiceSetProps { dice: { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }[]; setDice: React.Dispatch< React.SetStateAction< @@ -14,6 +16,7 @@ export interface DiceSetProps { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }[] > >; @@ -24,6 +27,7 @@ const DiceSet = (props: DiceSetProps) => { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }) => { if (die.isDisabled) return; const newDiceState = dice.map((oldDie) => { @@ -36,12 +40,37 @@ const DiceSet = (props: DiceSetProps) => { setDice(newDiceState); }; + const handleRotateButton = (rotation: number) => { + const newDiceState = dice.map((die) => { + if (!die.isSelected) return die; + const rotationAngle = (die.rotation + rotation + 360) % 360; + return { + ...die, + rotation: rotationAngle, + }; + }); + setDice(newDiceState); + }; + return ( -
- {dice.map((die) => ( - - ))} -
+ +
+ handleRotateButton(-90)} + > + handleRotateButton(90)} + > +
+
+ {dice.map((die) => ( + + ))} +
+
); }; diff --git a/web/src/pages/game/components/Die.scss b/web/src/pages/game/components/Die.scss index d96f7f8..b3f4195 100644 --- a/web/src/pages/game/components/Die.scss +++ b/web/src/pages/game/components/Die.scss @@ -15,4 +15,18 @@ &.disabled { box-shadow: 0 0 20px grey; } + + &.rotate-90 { + transform: rotate(-90deg); + } + + &.rotate-180 { + transform: rotate(180deg); + } + + &.rotate-270 { + transform: rotate(90deg); + } + + transition: transform 0.5s cubic-bezier(.47,1.64,.41,.8); } \ No newline at end of file diff --git a/web/src/pages/game/components/Die.tsx b/web/src/pages/game/components/Die.tsx index f6644b0..ba8474f 100644 --- a/web/src/pages/game/components/Die.tsx +++ b/web/src/pages/game/components/Die.tsx @@ -2,23 +2,31 @@ import { PieceId } from "interface"; import "./Die.scss"; interface DieProps { - die: { pieceId: PieceId; isSelected: boolean; isDisabled: boolean }; + die: { + pieceId: PieceId; + isSelected: boolean; + isDisabled: boolean; + rotation: number; + }; handleDieClick: (die: { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }) => void; } const Die = (props: DieProps) => { const { die, handleDieClick } = props; - const { pieceId, isSelected, isDisabled } = die; + const { pieceId, isSelected, isDisabled, rotation } = die; + const className = + "dice" + + (isSelected ? " selected" : "") + + (isDisabled ? " disabled" : "") + + ` rotate-${rotation}`; return ( -
handleDieClick(die)} - > +
handleDieClick(die)}>
); diff --git a/web/src/pages/game/components/GameBoard.tsx b/web/src/pages/game/components/GameBoard.tsx index a713e7d..0997041 100644 --- a/web/src/pages/game/components/GameBoard.tsx +++ b/web/src/pages/game/components/GameBoard.tsx @@ -8,6 +8,7 @@ export interface GameBoardProps { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }[]; setDice: React.Dispatch< React.SetStateAction< @@ -15,6 +16,7 @@ export interface GameBoardProps { pieceId: PieceId; isSelected: boolean; isDisabled: boolean; + rotation: number; }[] > >;