use engine::TranspositionTable; use model::{CheckersBitBoard, Move, PieceColor, PossibleMoves, SquareCoordinate}; use std::collections::HashSet; use tetra::graphics::{self, Color, DrawParams, Texture}; use tetra::input::MouseButton; use tetra::math::Vec2; use tetra::{input, Context, ContextBuilder, State}; const WINDOW_WIDTH: f32 = 640.0; const WINDOW_HEIGHT: f32 = 480.0; const DARK_SLATE_BLUE: Color = Color::rgb(0.2823529, 0.2392157, 0.545098); struct GameState { chess_board: Texture, possible_move_square: Texture, dark_piece: Texture, light_piece: Texture, dark_king: Texture, light_king: Texture, bit_board: CheckersBitBoard, selected_square: Option, possible_moves: HashSet, evaluation: f32, transposition_table: TranspositionTable, } impl GameState { fn new(ctx: &mut Context) -> tetra::Result { Ok(GameState { chess_board: Texture::new(ctx, "./ui/resources/chess_board.png")?, possible_move_square: Texture::new(ctx, "./ui/resources/possible_move.png")?, dark_piece: Texture::new(ctx, "./ui/resources/red_piece.png")?, light_piece: Texture::new(ctx, "./ui/resources/white_piece.png")?, dark_king: Texture::new(ctx, "./ui/resources/red_king.png")?, light_king: Texture::new(ctx, "./ui/resources/white_king.png")?, bit_board: CheckersBitBoard::starting_position(), selected_square: None, possible_moves: HashSet::new(), evaluation: 0.0, transposition_table: TranspositionTable::new(5_000_000 / 18), }) } } impl GameState { fn draw_highlighted_square(&self, ctx: &mut Context, square: SquareCoordinate) { let square_draw_params = DrawParams::new() .position(Vec2::new( 120.0 + (50.0 * square.file() as f32), 390.0 - (50.0 * square.rank() as f32), )) .scale(Vec2::new(0.5, 0.5)); self.possible_move_square.draw(ctx, square_draw_params); } } impl State for GameState { fn update(&mut self, ctx: &mut Context) -> tetra::Result { if input::is_mouse_button_released(ctx, MouseButton::Left) { let x = input::get_mouse_x(ctx); let y = input::get_mouse_y(ctx); if x > 120.0 && y > 40.0 && x < 520.0 && y < 440.0 { let file = ((x - 140.0) / 50.0).round(); let rank = ((410.0 - y) / 50.0).round(); let square = SquareCoordinate::new(rank as u8, file as u8); if self .possible_moves .iter() .map(|m| SquareCoordinate::from_value(m.end_position())) .collect::>() .contains(&square) { let selected_move = self .possible_moves .iter() .find(|m| SquareCoordinate::from_value(m.end_position()) == square) .unwrap(); // safety: this was determined to be in the list of possible moves self.bit_board = unsafe { selected_move.apply_to(self.bit_board) }; let evaluation = engine::evaluate(7, self.bit_board, self.transposition_table.mut_ref()); println!("AI advantage: {}", evaluation); // ai makes a move while self.bit_board.turn() == PieceColor::Light && !PossibleMoves::moves(self.bit_board).is_empty() { let best_move = dbg!(engine::best_move( 14, self.bit_board, self.transposition_table.mut_ref() )); self.bit_board = unsafe { best_move.apply_to(self.bit_board) }; } self.selected_square = None; self.possible_moves.clear(); let evaluation = engine::evaluate(7, self.bit_board, self.transposition_table.mut_ref()); println!("Your advantage: {}", evaluation); } else { self.selected_square = Some(square); let moves = PossibleMoves::moves(self.bit_board); self.possible_moves = moves .into_iter() .filter(|m| SquareCoordinate::from_value(m.start() as usize) == square) .collect(); } } } Ok(()) } fn draw(&mut self, ctx: &mut Context) -> tetra::Result { graphics::clear(ctx, DARK_SLATE_BLUE); let board_draw_params = DrawParams::new() .position(Vec2::new(120.0, 40.0)) .scale(Vec2::new(0.4938272, 0.4938272)); self.chess_board.draw(ctx, board_draw_params); if let Some(square) = self.selected_square { self.draw_highlighted_square(ctx, square); } for possible_move in &self.possible_moves { self.draw_highlighted_square( ctx, SquareCoordinate::from_value(possible_move.end_position()), ); } for row in 0..8 { for col in 0..8 { if let Some(piece) = self.bit_board.get_at_row_col(row, col) { let piece_draw_params = DrawParams::new() .position(Vec2::new( 130.0 + (50.0 * col as f32), 400.0 - (50.0 * row as f32), )) .scale(Vec2::new(0.3, 0.3)); match piece.color() { PieceColor::Dark => { if piece.is_king() { self.dark_king.draw(ctx, piece_draw_params) } else { self.dark_piece.draw(ctx, piece_draw_params) } } PieceColor::Light => { if piece.is_king() { self.light_king.draw(ctx, piece_draw_params) } else { self.light_piece.draw(ctx, piece_draw_params) } } } } } } Ok(()) } } fn main() -> tetra::Result { let title = "Checkers with Ampere"; let mut builder = ContextBuilder::new(title, WINDOW_WIDTH as i32, WINDOW_HEIGHT as i32); builder.show_mouse(true); builder.quit_on_escape(true); if cfg!(debug_assertions) { builder.debug_info(true); } else { builder.debug_info(false); } builder.build()?.run(GameState::new) }