Compare commits
2 commits
4160032cba
...
ece42e982c
Author | SHA1 | Date | |
---|---|---|---|
Kumu | ece42e982c | ||
Kumu | 35a01262b1 |
|
@ -1,7 +1,7 @@
|
|||
use crate::api_types::{
|
||||
self,
|
||||
Color::{self, White},
|
||||
Coordinates, Move, PieceKind, PollResponse,
|
||||
Coordinates, PieceKind, PollResponse,
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
|
@ -40,6 +40,25 @@ impl ops::Add<(i8, i8)> for &Position {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Move {
|
||||
from: Position,
|
||||
to: Position,
|
||||
promote_to: Option<PieceKind>,
|
||||
}
|
||||
|
||||
impl Move {
|
||||
fn from_api_move(api_move: api_types::Move) -> Self {
|
||||
let from = Position::from_coordinates(api_move.from);
|
||||
let to = Position::from_coordinates(api_move.to);
|
||||
Move {
|
||||
from,
|
||||
to,
|
||||
promote_to: api_move.kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Piece {
|
||||
kind: PieceKind,
|
||||
|
@ -146,8 +165,8 @@ impl Piece {
|
|||
*self.visible.borrow_mut() = visible;
|
||||
}
|
||||
|
||||
fn get_possible_moves(&self, piece_pos: &Position, state: &GameState) -> HashSet<Position> {
|
||||
let mut possible_moves = HashSet::new();
|
||||
fn get_possible_moves(&self, piece_pos: &Position, state: &GameState) -> HashSet<Move> {
|
||||
let mut possible_moves: HashSet<Move> = HashSet::new();
|
||||
|
||||
// for all pieces except pawn, visible squares are squares you can move to
|
||||
// for pawns, visible squares are diagonally ahead, possible moves are either 1 or 2 squares
|
||||
|
@ -163,23 +182,87 @@ impl Piece {
|
|||
for pos in self.visible.borrow().iter() {
|
||||
if let Some(piece) = state.board.get(pos) {
|
||||
if piece.color == opposite_color {
|
||||
possible_moves.insert(pos.clone());
|
||||
// if moving to back rank, add a move for each promotion option, otherwise don't promote
|
||||
if pos.rank == 0 || pos.rank == 7 {
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: Some(PieceKind::Bishop),
|
||||
});
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: Some(PieceKind::Knight),
|
||||
});
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: Some(PieceKind::Rook),
|
||||
});
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: Some(PieceKind::Queen),
|
||||
});
|
||||
} else {
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if state.en_passant_target.as_ref() == Some(pos) {
|
||||
possible_moves.insert(pos.clone());
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// can move to square directly ahead if no piece is there
|
||||
let one_square = (piece_pos + one_square).unwrap();
|
||||
if state.board.get(&one_square).is_none() {
|
||||
possible_moves.insert(one_square);
|
||||
// if moving to back rank, add a move for each promotion option, otherwise don't promote
|
||||
if one_square.rank == 0 || one_square.rank == 7 {
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: one_square.clone(),
|
||||
promote_to: Some(PieceKind::Bishop),
|
||||
});
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: one_square.clone(),
|
||||
promote_to: Some(PieceKind::Knight),
|
||||
});
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: one_square.clone(),
|
||||
promote_to: Some(PieceKind::Rook),
|
||||
});
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: one_square,
|
||||
promote_to: Some(PieceKind::Queen),
|
||||
});
|
||||
} else {
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: one_square,
|
||||
promote_to: None,
|
||||
});
|
||||
}
|
||||
// can move two squares ahead if we are on starting rank
|
||||
if piece_pos.rank == starting_rank {
|
||||
let two_square = (piece_pos + two_square).unwrap();
|
||||
if state.board.get(&two_square).is_none() {
|
||||
possible_moves.insert(two_square);
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: two_square,
|
||||
promote_to: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +282,11 @@ impl Piece {
|
|||
true
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.map(|pos| Move {
|
||||
from: piece_pos.clone(),
|
||||
to: pos.clone(),
|
||||
promote_to: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// check if king can castle
|
||||
|
@ -217,7 +304,12 @@ impl Piece {
|
|||
&& !state.player_in_check(&self.color)
|
||||
{
|
||||
// move is represented as king moving two squares
|
||||
possible_moves.insert((piece_pos + (2, 0)).unwrap());
|
||||
let castle_pos = (piece_pos + (2, 0)).unwrap();
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: castle_pos,
|
||||
promote_to: None,
|
||||
});
|
||||
}
|
||||
// queen side castle - must still be allowed and have three empty squares between
|
||||
// king and rook
|
||||
|
@ -233,7 +325,12 @@ impl Piece {
|
|||
&& !state.player_in_check(&self.color)
|
||||
{
|
||||
// move is represented as king moving two squares
|
||||
possible_moves.insert((piece_pos + (-2, 0)).unwrap());
|
||||
let castle_pos = (piece_pos + (-2, 0)).unwrap();
|
||||
possible_moves.insert(Move {
|
||||
from: piece_pos.clone(),
|
||||
to: castle_pos,
|
||||
promote_to: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,10 +338,10 @@ impl Piece {
|
|||
// final validation - possible moves must not lead to check
|
||||
let possible_moves = possible_moves
|
||||
.iter()
|
||||
.filter(|&to_pos| {
|
||||
.filter(|&mv| {
|
||||
let mut new_state = state.clone();
|
||||
// apply move
|
||||
new_state.apply_move(piece_pos.clone(), to_pos.clone());
|
||||
new_state.apply_move(mv);
|
||||
//new_state.generate_visible();
|
||||
!new_state.player_in_check(&self.color)
|
||||
})
|
||||
|
@ -260,6 +357,12 @@ struct CastleState {
|
|||
can_castle_queen_side: bool,
|
||||
}
|
||||
|
||||
pub(crate) enum GameOutcome {
|
||||
WhiteWin,
|
||||
BlackWin,
|
||||
Draw,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct GameState {
|
||||
board: HashMap<Position, Piece>,
|
||||
|
@ -361,7 +464,61 @@ impl GameState {
|
|||
opponent_visible.contains(king_pos)
|
||||
}
|
||||
|
||||
fn apply_move(&mut self, from: Position, to: Position) {
|
||||
fn get_possible_moves(&self, color: &Color) -> HashSet<Move> {
|
||||
let mut possible_moves = HashSet::new();
|
||||
for (pos, piece) in self.board {
|
||||
if piece.color == color {
|
||||
possible_moves.extend(piece.get_possible_moves(&pos, &self));
|
||||
}
|
||||
}
|
||||
possible_moves
|
||||
}
|
||||
|
||||
// returns outcome of game if it is over, otherwise returns None
|
||||
fn get_outcome(&self) -> Option<GameOutcome> {
|
||||
// checkmate
|
||||
if self.player_in_check(&Color::White) && self.get_possible_moves(&Color::White).is_empty() && self.allowed_turn == Color::White {
|
||||
Some(GameOutcome::BlackWin)
|
||||
} else if self.player_in_check(&Color::Black) && self.get_possible_moves(&Color::Black).is_empty() && self.allowed_turn == Color::Black {
|
||||
Some(GameOutcome::WhiteWin)
|
||||
}
|
||||
|
||||
if !self.player_in_check(&Color::White) && self.get_possible_moves(&Color::White).is_empty() && self.allowed_turn == Color::White {
|
||||
Some(GameOutcome::Draw)
|
||||
} else if !self.player_in_check(&Color::Black) && self.get_possible_moves(&Color::Black).is_empty() && self.allowed_turn == Color::Black {
|
||||
Some(GameOutcome::Draw)
|
||||
}
|
||||
|
||||
// draw by insufficient material - occurs if both sides only have king, or king and minor piece
|
||||
let is_insufficient = True;
|
||||
if self.board.len() <= 4 {
|
||||
let mut white_pieces = 0;
|
||||
let mut black_pieces = 0;
|
||||
for (_, piece) in self.board {
|
||||
if piece.kind == PieceKind::Rook || piece.kind == PieceKind::Queen || piece.kind == PieceKind::Pawn {
|
||||
is_insufficient = False;
|
||||
} else {
|
||||
if piece.color == Color::White {
|
||||
white_pieces += 1;
|
||||
} else {
|
||||
black_pieces += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if white_pieces > 2 || black_pieces > 2 {
|
||||
is_insufficient = False;
|
||||
}
|
||||
}
|
||||
if is_insufficient {
|
||||
Some(GameOutcome::Draw)
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn apply_move(&mut self, board_move: &Move) {
|
||||
let from = board_move.from.clone();
|
||||
let to = board_move.to.clone();
|
||||
let piece = self.board.remove(&from).unwrap();
|
||||
|
||||
// if pawn is moving to en passant square, remove captured pawn
|
||||
|
@ -439,17 +596,26 @@ impl GameState {
|
|||
}
|
||||
}
|
||||
|
||||
// in the case of promotion, change the piecekind
|
||||
let piece = match &board_move.promote_to {
|
||||
None => piece,
|
||||
Some(new_kind) => {
|
||||
let mut piece = piece.clone();
|
||||
piece.kind = new_kind.clone();
|
||||
piece
|
||||
}
|
||||
};
|
||||
|
||||
// move the piece
|
||||
self.board.insert(to, piece);
|
||||
|
||||
self.generate_visible()
|
||||
}
|
||||
|
||||
pub(crate) fn handle(&mut self, action: Move) {
|
||||
let from_pos = Position::from_coordinates(action.from);
|
||||
let to_pos = Position::from_coordinates(action.to);
|
||||
pub(crate) fn handle(&mut self, action: api_types::Move) {
|
||||
let board_move = Move::from_api_move(action);
|
||||
|
||||
let piece = match self.board.get(&from_pos) {
|
||||
let piece = match self.board.get(&board_move.from) {
|
||||
None => {
|
||||
panic!("No piece found at position specified in move");
|
||||
}
|
||||
|
@ -457,8 +623,11 @@ impl GameState {
|
|||
}
|
||||
.clone();
|
||||
|
||||
if piece.get_possible_moves(&from_pos, self).contains(&to_pos) {
|
||||
self.apply_move(from_pos, to_pos);
|
||||
if piece
|
||||
.get_possible_moves(&board_move.from, self)
|
||||
.contains(&board_move)
|
||||
{
|
||||
self.apply_move(&board_move);
|
||||
self.allowed_turn = self.allowed_turn.opposite();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue