Skip to content

Commit

Permalink
feat: persistent game state
Browse files Browse the repository at this point in the history
  • Loading branch information
sownfam committed Dec 10, 2023
1 parent c74a525 commit 44dd61b
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 15 deletions.
8 changes: 4 additions & 4 deletions src/components/ChessGame/ChessGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const ChessGame = ({ id, type }: ChessGameType) => {
moves,
playing,
customSquares,
gameFen,
setGameFen,
// gameFen,
// setGameFen,
onPieceDrop,
undoMove,
startGame,
Expand Down Expand Up @@ -65,7 +65,7 @@ const ChessGame = ({ id, type }: ChessGameType) => {
id={id}
onPieceDrop={onPieceDrop}
// boardOrientation="black" TODO: make the bot play 1st, or just ignore, bot ALWAYS 'black'.
position={gameFen}
position={game.fen()}
customBoardStyle={{
borderRadius: "8px",
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.5)",
Expand All @@ -81,7 +81,7 @@ const ChessGame = ({ id, type }: ChessGameType) => {
<div>
{playing ? (
<MoveList
moves={moves}
moves={moves.reverse()}
bot={bot ? bot : { id: "0", name: "shark" }}
/>
) : (
Expand Down
48 changes: 37 additions & 11 deletions src/hooks/useChessSocket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
import { Chess, Move, Square } from "chess.js";
import { io, Socket } from "socket.io-client";
import { CustomSquares, ShortMove } from "@/types";
import { useLocalStorage } from "./useLocalStorage";

export type ChessType = "random" | "computer" | "minimax";

Expand Down Expand Up @@ -65,6 +66,22 @@ const useChessSocket = ({ type, id }: Props) => {
}
}, [disconnectSocket, setConnectionStatus, socket]);

React.useEffect(() => {
// Auto open socket connection on page load
connectSocket();
const savedMoves = localStorage.getItem(`@stockchess/useLocalStorage/ComputerChessBoard-moves`)
if (savedMoves) {
const parsedSavedMoves = JSON.parse(savedMoves) as Move[];
const newGame = new Chess();
try {
parsedSavedMoves.forEach((move) => newGame.move(move));
} catch (error) {
console.log("[!!!] Error in initializing saved game: ", error);
}
setGame(newGame);
}
}, []);

// Send latest move over socket
const sendMove = (move: string) => {
console.log("MOVE: ", move);
Expand Down Expand Up @@ -93,9 +110,17 @@ const useChessSocket = ({ type, id }: Props) => {

// End of socket
const [game, setGame] = useState(new Chess());
const [playing, setPlaying] = useState(false);
const [moves, setMoves] = useState<Move[]>([]);
const [gameFen, setGameFen] = useState<string>(game.fen());
// const [playing, setPlaying] = useState(false);
const [playing, setPlaying] = useLocalStorage({
name: `${id}-is-playing`,
defaultValue: false,
});
const [moves, setMoves] = useLocalStorage<Move[]>({
name: `${id}-moves`,
defaultValue: [],
});
// const [moves, setMoves] = useState<Move[]>([]);
// const [gameFen, setGameFen] = useState<string>("start");
const [currentTimeout, setCurrentTimeout] = useState<NodeJS.Timeout>();
const [customSquares, updateCustomSquares] = useReducer(squareReducer, {
check: {},
Expand All @@ -107,9 +132,9 @@ const useChessSocket = ({ type, id }: Props) => {
const result = gameCopy.move(move);

if (result) {
setMoves((prevMoves) => [result, ...prevMoves]);
setMoves(gameCopy.history({verbose: true}));
setGame(gameCopy);
setGameFen(gameCopy.fen());
// setGameFen(gameCopy.fen());

let kingSquare = undefined;
if (game.inCheck()) {
Expand Down Expand Up @@ -156,7 +181,7 @@ const useChessSocket = ({ type, id }: Props) => {
const gameCopy = game;
gameCopy.reset();
setGame(gameCopy);
setGameFen(gameCopy.fen());
// setGameFen(gameCopy.fen());
cleanOldGame();
setMoves([]);

Expand All @@ -166,7 +191,8 @@ const useChessSocket = ({ type, id }: Props) => {
};

const startGame = () => {
connectSocket();
// NOTE: I think it's better to init socket on page load
// connectSocket();
resetGame();
setPlaying(true);
};
Expand All @@ -183,8 +209,7 @@ const useChessSocket = ({ type, id }: Props) => {
movesCopy.shift();

setGame(gameCopy);
setGameFen(gameCopy.fen());
setMoves(movesCopy);
// setGameFen(gameCopy.fen());
updateCustomSquares({ check: undefined }); // Reset style
};

Expand All @@ -195,8 +220,9 @@ const useChessSocket = ({ type, id }: Props) => {

customSquares,

gameFen,
setGameFen,
// NOTE: Since FEN can be easily extract from `games`, we dont need to maintain it
// gameFen,
// setGameFen,

onPieceDrop,
undoMove,
Expand Down
55 changes: 55 additions & 0 deletions src/hooks/useLocalStorage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState, useEffect } from 'react';

type Args<T> = {
name: string;
defaultValue: T;
};

export function useLocalStorage<T>(args: Args<T>): [T, (arg: T) => void, () => void] {
const { defaultValue, name } = args;
const [value, setValue] = useState<T | undefined>(undefined);
const persistenceKey = `@stockchess/useLocalStorage/${name}`;

useEffect(function didMount() {
if (isLocalStorageAvailable()) {
const persistedState = localStorage.getItem(persistenceKey);
if (persistedState) {
setValue(JSON.parse(persistedState));
} else {
// setValue(defaultValue);
// setValue()
}
}
}, []);

useEffect(
function persistOnChange() {
if (isLocalStorageAvailable() && value !== undefined)
localStorage.setItem(persistenceKey, JSON.stringify(value));
},
[value]
);

function removeValue() {
if (isLocalStorageAvailable()) localStorage.removeItem(persistenceKey);
}

return [value ?? defaultValue, setValue, removeValue];
}

export function isLocalStorageAvailable(): boolean {
try {
if (!window.localStorage || localStorage === null || typeof localStorage === 'undefined') {
return false;
}

localStorage.setItem('localStorage:test', 'value');
if (localStorage.getItem('localStorage:test') !== 'value') {
return false;
}
localStorage.removeItem('localStorage:test');
return true;
} catch {
return false;
}
}

0 comments on commit 44dd61b

Please sign in to comment.