Skip to content

179 server create chess puzzle framework #210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/client/game/game-end-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ function gameOverIcon(reason: GameEndReason, side: Side) {
// check which side won
const whiteWon =
reason === GameFinishedReason.BLACK_CHECKMATED ||
reason === GameInterruptedReason.BLACK_RESIGNED;
reason === GameInterruptedReason.BLACK_RESIGNED ||
reason === GameFinishedReason.PUZZLE_SOLVED;
const blackWon =
reason === GameFinishedReason.WHITE_CHECKMATED ||
reason === GameInterruptedReason.WHITE_RESIGNED;
reason === GameInterruptedReason.WHITE_RESIGNED ||
reason === GameFinishedReason.PUZZLE_SOLVED;

// checks which side is asking and assigns win/lost accordingly
const won = side === Side.WHITE ? whiteWon : blackWon;
Expand Down Expand Up @@ -133,6 +135,8 @@ function gameOverMessage(reason: GameEndReason) {
return "Checkmate - White Wins";
case GameFinishedReason.STALEMATE:
return "Draw - Stalemate";
case GameFinishedReason.PUZZLE_SOLVED:
return "Puzzle Solved";
case GameFinishedReason.THREEFOLD_REPETITION:
return "Draw - Threefold Repetition";
case GameFinishedReason.INSUFFICIENT_MATERIAL:
Expand Down
27 changes: 24 additions & 3 deletions src/client/game/game.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Dispatch, useState } from "react";

