feat: win conditions

This commit is contained in:
Ashhhleyyy 2022-11-02 14:25:59 +00:00
parent c58753cfd3
commit 1b60f8aee3
Signed by: ash
GPG key ID: 83B789081A0878FB
8 changed files with 124 additions and 7 deletions

View file

@ -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 { wsUrl } from './constants';
import Spinner from './components/Spinner';
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 Popup from './components/Popup';
interface Props {
gameId: string;
@ -14,6 +15,7 @@ const App: Component<Props> = (props) => {
const [board, setBoard] = createSignal<BoardData>(Array(64).fill(null));
const [possibleMoves, setPossibleMoves] = createSignal<Move[]>([]);
const [side, setSide] = createSignal<Side | null>(null);
const [currentEvent, setCurrentEvent] = createSignal<GameEvent | null>(null);
function handleEvent(e: MessageEvent<string>) {
const data = JSON.parse(e.data);
@ -24,6 +26,10 @@ const App: Component<Props> = (props) => {
setPossibleMoves(event.data.moves);
} else if (event.event === 'StartGame') {
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);
setBoard(Array(64).fill(null));
setPossibleMoves([]);
setCurrentEvent(null);
}
function makeMove(move: Move) {
@ -57,7 +64,10 @@ const App: Component<Props> = (props) => {
<h1>Waiting for opponent...</h1>
<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>
</Match>
</Switch>

View 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;
}

View 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;

View file

@ -57,10 +57,19 @@ export const StartGameEvent = z.object({
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", [
z.object({ event: z.literal('BoardUpdate'), data: BoardUpdateEvent }),
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>;

View file

@ -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)]
#[serde(rename_all = "snake_case")]
pub enum PieceType {

View file

@ -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_6: &str =
"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";

View file

@ -173,8 +173,51 @@ impl ChessGame {
Side::White => (self.white.clone(), self.black.clone().unwrap()),
};
let moves = generate_legal(&self.board);
if moves.is_empty() {
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![] }));
}
}
}

View file

@ -31,6 +31,8 @@ pub enum OutgoingPlayerEvent {
StartGame { side: Side },
BoardUpdate { board: Vec<Option<Piece>> },
PossibleMoves { moves: Vec<Move> },
GameEvent { title: String, message: String },
ClearGameEvent,
}
#[async_trait]