Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
elkozmon committed Mar 28, 2021
1 parent be67ab0 commit d8ec2f9
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 23 deletions.
53 changes: 44 additions & 9 deletions dot_chess/board/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,11 @@ pub struct Board {
knights: BitBoard,
pawns: BitBoard,
flags: Flags,
halfmove_clock: u32,
}

impl Board {
pub fn new(pieces: Vec<(Side, Piece, Square)>, flags: Flags) -> Self {
pub fn new(pieces: Vec<(Side, Piece, Square)>, flags: Flags, halfmove_clock: u32) -> Self {
let mut black = BitBoard::EMPTY;
let mut white = BitBoard::EMPTY;
let mut kings = BitBoard::EMPTY;
Expand Down Expand Up @@ -188,6 +189,7 @@ impl Board {
knights,
pawns,
flags,
halfmove_clock,
}
}

Expand All @@ -202,13 +204,18 @@ impl Board {
knights: 0x4200000000000042.into(),
pawns: 0xff00000000ff00.into(),
flags: Flags::default(),
halfmove_clock: 0,
}
}

pub fn halfmove_clock(&self) -> u32 {
self.halfmove_clock
}

// TODO test
pub fn try_make_move(&self, ply: Ply) -> Result<(Self, Vec<Event>), Error> {
// Assert move is pseudo legal
if (self.get_pseudo_legal_moves(ply.from()) & BitBoard::square(ply.to())).is_empty() {
if (self.get_pseudo_legal_moves_from(ply.from()) & BitBoard::square(ply.to())).is_empty() {
return Err(Error::IllegalMove);
}

Expand All @@ -225,9 +232,24 @@ impl Board {
}

// TODO test
pub fn get_legal_moves(&self, from: Square) -> BitBoard {
pub fn side_has_legal_move(&self, side: Side) -> bool {
let mut pieces = self.get_pieces_by_side(side);

while pieces.not_empty() {
let square = pieces.pop_square();

if self.get_legal_moves_from(square).not_empty() {
return true;
}
}

false
}

// TODO test
pub fn get_legal_moves_from(&self, from: Square) -> BitBoard {
let mut moves = BitBoard::EMPTY;
let mut move_bb = self.get_pseudo_legal_moves(from);
let mut move_bb = self.get_pseudo_legal_moves_from(from);

while move_bb.not_empty() {
let to = move_bb.pop_square();
Expand Down Expand Up @@ -292,6 +314,7 @@ impl Board {
knights: self.knights,
pawns: self.pawns,
flags: Flags(self.flags.0),
halfmove_clock: self.halfmove_clock,
}
}

Expand Down Expand Up @@ -376,13 +399,13 @@ impl Board {
return true;
}

let bishopsQueens = attack_pieces & (self.bishops | self.queens);
if (self.bishop_attacks(square) & bishopsQueens).not_empty() {
let bishops_queens = attack_pieces & (self.bishops | self.queens);
if (self.bishop_attacks(square) & bishops_queens).not_empty() {
return true;
}

let rooksQueens = attack_pieces & (self.rooks | self.queens);
if (self.rook_attacks(square) & rooksQueens).not_empty() {
let rooks_queens = attack_pieces & (self.rooks | self.queens);
if (self.rook_attacks(square) & rooks_queens).not_empty() {
return true;
}

Expand Down Expand Up @@ -423,6 +446,9 @@ impl Board {
let from_file: File = from.into();
let to_file: File = to.into();

// Reset halfmove clock
board_new.halfmove_clock = 0;

// Is capture?
if from_file != to_file {
let en_passant = (BitBoard::square(to) & opponent_pieces).is_empty();
Expand Down Expand Up @@ -463,6 +489,9 @@ impl Board {
let (_, captured_piece) = board_new.get_piece_at(to).unwrap();
board_new.clear_piece(to);
events.push(Event::PieceLeftSquare(opponent_side, captured_piece, to));

// Reset halfmove clock
board_new.halfmove_clock = 0;
} else {
let (
king_square,
Expand Down Expand Up @@ -549,6 +578,9 @@ impl Board {
let (_, captured_piece) = board_new.get_piece_at(to).unwrap();
board_new.clear_piece(to);
events.push(Event::PieceLeftSquare(opponent_side, captured_piece, to));

// Reset halfmove clock
board_new.halfmove_clock = 0;
}
}
Piece::Rook => {
Expand All @@ -558,6 +590,9 @@ impl Board {
let (_, captured_piece) = board_new.get_piece_at(to).unwrap();
board_new.clear_piece(to);
events.push(Event::PieceLeftSquare(opponent_side, captured_piece, to));

// Reset halfmove clock
board_new.halfmove_clock = 0;
}

// Revoke castling rights
Expand Down Expand Up @@ -590,7 +625,7 @@ impl Board {
}

// TODO test
fn get_pseudo_legal_moves(&self, from: Square) -> BitBoard {
fn get_pseudo_legal_moves_from(&self, from: Square) -> BitBoard {
match self.get_piece_at(from) {
None => BitBoard::EMPTY,
Some((side, piece)) => {
Expand Down
145 changes: 131 additions & 14 deletions dot_chess/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@ mod dot_chess {
IllegalMove,
}

#[derive(Encode, Decode, Debug, Copy, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum GameOverReason {
Checkmate,
Stalemate,
InsufficientMatingMaterial,
Resignation,
Repetition,
FiftyMoveRule,
}

#[ink(event)]
pub struct PlayerMoved {
#[ink(topic)]
side: Side,
from: Square,
to: Square,
}

#[ink(event)]
pub struct GameOver {
winner: Option<Side>,
reason: GameOverReason,
}

#[ink(storage)]
pub struct DotChess {
/// Account playing as white
Expand All @@ -29,7 +54,7 @@ mod dot_chess {
black: AccountId,
/// Chess board
board: ink_storage::Pack<Board>,
/// Board history of up to 100 states
/// Board history up to last capture or pawn movement
board_history: Vec<ZobristHash>,
}

Expand Down Expand Up @@ -113,32 +138,86 @@ mod dot_chess {
/// Returns true if move was successful
#[ink(message)]
pub fn make_move(&mut self, from: Square, to: Square, flags: PlyFlags) -> bool {
let caller = self.env().caller();

// Assert it's callers turn
let account_in_turn = match self.board.get_side_turn() {
Side::White => self.white,
Side::Black => self.black,
};

if caller != account_in_turn {
if !self.is_callers_turn() {
todo!("Emit event: Invalid caller");
return false;
}

let side = self.board.get_side_turn();
let ply = Ply::new(from, to, flags);

match self.board.try_make_move(ply) {
Ok((board_new, events)) => {
// Update board
self.board = ink_storage::Pack::new(board_new);

let opponent_side = side.flip();
let opponent_has_legal_moves = self.board.side_has_legal_move(opponent_side);
if !opponent_has_legal_moves {
let opponent_king_square = self.board.get_king_square(opponent_side);

if self.board.is_attacked(opponent_king_square, side) {
// Checkmate
// TODO Announce result
} else {
// Stalemate
// TODO Announce result
}
}

// Is insufficient mating material?
let mut white_score = 0;
let mut black_score = 0;
let mut insufficient_mating_material = true;

for (side, piece, square) in self.board.get_pieces().iter() {
let ref_score = match side {
Side::White => &mut white_score,
Side::Black => &mut black_score,
};

match piece {
Piece::Knight | Piece::Bishop => *ref_score += 1,
Piece::Pawn | Piece::Rook | Piece::Queen => *ref_score = i32::MAX,
Piece::King => {}
}

drop(ref_score);

if white_score > 1 && black_score > 1 {
insufficient_mating_material = false;
break;
}
}

if insufficient_mating_material {
// TODO Announce result
}

// Clear board history to save space
if self.board.halfmove_clock() == 0 {
self.board_history.clear()
}

// Is repetition?
let new_hash = self.board_history.last().unwrap().apply(events);

self.board_history.push(new_hash);
let is_repetition = self
.board_history
.iter()
.filter(|hash| hash == new_hash)
.take(2)
.count()
== 2;

if self.board_history.len() == 100 {
// Draw
if is_repetition {
// TODO Announce result
}

self.board = ink_storage::Pack::new(board_new);
// Update history
self.board_history.push(new_hash);

// TODO Emit events

true
}
Expand All @@ -149,5 +228,43 @@ mod dot_chess {
}
}
}

pub fn claim_draw(&mut self, reason: GameOverReason) -> bool {
if !self.is_callers_turn() {
todo!("Emit event: Invalid caller");
return false;
}

match reason {
GameOverReason::FiftyMoveRule if self.board.halfmove_clock() >= 100 => {
// TODO Announce draw
}
_ => {
panic!("Invalid claim")
}
}
}

pub fn resign(&mut self) -> bool {
if !self.is_callers_turn() {
todo!("Emit event: Invalid caller");
return false;
}

// TODO Announce result
}

fn is_callers_turn(&self) -> bool {
let caller_account = self.env().caller();

// Assert it's callers turn
let side = self.board.get_side_turn();
let side_account = match side {
Side::White => self.white,
Side::Black => self.black,
};

caller_account == side_account
}
}
}

0 comments on commit d8ec2f9

Please sign in to comment.