From d415a03dec99dc78b48dd4054c83e90a65584d18 Mon Sep 17 00:00:00 2001 From: Usman S Date: Thu, 20 Jun 2024 13:20:41 +0530 Subject: [PATCH 1/4] Basic choice selection --- package.json | 1 + pnpm-lock.yaml | 31 +++++++ src/App.tsx | 13 +-- src/components/DiceView.tsx | 6 +- src/components/InitialHome.tsx | 5 +- src/components/PageRoutes.tsx | 14 ++++ src/components/Pawns.tsx | 12 ++- src/main.tsx | 12 ++- src/pages/Choice.tsx | 146 +++++++++++++++++++++++++++++++++ src/pages/GameView.tsx | 9 ++ src/utils/atoms.ts | 12 +-- 11 files changed, 237 insertions(+), 24 deletions(-) create mode 100644 src/components/PageRoutes.tsx create mode 100644 src/pages/Choice.tsx create mode 100644 src/pages/GameView.tsx diff --git a/package.json b/package.json index 1958ba7..0abaf54 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react": "^18.2.0", "react-dice-roll": "^1.2.2", "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1", "react-use": "^17.5.0", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10ed5ec..f9a890e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.23.1 + version: 6.23.1(react-dom@18.3.1)(react@18.3.1) react-use: specifier: ^17.5.0 version: 17.5.0(react-dom@18.3.1)(react@18.3.1) @@ -1104,6 +1107,11 @@ packages: redux: 5.0.1 dev: false + /@remix-run/router@1.16.1: + resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==} + engines: {node: '>=14.0.0'} + dev: false + /@rollup/pluginutils@5.1.0: resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} @@ -3168,6 +3176,29 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false + /react-router-dom@6.23.1(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.16.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.23.1(react@18.3.1) + dev: false + + /react-router@6.23.1(react@18.3.1): + resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.16.1 + react: 18.3.1 + dev: false + /react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.3.1): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} diff --git a/src/App.tsx b/src/App.tsx index c29ca48..5f4b5fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,10 @@ -import { Provider } from "jotai"; import { Board } from "./components/Board"; -import { ludoStore } from "./utils/atoms"; -import { DevTools } from "jotai-devtools"; -import "jotai-devtools/styles.css"; function App() { return ( - - -
- -
-
+
+ +
); } diff --git a/src/components/DiceView.tsx b/src/components/DiceView.tsx index c70eb01..2b7d22d 100644 --- a/src/components/DiceView.tsx +++ b/src/components/DiceView.tsx @@ -3,7 +3,7 @@ import { PlayerColor } from "@/types"; import { bluePlayerAtom, colorToAtomMap, - colors, + colorsAtom, diceValueAtom, disabledDiceAtom, greenPlayerAtom, @@ -59,6 +59,7 @@ export function DiceView() { const player = useAtomValue(colorToAtomMap[playerTurn]); const [, setMoveLogs] = useAtom(moveLogsAtom); const { pawns } = useAtomValue(colorAtom[playerTurn]); + const colors = useAtomValue(colorsAtom); function handleRoll(value: number) { setDiceValue(value); @@ -125,7 +126,8 @@ export function DiceView() { function setNextPlayer() { setTimeout(() => { - const nextPlayer = colors[(colors.indexOf(playerTurn) + 1) % 4]; + const nextPlayer = + colors[(colors.indexOf(playerTurn) + 1) % colors.length]; setPlayerTurn(nextPlayer); }, 200); } diff --git a/src/components/InitialHome.tsx b/src/components/InitialHome.tsx index 586d18d..ba60834 100644 --- a/src/components/InitialHome.tsx +++ b/src/components/InitialHome.tsx @@ -2,7 +2,7 @@ import { cn } from "@/lib/utils"; import { type PlayerColor } from "@/types"; import { PawnPlace } from "./PawnPlace"; import { useAtomValue } from "jotai"; -import { playerTurnAtom } from "@/utils/atoms"; +import { colorsAtom, playerTurnAtom } from "@/utils/atoms"; interface InitialHomeProps { playerColor: PlayerColor; @@ -10,6 +10,8 @@ interface InitialHomeProps { export function InitialHome({ playerColor }: InitialHomeProps) { const playerTurn = useAtomValue(playerTurnAtom); + const playerColors = useAtomValue(colorsAtom); + const colors = { bg: { red: "bg-red-500", @@ -28,6 +30,7 @@ export function InitialHome({ playerColor }: InitialHomeProps) { return (
+ + } /> + } /> + + + ); +} diff --git a/src/components/Pawns.tsx b/src/components/Pawns.tsx index 5c2b419..6f4e81f 100644 --- a/src/components/Pawns.tsx +++ b/src/components/Pawns.tsx @@ -3,15 +3,15 @@ import { type PlayerColor } from "@/types"; import { diceValueAtom, playerTurnAtom, - colors as playerColors, disabledDiceAtom, moveLogsAtom, colorToAtomMap, ludoStore, + colorsAtom, } from "@/utils/atoms"; import { Pawn } from "@/utils/pawn-controller"; import { motion, useMotionValue, useAnimation } from "framer-motion"; -import { useAtom, useSetAtom } from "jotai"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useEffect, useRef } from "react"; interface PawnProps { @@ -47,8 +47,9 @@ export function PawnButton({ playerColor, index }: PawnProps) { const [player, setPlayer] = useAtom(colorToAtomMap[playerColor]); const [diceNumber, setDiceNumber] = useAtom(diceValueAtom); const [, setMoveLogs] = useAtom(moveLogsAtom); - const pawnRef = useRef(null); const setDisabledDice = useSetAtom(disabledDiceAtom); + const playerColors = useAtomValue(colorsAtom); + const pawnRef = useRef(null); const controls = useAnimation(); const x = useMotionValue(index % 2 === 0 ? Pawn.STEP : Pawn.STEP * 4); @@ -86,7 +87,9 @@ export function PawnButton({ playerColor, index }: PawnProps) { function setNextPlayer() { setTimeout(() => { const nextPlayer = - playerColors[(playerColors.indexOf(playerTurn) + 1) % 4]; + playerColors[ + (playerColors.indexOf(playerTurn) + 1) % playerColors.length + ]; setPlayerTurn(nextPlayer); }, 200); } @@ -186,6 +189,7 @@ export function PawnButton({ playerColor, index }: PawnProps) { return ( @@ -13,7 +18,10 @@ ReactDOM.createRoot(document.getElementById("root")!).render( person_profiles: "always", }} > - + + + + , ); diff --git a/src/pages/Choice.tsx b/src/pages/Choice.tsx new file mode 100644 index 0000000..df3cb4b --- /dev/null +++ b/src/pages/Choice.tsx @@ -0,0 +1,146 @@ +import { cn } from "@/lib/utils"; +import { PlayerColor } from "@/types"; +import { colorsAtom, playerTurnAtom } from "@/utils/atoms"; +import { useSetAtom } from "jotai"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +export function Choice() { + const [selectedColor, setSelectedColor] = useState("red"); + const [numberOfPlayers, setNumberOfPlayers] = useState(4); + const setColors = useSetAtom(colorsAtom); + const setPlayerTurn = useSetAtom(playerTurnAtom); + const navigate = useNavigate(); + + function chooseColor(color: PlayerColor) { + setSelectedColor(color); + } + function chooseNumberOfPlayers(number: number) { + setNumberOfPlayers(number); + } + + function startGame() { + let colors: PlayerColor[] = []; + + if (numberOfPlayers === 2) { + if (selectedColor === "red" || selectedColor === "yellow") { + colors = ["red", "yellow"]; + } + else { + colors = ["green", "blue"]; + } + } + else if (numberOfPlayers === 3) { + if (selectedColor === "red") { + colors = ["red", "green", "yellow"]; + } + else if (selectedColor === "green") { + colors = ["green", "yellow", "blue"]; + } + else if (selectedColor === "yellow") { + colors = ["yellow", "blue", "red"]; + } + else { + colors = ["blue", "red", "green"]; + } + } + else { + colors = ["red", "green", "yellow", "blue"]; + } + setColors(colors); + setPlayerTurn(pickRandomColor(colors)); + } + + function pickRandomColor(colors: PlayerColor[]) { + return colors[Math.floor(Math.random() * colors.length)]; + } + + return ( +
+
+
+

Choose number of players

+
+ + + +
+
+
+

Choose color

+
+ + + + +
+
+
+ +
+ ); +} diff --git a/src/pages/GameView.tsx b/src/pages/GameView.tsx new file mode 100644 index 0000000..f8b58e1 --- /dev/null +++ b/src/pages/GameView.tsx @@ -0,0 +1,9 @@ +import { Board } from "@/components/Board"; + +export function GameView() { + return ( +
+ +
+ ); +} diff --git a/src/utils/atoms.ts b/src/utils/atoms.ts index 846b6be..0f7fedf 100644 --- a/src/utils/atoms.ts +++ b/src/utils/atoms.ts @@ -2,16 +2,18 @@ import { PlayerColor } from "@/types"; import { atom, createStore } from "jotai"; import { Player } from "./player"; -export const colors: PlayerColor[] = ["red", "green", "yellow", "blue"]; - export const ludoStore = createStore(); export const diceValueAtom = atom(0); export const disabledDiceAtom = atom(false); export const moveLogsAtom = atom([]); -export const playerTurnAtom = atom( - colors[Math.floor(Math.random() * 4)], -); +export const playerTurnAtom = atom("red"); +export const colorsAtom = atom([ + "red", + "green", + "yellow", + "blue", +]); export const redPlayerAtom = atom(new Player("red")); export const greenPlayerAtom = atom(new Player("green")); From 4814df09a5d95ceb7fdf8b7008982750f7dc3834 Mon Sep 17 00:00:00 2001 From: Usman S Date: Thu, 20 Jun 2024 14:18:26 +0530 Subject: [PATCH 2/4] Small bug fix in dev mode --- src/components/DiceView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DiceView.tsx b/src/components/DiceView.tsx index 2b7d22d..77687e8 100644 --- a/src/components/DiceView.tsx +++ b/src/components/DiceView.tsx @@ -191,7 +191,7 @@ export function DiceView() { size={100} disabled={disabledDice} onRoll={handleRoll} - cheatValue={cheatNumber} + cheatValue={isCheatMode ? cheatNumber : undefined} />
From 464972be9ec46e42e5ac40d7ac944133642e1b83 Mon Sep 17 00:00:00 2001 From: Usman S Date: Thu, 20 Jun 2024 14:18:49 +0530 Subject: [PATCH 3/4] Bug fixes regarding entering home --- src/components/Pawns.tsx | 2 +- src/utils/pawn-controller.ts | 37 +++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/components/Pawns.tsx b/src/components/Pawns.tsx index 6f4e81f..b6c65d4 100644 --- a/src/components/Pawns.tsx +++ b/src/components/Pawns.tsx @@ -74,7 +74,7 @@ export function PawnButton({ playerColor, index }: PawnProps) { }); setPlayer((p) => ({ ...p, pawns: [...p.pawns, newPawn] })); - // NEVER FORGET THE CLEANUP OR ELSE STRICT MODE WILL FU + // NEVER FORGET THE CLEANUP return () => { setPlayer((p) => ({ ...p, diff --git a/src/utils/pawn-controller.ts b/src/utils/pawn-controller.ts index 31a07ac..082c5b9 100644 --- a/src/utils/pawn-controller.ts +++ b/src/utils/pawn-controller.ts @@ -131,7 +131,8 @@ export class Pawn { } public handleTurn() { - if (!this.currentBoxId) return; + if (!this.currentBoxId || !!this.stepsToHome) return; + if (this.direction === "right") { if (this.checkIsNextBox("down")) { this.turnDown(); @@ -167,6 +168,7 @@ export class Pawn { } public async moveWithValue(value: number) { + this.player = Pawn.getPlayer(this.color); if (this.hasWon) return; let didKill = false; @@ -176,7 +178,6 @@ export class Pawn { if (this.stepsToHome && remainingSteps > this.stepsToHome) return; didKill = await this.move(i === value - 1); remainingSteps--; - this.progress++; } return didKill; } @@ -281,8 +282,10 @@ export class Pawn { pawnRect.left, pawnRect.top, ); - const box = elementsAtNextPosition.find((el) => - el.classList.contains("box"), + const box = elementsAtNextPosition.find( + (el) => + el.classList.contains("box") || + el.classList.contains(`box-${this.color}`), ); this.currentBoxId = box?.id || null; } @@ -345,6 +348,7 @@ export class Pawn { await this.moveLeft(soundToPlay, isLastMove); break; } + this.progress++; this.setBoxId(); // refactor code after this comment to make it more readable and DRY @@ -375,10 +379,15 @@ export class Pawn { if (this.stepsToHome) this.stepsToHome--; } + // reset the progress if the pawn does not enter the home area (i.e., has not killed another pawn) + if (this.progress === 50 && !hasKilled) { + this.progress = -2; + } const isNextBox = this.checkIsNextBox(); if (isNextBox) return didKill; this.handleTurn(); + this.hasWon = this.progress === 56; return didKill; } @@ -423,6 +432,8 @@ export class Pawn { } private handleDoor(): boolean { + if (this.progress !== 50 || !this.player.hasKilled) return false; + const colorToDoor: Record = { red: "right", green: "down", @@ -432,17 +443,13 @@ export class Pawn { for (const [color, direction] of Object.entries(colorToDoor)) { if (color !== this.color) continue; - const isNextDoor = this.checkIsNextBox(direction, true); - if (isNextDoor) { - if (this.color === "red" && direction === "right") this.turnRight(); - else if (this.color === "green" && direction === "down") - this.turnDown(); - else if (this.color === "blue" && direction === "up") this.turnUp(); - else if (this.color === "yellow" && direction === "left") - this.turnLeft(); - this.stepsToHome = 6; - } - return isNextDoor; + if (this.color === "red" && direction === "right") this.turnRight(); + else if (this.color === "green" && direction === "down") this.turnDown(); + else if (this.color === "blue" && direction === "up") this.turnUp(); + else if (this.color === "yellow" && direction === "left") this.turnLeft(); + this.stepsToHome = 6; + + return true; } return false; } From 9c6f7befc08049dcb8157a42b24bb7ea531301ad Mon Sep 17 00:00:00 2001 From: Usman S Date: Thu, 20 Jun 2024 14:21:04 +0530 Subject: [PATCH 4/4] Store some atoms in local storage --- src/utils/atoms.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/atoms.ts b/src/utils/atoms.ts index 0f7fedf..cfd3acc 100644 --- a/src/utils/atoms.ts +++ b/src/utils/atoms.ts @@ -1,5 +1,6 @@ import { PlayerColor } from "@/types"; import { atom, createStore } from "jotai"; +import { atomWithStorage } from "jotai/utils"; import { Player } from "./player"; export const ludoStore = createStore(); @@ -7,8 +8,11 @@ export const ludoStore = createStore(); export const diceValueAtom = atom(0); export const disabledDiceAtom = atom(false); export const moveLogsAtom = atom([]); -export const playerTurnAtom = atom("red"); -export const colorsAtom = atom([ +export const playerTurnAtom = atomWithStorage( + "player_turn", + "red", +); +export const colorsAtom = atomWithStorage("colors", [ "red", "green", "yellow",