diff --git a/Cargo.toml b/Cargo.toml index a2c9592..7809ba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["rdraught", "rdraught-cli", "rdraught-pi", "rdraught-ui", "rdraught-w4"] +members = ["rdraught", "rdraught-cli", "rdraught-ui", "rdraught-w4"] resolver = "3" [workspace.package] @@ -26,6 +26,3 @@ strum = "0.27.1" strum_macros = "0.27.1" heapless = "0.8.0" -#[patch.crates-io] -#cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core.git", package = "cairo-rs", tag="0.20.12" } -#cairo-sys-rs = { git = "https://github.com/gtk-rs/gtk-rs-core.git", package = "cairo-sys-rs", tag="0.20.12" } diff --git a/rdraught-pi/Cargo.toml b/rdraught-pi/Cargo.toml deleted file mode 100644 index 3efcb84..0000000 --- a/rdraught-pi/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "rdraught-pi" -version = "0.1.0" -edition = "2024" -authors = ["Walter Oggioni "] -license = "MIT" -rust-version = "1.87" - -[[bin]] -name = "hello" -path = "src/hello.rs" - -[dependencies] -panic-halt = "1.0" -libc = { version = "0.2", default-features = false } - diff --git a/rdraught-pi/src/hello.rs b/rdraught-pi/src/hello.rs deleted file mode 100644 index dc995b3..0000000 --- a/rdraught-pi/src/hello.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_std] -#![no_main] - -use libc::printf; - -extern crate libc; -extern crate panic_halt; - -#[link(name = "c", kind = "static")] -unsafe extern "C" {} - -#[unsafe(no_mangle)] -pub extern "C" fn main() { - unsafe { - printf("Hello world!!\n".as_ptr() as *const i8); - } -} diff --git a/rdraught-ui/src/final_dialog.rs b/rdraught-ui/src/final_dialog.rs index c1f012b..128a93a 100644 --- a/rdraught-ui/src/final_dialog.rs +++ b/rdraught-ui/src/final_dialog.rs @@ -1,5 +1,5 @@ use gtk4::{AlertDialog, Window, prelude::IsA}; -use rdraught::draughts::Player; +use rdraught::Player; pub(crate) async fn create_dialog>(window: W, winner: Player) { let msg = match winner { diff --git a/rdraught-ui/src/greeting_dialog.rs b/rdraught-ui/src/greeting_dialog.rs index 32788f7..2afaa6a 100644 --- a/rdraught-ui/src/greeting_dialog.rs +++ b/rdraught-ui/src/greeting_dialog.rs @@ -1,7 +1,7 @@ use gtk4::{Align, Application, Box, CheckButton, Label, Orientation, Window, prelude::*}; use crate::types::SharedMutable; -use rdraught::draughts::Player; +use rdraught::Player; pub(crate) fn create(application: &Application, current_player: SharedMutable) -> Window { let label = Label::builder().label("Main player:").build(); diff --git a/rdraught-ui/src/main.rs b/rdraught-ui/src/main.rs index 396d7dc..c9fc248 100644 --- a/rdraught-ui/src/main.rs +++ b/rdraught-ui/src/main.rs @@ -1,13 +1,5 @@ use glib::ExitCode; -use rdraught; use rdraught::draughts::DraughtsGame; -use rdraught_ui; -mod geo2d; - -mod final_dialog; -mod greeting_dialog; -mod rdraught_application; -mod types; fn main() -> ExitCode { let game = DraughtsGame::default(); diff --git a/rdraught-ui/src/rdraught_application.rs b/rdraught-ui/src/rdraught_application.rs index 4d8920b..fcf92bc 100644 --- a/rdraught-ui/src/rdraught_application.rs +++ b/rdraught-ui/src/rdraught_application.rs @@ -5,8 +5,10 @@ use glib::ExitCode; use gtk4::glib::{MainContext, Propagation}; use gtk4::{self as gtk, gdk::ffi::GDK_BUTTON_PRIMARY}; use gtk4::{Application, DrawingArea, prelude::*}; -use rdraught::draughts::{DraughtsBoard, DraughtsGame, Error, Move, Piece, Player}; -use rdraught::position::Position; +use rdraught::{ + DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication, + RectangularBoard, +}; use rsvg::SvgHandle; use std::thread; const SQUARE_SIZE: f64 = 1.0; @@ -169,7 +171,7 @@ fn draw_score_bar( ) { fn modulate_score(relative_score: f64) -> f64 { let x = relative_score; - f64::atan(8.0 * x - 4.0) / f64::atan(4.0) / 2.0 + 0.5 + 1.0 - (f64::atan(8.0 * x - 4.0) / f64::atan(4.0) / 2.0 + 0.5) } let score_bar = Rectangle::new( board.tl().x() - board.width() / 10.0, @@ -177,31 +179,56 @@ fn draw_score_bar( board.width() / 16.0, board.height(), ); + let num_rects = 40usize; + let spacing = board.height() / 200.0; + let rect_height = (board.height() - spacing * (num_rects - 1) as f64) / (num_rects as f64); let score_percentage = modulate_score(draughts_game.relative_score(current_player) as f64); + let threshold = (score_percentage * num_rects as f64) as usize; let tl = score_bar.tl(); cr.save().unwrap(); - match current_player { - Player::White => cr.set_source_rgb(1.0, 0.0, 0.0), - Player::Red => cr.set_source_rgb(1.0, 1.0, 1.0), + cr.set_line_width(spacing / 4.0); + for i in 0..threshold { + match current_player { + Player::White => cr.set_source_rgb(1.0, 0.0, 0.0), + Player::Red => cr.set_source_rgb(1.0, 1.0, 1.0), + } + cr.rectangle( + tl.x(), + tl.y() + i as f64 * (rect_height + spacing), + score_bar.width(), + rect_height, + ); + cr.fill().unwrap(); + cr.set_source_rgb(0.0, 0.0, 0.0); + cr.rectangle( + tl.x(), + tl.y() + i as f64 * (rect_height + spacing), + score_bar.width(), + rect_height, + ); + cr.stroke().unwrap(); } - cr.rectangle( - score_bar.tl().x(), - score_bar.tl().y(), - score_bar.width(), - score_bar.height() * (1.0 - score_percentage), - ); - cr.fill().unwrap(); - match current_player { - Player::White => cr.set_source_rgb(1.0, 1.0, 1.0), - Player::Red => cr.set_source_rgb(1.0, 0.0, 0.0), + for i in threshold..num_rects { + match current_player { + Player::White => cr.set_source_rgb(1.0, 1.0, 1.0), + Player::Red => cr.set_source_rgb(1.0, 0.0, 0.0), + } + cr.rectangle( + tl.x(), + tl.y() + i as f64 * (rect_height + spacing), + score_bar.width(), + rect_height, + ); + cr.fill().unwrap(); + cr.set_source_rgb(0.0, 0.0, 0.0); + cr.rectangle( + tl.x(), + tl.y() + i as f64 * (rect_height + spacing), + score_bar.width(), + rect_height, + ); + cr.stroke().unwrap(); } - cr.rectangle( - tl.x(), - tl.y() + score_bar.height() * (1.0 - score_percentage), - score_bar.width(), - score_bar.height() * score_percentage, - ); - cr.fill().unwrap(); cr.restore().unwrap(); } @@ -316,7 +343,7 @@ fn create_game_window( draw_piece( cr, &square, - rd.borrow().game.piece_at(position), + position.map_or(Piece::NoPiece, |p| rd.borrow().game().piece_at(p)), &crown_red_handle, &crown_white_handle, ) @@ -326,16 +353,12 @@ fn create_game_window( } if let Some(selected_position) = selected_piece.get() { let screen_position = match current_player { - Player::White => { - Position::new(8 - 1 - selected_position.row(), selected_position.col()) - } - Player::Red => { - Position::new(selected_position.row(), 8 - 1 - selected_position.col()) - } + Player::White => (8 - 1 - selected_position.row(), selected_position.col()), + Player::Red => (selected_position.row(), 8 - 1 - selected_position.col()), }; let square = Rectangle::new( - screen_position.col() as f64 * SQUARE_SIZE, - screen_position.row() as f64 * SQUARE_SIZE, + screen_position.1 as f64 * SQUARE_SIZE, + screen_position.0 as f64 * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE, ); @@ -349,7 +372,7 @@ fn create_game_window( cr.clip(); cr.new_path(); cr.set_source_rgb(0.0, 0.0, 1.0); - cr.set_line_width((square.width() + square.height()) * 0.05); + cr.set_line_width((square.width() + square.height()) * 0.035); cr.move_to(square.tl().x(), square.tl().y()); cr.line_to(square.tl().x(), square.br().y()); cr.line_to(square.br().x(), square.br().y()); @@ -364,12 +387,12 @@ fn create_game_window( for mv in am.iter() { let end_pos = mv.get_end_position(); let screen_position = match current_player { - Player::White => Position::new(8 - 1 - end_pos.row(), end_pos.col()), - Player::Red => Position::new(end_pos.row(), 8 - 1 - end_pos.col()), + Player::White => (8 - 1 - end_pos.row(), end_pos.col()), + Player::Red => (end_pos.row(), 8 - 1 - end_pos.col()), }; let square = Rectangle::new( - screen_position.col() as f64 * SQUARE_SIZE, - screen_position.row() as f64 * SQUARE_SIZE, + screen_position.1 as f64 * SQUARE_SIZE, + screen_position.0 as f64 * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE, ); @@ -383,7 +406,7 @@ fn create_game_window( cr.clip(); cr.new_path(); cr.set_source_rgb(0.0, 1.0, 0.0); - cr.set_line_width((square.width() + square.height()) * 0.05); + cr.set_line_width((square.width() + square.height()) * 0.035); cr.move_to(square.tl().x(), square.tl().y()); cr.line_to(square.tl().x(), square.br().y()); cr.line_to(square.br().x(), square.br().y()); @@ -393,7 +416,7 @@ fn create_game_window( cr.restore().unwrap(); } } - draw_score_bar(cr, &board, &rd.borrow().game, current_player); + draw_score_bar(cr, &board, rd.borrow().game(), current_player); }); } let gesture = gtk::GestureClick::new(); @@ -408,7 +431,7 @@ fn create_game_window( let window = window.clone(); gesture.connect_pressed(move |gesture, _, x, y| { gesture.set_state(gtk::EventSequenceState::Claimed); - if let Some(winner) = rd.borrow().game.winner() { + if let Some(winner) = rd.borrow().game().winner() { MainContext::default() .spawn_local(final_dialog::create_dialog(window.clone(), winner)); } else { @@ -432,32 +455,38 @@ fn create_game_window( (8.0 - p.x() / SQUARE_SIZE) as u8, ), }; - // println!("Selected position: {:?}", position); + println!("Selected position: {:?}", position); let piece = { - let draughts_game = &rd.borrow().game; - draughts_game.piece_at(position) + let rd = rd.borrow(); + let draughts_game = rd.game(); + position + .clone() + .map_or(Piece::NoPiece, |it| draughts_game.piece_at(it)) // println!("Selected piece: {:?}", piece); }; let am = available_moves.replace(Vec::new()); let mut move_applied = false; if !am.is_empty() { - for mv in am.into_iter() { - if mv.get_end_position() == position { - let mut rd_app = rd.borrow_mut(); - println!("Applied move: {:?}", mv); - rd_app.apply_move(mv).unwrap(); - let game_copy = rd_app.game.clone(); - thread::spawn(move || { - if let (Some(mv), analyzed_moves) = game_copy.get_best_move(10) - { - println!( - "Next best move: {:?}, analyzed moves: {}", - mv, analyzed_moves - ); - } - }); - move_applied = true; - break; + if let Ok(pos) = position { + for mv in am.into_iter() { + if mv.get_end_position() == pos { + let mut rd_app = rd.borrow_mut(); + println!("Applied move: {:?}", mv); + rd_app.apply_move(mv).unwrap(); + // let game_copy = rd_app.game().clone(); + // thread::spawn(move || { + // if let (Some(mv), analyzed_moves) = + // game_copy.get_best_move(10) + // { + // println!( + // "Next best move: {:?}, analyzed moves: {}", + // mv, analyzed_moves + // ); + // } + // }); + move_applied = true; + break; + } } } if move_applied { @@ -465,18 +494,21 @@ fn create_game_window( } } if !move_applied { + let position = position.ok(); match piece.player() { - Some(Player::Red) => selected_piece.set(Some(position)), - Some(Player::White) => selected_piece.set(Some(position)), + Some(Player::Red) => selected_piece.set(position), + Some(Player::White) => selected_piece.set(position), None => selected_piece.set(None), } if piece.player().is_none() { selected_piece.set(None) } else { let mut am = available_moves.borrow_mut(); - selected_piece.set(Some(position)); - for mv in rd.borrow().game.moves_for_piece(position) { - am.push(mv); + selected_piece.set(position); + if let Some(position) = position { + for mv in rd.borrow().game().moves_for_piece(position) { + am.push(mv); + } } } } @@ -491,12 +523,6 @@ fn create_game_window( drawing_area.add_controller(gesture); window.present(); } -struct RDraughtApplication { - initial_state: DraughtsGame, - game: DraughtsGame, - moves: Vec, - cursor: usize, -} fn on_activate(application: &Application, game: DraughtsGame) { // Initialize GTK before using any GTK functions. @@ -515,53 +541,6 @@ fn on_activate(application: &Application, game: DraughtsGame) { }); } -impl RDraughtApplication { - fn new(game: DraughtsGame) -> RDraughtApplication { - RDraughtApplication { - initial_state: game.clone(), - game, - moves: Vec::::new(), - cursor: 0usize, - } - } - - fn undo(&mut self) -> Result<(), Error> { - let mut new_state = self.initial_state.clone(); - if self.cursor > 0 && !self.moves.is_empty() { - self.cursor -= 1; - for mv in &self.moves[0..self.cursor] { - new_state.apply_move(mv)?; - } - self.game = new_state; - Ok(()) - } else { - Err(Error::InvalidMove) - } - } - - fn redo(&mut self) -> Result<(), Error> { - let mut new_state = self.initial_state.clone(); - if self.cursor < self.moves.len() { - for mv in &self.moves[0..self.cursor] { - new_state.apply_move(mv)?; - } - self.game = new_state; - self.cursor += 1; - Ok(()) - } else { - Err(Error::InvalidMove) - } - } - - fn apply_move(&mut self, mv: Move) -> Result<(), Error> { - self.game.apply_move(&mv)?; - self.moves.truncate(self.cursor); - self.moves.push(mv); - self.cursor += 1; - Ok(()) - } -} - pub fn run(game: DraughtsGame) -> ExitCode { // Create a new application with the builder pattern let app = Application::builder() diff --git a/rdraught/src/board.rs b/rdraught/src/board.rs deleted file mode 100644 index 68eb525..0000000 --- a/rdraught/src/board.rs +++ /dev/null @@ -1,131 +0,0 @@ -use core::iter::Enumerate; -use core::iter::Flatten; -use core::ops::Index; -use core::ops::IndexMut; - -use super::position::Position; - -pub trait RectangularBoard { - fn rows() -> usize; - fn columns() -> usize; -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct Board { - data: [[TYPE; COLUMNS]; ROWS], -} - -impl RectangularBoard - for Board -{ - fn rows() -> usize { - ROWS - } - - fn columns() -> usize { - COLUMNS - } -} - -impl Default for Board -where - TYPE: Default, -{ - fn default() -> Self { - Self::new(|_| TYPE::default()) - } -} - -impl IntoIterator for Board { - type Item = (usize, usize, TYPE); - - type IntoIter = BoardIterator; - - fn into_iter(self) -> Self::IntoIter { - BoardIterator { - it: self.data.into_iter().flatten().enumerate(), - } - } -} - -pub struct BoardIteratorRef<'a, TYPE, const ROWS: usize, const COLUMNS: usize> { - it: Enumerate>>, -} - -impl<'a, TYPE, const ROWS: usize, const COLUMNS: usize> Iterator - for BoardIteratorRef<'a, TYPE, ROWS, COLUMNS> -where - TYPE: Clone + Copy, -{ - fn next(&mut self) -> Option { - self.it - .next() - .map(|(index, value)| (index / COLUMNS, index % COLUMNS, *value)) - } - type Item = (usize, usize, TYPE); -} - -impl<'a, TYPE, const ROWS: usize, const COLUMNS: usize> IntoIterator - for &'a Board -where - TYPE: Clone + Copy, -{ - fn into_iter(self) -> BoardIteratorRef<'a, TYPE, ROWS, COLUMNS> { - BoardIteratorRef { - it: (self.data).iter().flatten().enumerate(), - } - } - - type IntoIter = BoardIteratorRef<'a, TYPE, ROWS, COLUMNS>; - type Item = (usize, usize, TYPE); -} - -impl Board { - pub(crate) fn new(mut cb: INITIALIZER) -> Self - where - INITIALIZER: FnMut((usize, usize)) -> TYPE, - { - let values: [[TYPE; COLUMNS]; ROWS] = - core::array::from_fn(|i| core::array::from_fn(|j| cb((i, j)))); - Board { data: values } - } - - pub(crate) fn iter(&self) -> BoardIteratorRef<'_, TYPE, ROWS, COLUMNS> { - BoardIteratorRef { - it: (self.data).iter().flatten().enumerate(), - } - } -} - -pub struct BoardIterator { - it: Enumerate>>, -} - -impl Iterator - for BoardIterator -{ - fn next(&mut self) -> Option { - self.it - .next() - .map(|(index, value)| (index / COLUMNS, index % COLUMNS, value)) - } - type Item = (usize, usize, TYPE); -} - -impl Index for Board { - type Output = TYPE; - - fn index(&self, position: Position) -> &Self::Output { - let index = position.to_index(); - &self.data[index.1][index.0] - } -} - -impl IndexMut - for Board -{ - fn index_mut(&mut self, position: Position) -> &mut Self::Output { - let index = position.to_index(); - &mut self.data[index.1][index.0] - } -} diff --git a/rdraught/src/constants.rs b/rdraught/src/constants.rs new file mode 100644 index 0000000..cf021a9 --- /dev/null +++ b/rdraught/src/constants.rs @@ -0,0 +1,3 @@ +pub const BITS_PER_POSITION: usize = 3; +pub const POSITIONS: usize = 32; +pub const POSITIONS_PER_ROW: usize = 4; diff --git a/rdraught/src/draughts.rs b/rdraught/src/draughts.rs index 1150daa..f23c7da 100644 --- a/rdraught/src/draughts.rs +++ b/rdraught/src/draughts.rs @@ -1,57 +1,214 @@ -use super::board::Board; -use super::board::RectangularBoard; +use super::constants::{BITS_PER_POSITION, POSITIONS, POSITIONS_PER_ROW}; +use super::movement::{Move, MoveDirection}; use super::position::Position; -use core::ops::Index; -use core::ops::IndexMut; -use heapless::BinaryHeap; -use heapless::Vec; -use strum_macros::FromRepr; +use crate::piece::Piece; +use crate::player::Player; +use heapless::{HistoryBuffer, Vec}; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Player { - White = 0, - Red = 1, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, FromRepr)] -pub enum Piece { - NoPiece = 0, - SimpleRedPawn = 1, - CrownedRedPawn = 3, - SimpleWhitePawn = 2, - CrownedWhitePawn = 4, -} - -impl Piece { - pub fn player(&self) -> Option { - if self.is_present() && (*self as u32) % 2 == 1 { - Some(Player::Red) - } else if self.is_present() && (*self as u32) % 2 == 0 { - Some(Player::White) - } else { - None - } - } - - fn is_present(&self) -> bool { - *self != Piece::NoPiece - } - - fn is_crowned(&self) -> bool { - (*self as u32) > 2 - } - - fn can_move_backward(&self) -> bool { - self.is_crowned() - } +pub trait RectangularBoard { + fn rows() -> usize; + fn columns() -> usize; } #[derive(Debug, Clone, Eq, PartialEq)] -pub struct DraughtsBoard(Board); +pub struct DraughtsBoard { + data: [u8; (POSITIONS * BITS_PER_POSITION).div_ceil(8)], +} + +impl DraughtsBoard { + fn offset_bits(p: Position) -> usize { + (p.row() as usize * POSITIONS_PER_ROW + (p.col() / 2) as usize) * BITS_PER_POSITION + } + + fn set(&mut self, position: Position, piece: Piece) { + let offset_bits = DraughtsBoard::offset_bits(position); + let index = offset_bits / 8; + let remainder = offset_bits % 8; + let determinant = piece as u8; + self.data[index] = (self.data[index] & !(7 << remainder)) | (determinant << remainder); + if remainder > 5 { + let written_bits = 8 - remainder; + self.data[index + 1] = + self.data[index + 1] & !(7 >> written_bits) | (determinant >> written_bits); + } + } + + pub fn get(&self, position: Position) -> Piece { + let offset_bits = DraughtsBoard::offset_bits(position); + let index = offset_bits / 8; + let remainder = offset_bits % 8; + let mut value = 0; + value |= (self.data[index] >> remainder) & 7; + if remainder > 5 { + let written_bits = 8 - remainder; + value |= (self.data[index + 1] & (7 >> written_bits)) << written_bits + } + Piece::from_repr(value as usize).unwrap() + } + + pub fn new Piece>(mut cb: T) -> DraughtsBoard { + let mut result = DraughtsBoard { + data: [0u8; (POSITIONS * BITS_PER_POSITION).div_ceil(8)], + }; + for i in 0..DraughtsBoard::rows() { + for j in 0..DraughtsBoard::columns() { + if (i + j) % 2 == 0 { + let position = Position::new(i as u8, j as u8).unwrap(); + let piece = cb(position); + result.set(position, piece); + } + } + } + result + } + + pub fn pieces( + &self, + pieces: Vec, + ) -> impl Iterator { + self.iter() + .filter(move |(_, piece)| { + for p in &pieces { + if piece == p { + return true; + } + } + false + }) + .map(|(pos, _)| Position::new(pos.row(), pos.col()).unwrap()) + } + + fn is_position_valid(position: Position) -> bool { + let position = position.to_index(); + position.0 < DraughtsBoard::rows() && position.1 < DraughtsBoard::columns() + } + + fn get_piece(&self, p: &Position) -> Piece { + self.get(*p) + } + + fn check_move_valid(&self, mv: &Move) -> Result<(), Error> { + let start = mv.start_position(); + if mv.is_movement() { + let mut move_is_possible = false; + for possible_move in self.moves_for_piece(start, false) { + if possible_move.is_capture() { + // Capture is mandatory + return Err(Error::MovementInsteadOfCapture); + } + if possible_move == *mv { + move_is_possible = true; + } + } + if move_is_possible { + Ok(()) + } else { + Err(Error::InvalidMove) + } + } else { + let mut move_is_possible = false; + for possible_move in self.moves_for_piece(start, true) { + if possible_move == *mv { + move_is_possible = true; + } + } + if move_is_possible { + Ok(()) + } else { + Err(Error::InvalidMove) + } + } + } + + fn apply_move(&mut self, mv: &Move) -> Result<(), Error> { + let start = mv.start_position(); + let piece = self.get_piece(&start); + if let Piece::NoPiece = piece { + Err(Error::InvalidMove) + } else { + let player = piece.player().unwrap(); + if mv.is_movement() { + let end = mv.get_end_position(); + // Make sure the move ends in a vlid position + if !DraughtsBoard::is_position_valid(end) { + return Err(Error::InvalidMove); + } + let piece_at_destination = self.get(mv.get_end_position()); + // Make sure there is no piece at destination + if let Piece::NoPiece = piece_at_destination { + self.set(start, Piece::NoPiece); + self.set(end, piece); + } else { + return Err(Error::InvalidMove); + } + } else { + let end = mv.get_end_position(); + // Make sure the move ends in a valid position + if !DraughtsBoard::is_position_valid(end) { + return Err(Error::InvalidMove); + } + let piece_at_destination = self.get(mv.get_end_position()); + // Make sure there is no piece at destination + if let Piece::NoPiece = piece_at_destination { + let captured = self.get((end + start) / (2, 2)); + // Make sure there is a piece to be captured + if let Piece::NoPiece = captured { + return Err(Error::InvalidMove); + } + // Make sure the captured piece belongs to the opposite player + if let Some(captured_piece_player) = captured.player() { + if captured_piece_player != player { + self.set(start, Piece::NoPiece); + self.set(end, piece); + self.set((end + start) / (2, 2), Piece::NoPiece); + } else { + return Err(Error::InvalidMove); + } + } else { + return Err(Error::InvalidMove); + } + } else { + return Err(Error::InvalidMove); + } + }; + Ok(()) + } + } + + pub fn moves_for_piece<'a>( + &'a self, + position: Position, + captures_only: bool, + ) -> MoveIterator<'a> { + let piece = self.get(position); + match piece.player() { + Some(_) => MoveIterator::new(self, position, captures_only), + None => MoveIterator::empty_iterator(self), + } + } + + fn iter<'a>(&'a self) -> BoardIterator<'a> { + BoardIterator { + board: self, + index: 0u8, + } + } +} + +impl RectangularBoard for DraughtsBoard { + fn rows() -> usize { + 8 + } + + fn columns() -> usize { + 8 + } +} impl Default for DraughtsBoard { fn default() -> DraughtsBoard { - DraughtsBoard(Board::::new(|(i, j)| { + DraughtsBoard::new(|p| { + let (i, j) = (p.row(), p.col()); if i < 3 { if (i + j) % 2 == 0 { Piece::SimpleWhitePawn @@ -67,183 +224,28 @@ impl Default for DraughtsBoard { } else { Piece::NoPiece } - })) + }) } } -impl Index for DraughtsBoard { - type Output = Piece; - - fn index(&self, position: Position) -> &Self::Output { - &self.0[position] - } +pub struct BoardIterator<'a> { + board: &'a DraughtsBoard, + index: u8, } -impl IndexMut for DraughtsBoard { - fn index_mut(&mut self, position: Position) -> &mut Self::Output { - &mut self.0[position] - } -} - -impl RectangularBoard for DraughtsBoard { - fn rows() -> usize { - Board::::rows() - } - - fn columns() -> usize { - Board::::columns() - } -} - -impl DraughtsBoard { - pub fn new(mut cb: T) -> DraughtsBoard - where - T: FnMut(Position) -> Piece, - { - DraughtsBoard(Board::::new(|(i, j)| { - cb(Position::new(i as u8, j as u8)) - })) - } - - pub fn rows() -> usize { - Board::::rows() - } - - pub fn columns() -> usize { - Board::::columns() - } - - pub fn pieces( - &self, - pieces: Vec, - ) -> impl Iterator { - self.0 - .iter() - .filter(move |(_, _, piece)| { - for p in &pieces { - if piece == p { - return true; - } - } - false - }) - .map(|(row, col, _)| Position::new(row as u8, col as u8)) - } - - fn is_position_valid(position: Position) -> bool { - let position = position.to_index(); - position.0 < DraughtsBoard::rows() && position.1 < DraughtsBoard::columns() - } - - fn get_piece(&self, p: &Position) -> Piece { - self[*p] - } - - fn check_move_valid(&self, mv: &Move) -> Result<(), Error> { - let start = mv.get_start_position(); - match mv { - Move::Movement { .. } => { - let mut move_is_possible = false; - for possible_move in self.moves_for_piece(start, false) { - if let Move::Capture { .. } = possible_move { - // Capture is mandatory - return Err(Error::MovementInsteadOfCapture); - } - if possible_move == *mv { - move_is_possible = true; - } - } - if move_is_possible { - Ok(()) - } else { - Err(Error::InvalidMove) - } - } - Move::Capture { .. } => { - let mut move_is_possible = false; - for possible_move in self.moves_for_piece(start, true) { - if possible_move == *mv { - move_is_possible = true; - } - } - if move_is_possible { - Ok(()) - } else { - Err(Error::InvalidMove) - } - } - } - } - - fn apply_move(&mut self, mv: &Move) -> Result<(), Error> { - let start = mv.get_start_position(); - let piece = self.get_piece(&start); - if let Piece::NoPiece = piece { - Err(Error::InvalidMove) +impl<'a> Iterator for BoardIterator<'a> { + fn next(&mut self) -> Option { + let res = if self.index < (POSITIONS as u8) { + let pos = Position::from(self.index); + Some((pos, self.board.get(pos))) } else { - let player = piece.player().unwrap(); - match mv { - Move::Movement { .. } => { - let end = mv.get_end_position(); - // Make sure the move ends in a vlid position - if !DraughtsBoard::is_position_valid(end) { - return Err(Error::InvalidMove); - } - let piece_at_destination = self[mv.get_end_position()]; - // Make sure there is no piece at destination - if let Piece::NoPiece = piece_at_destination { - self[start] = Piece::NoPiece; - self[end] = piece; - } else { - return Err(Error::InvalidMove); - } - } - Move::Capture { .. } => { - let end = mv.get_end_position(); - // Make sure the move ends in a valid position - if !DraughtsBoard::is_position_valid(end) { - return Err(Error::InvalidMove); - } - let piece_at_destination = self[mv.get_end_position()]; - // Make sure there is no piece at destination - if let Piece::NoPiece = piece_at_destination { - let captured = self[(end + start) / (2, 2)]; - // Make sure there is a piece to be captured - if let Piece::NoPiece = captured { - return Err(Error::InvalidMove); - } - // Make sure the captured piece belongs to the opposite player - if let Some(captured_piece_player) = captured.player() { - if captured_piece_player != player { - self[start] = Piece::NoPiece; - self[end] = piece; - self[(end + start) / (2, 2)] = Piece::NoPiece; - } else { - return Err(Error::InvalidMove); - } - } else { - return Err(Error::InvalidMove); - } - } else { - return Err(Error::InvalidMove); - } - } - }; - Ok(()) - } + None + }; + self.index += 1; + res } - pub fn moves_for_piece<'a>( - &'a self, - position: Position, - captures_only: bool, - ) -> MoveIterator<'a> { - let piece = self[position]; - match piece.player() { - Some(_) => MoveIterator::new(self, position, captures_only), - None => MoveIterator::empty_iterator(self), - } - } + type Item = (Position, Piece); } #[derive(Debug, Clone, Eq, PartialEq)] @@ -252,18 +254,13 @@ pub struct DraughtsGame { next_move: Player, } -#[derive(Clone)] -pub struct SerializedDraughtsGame { - data: Vec, - next_move: Player, -} - -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Error { WrongPlayer, NoPiece, InvalidMove, InvalidPosition, + InvalidPiece, NoPieceCaptured, FirendlyPieceCaptured, PositionIsOccupied, @@ -279,9 +276,6 @@ impl Default for DraughtsGame { } } -type MoveRanking = - BinaryHeap; - impl DraughtsGame { pub fn new Piece>(cb: T, next_move: Player) -> DraughtsGame { DraughtsGame { @@ -291,7 +285,7 @@ impl DraughtsGame { } pub fn piece_at(&self, p: Position) -> Piece { - self.board[p] + self.board.get(p) } pub fn moves_for_piece(&self, position: Position) -> MoveIterator { @@ -345,43 +339,41 @@ impl DraughtsGame { } pub fn apply_move(&mut self, mv: &Move) -> Result<(), Error> { - let start = mv.get_start_position(); + let start = mv.start_position(); let piece = self.board.get_piece(&start); if let Some(player) = piece.player() { - match mv { - Move::Movement { .. } => { - if self.next_move == player { - self.board.apply_move(mv)?; - self.next_turn(); - } else { - return Err(Error::WrongPlayer); - } + if mv.is_movement() { + if self.next_move == player { + self.board.apply_move(mv)?; + self.next_turn(); + } else { + return Err(Error::WrongPlayer); } - Move::Capture { .. } => { - if self.next_move == player { - self.board.apply_move(mv)?; - // Check if more captures are available for the current piece - if self - .board - .moves_for_piece(mv.get_end_position(), true) - .next() - .is_some() - { - return Ok(()); - } - self.next_turn(); - } else { - return Err(Error::WrongPlayer); - } + } else if self.next_move == player { + self.board.apply_move(mv)?; + // Check if more captures are available for the current piece + if self + .board + .moves_for_piece(mv.get_end_position(), true) + .next() + .is_some() + { + return Ok(()); } + self.next_turn(); + } else { + return Err(Error::WrongPlayer); }; let end = mv.get_end_position(); //Promote pawns that reach the last row if !piece.is_crowned() && DraughtsGame::is_last_row(&end, player) { - self.board[end] = match player { - Player::White => Piece::CrownedWhitePawn, - Player::Red => Piece::CrownedRedPawn, - }; + self.board.set( + end, + match player { + Player::White => Piece::CrownedWhitePawn, + Player::Red => Piece::CrownedRedPawn, + }, + ); } Ok(()) } else { @@ -440,9 +432,6 @@ impl DraughtsGame { depth: u32, analyzed_moves: &mut usize, ) -> f32 { - let pos = mv.get_start_position(); - let piece = self.board.get_piece(&pos); - let moving_player = piece.player().unwrap(); self.apply_move(mv).unwrap(); if depth != 0 { let mut best_score = None; @@ -522,137 +511,6 @@ impl DraughtsGame { } } -impl SerializedDraughtsGame { - fn serialize(position: Position, piece: Piece) -> u8 { - let p = position.row() * 4 + position.col() / 2; - p << 3 | piece as u8 - } - - fn deserialize(byte: u8) -> (Position, Piece) { - let index = byte >> 3; - let row = index >> 2; - let col = ((index - (row << 2)) % 4) * 2 + (row % 2); - let pos = Position::new(row, col); - (pos, Piece::from_repr((byte & 7) as usize).unwrap()) - } - - fn from(DraughtsGame { board, next_move }: &DraughtsGame) -> SerializedDraughtsGame { - let mut data = Vec::::new(); - for i in 0..DraughtsBoard::rows() { - for j in 0..DraughtsBoard::columns() { - let p = Position::new(i as u8, j as u8); - let piece = board.get_piece(&p); - if piece != Piece::NoPiece { - data.push(Self::serialize(p, piece)).unwrap(); - } - } - } - SerializedDraughtsGame { - data, - next_move: next_move.clone(), - } - } -} - -impl From for SerializedDraughtsGame { - fn from(DraughtsGame { board, next_move }: DraughtsGame) -> Self { - let mut data = Vec::::new(); - for i in 0..DraughtsBoard::rows() { - for j in 0..DraughtsBoard::columns() { - let p = Position::new(i as u8, j as u8); - let piece = board.get_piece(&p); - if piece != Piece::NoPiece { - data.push(Self::serialize(p, piece)).unwrap(); - } - } - } - SerializedDraughtsGame { - data, - next_move: next_move.clone(), - } - } -} - -impl Into for SerializedDraughtsGame { - fn into(self) -> DraughtsGame { - todo!() - } -} - -#[derive(Debug, PartialEq, Eq)] -struct MoveHeapEntry { - mv: Move, - score: u32, -} - -impl PartialOrd for MoveHeapEntry { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.score.cmp(&other.score)) - } -} - -impl Ord for MoveHeapEntry { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.score.cmp(&other.score) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum MoveDirection { - NE, - SE, - SW, - NW, -} - -impl MoveDirection { - fn is_forward(&self, piece: Piece) -> Result { - match piece.player() { - Some(Player::Red) => Ok(matches!(self, Self::SE) || matches!(self, Self::SW)), - Some(Player::White) => Ok(matches!(self, Self::NE) || matches!(self, Self::NW)), - None => Err(Error::NoPiece), - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum Move { - Movement { - start: Position, - direction: MoveDirection, - }, - Capture { - start: Position, - direction: MoveDirection, - }, -} - -impl Move { - pub fn get_end_position(&self) -> Position { - match self { - Move::Movement { start, direction } => match direction { - MoveDirection::NE => *start + (1, 1), - MoveDirection::SE => *start + (-1, 1), - MoveDirection::SW => *start + (-1, -1), - MoveDirection::NW => *start + (1, -1), - }, - Move::Capture { start, direction } => match direction { - MoveDirection::NE => *start + (2, 2), - MoveDirection::SE => *start + (-2, 2), - MoveDirection::SW => *start + (-2, -2), - MoveDirection::NW => *start + (2, -2), - }, - } - } - - pub fn get_start_position(&self) -> Position { - match self { - Move::Movement { start, .. } => *start, - Move::Capture { start, .. } => *start, - } - } -} - pub struct MoveIterator<'a> { board: &'a DraughtsBoard, position: Position, @@ -669,7 +527,7 @@ impl MoveIterator<'_> { ]; fn piece(&self) -> Piece { - self.board[self.position] + self.board.get(self.position) } fn new<'a>( @@ -687,7 +545,7 @@ impl MoveIterator<'_> { fn empty_iterator<'a>(board: &'a DraughtsBoard) -> MoveIterator<'a> { MoveIterator { board, - position: Position::new(0, 0), + position: Position::new(0, 0).unwrap(), state: 4, capture_only: false, } @@ -705,13 +563,10 @@ impl<'a> Iterator for MoveIterator<'a> { if !piece.can_move_backward() && !direction.is_forward(piece).unwrap() { continue; } - let movement = Move::Movement { - start: self.position, - direction, - }; + let movement = Move::movement(self.position, direction); let next_pos = movement.get_end_position(); if DraughtsBoard::is_position_valid(next_pos) { - let piece_at_next_position = self.board[next_pos]; + let piece_at_next_position = self.board.get(next_pos); match piece_at_next_position { Piece::NoPiece => { if self.capture_only { @@ -722,13 +577,14 @@ impl<'a> Iterator for MoveIterator<'a> { } _ => { if piece.player() != piece_at_next_position.player() { - let capture = Move::Capture { - start: self.position, + let capture = Move::capture( + self.position, direction, - }; + piece_at_next_position.is_crowned(), + ); let next_position = capture.get_end_position(); if DraughtsBoard::is_position_valid(next_position) { - let piece_at_end_position = self.board[next_position]; + let piece_at_end_position = self.board.get(next_position); if piece_at_end_position == Piece::NoPiece { return Some(capture); } @@ -742,6 +598,66 @@ impl<'a> Iterator for MoveIterator<'a> { } } +pub struct RDraughtApplication { + initial_state: DraughtsGame, + game: DraughtsGame, + moves: HistoryBuffer, + cursor: usize, +} + +impl RDraughtApplication { + pub fn new(game: DraughtsGame) -> RDraughtApplication { + RDraughtApplication { + initial_state: game.clone(), + game, + moves: HistoryBuffer::::new(), + cursor: 0usize, + } + } + + pub fn undo(&mut self) -> Result<(), Error> { + let mut new_state = self.initial_state.clone(); + if self.cursor > 0 && !self.moves.is_empty() { + self.cursor -= 1; + for mv in &self.moves[0..self.cursor] { + new_state.apply_move(mv)?; + } + self.game = new_state; + Ok(()) + } else { + Err(Error::InvalidMove) + } + } + + pub fn redo(&mut self) -> Result<(), Error> { + let mut new_state = self.initial_state.clone(); + if self.cursor < self.moves.len() { + for mv in &self.moves[0..self.cursor] { + new_state.apply_move(mv)?; + } + self.game = new_state; + self.cursor += 1; + Ok(()) + } else { + Err(Error::InvalidMove) + } + } + + pub fn apply_move(&mut self, mv: Move) -> Result<(), Error> { + self.game.apply_move(&mv)?; + if self.moves.len() == self.moves.capacity() { + self.initial_state.apply_move(self.moves.first().unwrap())?; + } + self.moves.write(mv); + self.cursor += 1; + Ok(()) + } + + pub fn game(&self) -> &DraughtsGame { + &self.game + } +} + #[cfg(feature = "std")] mod std { extern crate std; @@ -756,40 +672,47 @@ mod std { } } -#[cfg(feature = "std")] -pub use std::*; - #[cfg(test)] +#[cfg(feature = "std")] mod tests { extern crate std; - use super::{ - DraughtsBoard, DraughtsGame, Move, MoveDirection, Piece, Player, Position, - SerializedDraughtsGame, - }; + use super::{DraughtsBoard, Piece, Position}; use std::collections::HashMap; - use std::hash::{Hash, Hasher}; #[test] - fn test_serialize_deserialize_piece() { - for (pos, piece) in [ - (Position::new(4u8, 2u8), Piece::SimpleRedPawn), - (Position::new(0u8, 6u8), Piece::CrownedRedPawn), - (Position::new(1u8, 7u8), Piece::SimpleWhitePawn), - (Position::new(2u8, 4u8), Piece::CrownedWhitePawn), - (Position::new(3u8, 5u8), Piece::SimpleRedPawn), - (Position::new(6u8, 2u8), Piece::CrownedRedPawn), - (Position::new(7u8, 7u8), Piece::SimpleWhitePawn), - (Position::new(7u8, 1u8), Piece::CrownedWhitePawn), - (Position::new(6u8, 6u8), Piece::SimpleRedPawn), - (Position::new(4u8, 6u8), Piece::CrownedRedPawn), - (Position::new(6u8, 6u8), Piece::SimpleWhitePawn), - (Position::new(4u8, 6u8), Piece::CrownedWhitePawn), - ] { - let serialized = SerializedDraughtsGame::serialize(pos, piece); - let (deserialized_pos, deserialized_piece) = - SerializedDraughtsGame::deserialize(serialized); - assert_eq!(piece, deserialized_piece); - assert_eq!(pos, deserialized_pos); + fn test_create() { + let boards = [ + [ + (Position::new(2, 4), Piece::CrownedRedPawn), + (Position::new(0, 0), Piece::CrownedWhitePawn), + (Position::new(5, 1), Piece::SimpleWhitePawn), + (Position::new(7, 1), Piece::SimpleRedPawn), + (Position::new(7, 7), Piece::SimpleRedPawn), + (Position::new(1, 3), Piece::CrownedRedPawn), + ], + [ + (Position::new(1, 3), Piece::CrownedRedPawn), + (Position::new(2, 0), Piece::CrownedWhitePawn), + (Position::new(4, 2), Piece::SimpleWhitePawn), + (Position::new(5, 1), Piece::SimpleRedPawn), + (Position::new(6, 6), Piece::SimpleRedPawn), + (Position::new(7, 5), Piece::CrownedRedPawn), + ], + ]; + + for pieces in boards.into_iter() { + let map = pieces + .into_iter() + .map(|(pos, piece)| (pos.unwrap(), piece)) + .collect::>(); + let board = DraughtsBoard::new(|p| match map.get(&p) { + None => Piece::NoPiece, + Some(piece) => *piece, + }); + + for (pos, piece) in map.iter() { + assert_eq!(*piece, board.get(*pos)); + } } } } @@ -804,8 +727,8 @@ mod ai_tests { #[test] fn ai_test_simple() { let mut pieces = HashMap::::new(); - pieces.insert(Position::new(2, 4), Piece::CrownedRedPawn); - pieces.insert(Position::new(7, 3), Piece::CrownedWhitePawn); + pieces.insert(Position::new(2, 4).unwrap(), Piece::CrownedRedPawn); + pieces.insert(Position::new(7, 3).unwrap(), Piece::CrownedWhitePawn); let game = DraughtsGame::new( |p| match pieces.get(&p) { None => Piece::NoPiece, @@ -816,10 +739,10 @@ mod ai_tests { let (best_move, _) = game.get_best_move(8); assert_eq!( - Some(Move::Movement { - start: Position::new(2, 4), - direction: MoveDirection::NE - }), + Some(Move::movement( + Position::new(2, 4).unwrap(), + MoveDirection::NE + )), best_move ); } @@ -827,10 +750,10 @@ mod ai_tests { #[test] fn ai_test_multiple_capture() { let mut pieces = HashMap::::new(); - pieces.insert(Position::new(4, 4), Piece::SimpleWhitePawn); - pieces.insert(Position::new(4, 0), Piece::SimpleWhitePawn); - pieces.insert(Position::new(6, 2), Piece::SimpleRedPawn); - pieces.insert(Position::new(7, 1), Piece::SimpleRedPawn); + pieces.insert(Position::new(4, 4).unwrap(), Piece::SimpleWhitePawn); + pieces.insert(Position::new(4, 0).unwrap(), Piece::SimpleWhitePawn); + pieces.insert(Position::new(6, 2).unwrap(), Piece::SimpleRedPawn); + pieces.insert(Position::new(7, 1).unwrap(), Piece::SimpleRedPawn); let game = DraughtsGame::new( |p| match pieces.get(&p) { None => Piece::NoPiece, @@ -841,10 +764,10 @@ mod ai_tests { let (best_move, _) = game.get_best_move(5); assert_eq!( - Some(Move::Movement { - start: Position::new(6, 2), - direction: MoveDirection::SW - }), + Some(Move::movement( + Position::new(6, 2).unwrap(), + MoveDirection::SW + )), best_move ); } diff --git a/rdraught/src/lib.rs b/rdraught/src/lib.rs index 0c81326..5c6cd2d 100644 --- a/rdraught/src/lib.rs +++ b/rdraught/src/lib.rs @@ -1,10 +1,14 @@ #![no_std] -#[macro_use] -extern crate strum; -mod board; +mod constants; pub mod draughts; -pub mod position; +mod movement; +mod piece; +mod player; +mod position; -//use draughts::DraughtsBoard; -//use draughts::Piece; +pub use draughts::{DraughtsBoard, DraughtsGame, Error, RDraughtApplication, RectangularBoard}; +pub use movement::Move; +pub use piece::Piece; +pub use player::Player; +pub use position::Position; diff --git a/rdraught/src/movement.rs b/rdraught/src/movement.rs new file mode 100644 index 0000000..e21f7f5 --- /dev/null +++ b/rdraught/src/movement.rs @@ -0,0 +1,121 @@ +use crate::position::Position; + +use crate::{Piece, Player, draughts::Error}; +use strum_macros::FromRepr; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromRepr)] +pub enum MoveDirection { + NE = 0, + SE = 1, + SW = 2, + NW = 3, +} + +impl MoveDirection { + pub fn is_forward(&self, piece: Piece) -> Result { + match piece.player() { + Some(Player::Red) => Ok(matches!(self, Self::SE) || matches!(self, Self::SW)), + Some(Player::White) => Ok(matches!(self, Self::NE) || matches!(self, Self::NW)), + None => Err(Error::NoPiece), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Move { + data: u16, +} + +impl Move { + pub fn movement(start: Position, dir: MoveDirection) -> Move { + let mut result = >::into(start) as u16; + result |= (dir as u16) << 5; + Move { data: result } + } + + pub fn capture(start: Position, dir: MoveDirection, crowned_capture: bool) -> Move { + let mut result = >::into(start) as u16; + result |= (dir as u16) << 5; + result |= 1u16 << 7; + if crowned_capture { + result |= 1u16 << 8; + } + Move { data: result } + } + + pub fn start_position(&self) -> Position { + Position::from((self.data & 31) as u8) + } + + pub fn direction(&self) -> MoveDirection { + MoveDirection::from_repr(((self.data & 96) >> 5) as usize).unwrap() + } + + pub fn is_capture(&self) -> bool { + (self.data & 128) != 0 + } + + pub fn is_movement(&self) -> bool { + !self.is_capture() + } + + pub fn crowned_captured(&self) -> bool { + (self.data & 256) != 0 + } + pub fn get_end_position(&self) -> Position { + if self.is_capture() { + let start = self.start_position(); + let direction = self.direction(); + match direction { + MoveDirection::NE => start + (2, 2), + MoveDirection::SE => start + (-2, 2), + MoveDirection::SW => start + (-2, -2), + MoveDirection::NW => start + (2, -2), + } + } else { + let start = self.start_position(); + let direction = self.direction(); + match direction { + MoveDirection::NE => start + (1, 1), + MoveDirection::SE => start + (-1, 1), + MoveDirection::SW => start + (-1, -1), + MoveDirection::NW => start + (1, -1), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{Move, MoveDirection}; + use crate::Position; + + struct MoveData { + start: Position, + dir: MoveDirection, + capture: bool, + crowned_capture: bool, + } + + #[test] + fn test_from_into() { + let move_datas = [MoveData { + start: Position::new(0, 0).unwrap(), + dir: super::MoveDirection::NE, + capture: false, + crowned_capture: false, + }]; + + for md in move_datas { + let mv = if md.capture { + Move::capture(md.start, md.dir, md.crowned_capture) + } else { + Move::movement(md.start, md.dir) + }; + assert_eq!(md.start, mv.start_position()); + assert_eq!(md.dir, mv.direction()); + assert_eq!(md.capture, mv.is_capture()); + assert_eq!(md.crowned_capture, mv.crowned_captured()); + } + } +} diff --git a/rdraught/src/piece.rs b/rdraught/src/piece.rs new file mode 100644 index 0000000..4b78d6e --- /dev/null +++ b/rdraught/src/piece.rs @@ -0,0 +1,35 @@ +use crate::player::Player; +use strum_macros::FromRepr; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, FromRepr)] +pub enum Piece { + NoPiece = 0, + SimpleRedPawn = 1, + CrownedRedPawn = 3, + SimpleWhitePawn = 2, + CrownedWhitePawn = 4, +} + +impl Piece { + pub fn player(&self) -> Option { + if self.is_present() && (*self as u32) % 2 == 1 { + Some(Player::Red) + } else if self.is_present() && (*self as u32) % 2 == 0 { + Some(Player::White) + } else { + None + } + } + + pub fn is_present(&self) -> bool { + *self != Piece::NoPiece + } + + pub fn is_crowned(&self) -> bool { + (*self as u32) > 2 + } + + pub fn can_move_backward(&self) -> bool { + self.is_crowned() + } +} diff --git a/rdraught/src/player.rs b/rdraught/src/player.rs new file mode 100644 index 0000000..2c314cf --- /dev/null +++ b/rdraught/src/player.rs @@ -0,0 +1,5 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Player { + White = 0, + Red = 1, +} diff --git a/rdraught/src/position.rs b/rdraught/src/position.rs index 35210b1..80706eb 100644 --- a/rdraught/src/position.rs +++ b/rdraught/src/position.rs @@ -1,9 +1,12 @@ +use crate::Error; use core::fmt::Display; use core::ops::Add; use core::ops::Div; use core::ops::Mul; use core::ops::Sub; +use crate::constants::POSITIONS_PER_ROW; + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Position((u8, u8)); @@ -22,8 +25,12 @@ impl Position { Position((index.0 as u8, index.1 as u8)) } - pub fn new(row: u8, column: u8) -> Position { - Position((row, column)) + pub fn new(row: u8, column: u8) -> Result { + if (row + column) % 2 == 0 { + Ok(Position((row, column))) + } else { + Err(Error::InvalidPosition) + } } pub fn row(&self) -> u8 { @@ -109,3 +116,46 @@ impl Mul<(i32, i32)> for Position { )) } } + +impl From for u8 { + fn from(pos: Position) -> Self { + (8 - 1 - pos.row()) * (POSITIONS_PER_ROW as u8) + pos.col() / 2 + } +} + +impl From for Position { + fn from(n: u8) -> Self { + // println!("{}", n); + let row = 8 - 1 - n / (POSITIONS_PER_ROW as u8); + let offset = if row % 2 == 0 { 0u8 } else { 1u8 }; + let col = n % (POSITIONS_PER_ROW as u8) * 2 + offset; + Position::new(row, col).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::Position; + + #[test] + fn test_from_into() { + let positions = [ + Position::new(4, 6), + Position::new(0, 4), + Position::new(1, 3), + Position::new(7, 1), + Position::new(2, 2), + Position::new(5, 3), + Position::new(3, 7), + Position::new(6, 0), + Position::new(7, 7), + ]; + + for p in positions { + let p = p.unwrap(); + let n = >::into(p); + let p2 = >::from(n); + assert_eq!(p, p2); + } + } +}