multiple changes

- add gameover overlay
- fix active tile background
- redirect to home on invalid page
- compute checkmate and draw after making a move
- cleanup code
- error handling on making moves
This commit is contained in:
Cozma Rares
2023-07-31 01:24:26 +03:00
parent 259393ca1b
commit 0b68d701c1
8 changed files with 188 additions and 140 deletions
+5 -2
View File
@@ -169,7 +169,7 @@ const Tile: React.FC<{
const color = squareColor(tileNumber);
const { bg: bgColor, text: textColor } = TILE_COLORS[color];
const activeColor = isActive ? TILE_COLORS[color].active : "";
const activeColor = TILE_COLORS[color].active;
const tileFile = file(tileNumber);
const tileRank = rank(tileNumber);
@@ -180,7 +180,10 @@ const Tile: React.FC<{
return (
<div
className={`relative aspect-square font-bold text-xl isolate group [&>*]:pointer-events-none ${bgColor} ${activeColor}`}
className={
"relative aspect-square font-bold text-xl isolate group [&>*]:pointer-events-none " +
(isActive ? activeColor : bgColor)
}
data-tile={tileNumber}
>
{piece == null ? <></> : <img src={PIECES[piece.color][piece.type]} />}
+21 -2
View File
@@ -1,8 +1,21 @@
import ReactDOM from "react-dom/client";
import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import {
createBrowserRouter,
RouterProvider,
useNavigate,
} from "react-router-dom";
import Home from "./routes/Home";
import Game from "./routes/Game";
import { useEffect } from "react";
const ErrorElement = () => {
const naviagte = useNavigate();
useEffect(() => naviagte("/"), []);
return <></>;
};
const router = createBrowserRouter([
{
@@ -13,9 +26,15 @@ const router = createBrowserRouter([
path: "/game",
element: <Game />,
},
{
path: "*",
element: <ErrorElement />,
},
]);
// React's StricMode doesn't play well with how I implemented the socket connection
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<RouterProvider router={router} />
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-zinc-200 -z-10">
<RouterProvider router={router} />
</div>
);
+19 -3
View File
@@ -8,6 +8,7 @@ import { socket } from "../sockets/socket";
import { CopyIcon } from "../utils/icons";
import Show from "../utils/Show";
import ErrorNorification from "../utils/ErrorNotification";
import Modal, { ModalButton } from "../utils/Modal";
const Game = () => {
const navigate = useNavigate();
@@ -18,7 +19,7 @@ const Game = () => {
const [chess] = useState(Chess.load());
const [game, setGame] = useState(false);
const [, setRerender] = useState(false);
const [, rerender] = useState(false);
const [err, setErr] = useState<Error>();
const makeMove = (move: Move) => socket.emit("make move", id, move);
@@ -36,7 +37,6 @@ const Game = () => {
});
socket.on("join error", () => {
socket.disconnect();
navigate("/", {
state: { error: "Could not join, please try again." },
});
@@ -50,7 +50,7 @@ const Game = () => {
socket.on("receive move", (move: Move) => {
chess.makeMove(move);
setRerender((prev) => !prev);
rerender((prev) => !prev);
});
return () => {
@@ -71,6 +71,22 @@ const Game = () => {
blackPerspective={color === COLOR.BLACK}
disabled={color !== chess.getTurn()}
/>
<Show when={chess.isGameOver()}>
<Modal overlay>
<div className="text-center w-40 max-w-full">
<h2 className="text-2xl mb-2">Game Over</h2>
<h1 className="text-lg font-bold">
{chess.isCheckMate()
? (chess.getTurn() == color ? "Opponent" : "You") + " won!"
: "It's a draw!"}
</h1>
<div className="w-full h-[4px] rounded-b-lg bg-white mb-4"></div>
<ModalButton onClick={() => navigate("/")}>
Go to main page
</ModalButton>
</div>
</Modal>
</Show>
</Show>
);
};
+55 -63
View File
@@ -3,14 +3,16 @@ import { useLocation, useNavigate } from "react-router-dom";
import { Color, COLOR } from "../../../server/src/engine";
import Show from "../utils/Show";
import ErrorNorification from "../utils/ErrorNotification";
import Modal, { ModalButton } from "../utils/Modal";
const Home = () => {
const navigate = useNavigate();
const location = useLocation();
const [id, setID] = useState<string>("");
const [join, setJoin] = useState(false);
const [color, setColor] = useState<Color>(COLOR.WHITE);
const [join, setJoin] = useState(false);
const [create, setCreate] = useState(false);
const [err, setErr] = useState<Error>();
const { error } =
@@ -22,8 +24,8 @@ const Home = () => {
if (res.ok) return res.text();
throw res;
})
.then((id) => {
setID(id);
.then((text) => {
navigate("/game", { state: { id: text, color } });
})
.catch((err) => {
console.error(err);
@@ -56,89 +58,79 @@ const Home = () => {
const errObj = err?.message
? {
error: err.message,
removeError: () => setErr(undefined),
}
error: err.message,
removeError: () => setErr(undefined),
}
: {
error,
removeError: removeLocationState,
};
error,
removeError: removeLocationState,
};
return (
<>
<ErrorNorification key={errObj.error} {...errObj} />
<div className="text-xl">
<div className="absolute top-0 left-0 right-0 bottom-0 m-auto p-6 bg-gray-800 text-white w-fit h-fit rounded-[25px]">
<button
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mb-3"
onClick={createGame}
>
Create Game
</button>
<button
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors"
onClick={() => setJoin(true)}
>
Join Game
</button>
<div className="absolute top-0 left-0 right-0 bottom-0 m-auto w-fit h-fit">
<Modal>
<ModalButton onClick={() => setCreate(true)}>Create Game</ModalButton>
<ModalButton onClick={() => setJoin(true)}>Join Game</ModalButton>
</Modal>
</div>
<Show when={join == false && id != ""}>
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex justify-center items-center bg-zinc-800 bg-opacity-70">
<div className="bg-gray-900 text-white p-4 rounded-lg">
<Show when={create}>
<Modal overlay>
<div className="w-[20rem] max-w-full">
<p>Select color:</p>
<div className="grid grid-cols-[auto,minmax(0,1fr)] text-center ">
<input
type="radio"
name="color"
id="white"
checked={color == COLOR.WHITE}
onChange={() => setColor(COLOR.WHITE)}
/>
<label htmlFor="white">White</label>
<input
type="radio"
name="color"
id="black"
checked={color == COLOR.BLACK}
onChange={() => setColor(COLOR.BLACK)}
/>
<label htmlFor="black">Black</label>
<div className="flex flex-row justify-evenly">
<div className="flex flex-row items-center gap-2">
<input
type="radio"
name="color"
id="white"
checked={color == COLOR.WHITE}
onChange={() => setColor(COLOR.WHITE)}
/>
<label htmlFor="white">White</label>
</div>
<div className="flex flex-row items-center gap-2">
<input
type="radio"
name="color"
id="black"
checked={color == COLOR.BLACK}
onChange={() => setColor(COLOR.BLACK)}
/>
<label htmlFor="black">Black</label>
</div>
</div>
<button
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mt-3"
onClick={() => navigate("/game", { state: { id, color } })}
<ModalButton
onClick={createGame}
>
Start Game
</button>
</ModalButton>
<ModalButton
onClick={() => setCreate(false)}
>
Cancel
</ModalButton>
</div>
</div>
</Modal>
</Show>
<Show when={join}>
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex justify-center items-center bg-zinc-800 bg-opacity-70">
<div className="bg-gray-900 text-white p-4 rounded-lg">
<Modal overlay>
<div className="w-[20rem] max-w-full">
<label htmlFor="game-id">Insert game ID: </label>
<input
className="block bg-zinc-700 p-1 mt-2"
className="block bg-zinc-700 p-1 mt-2 w-full"
type="text"
name="game-id"
id="game-id"
value={id}
onChange={(e) => setID(e.target.value)}
/>
<button
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mt-3"
onClick={joinGame}
>
Join
</button>
<button
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mt-3"
onClick={() => setJoin(false)}
>
Cancel
</button>
<ModalButton onClick={joinGame}>Join</ModalButton>
<ModalButton onClick={() => setJoin(false)}>Cancel</ModalButton>
</div>
</div>
</Modal>
</Show>
</div>
</>
+33
View File
@@ -0,0 +1,33 @@
import Show from "./Show";
const Modal: React.FC<{
children: React.ReactNode;
overlay?: boolean;
}> = ({ children, overlay }) => {
const modal = (
<div className="bg-gray-900 text-white p-4 rounded-lg w-fit">
{children}
</div>
);
return overlay ? (
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex justify-center items-center bg-zinc-800 bg-opacity-70">
{modal}
</div>
) : (
modal
);
};
export const ModalButton: React.FC<{
children: React.ReactNode;
onClick: () => void;
}> = ({ children, onClick }) => (
<button
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors [&:nth-child(n+2)]:mt-3"
onClick={onClick}
>
{children}
</button>
);
export default Modal;