Implemented Jotai state management

This commit is contained in:
martin 2023-07-07 23:06:14 +02:00
parent 35295b9705
commit 5be200cc56
7 changed files with 79 additions and 84 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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<Character[]>();
const [players, setPlayers] = useState<Player[]>([player]);
const [characters] = useAtom(charactersAtom);
const [players] = useAtom(playersAtom);
const [dice, setDice] = useState<number[]>();
const [selectedDice, setSelectedDice] = useState<SelectedDice>();
const [currentPlayer, setCurrentPlayer] = useState<Player>();
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<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 {
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();
}, []);

View File

@ -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 <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;

View File

@ -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<HTMLInputElement>(null);
const dropdown = useRef<HTMLSelectElement>(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 (

View File

@ -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<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);
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);
}
}

View 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);