Chess Bevy
Chess GUI
A chess GUI written using a custom-built chess library, chess_core, which intended for future use in a chess engine.
The GUI currently uses stockfish as its chess engine, communicating via UCI to get the best move for the black pieces to make.
The engine provides information about moves, and using this, the GUI can classify moves into categories (Best Move, Excellent, Good, Mistake, Blunder).
An evaluation bar is also provided via stockfish and updates after each move.
The GUI, via the chess library, is aware of legal moves, so it prevents moving pieces to illegal positions. Possible moves for each piece are shown when the piece is picked up, and the user can also undo/redo moves since move history is implemented in the chess library. The GUI shows an indicator for the last move which was made, making it clearer what is happening.
Promotion is mostly implemented; currently, pawns can only promote to queens. En passant, and castling are fully implemented, and all moves work with the move history feature, including remembering the state of castling rights and the en passant square.
The board is created using a FEN starting position, and internally represented via bitboards. There are keybinds to show the piece bitboards, the attacked squares for each player, as well as the en passant square.
Chess Core
Board
There is a Board struct which keeps track of the board state, along with the move history.
boards is an array of bitboards for each piece type, meaning that there are twelve u64 values representing the positions.
Each bitboard can be manipulated using bitwise operations to get or set certain positions, as well as more complicated tests like finding if the space between two positions is empty.
struct Board {
boards: [BitBoard; PIECE_AMT * COLOUR_AMT],
en_passant_tile: u64,
castling_rights: [(bool, bool); COLOUR_AMT],
player: Player,
half_move_counter: usize,
full_move_counter: usize,
move_history: PieceMoveHistory,
}
Move History
When adding moves to move_history, there is a check to see if this move matches the next move in the history, and if it is, the history is cleared before adding pushing move; if the move is the same as the current HistoryMove, the index is simply incremented.
fn make_move(
&mut self,
piece_move: PieceMove,
captured_piece: Option<Piece>,
en_passant_tile: Option<TilePos>,
castling_rights: [(bool, bool); COLOUR_AMT],
) {
if piece_move.show {
// Clear history depending on where current_idx is (if the move is different from the history)
if let Some(current_idx) = self.current_idx {
// If the suggested move is different to the current move in history, and is not the last move in the history
if piece_move != self.moves[current_idx].piece_move && current_idx + 1 < self.moves.len() {
self.clear_excess_moves();
}
} else if !self.moves.is_empty() {
self.clear_excess_moves();
}
self.moves
.push(HistoryMove::new(piece_move, captured_piece, en_passant_tile, castling_rights));
let _ = self.increment_index();
}
}