diff --git a/pac-man-board-game/ClientApp/package-lock.json b/pac-man-board-game/ClientApp/package-lock.json index b6ad05c..0a73036 100644 --- a/pac-man-board-game/ClientApp/package-lock.json +++ b/pac-man-board-game/ClientApp/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@headlessui/react": "^1.7.14", "@heroicons/react": "^2.0.18", + "jotai": "^2.2.2", "oidc-client": "^1.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2235,6 +2236,22 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jotai": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.2.2.tgz", + "integrity": "sha512-Cn8hnBg1sc5ppFwEgVGTfMR5WSM0hbAasd/bdAwIwTaum0j3OUPqBSC4tyk3jtB95vicML+RRWgKFOn6gtfN0A==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", diff --git a/pac-man-board-game/ClientApp/package.json b/pac-man-board-game/ClientApp/package.json index 2d687fa..66c5f43 100644 --- a/pac-man-board-game/ClientApp/package.json +++ b/pac-man-board-game/ClientApp/package.json @@ -5,6 +5,7 @@ "dependencies": { "@headlessui/react": "^1.7.14", "@heroicons/react": "^2.0.18", + "jotai": "^2.2.2", "oidc-client": "^1.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/pac-man-board-game/ClientApp/src/components/gameComponent.tsx b/pac-man-board-game/ClientApp/src/components/gameComponent.tsx index 7709844..dda65c4 100644 --- a/pac-man-board-game/ClientApp/src/components/gameComponent.tsx +++ b/pac-man-board-game/ClientApp/src/components/gameComponent.tsx @@ -1,30 +1,24 @@ -import React, {useEffect, useState} from "react"; +import React, {useEffect} from "react"; import {AllDice} from "./dice"; -import {GameAction} from "../utils/actions"; +import {doAction, GameAction} from "../utils/actions"; import GameBoard from "./gameBoard"; -import {Character, Ghost, PacMan} from "../game/character"; import WebSocketService from "../websockets/WebSocketService"; import {testMap} from "../game/map"; -import {TileType} from "../game/tileType"; import Player, {State} from "../game/player"; -import {Colour} from "../game/colour"; import PlayerStats from "../components/playerStats"; +import {useAtom} from "jotai"; +import {charactersAtom, currentPlayerAtom, diceAtom, playersAtom, selectedDiceAtom} from "../utils/state"; const wsService = new WebSocketService(import.meta.env.VITE_API); -const ghosts = [ - new Ghost({Colour: Colour.Purple}), - new Ghost({Colour: Colour.Purple}), -]; - export const GameComponent: Component<{ player: Player }> = ({player}) => { // TODO find spawn points - const [characters, setCharacters] = useState(); - const [players, setPlayers] = useState([player]); + const [characters] = useAtom(charactersAtom); + const [players] = useAtom(playersAtom); - const [dice, setDice] = useState(); - const [selectedDice, setSelectedDice] = useState(); - const [currentPlayer, setCurrentPlayer] = useState(); + const [dice] = useAtom(diceAtom); + const [selectedDice, setSelectedDice] = useAtom(selectedDiceAtom); + const [currentPlayer] = useAtom(currentPlayerAtom); function handleDiceClick(selected: SelectedDice): void { setSelectedDice(selected); @@ -45,57 +39,6 @@ export const GameComponent: Component<{ player: Player }> = ({player}) => { rollDice(); } - function doAction(message: MessageEvent): void { // TODO move to actions.ts - const parsed: ActionMessage = JSON.parse(message.data); - - switch (parsed.Action) { - case GameAction.rollDice: - setDice(parsed.Data as number[]); - break; - case GameAction.moveCharacter: - setDice(parsed.Data?.dice as number[]); - updateCharacters(parsed); - removeEatenPellets(parsed); - break; - case GameAction.playerInfo: - const playerProps = parsed.Data as PlayerProps[]; - console.log(playerProps); - setPlayers(playerProps.map(p => new Player(p))); - const pacMen = playerProps.filter(p => p.PacMan).map(p => new PacMan(p.PacMan!)); - console.log(pacMen); - // TODO find spawn points - setCharacters([...pacMen, ...ghosts]); - break; - case GameAction.ready: - const isReady = parsed.Data.AllReady as boolean; - if (isReady) { - setCurrentPlayer(new Player(parsed.Data.Starter as PlayerProps)); - } - setPlayers((parsed.Data.Players as PlayerProps[]).map(p => new Player(p))); - break; - } - } - - function removeEatenPellets(parsed: ActionMessage): void { - const pellets = parsed.Data?.eatenPellets as Position[]; - - for (const pellet of pellets) { - testMap[pellet.y][pellet.x] = TileType.empty; - } - } - - function updateCharacters(parsed: ActionMessage): void { - const updatedCharacters = parsed.Data?.characters as CharacterProps[] | undefined; - - if (updatedCharacters) { - const newList: Character[] = []; - for (const character of updatedCharacters) { - newList.push(new Character(character)); - } - setCharacters(newList); - } - } - function onCharacterMove(eatenPellets: Position[]): void { if (dice && selectedDice) { dice.splice(selectedDice.index, 1); @@ -122,8 +65,7 @@ export const GameComponent: Component<{ player: Player }> = ({player}) => { wsService.open(); void sendPlayer(); - // TODO send action to backend when all players are ready - // The backend should then send the first player as current player + return () => wsService.close(); }, []); diff --git a/pac-man-board-game/ClientApp/src/pages/game.tsx b/pac-man-board-game/ClientApp/src/pages/game.tsx index decafd5..1c3fb69 100644 --- a/pac-man-board-game/ClientApp/src/pages/game.tsx +++ b/pac-man-board-game/ClientApp/src/pages/game.tsx @@ -1,13 +1,23 @@ -import React from "react"; +import React, {useEffect} from "react"; import {GameComponent} from "../components/gameComponent"; -import {useLocation} from "react-router-dom"; -import Player from "../game/player"; +import {useAtom} from "jotai"; +import {thisPlayerAtom} from "../utils/state"; const Game: Component = () => { - const location = useLocation(); - const player = location.state as Player; + const [player] = useAtom(thisPlayerAtom); - return ; + useEffect(() => { + if (!player) { + // TODO state dissapears on refresh + window.location.href = "/"; + } + }, []); + + if (player) { + return ; + } else { + return null; + } }; export default Game; diff --git a/pac-man-board-game/ClientApp/src/pages/home.tsx b/pac-man-board-game/ClientApp/src/pages/home.tsx index 83341df..dece757 100644 --- a/pac-man-board-game/ClientApp/src/pages/home.tsx +++ b/pac-man-board-game/ClientApp/src/pages/home.tsx @@ -4,11 +4,14 @@ import Input from "../components/input"; import Dropdown from "../components/dropdown"; import {Colour, getColours} from "../game/colour"; import {useNavigate} from "react-router-dom"; +import {useAtom} from "jotai"; +import {thisPlayerAtom} from "../utils/state"; const Home: Component = () => { const input = useRef(null); const dropdown = useRef(null); + const [, setPlayer] = useAtom(thisPlayerAtom); const navigate = useNavigate(); function formHandler(): void { @@ -17,7 +20,8 @@ const Home: Component = () => { Name: input.current.value, Colour: dropdown.current.value as Colour, }); - navigate("/game", {state: player}); + setPlayer(player); + navigate("/game"); } return ( diff --git a/pac-man-board-game/ClientApp/src/utils/actions.ts b/pac-man-board-game/ClientApp/src/utils/actions.ts index fe4bc72..013a056 100644 --- a/pac-man-board-game/ClientApp/src/utils/actions.ts +++ b/pac-man-board-game/ClientApp/src/utils/actions.ts @@ -1,7 +1,10 @@ import Player from "../game/player"; -import {Character, PacMan} from "../game/character"; +import {Character, Ghost, PacMan} from "../game/character"; import {testMap} from "../game/map"; import {TileType} from "../game/tileType"; +import {getDefaultStore} from "jotai"; +import {charactersAtom, currentPlayerAtom, diceAtom, playersAtom} from "./state"; +import {Colour} from "../game/colour"; export enum GameAction { rollDice, @@ -10,33 +13,40 @@ export enum GameAction { ready, } -function doAction(message: MessageEvent): void { // TODO Jotai state management? +const ghosts = [ + new Ghost({Colour: Colour.Purple}), + new Ghost({Colour: Colour.Purple}), +]; + +const store = getDefaultStore(); + +export function doAction(message: MessageEvent): void { // TODO divide into smaller functions const parsed: ActionMessage = JSON.parse(message.data); switch (parsed.Action) { case GameAction.rollDice: - setDice(parsed.Data as number[]); + store.set(diceAtom, parsed.Data as number[]); break; case GameAction.moveCharacter: - setDice(parsed.Data?.dice as number[]); + store.set(diceAtom, parsed.Data?.dice as number[]); updateCharacters(parsed); removeEatenPellets(parsed); break; case GameAction.playerInfo: const playerProps = parsed.Data as PlayerProps[]; console.log(playerProps); - setPlayers(playerProps.map(p => new Player(p))); + store.set(playersAtom, playerProps.map(p => new Player(p))); const pacMen = playerProps.filter(p => p.PacMan).map(p => new PacMan(p.PacMan!)); console.log(pacMen); // TODO find spawn points - setCharacters([...pacMen, ...ghosts]); + store.set(charactersAtom, [...pacMen, ...ghosts]); break; case GameAction.ready: const isReady = parsed.Data.AllReady as boolean; if (isReady) { - setCurrentPlayer(new Player(parsed.Data.Starter as PlayerProps)); + store.set(currentPlayerAtom, new Player(parsed.Data.Starter as PlayerProps)); } - setPlayers((parsed.Data.Players as PlayerProps[]).map(p => new Player(p))); + store.set(playersAtom, (parsed.Data.Players as PlayerProps[]).map(p => new Player(p))); break; } } @@ -57,6 +67,6 @@ function updateCharacters(parsed: ActionMessage): void { for (const character of updatedCharacters) { newList.push(new Character(character)); } - setCharacters(newList); + store.set(charactersAtom, newList); } } diff --git a/pac-man-board-game/ClientApp/src/utils/state.ts b/pac-man-board-game/ClientApp/src/utils/state.ts new file mode 100644 index 0000000..b3bd078 --- /dev/null +++ b/pac-man-board-game/ClientApp/src/utils/state.ts @@ -0,0 +1,11 @@ +// TODO merge character and player atoms, since the player is the owner of the character +import Player from "../game/player"; +import {atom} from "jotai"; +import {Character} from "../game/character"; + +export const charactersAtom = atom(undefined); +export const playersAtom = atom([]); +export const thisPlayerAtom = atom(undefined); +export const diceAtom = atom(undefined); +export const selectedDiceAtom = atom(undefined); +export const currentPlayerAtom = atom(undefined);