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:
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user