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..77687e8 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);
}
@@ -189,7 +191,7 @@ export function DiceView() {
size={100}
disabled={disabledDice}
onRoll={handleRoll}
- cheatValue={cheatNumber}
+ cheatValue={isCheatMode ? cheatNumber : undefined}
/>
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..b6c65d4 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);
@@ -73,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,
@@ -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..cfd3acc 100644
--- a/src/utils/atoms.ts
+++ b/src/utils/atoms.ts
@@ -1,17 +1,23 @@
import { PlayerColor } from "@/types";
import { atom, createStore } from "jotai";
+import { atomWithStorage } from "jotai/utils";
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 = atomWithStorage(
+ "player_turn",
+ "red",
);
+export const colorsAtom = atomWithStorage("colors", [
+ "red",
+ "green",
+ "yellow",
+ "blue",
+]);
export const redPlayerAtom = atom(new Player("red"));
export const greenPlayerAtom = atom(new Player("green"));
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;
}