feat: win conditions
This commit is contained in:
parent
c58753cfd3
commit
1b60f8aee3
8 changed files with 124 additions and 7 deletions
|
@ -1,10 +1,11 @@
|
||||||
import { Component, createSignal, Match, Show, Switch } from 'solid-js';
|
import { Component, createEffect, createSignal, Match, Show, Switch } from 'solid-js';
|
||||||
import createWebsocket from '@solid-primitives/websocket';
|
import createWebsocket from '@solid-primitives/websocket';
|
||||||
import { wsUrl } from './constants';
|
import { wsUrl } from './constants';
|
||||||
import Spinner from './components/Spinner';
|
import Spinner from './components/Spinner';
|
||||||
import Welcome from './components/Welcome';
|
import Welcome from './components/Welcome';
|
||||||
import { Board as BoardData, Move, ServerChessEvent, Side } from './events';
|
import { Board as BoardData, GameEvent, Move, ServerChessEvent, Side } from './events';
|
||||||
import Board from './components/Board';
|
import Board from './components/Board';
|
||||||
|
import Popup from './components/Popup';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gameId: string;
|
gameId: string;
|
||||||
|
@ -14,6 +15,7 @@ const App: Component<Props> = (props) => {
|
||||||
const [board, setBoard] = createSignal<BoardData>(Array(64).fill(null));
|
const [board, setBoard] = createSignal<BoardData>(Array(64).fill(null));
|
||||||
const [possibleMoves, setPossibleMoves] = createSignal<Move[]>([]);
|
const [possibleMoves, setPossibleMoves] = createSignal<Move[]>([]);
|
||||||
const [side, setSide] = createSignal<Side | null>(null);
|
const [side, setSide] = createSignal<Side | null>(null);
|
||||||
|
const [currentEvent, setCurrentEvent] = createSignal<GameEvent | null>(null);
|
||||||
|
|
||||||
function handleEvent(e: MessageEvent<string>) {
|
function handleEvent(e: MessageEvent<string>) {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
@ -24,6 +26,10 @@ const App: Component<Props> = (props) => {
|
||||||
setPossibleMoves(event.data.moves);
|
setPossibleMoves(event.data.moves);
|
||||||
} else if (event.event === 'StartGame') {
|
} else if (event.event === 'StartGame') {
|
||||||
setSide(event.data.side);
|
setSide(event.data.side);
|
||||||
|
} else if (event.event === 'GameEvent') {
|
||||||
|
setCurrentEvent(event.data);
|
||||||
|
} else if (event.event === 'ClearGameEvent') {
|
||||||
|
setCurrentEvent(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +40,7 @@ const App: Component<Props> = (props) => {
|
||||||
setSide(null);
|
setSide(null);
|
||||||
setBoard(Array(64).fill(null));
|
setBoard(Array(64).fill(null));
|
||||||
setPossibleMoves([]);
|
setPossibleMoves([]);
|
||||||
|
setCurrentEvent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeMove(move: Move) {
|
function makeMove(move: Move) {
|
||||||
|
@ -57,7 +64,10 @@ const App: Component<Props> = (props) => {
|
||||||
<h1>Waiting for opponent...</h1>
|
<h1>Waiting for opponent...</h1>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</>}>
|
</>}>
|
||||||
<Board board={board} moves={possibleMoves} makeMove={makeMove} />
|
<Board board={board} moves={possibleMoves} makeMove={makeMove} side={side()!} />
|
||||||
|
</Show>
|
||||||
|
<Show when={currentEvent() !== null}>
|
||||||
|
<Popup {...currentEvent()!} />
|
||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
21
src-web/components/Popup.css
Normal file
21
src-web/components/Popup.css
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.popup {
|
||||||
|
min-width: 512px;
|
||||||
|
max-width: 100vw;
|
||||||
|
/* min-height: 480px; */
|
||||||
|
|
||||||
|
background-color: #000000DD;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 2px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background-color: #00000088;
|
||||||
|
}
|
20
src-web/components/Popup.tsx
Normal file
20
src-web/components/Popup.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { Component } from "solid-js";
|
||||||
|
import './Popup.css';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Popup: Component<Props> = (props) => {
|
||||||
|
console.log('popup opened', props.title, props.message);
|
||||||
|
|
||||||
|
return <div class="popup-container">
|
||||||
|
<section class="popup">
|
||||||
|
<h2>{props.title}</h2>
|
||||||
|
<p>{props.message}</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Popup;
|
|
@ -57,10 +57,19 @@ export const StartGameEvent = z.object({
|
||||||
|
|
||||||
export type StartGameEvent = z.infer<typeof StartGameEvent>;
|
export type StartGameEvent = z.infer<typeof StartGameEvent>;
|
||||||
|
|
||||||
|
export const GameEvent = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
message: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GameEvent = z.infer<typeof GameEvent>;
|
||||||
|
|
||||||
export const ServerChessEvent = z.discriminatedUnion("event", [
|
export const ServerChessEvent = z.discriminatedUnion("event", [
|
||||||
z.object({ event: z.literal('BoardUpdate'), data: BoardUpdateEvent }),
|
z.object({ event: z.literal('BoardUpdate'), data: BoardUpdateEvent }),
|
||||||
z.object({ event: z.literal('PossibleMoves'), data: PossibleMovesEvent }),
|
z.object({ event: z.literal('PossibleMoves'), data: PossibleMovesEvent }),
|
||||||
z.object({ event: z.literal('StartGame'), data: StartGameEvent })
|
z.object({ event: z.literal('StartGame'), data: StartGameEvent }),
|
||||||
|
z.object({ event: z.literal('GameEvent'), data: GameEvent }),
|
||||||
|
z.object({ event: z.literal('ClearGameEvent') }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type ServerChessEvent = z.infer<typeof ServerChessEvent>;
|
export type ServerChessEvent = z.infer<typeof ServerChessEvent>;
|
||||||
|
|
|
@ -389,6 +389,15 @@ impl From<char> for Side {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Side {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", match self {
|
||||||
|
Self::White => "White",
|
||||||
|
Self::Black => "Black",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PieceType {
|
pub enum PieceType {
|
||||||
|
|
|
@ -5,3 +5,6 @@ pub const POSITION_4: &str = "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1
|
||||||
pub const POSITION_5: &str = "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8";
|
pub const POSITION_5: &str = "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8";
|
||||||
pub const POSITION_6: &str =
|
pub const POSITION_6: &str =
|
||||||
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10";
|
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10";
|
||||||
|
|
||||||
|
pub const CHECKMATE_POSITION: &str = "7k/3Q4/5K2/8/8/8/8/8 w - - 0 0";
|
||||||
|
pub const STALEMATE_POSITION: &str = "7k/R7/8/8/4K3/8/8/4R3 w - - 0 0";
|
||||||
|
|
49
src/game.rs
49
src/game.rs
|
@ -173,8 +173,51 @@ impl ChessGame {
|
||||||
Side::White => (self.white.clone(), self.black.clone().unwrap()),
|
Side::White => (self.white.clone(), self.black.clone().unwrap()),
|
||||||
};
|
};
|
||||||
let moves = generate_legal(&self.board);
|
let moves = generate_legal(&self.board);
|
||||||
// TODO: handle player disconnects
|
|
||||||
tokio::spawn(current.send(OutgoingPlayerEvent::PossibleMoves { moves }));
|
if moves.is_empty() {
|
||||||
tokio::spawn(other.send(OutgoingPlayerEvent::PossibleMoves { moves: Vec::new() }));
|
tracing::info!("no legal moves available!");
|
||||||
|
let check = self.board.calc_check_state();
|
||||||
|
if *check.get(self.board.to_move) {
|
||||||
|
tracing::info!("checkmate!");
|
||||||
|
self.checkmate(self.board.to_move.other());
|
||||||
|
} else {
|
||||||
|
tracing::info!("stalemate!");
|
||||||
|
self.stalemate_no_legal(self.board.to_move);
|
||||||
|
}
|
||||||
|
self.game_end();
|
||||||
|
} else {
|
||||||
|
// TODO: handle player disconnects
|
||||||
|
tokio::spawn(current.send(OutgoingPlayerEvent::PossibleMoves { moves }));
|
||||||
|
tokio::spawn(other.send(OutgoingPlayerEvent::PossibleMoves { moves: Vec::new() }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkmate(&self, winner: Side) {
|
||||||
|
let event = OutgoingPlayerEvent::GameEvent {
|
||||||
|
title: "Checkmate!".to_owned(),
|
||||||
|
message: format!("{winner} wins!")
|
||||||
|
};
|
||||||
|
tokio::spawn(self.white.send(event.clone()));
|
||||||
|
if let Some(black) = &self.black {
|
||||||
|
tokio::spawn(black.send(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stalemate_no_legal(&self, no_moves: Side) {
|
||||||
|
let event = OutgoingPlayerEvent::GameEvent {
|
||||||
|
title: "Stalemate".to_owned(),
|
||||||
|
message: format!("{no_moves} has no legal moves to make!")
|
||||||
|
};
|
||||||
|
tokio::spawn(self.white.send(event.clone()));
|
||||||
|
if let Some(black) = &self.black {
|
||||||
|
tokio::spawn(black.send(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_end(&self) {
|
||||||
|
tokio::spawn(self.white.send(OutgoingPlayerEvent::PossibleMoves { moves: vec![] }));
|
||||||
|
if let Some(black) = &self.black {
|
||||||
|
tokio::spawn(black.send(OutgoingPlayerEvent::PossibleMoves { moves: vec![] }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ pub enum OutgoingPlayerEvent {
|
||||||
StartGame { side: Side },
|
StartGame { side: Side },
|
||||||
BoardUpdate { board: Vec<Option<Piece>> },
|
BoardUpdate { board: Vec<Option<Piece>> },
|
||||||
PossibleMoves { moves: Vec<Move> },
|
PossibleMoves { moves: Vec<Move> },
|
||||||
|
GameEvent { title: String, message: String },
|
||||||
|
ClearGameEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
Loading…
Reference in a new issue