Implemented Jotai state management
This commit is contained in:
parent
35295b9705
commit
5be200cc56
17
pac-man-board-game/ClientApp/package-lock.json
generated
17
pac-man-board-game/ClientApp/package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.14",
|
"@headlessui/react": "^1.7.14",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
|
"jotai": "^2.2.2",
|
||||||
"oidc-client": "^1.11.5",
|
"oidc-client": "^1.11.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -2235,6 +2236,22 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"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": {
|
"node_modules/js-string-escape": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.14",
|
"@headlessui/react": "^1.7.14",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
|
"jotai": "^2.2.2",
|
||||||
"oidc-client": "^1.11.5",
|
"oidc-client": "^1.11.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -1,30 +1,24 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect} from "react";
|
||||||
import {AllDice} from "./dice";
|
import {AllDice} from "./dice";
|
||||||
import {GameAction} from "../utils/actions";
|
import {doAction, GameAction} from "../utils/actions";
|
||||||
import GameBoard from "./gameBoard";
|
import GameBoard from "./gameBoard";
|
||||||
import {Character, Ghost, PacMan} from "../game/character";
|
|
||||||
import WebSocketService from "../websockets/WebSocketService";
|
import WebSocketService from "../websockets/WebSocketService";
|
||||||
import {testMap} from "../game/map";
|
import {testMap} from "../game/map";
|
||||||
import {TileType} from "../game/tileType";
|
|
||||||
import Player, {State} from "../game/player";
|
import Player, {State} from "../game/player";
|
||||||
import {Colour} from "../game/colour";
|
|
||||||
import PlayerStats from "../components/playerStats";
|
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 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}) => {
|
export const GameComponent: Component<{ player: Player }> = ({player}) => {
|
||||||
// TODO find spawn points
|
// TODO find spawn points
|
||||||
const [characters, setCharacters] = useState<Character[]>();
|
const [characters] = useAtom(charactersAtom);
|
||||||
const [players, setPlayers] = useState<Player[]>([player]);
|
const [players] = useAtom(playersAtom);
|
||||||
|
|
||||||
const [dice, setDice] = useState<number[]>();
|
const [dice] = useAtom(diceAtom);
|
||||||
const [selectedDice, setSelectedDice] = useState<SelectedDice>();
|
const [selectedDice, setSelectedDice] = useAtom(selectedDiceAtom);
|
||||||
const [currentPlayer, setCurrentPlayer] = useState<Player>();
|
const [currentPlayer] = useAtom(currentPlayerAtom);
|
||||||
|
|
||||||
function handleDiceClick(selected: SelectedDice): void {
|
function handleDiceClick(selected: SelectedDice): void {
|
||||||
setSelectedDice(selected);
|
setSelectedDice(selected);
|
||||||
@ -45,57 +39,6 @@ export const GameComponent: Component<{ player: Player }> = ({player}) => {
|
|||||||
rollDice();
|
rollDice();
|
||||||
}
|
}
|
||||||
|
|
||||||
function doAction(message: MessageEvent<string>): 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 {
|
function onCharacterMove(eatenPellets: Position[]): void {
|
||||||
if (dice && selectedDice) {
|
if (dice && selectedDice) {
|
||||||
dice.splice(selectedDice.index, 1);
|
dice.splice(selectedDice.index, 1);
|
||||||
@ -122,8 +65,7 @@ export const GameComponent: Component<{ player: Player }> = ({player}) => {
|
|||||||
wsService.open();
|
wsService.open();
|
||||||
|
|
||||||
void sendPlayer();
|
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();
|
return () => wsService.close();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import React from "react";
|
import React, {useEffect} from "react";
|
||||||
import {GameComponent} from "../components/gameComponent";
|
import {GameComponent} from "../components/gameComponent";
|
||||||
import {useLocation} from "react-router-dom";
|
import {useAtom} from "jotai";
|
||||||
import Player from "../game/player";
|
import {thisPlayerAtom} from "../utils/state";
|
||||||
|
|
||||||
const Game: Component = () => {
|
const Game: Component = () => {
|
||||||
const location = useLocation();
|
const [player] = useAtom(thisPlayerAtom);
|
||||||
const player = location.state as Player;
|
|
||||||
|
|
||||||
return <GameComponent player={player}/>;
|
useEffect(() => {
|
||||||
|
if (!player) {
|
||||||
|
// TODO state dissapears on refresh
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
return <GameComponent player={player}/>;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Game;
|
export default Game;
|
||||||
|
@ -4,11 +4,14 @@ import Input from "../components/input";
|
|||||||
import Dropdown from "../components/dropdown";
|
import Dropdown from "../components/dropdown";
|
||||||
import {Colour, getColours} from "../game/colour";
|
import {Colour, getColours} from "../game/colour";
|
||||||
import {useNavigate} from "react-router-dom";
|
import {useNavigate} from "react-router-dom";
|
||||||
|
import {useAtom} from "jotai";
|
||||||
|
import {thisPlayerAtom} from "../utils/state";
|
||||||
|
|
||||||
const Home: Component = () => {
|
const Home: Component = () => {
|
||||||
|
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const dropdown = useRef<HTMLSelectElement>(null);
|
const dropdown = useRef<HTMLSelectElement>(null);
|
||||||
|
const [, setPlayer] = useAtom(thisPlayerAtom);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
function formHandler(): void {
|
function formHandler(): void {
|
||||||
@ -17,7 +20,8 @@ const Home: Component = () => {
|
|||||||
Name: input.current.value,
|
Name: input.current.value,
|
||||||
Colour: dropdown.current.value as Colour,
|
Colour: dropdown.current.value as Colour,
|
||||||
});
|
});
|
||||||
navigate("/game", {state: player});
|
setPlayer(player);
|
||||||
|
navigate("/game");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import Player from "../game/player";
|
import Player from "../game/player";
|
||||||
import {Character, PacMan} from "../game/character";
|
import {Character, Ghost, PacMan} from "../game/character";
|
||||||
import {testMap} from "../game/map";
|
import {testMap} from "../game/map";
|
||||||
import {TileType} from "../game/tileType";
|
import {TileType} from "../game/tileType";
|
||||||
|
import {getDefaultStore} from "jotai";
|
||||||
|
import {charactersAtom, currentPlayerAtom, diceAtom, playersAtom} from "./state";
|
||||||
|
import {Colour} from "../game/colour";
|
||||||
|
|
||||||
export enum GameAction {
|
export enum GameAction {
|
||||||
rollDice,
|
rollDice,
|
||||||
@ -10,33 +13,40 @@ export enum GameAction {
|
|||||||
ready,
|
ready,
|
||||||
}
|
}
|
||||||
|
|
||||||
function doAction(message: MessageEvent<string>): 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<string>): void { // TODO divide into smaller functions
|
||||||
const parsed: ActionMessage = JSON.parse(message.data);
|
const parsed: ActionMessage = JSON.parse(message.data);
|
||||||
|
|
||||||
switch (parsed.Action) {
|
switch (parsed.Action) {
|
||||||
case GameAction.rollDice:
|
case GameAction.rollDice:
|
||||||
setDice(parsed.Data as number[]);
|
store.set(diceAtom, parsed.Data as number[]);
|
||||||
break;
|
break;
|
||||||
case GameAction.moveCharacter:
|
case GameAction.moveCharacter:
|
||||||
setDice(parsed.Data?.dice as number[]);
|
store.set(diceAtom, parsed.Data?.dice as number[]);
|
||||||
updateCharacters(parsed);
|
updateCharacters(parsed);
|
||||||
removeEatenPellets(parsed);
|
removeEatenPellets(parsed);
|
||||||
break;
|
break;
|
||||||
case GameAction.playerInfo:
|
case GameAction.playerInfo:
|
||||||
const playerProps = parsed.Data as PlayerProps[];
|
const playerProps = parsed.Data as PlayerProps[];
|
||||||
console.log(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!));
|
const pacMen = playerProps.filter(p => p.PacMan).map(p => new PacMan(p.PacMan!));
|
||||||
console.log(pacMen);
|
console.log(pacMen);
|
||||||
// TODO find spawn points
|
// TODO find spawn points
|
||||||
setCharacters([...pacMen, ...ghosts]);
|
store.set(charactersAtom, [...pacMen, ...ghosts]);
|
||||||
break;
|
break;
|
||||||
case GameAction.ready:
|
case GameAction.ready:
|
||||||
const isReady = parsed.Data.AllReady as boolean;
|
const isReady = parsed.Data.AllReady as boolean;
|
||||||
if (isReady) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,6 +67,6 @@ function updateCharacters(parsed: ActionMessage): void {
|
|||||||
for (const character of updatedCharacters) {
|
for (const character of updatedCharacters) {
|
||||||
newList.push(new Character(character));
|
newList.push(new Character(character));
|
||||||
}
|
}
|
||||||
setCharacters(newList);
|
store.set(charactersAtom, newList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
pac-man-board-game/ClientApp/src/utils/state.ts
Normal file
11
pac-man-board-game/ClientApp/src/utils/state.ts
Normal file
@ -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<Character[] | undefined>(undefined);
|
||||||
|
export const playersAtom = atom<Player[]>([]);
|
||||||
|
export const thisPlayerAtom = atom<Player | undefined>(undefined);
|
||||||
|
export const diceAtom = atom<number[] | undefined>(undefined);
|
||||||
|
export const selectedDiceAtom = atom<SelectedDice | undefined>(undefined);
|
||||||
|
export const currentPlayerAtom = atom<Player | undefined>(undefined);
|
Loading…
x
Reference in New Issue
Block a user