import {
GameEndMessage,
GameFinishedMessage,
GameHoldMessage,
GameInterruptedMessage,
SetChessMessage,
} from "../../common/message/game-message";
import { MoveMessage } from "../../common/message/game-message";
import {
Expand Down Expand Up @@ -35,14 +37,28 @@ function getMessageHandler(
chess: ChessEngine,
setChess: Dispatch<ChessEngine>,
setGameInterruptedReason: Dispatch<GameInterruptedReason>,
setGameEndedReason: Dispatch<GameEndReason>,
setGameHoldReason: Dispatch<GameHoldReason>,
): MessageHandler {
return (message) => {
if (message instanceof MoveMessage) {
// Must be a new instance of ChessEngine to trigger UI redraw
setChess(chess.copy(message.move));
// short wait so the pieces don't teleport into place
setTimeout(() => {
setChess(chess.copy(message.move));
}, 500);
} else if (message instanceof SetChessMessage) {
const fen = message.chess;
if (fen) {
setTimeout(() => {
chess.loadFen(fen);
setChess(chess.copy());
}, 500);
}
} else if (message instanceof GameInterruptedMessage) {
setGameInterruptedReason(message.reason);
} else if (message instanceof GameEndMessage) {
setGameEndedReason(message.reason);
} else if (message instanceof GameHoldMessage) {
setGameHoldReason(message.reason);
}
Expand All @@ -57,6 +73,7 @@ export function Game(): JSX.Element {
const [chess, setChess] = useState(new ChessEngine());
const [gameInterruptedReason, setGameInterruptedReason] =
useState<GameInterruptedReason>();
const [gameEndedReason, setGameEndedReason] = useState<GameEndReason>();
const [gameHoldReason, setGameHoldReason] = useState<GameHoldReason>();
const [rotation, setRotation] = useState<number>(0);

Expand All @@ -66,6 +83,7 @@ export function Game(): JSX.Element {
chess,
setChess,
setGameInterruptedReason,
setGameEndedReason,
setGameHoldReason,
),
);
Expand Down Expand Up @@ -103,7 +121,9 @@ export function Game(): JSX.Element {
// check if the game has ended or been interrupted
let gameEndReason: GameEndReason | undefined = undefined;
const gameFinishedReason = chess.getGameFinishedReason();
if (gameFinishedReason !== undefined) {
if (gameEndedReason !== undefined) {
gameEndReason = gameEndedReason;
} else if (gameFinishedReason !== undefined) {
sendMessage(new GameFinishedMessage(gameFinishedReason));
gameEndReason = gameFinishedReason;
} else if (gameInterruptedReason !== undefined) {
Expand All @@ -115,7 +135,6 @@ export function Game(): JSX.Element {
gameEndReason !== undefined ?
<GameEndDialog reason={gameEndReason} side={side} />
: null;

const gameOfferDialog =
gameHoldReason !== undefined ?
gameHoldReason === GameHoldReason.DRAW_CONFIRMATION ?
Expand All @@ -142,6 +161,8 @@ export function Game(): JSX.Element {
<NavbarMenu
sendMessage={sendMessage}
side={side}
difficulty={data.difficulty}
AiDifficulty={data.AiDifficulty}
setRotation={setRotation}
/>
<div id="body-container" className={bgColor()}>
Expand Down
84 changes: 55 additions & 29 deletions src/client/game/navbar-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import "../colors.css";
interface NavbarMenuProps {
sendMessage: SendMessage;
side: Side;
difficulty?: string;
AiDifficulty?: number;
setRotation: Dispatch<React.SetStateAction<number>>; //set state type
}

Expand All @@ -40,6 +42,20 @@ interface NavbarMenuProps {
export function NavbarMenu(props: NavbarMenuProps): JSX.Element {
// Store react router state for game
const navigate = useNavigate();
const difficultyButton =
props.difficulty ?
<Button minimal disabled text={"rating: " + props.difficulty} />
: null;

const AiArray = ["Baby", "Beginner", "Intermediate", "Advances"];
const AiDifficultyButton =
props.AiDifficulty ?
<Button
minimal
disabled
text={"AI Difficulty: " + AiArray[props.AiDifficulty]}
/>
: null;

/** create navbar rotate button */
const rotateButton =
Expand All @@ -54,43 +70,53 @@ export function NavbarMenu(props: NavbarMenuProps): JSX.Element {
});
}}
/>
: "";
: undefined;

const resignButton =
props.side === Side.SPECTATOR ?
undefined
: <Button
icon="flag"
variant="minimal"
text="Resign"
intent="danger"
onClick={async () => {
props.sendMessage(
new GameInterruptedMessage(
props.side === Side.WHITE ?
GameInterruptedReason.WHITE_RESIGNED
: GameInterruptedReason.BLACK_RESIGNED,
),
);
}}
/>;

const drawButton =
props.side === Side.SPECTATOR ?
undefined
: <Button
icon="pause"
variant="minimal"
text="Draw"
intent="danger"
onClick={async () => {
props.sendMessage(
new GameHoldMessage(GameHoldReason.DRAW_CONFIRMATION),
);
}}
/>;

return (
<Navbar className={bgColor()}>
<NavbarGroup>
<NavbarHeading className={textColor()}>ChessBot</NavbarHeading>
<NavbarDivider />
<Button
icon="flag"
variant="minimal"
text="Resign"
intent="danger"
onClick={async () => {
props.sendMessage(
new GameInterruptedMessage(
props.side === Side.WHITE ?
GameInterruptedReason.WHITE_RESIGNED
: GameInterruptedReason.BLACK_RESIGNED,
),
);
}}
/>
<Button
icon="pause"
variant="minimal"
text="Draw"
intent="danger"
onClick={async () => {
props.sendMessage(
new GameHoldMessage(
GameHoldReason.DRAW_CONFIRMATION,
),
);
}}
/>
{resignButton}
{drawButton}
</NavbarGroup>
<NavbarGroup align="right">
{difficultyButton}
{AiDifficultyButton}
{rotateButton}
<Button
icon={darkModeIcon()}
Expand Down
8 changes: 0 additions & 8 deletions src/client/puzzle/puzzle.tsx

This file was deleted.

73 changes: 73 additions & 0 deletions src/client/puzzle/select-puzzle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Button, MenuItem } from "@blueprintjs/core";
import { ItemRenderer, Select } from "@blueprintjs/select";
import { post } from "../api";
import { useNavigate } from "react-router-dom";
import { PuzzleComponents } from "../../server/api/api";

const renderPuzzleOptions: ItemRenderer<string> = (
puzzleNumber,
{ modifiers, handleFocus, handleClick },
) => {
return (
<MenuItem
key={puzzleNumber}
active={modifiers.active}
roleStructure="listoption"
text={puzzleNumber}
onFocus={handleFocus}
onClick={handleClick}
/>
);
};

interface SelectPuzzleProps {
puzzles: Map<string, PuzzleComponents>;
selectedPuzzle: string | undefined;
onPuzzleSelected: (puzzle: string) => void;
}

export function SelectPuzzle(props: SelectPuzzleProps) {
const navigate = useNavigate();
const hasSelection = props.selectedPuzzle !== undefined;

const submit = (
<Button
text="Play"
icon="arrow-right"
intent="primary"
onClick={async () => {
if (props.selectedPuzzle && props.puzzles) {
//convert puzzle to map and send to start puzzles
const puzzle = props.puzzles as Map<string, object>;
const promise = post("/start-puzzle-game", {
puzzle: JSON.stringify(puzzle[props.selectedPuzzle]),
});
promise.then(() => {
navigate("/game");
});
}
}}
/>
);
return (
<>
<Select<string>
items={[...Object.keys(props.puzzles)]}
itemRenderer={renderPuzzleOptions}
onItemSelect={props.onPuzzleSelected}
filterable={false}
popoverProps={{ minimal: true }}
>
<Button
text={
hasSelection ?
props.selectedPuzzle
: "Select a puzzle..."
}
rightIcon="double-caret-vertical"
/>
</Select>
{submit}
</>
);
}
46 changes: 46 additions & 0 deletions src/client/puzzle/setup-puzzle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from "react";
import { SetupBase } from "../setup/setup-base";
import { SelectPuzzle } from "./select-puzzle";
import { NonIdealState, Spinner } from "@blueprintjs/core";
import { get, useEffectQuery } from "../api";
import { Navigate } from "react-router-dom";

export function SetupPuzzle() {
const [selectedPuzzle, setSelectedPuzzle] = useState<string | undefined>();

//get puzzles from api
const { isPending, data, isError } = useEffectQuery(
"get-puzzles",
async () => {
return get("/get-puzzles").then((puzzles) => {
return puzzles;
});
},
false,
);

if (isPending) {
return (
<NonIdealState
icon={<Spinner intent="primary" />}
title="Loading..."
/>
);
} else if (isError) {
return <Navigate to="/home" />;
}

if (data === undefined) {
return <Spinner intent="primary" />;
} else {
return (
<SetupBase>
<SelectPuzzle
puzzles={data}
selectedPuzzle={selectedPuzzle}
onPuzzleSelected={setSelectedPuzzle}
/>
</SetupBase>
);
}
}
5 changes: 0 additions & 5 deletions src/client/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createBrowserRouter } from "react-router-dom";
import { Setup } from "./setup/setup";
import { Debug } from "./debug/debug";
import { Game } from "./game/game";
import { Puzzle } from "./puzzle/puzzle";
import { Lobby } from "./setup/lobby";
import { Home } from "./home";
import { Debug2 } from "./debug/debug2";
Expand Down Expand Up @@ -33,10 +32,6 @@ export const router = createBrowserRouter([
path: "/lobby",
element: <Lobby />,
},
{
path: "/puzzle",
element: <Puzzle />,
},
{
path: "/game",
element: <Game />,
Expand Down
4 changes: 4 additions & 0 deletions src/client/setup/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Dispatch, useState } from "react";
import { SetupGame } from "./setup-game";
import { Navigate, useNavigate } from "react-router-dom";
import { ClientType, GameType } from "../../common/client-types";
import { SetupPuzzle } from "../puzzle/setup-puzzle";
import { get, useEffectQuery } from "../api";
import {
allSettings,
Expand Down Expand Up @@ -68,6 +69,9 @@ export function Setup(): JSX.Element {
}
/>
: null}
{setupType === SetupType.PUZZLE ?
<SetupPuzzle />
: null}
</SetupBase>
);
} else {
Expand Down
Loading