Compare commits

...

2 commits

Author SHA1 Message Date
Kumu ece42e982c Add basic game outcome determination
Some checks failed
continuous-integration/drone/push Build is failing
2022-01-14 10:33:05 -05:00
Kumu 35a01262b1 Add pawn promotion 2022-01-13 22:45:25 -05:00

View file

@ -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();
}
}