small structure changes for less redundancy
This commit is contained in:
@@ -1,20 +1,26 @@
|
|||||||
"use client";
|
"use client";
|
||||||
// TODO: restructure
|
// TODO: restructure
|
||||||
|
|
||||||
import { Chessboard } from "react-chessboard";
|
|
||||||
import { IconCopy } from "@tabler/icons-react";
|
import { IconCopy } from "@tabler/icons-react";
|
||||||
import { useState, useEffect, useContext, useReducer, useRef } from "react";
|
|
||||||
import type { KeyboardEvent, FormEvent } from "react";
|
|
||||||
//import Image from "next/image";
|
|
||||||
import type { Game } from "@chessu/types";
|
|
||||||
import type { Message } from "@/types";
|
|
||||||
|
|
||||||
import { io } from "socket.io-client";
|
import type { FormEvent, KeyboardEvent } from "react";
|
||||||
import { API_URL } from "@/config";
|
|
||||||
import { SessionContext } from "@/context/session";
|
import { SessionContext } from "@/context/session";
|
||||||
|
import { useContext, useEffect, useReducer, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import type { Message } from "@/types";
|
||||||
|
import type { Game } from "@chessu/types";
|
||||||
|
|
||||||
|
import type { Move, Square } from "chess.js";
|
||||||
import { Chess } from "chess.js";
|
import { Chess } from "chess.js";
|
||||||
import type { Square, Move } from "chess.js";
|
import { Chessboard } from "react-chessboard";
|
||||||
import { initSocket, lobbyReducer, squareReducer } from "./handlers";
|
|
||||||
|
import { API_URL } from "@/config";
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
|
||||||
|
import { lobbyReducer, squareReducer } from "./reducers";
|
||||||
|
import { initSocket } from "./socketEvents";
|
||||||
|
import { syncPgn, syncSide } from "./utils";
|
||||||
|
|
||||||
const socket = io(API_URL, { withCredentials: true, autoConnect: false });
|
const socket = io(API_URL, { withCredentials: true, autoConnect: false });
|
||||||
|
|
||||||
@@ -52,45 +58,10 @@ export default function GamePage({ initialLobby }: { initialLobby: Game }) {
|
|||||||
handleResize();
|
handleResize();
|
||||||
|
|
||||||
if (lobby.pgn && lobby.actualGame.pgn() !== lobby.pgn) {
|
if (lobby.pgn && lobby.actualGame.pgn() !== lobby.pgn) {
|
||||||
lobby.actualGame.loadPgn(lobby.pgn as string);
|
syncPgn(lobby.pgn, lobby, { updateCustomSquares });
|
||||||
|
|
||||||
const lastMove = lobby.actualGame.history({ verbose: true }).pop();
|
|
||||||
|
|
||||||
let lastMoveSquares = undefined;
|
|
||||||
let kingSquare = undefined;
|
|
||||||
if (lastMove) {
|
|
||||||
lastMoveSquares = {
|
|
||||||
[lastMove.from]: { background: "rgba(255, 255, 0, 0.4)" },
|
|
||||||
[lastMove.to]: { background: "rgba(255, 255, 0, 0.4)" }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (lobby.actualGame.inCheck()) {
|
|
||||||
const kingPos = lobby.actualGame.board().reduce((acc, row, index) => {
|
|
||||||
const squareIndex = row.findIndex(
|
|
||||||
(square) => square && square.type === "k" && square.color === lobby.actualGame.turn()
|
|
||||||
);
|
|
||||||
return squareIndex >= 0 ? `${String.fromCharCode(squareIndex + 97)}${8 - index}` : acc;
|
|
||||||
}, "");
|
|
||||||
kingSquare = {
|
|
||||||
[kingPos]: {
|
|
||||||
background: "radial-gradient(red, rgba(255,0,0,.4), transparent 70%)",
|
|
||||||
borderRadius: "50%"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
updateCustomSquares({
|
|
||||||
lastMove: lastMoveSquares,
|
|
||||||
check: kingSquare
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lobby.black?.id === session?.user?.id) {
|
syncSide(session.user, undefined, lobby, { updateLobby });
|
||||||
if (lobby.side !== "b") updateLobby({ type: "setSide", payload: "b" });
|
|
||||||
} else if (lobby.white?.id === session?.user?.id) {
|
|
||||||
if (lobby.side !== "w") updateLobby({ type: "setSide", payload: "w" });
|
|
||||||
} else if (lobby.side !== "s") {
|
|
||||||
updateLobby({ type: "setSide", payload: "s" });
|
|
||||||
}
|
|
||||||
|
|
||||||
initSocket(session.user, socket, lobby, {
|
initSocket(session.user, socket, lobby, {
|
||||||
updateLobby,
|
updateLobby,
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
import type { Dispatch } from "react";
|
|
||||||
import type { Action, Lobby, Message, CustomSquares } from "@/types";
|
|
||||||
import { Chess } from "chess.js";
|
|
||||||
import type { Game, User } from "@chessu/types";
|
|
||||||
import type { Socket } from "socket.io-client";
|
|
||||||
|
|
||||||
export function lobbyReducer(lobby: Lobby, action: Action): Lobby {
|
|
||||||
switch (action.type) {
|
|
||||||
case "updateLobby":
|
|
||||||
return { ...lobby, ...action.payload };
|
|
||||||
|
|
||||||
case "setSide":
|
|
||||||
return { ...lobby, side: action.payload };
|
|
||||||
|
|
||||||
case "setGame":
|
|
||||||
return { ...lobby, actualGame: action.payload };
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error("Invalid action type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function squareReducer(squares: CustomSquares, action: Partial<CustomSquares>) {
|
|
||||||
return { ...squares, ...action };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initSocket(
|
|
||||||
user: User,
|
|
||||||
socket: Socket,
|
|
||||||
lobby: Lobby,
|
|
||||||
actions: {
|
|
||||||
updateLobby: Dispatch<Action>;
|
|
||||||
addMessage: Function;
|
|
||||||
updateCustomSquares: Dispatch<Partial<CustomSquares>>;
|
|
||||||
makeMove: Function;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
socket.on("connect", () => {
|
|
||||||
console.log("connected!");
|
|
||||||
socket.emit("joinLobby", lobby.code);
|
|
||||||
});
|
|
||||||
socket.on("disconnect", () => {
|
|
||||||
console.log("disconnected!");
|
|
||||||
});
|
|
||||||
// TODO: handle disconnect
|
|
||||||
|
|
||||||
socket.on("chat", (message: Message) => {
|
|
||||||
actions.addMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("receivedLatestGame", (latestGame: Game) => {
|
|
||||||
if (latestGame.pgn && latestGame.pgn !== lobby.actualGame.pgn()) {
|
|
||||||
lobby.actualGame.loadPgn(latestGame.pgn as string);
|
|
||||||
|
|
||||||
const lastMove = lobby.actualGame.history({ verbose: true }).pop();
|
|
||||||
|
|
||||||
let lastMoveSquares = undefined;
|
|
||||||
let kingSquare = undefined;
|
|
||||||
if (lastMove) {
|
|
||||||
lastMoveSquares = {
|
|
||||||
[lastMove.from]: { background: "rgba(255, 255, 0, 0.4)" },
|
|
||||||
[lastMove.to]: { background: "rgba(255, 255, 0, 0.4)" }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (lobby.actualGame.inCheck()) {
|
|
||||||
const kingPos = lobby.actualGame.board().reduce((acc, row, index) => {
|
|
||||||
const squareIndex = row.findIndex(
|
|
||||||
(square) =>
|
|
||||||
square &&
|
|
||||||
square.type === "k" &&
|
|
||||||
square.color === lobby.actualGame.turn()
|
|
||||||
);
|
|
||||||
return squareIndex >= 0
|
|
||||||
? `${String.fromCharCode(squareIndex + 97)}${8 - index}`
|
|
||||||
: acc;
|
|
||||||
}, "");
|
|
||||||
kingSquare = {
|
|
||||||
[kingPos]: {
|
|
||||||
background: "radial-gradient(red, rgba(255,0,0,.4), transparent 70%)",
|
|
||||||
borderRadius: "50%"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
actions.updateCustomSquares({
|
|
||||||
lastMove: lastMoveSquares,
|
|
||||||
check: kingSquare
|
|
||||||
});
|
|
||||||
}
|
|
||||||
actions.updateLobby({ type: "updateLobby", payload: latestGame });
|
|
||||||
|
|
||||||
if (latestGame.black?.id === user?.id) {
|
|
||||||
if (lobby.side !== "b") actions.updateLobby({ type: "setSide", payload: "b" });
|
|
||||||
} else if (latestGame.white?.id === user?.id) {
|
|
||||||
if (lobby.side !== "w") actions.updateLobby({ type: "setSide", payload: "w" });
|
|
||||||
} else if (lobby.side !== "s") {
|
|
||||||
actions.updateLobby({ type: "setSide", payload: "s" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("receivedMove", (m: { from: string; to: string; promotion?: string }) => {
|
|
||||||
const success = actions.makeMove(m);
|
|
||||||
if (!success) {
|
|
||||||
socket.emit("getLatestGame");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("userJoinedAsPlayer", ({ name, side }: { name: string; side: "white" | "black" }) => {
|
|
||||||
actions.addMessage({
|
|
||||||
author: { name: "server" },
|
|
||||||
message: `${name} is now playing as ${side}.`
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on(
|
|
||||||
"gameOver",
|
|
||||||
({
|
|
||||||
reason,
|
|
||||||
winnerName,
|
|
||||||
winnerSide
|
|
||||||
}: {
|
|
||||||
reason: string;
|
|
||||||
winnerName?: string;
|
|
||||||
winnerSide?: string;
|
|
||||||
}) => {
|
|
||||||
const m = {
|
|
||||||
author: { name: "server" }
|
|
||||||
} as Message;
|
|
||||||
|
|
||||||
if (reason === "checkmate") {
|
|
||||||
m.message = `${winnerName} (${winnerSide}) has won by checkmate.`;
|
|
||||||
} else {
|
|
||||||
let message = "The game has ended in a draw";
|
|
||||||
if (reason === "repetition") {
|
|
||||||
message = message.concat(" due to threefold repetition");
|
|
||||||
} else if (reason === "insufficient") {
|
|
||||||
message = message.concat(" due to insufficient material");
|
|
||||||
} else if (reason === "stalemate") {
|
|
||||||
message = "The game has been drawn due to stalemate";
|
|
||||||
}
|
|
||||||
m.message = message.concat(".");
|
|
||||||
}
|
|
||||||
actions.addMessage(m);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
21
client/src/components/game/reducers.ts
Normal file
21
client/src/components/game/reducers.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { Action, CustomSquares, Lobby } from "@/types";
|
||||||
|
|
||||||
|
export function lobbyReducer(lobby: Lobby, action: Action): Lobby {
|
||||||
|
switch (action.type) {
|
||||||
|
case "updateLobby":
|
||||||
|
return { ...lobby, ...action.payload };
|
||||||
|
|
||||||
|
case "setSide":
|
||||||
|
return { ...lobby, side: action.payload };
|
||||||
|
|
||||||
|
case "setGame":
|
||||||
|
return { ...lobby, actualGame: action.payload };
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid action type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function squareReducer(squares: CustomSquares, action: Partial<CustomSquares>) {
|
||||||
|
return { ...squares, ...action };
|
||||||
|
}
|
||||||
85
client/src/components/game/socketEvents.ts
Normal file
85
client/src/components/game/socketEvents.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import type { Action, CustomSquares, Lobby, Message } from "@/types";
|
||||||
|
import type { Game, User } from "@chessu/types";
|
||||||
|
import type { Dispatch } from "react";
|
||||||
|
import type { Socket } from "socket.io-client";
|
||||||
|
import { syncPgn, syncSide } from "./utils";
|
||||||
|
|
||||||
|
export function initSocket(
|
||||||
|
user: User,
|
||||||
|
socket: Socket,
|
||||||
|
lobby: Lobby,
|
||||||
|
actions: {
|
||||||
|
updateLobby: Dispatch<Action>;
|
||||||
|
addMessage: Function;
|
||||||
|
updateCustomSquares: Dispatch<Partial<CustomSquares>>;
|
||||||
|
makeMove: Function;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
socket.on("connect", () => {
|
||||||
|
console.log("connected!");
|
||||||
|
socket.emit("joinLobby", lobby.code);
|
||||||
|
});
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log("disconnected!");
|
||||||
|
});
|
||||||
|
// TODO: handle disconnect
|
||||||
|
|
||||||
|
socket.on("chat", (message: Message) => {
|
||||||
|
actions.addMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("receivedLatestGame", (latestGame: Game) => {
|
||||||
|
if (latestGame.pgn && latestGame.pgn !== lobby.actualGame.pgn()) {
|
||||||
|
syncPgn(latestGame.pgn, lobby, actions);
|
||||||
|
}
|
||||||
|
actions.updateLobby({ type: "updateLobby", payload: latestGame });
|
||||||
|
|
||||||
|
syncSide(user, latestGame, lobby, actions);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("receivedMove", (m: { from: string; to: string; promotion?: string }) => {
|
||||||
|
const success = actions.makeMove(m);
|
||||||
|
if (!success) {
|
||||||
|
socket.emit("getLatestGame");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("userJoinedAsPlayer", ({ name, side }: { name: string; side: "white" | "black" }) => {
|
||||||
|
actions.addMessage({
|
||||||
|
author: { name: "server" },
|
||||||
|
message: `${name} is now playing as ${side}.`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
"gameOver",
|
||||||
|
({
|
||||||
|
reason,
|
||||||
|
winnerName,
|
||||||
|
winnerSide
|
||||||
|
}: {
|
||||||
|
reason: string;
|
||||||
|
winnerName?: string;
|
||||||
|
winnerSide?: string;
|
||||||
|
}) => {
|
||||||
|
const m = {
|
||||||
|
author: { name: "server" }
|
||||||
|
} as Message;
|
||||||
|
|
||||||
|
if (reason === "checkmate") {
|
||||||
|
m.message = `${winnerName} (${winnerSide}) has won by checkmate.`;
|
||||||
|
} else {
|
||||||
|
let message = "The game has ended in a draw";
|
||||||
|
if (reason === "repetition") {
|
||||||
|
message = message.concat(" due to threefold repetition");
|
||||||
|
} else if (reason === "insufficient") {
|
||||||
|
message = message.concat(" due to insufficient material");
|
||||||
|
} else if (reason === "stalemate") {
|
||||||
|
message = "The game has been drawn due to stalemate";
|
||||||
|
}
|
||||||
|
m.message = message.concat(".");
|
||||||
|
}
|
||||||
|
actions.addMessage(m);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
59
client/src/components/game/utils.ts
Normal file
59
client/src/components/game/utils.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type { Action, CustomSquares, Lobby } from "@/types";
|
||||||
|
import type { Game, User } from "@chessu/types";
|
||||||
|
import type { Dispatch } from "react";
|
||||||
|
|
||||||
|
export const syncPgn = (
|
||||||
|
latestPgn: string,
|
||||||
|
lobby: Lobby,
|
||||||
|
actions: {
|
||||||
|
updateCustomSquares: Dispatch<Partial<CustomSquares>>;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
lobby.actualGame.loadPgn(latestPgn as string);
|
||||||
|
|
||||||
|
const lastMove = lobby.actualGame.history({ verbose: true }).pop();
|
||||||
|
|
||||||
|
let lastMoveSquares = undefined;
|
||||||
|
let kingSquare = undefined;
|
||||||
|
if (lastMove) {
|
||||||
|
lastMoveSquares = {
|
||||||
|
[lastMove.from]: { background: "rgba(255, 255, 0, 0.4)" },
|
||||||
|
[lastMove.to]: { background: "rgba(255, 255, 0, 0.4)" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (lobby.actualGame.inCheck()) {
|
||||||
|
const kingPos = lobby.actualGame.board().reduce((acc, row, index) => {
|
||||||
|
const squareIndex = row.findIndex(
|
||||||
|
(square) =>
|
||||||
|
square && square.type === "k" && square.color === lobby.actualGame.turn()
|
||||||
|
);
|
||||||
|
return squareIndex >= 0 ? `${String.fromCharCode(squareIndex + 97)}${8 - index}` : acc;
|
||||||
|
}, "");
|
||||||
|
kingSquare = {
|
||||||
|
[kingPos]: {
|
||||||
|
background: "radial-gradient(red, rgba(255,0,0,.4), transparent 70%)",
|
||||||
|
borderRadius: "50%"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
actions.updateCustomSquares({
|
||||||
|
lastMove: lastMoveSquares,
|
||||||
|
check: kingSquare
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const syncSide = (
|
||||||
|
user: User,
|
||||||
|
game: Game | undefined,
|
||||||
|
lobby: Lobby,
|
||||||
|
actions: { updateLobby: Dispatch<Action> }
|
||||||
|
) => {
|
||||||
|
if (!game) game = lobby;
|
||||||
|
if (game.black?.id === user?.id) {
|
||||||
|
if (lobby.side !== "b") actions.updateLobby({ type: "setSide", payload: "b" });
|
||||||
|
} else if (game.white?.id === user?.id) {
|
||||||
|
if (lobby.side !== "w") actions.updateLobby({ type: "setSide", payload: "w" });
|
||||||
|
} else if (lobby.side !== "s") {
|
||||||
|
actions.updateLobby({ type: "setSide", payload: "s" });
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user