From 8b9d0662578377223b596e7a548f4e4a0133c794 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Fri, 20 Jun 2025 22:11:47 +0800 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.toml | 24 ++ examples/checkers.rs | 27 ++ rdraught-cli/Cargo.toml | 17 + rdraught-cli/src/rustyline-test.rs | 33 ++ rdraught-pi/Cargo.toml | 16 + rdraught-pi/src/hello.rs | 17 + rdraught-ui/Cargo.toml | 21 ++ rdraught-ui/Chess_Pieces_Sprite.svg | 254 +++++++++++++++ rdraught-ui/src/crown.svg | 27 ++ rdraught-ui/src/geo2d.rs | 211 +++++++++++++ rdraught-ui/src/main.rs | 180 +++++++++++ src/board.rs | 131 ++++++++ src/draughts.rs | 462 ++++++++++++++++++++++++++++ src/lib.rs | 7 + src/position.rs | 97 ++++++ 16 files changed, 1525 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 examples/checkers.rs create mode 100644 rdraught-cli/Cargo.toml create mode 100644 rdraught-cli/src/rustyline-test.rs create mode 100644 rdraught-pi/Cargo.toml create mode 100644 rdraught-pi/src/hello.rs create mode 100644 rdraught-ui/Cargo.toml create mode 100644 rdraught-ui/Chess_Pieces_Sprite.svg create mode 100644 rdraught-ui/src/crown.svg create mode 100644 rdraught-ui/src/geo2d.rs create mode 100644 rdraught-ui/src/main.rs create mode 100644 src/board.rs create mode 100644 src/draughts.rs create mode 100644 src/lib.rs create mode 100644 src/position.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..134d8f0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[workspace.package] +homepage = "https://gitea.woggioni.net/woggioni/rdraught" +authors = ["Walter Oggioni "] +version = "0.1.0" +repository = "https://github.com/gtk-rs/gtk-rs-core" +license = "MIT" +edition = "2024" +rust-version = "1.87" + +[package] +name = "rdraught" + +[lib] +name = "rdraught" +crate-type = ["lib"] + +[dependencies] +heapless = "0.8" + +[dev-dependencies] +rand = "0.9" + +[workspace] +members = ["rdraught-cli", "rdraught-pi", "rdraught-ui"] diff --git a/examples/checkers.rs b/examples/checkers.rs new file mode 100644 index 0000000..f7121cc --- /dev/null +++ b/examples/checkers.rs @@ -0,0 +1,27 @@ +use heapless::Vec; +use rdraught::draughts::{DraughtsBoard, Piece}; +use rdraught::position::Position; + +fn main() { + let board = DraughtsBoard::default(); + println!("{:?}", board); + // let red_pieces: Vec<(usize, usize, Piece)> = board + // .into_iter() + // .filter(|(i, j, p)| *p == Piece::SimpleRedPawn || *p == Piece::CrownedRedPawn) + // .collect(); + + let mut pieces = Vec::::new(); + pieces.push(Piece::SimpleRedPawn).unwrap(); + pieces.push(Piece::CrownedRedPawn).unwrap(); + + // board + // .pieces(&pieces) + // .for_each(|(i, j, piece)| println!("({}, {}): {:?}", i, j, piece)); + // println!("{:?}", board[Position::new(0, 0)]); + + board + .moves_for_piece(Position::new(2, 0), false) + .for_each(|mv| { + println!("{:?}", mv); + }); +} diff --git a/rdraught-cli/Cargo.toml b/rdraught-cli/Cargo.toml new file mode 100644 index 0000000..265eaba --- /dev/null +++ b/rdraught-cli/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rdraught-cli" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[[bin]] +name = "rustyline-test" +path = "src/rustyline-test.rs" + +[dependencies] +#reedline = "0.40" +rustyline = {version = "16.0", default-features = false } diff --git a/rdraught-cli/src/rustyline-test.rs b/rdraught-cli/src/rustyline-test.rs new file mode 100644 index 0000000..f00b609 --- /dev/null +++ b/rdraught-cli/src/rustyline-test.rs @@ -0,0 +1,33 @@ +use rustyline::error::ReadlineError; +use rustyline::{DefaultEditor, Result}; + +fn main() -> Result<()> { + // `()` can be used when no completer is required + let mut rl = DefaultEditor::new()?; + /* if rl.load_history("history.txt").is_err() { + println!("No previous history."); + } */ + loop { + let readline = rl.readline(">> "); + match readline { + Ok(line) => { + rl.add_history_entry(line.as_str())?; + println!("Line: {}", line); + } + Err(ReadlineError::Interrupted) => { + println!("CTRL-C"); + break; + } + Err(ReadlineError::Eof) => { + println!("CTRL-D"); + break; + } + Err(err) => { + println!("Error: {:?}", err); + break; + } + } + } + // rl.save_history("history.txt").unwrap(); + Ok(()) +} diff --git a/rdraught-pi/Cargo.toml b/rdraught-pi/Cargo.toml new file mode 100644 index 0000000..3efcb84 --- /dev/null +++ b/rdraught-pi/Cargo.toml @@ -0,0 +1,16 @@ +[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 new file mode 100644 index 0000000..dc995b3 --- /dev/null +++ b/rdraught-pi/src/hello.rs @@ -0,0 +1,17 @@ +#![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/Cargo.toml b/rdraught-ui/Cargo.toml new file mode 100644 index 0000000..4917061 --- /dev/null +++ b/rdraught-ui/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rdraught-ui" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[dependencies] +gtk4 = "0.9" +gdk4 = "0.9" +rdraught = {version = "0.1", path= ".." } +rmath = { version="0.1", registry="gitea" } +rsvg = "0.4" +cairo-rs = "0.20" + +[patch.crates-io] +cairo-rs = "0.20.10" +cairo-sys-rs = "0.20.10" diff --git a/rdraught-ui/Chess_Pieces_Sprite.svg b/rdraught-ui/Chess_Pieces_Sprite.svg new file mode 100644 index 0000000..20649d1 --- /dev/null +++ b/rdraught-ui/Chess_Pieces_Sprite.svg @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rdraught-ui/src/crown.svg b/rdraught-ui/src/crown.svg new file mode 100644 index 0000000..07cf3f0 --- /dev/null +++ b/rdraught-ui/src/crown.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/rdraught-ui/src/geo2d.rs b/rdraught-ui/src/geo2d.rs new file mode 100644 index 0000000..88c32c5 --- /dev/null +++ b/rdraught-ui/src/geo2d.rs @@ -0,0 +1,211 @@ +use rmath::SMatrix; +use std::clone::Clone; +use std::cmp::Eq; +use std::cmp::PartialEq; +use std::fmt::Display; +use std::marker::Copy; +use std::ops::Add; +use std::ops::Div; +use std::ops::Mul; +use std::ops::Neg; +use std::ops::Sub; + +pub type Xform = SMatrix; +pub struct Point(SMatrix); + +impl Point { + pub fn x(&self) -> f64 { + self.0[(0, 0)] + } + + pub fn y(&self) -> f64 { + self.0[(0, 1)] + } + + pub fn new(x: f64, y: f64) -> Point { + Point(SMatrix::new(|pos| match pos { + (0, 0) => x, + (0, 1) => y, + (0, 2) => 1f64, + _ => 0f64, + })) + } +} + +impl Add for Point { + fn add(self, rhs: Point) -> Self::Output { + self * &xlate(rhs.x(), rhs.y()) + } + type Output = Point; +} + +impl Sub for Point { + fn sub(self, rhs: Point) -> Self::Output { + self * &xlate(-rhs.x(), -rhs.y()) + } + type Output = Point; +} + +impl Mul for Point { + fn mul(self, rhs: f64) -> Self::Output { + Point::new(self.0[(0, 0)] * rhs, self.0[(0, 1)] * rhs) + } + type Output = Point; +} + +impl Div for Point { + fn div(self, rhs: f64) -> Self::Output { + Point::new(self.0[(0, 0)] / rhs, self.0[(0, 1)] / rhs) + } + type Output = Point; +} + +impl Neg for Point { + fn neg(self) -> Self::Output { + Point::new(-self.x(), -self.y()) + } + + type Output = Point; +} + +impl PartialEq for Point { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for Point {} + +impl Mul<&Xform> for Point { + fn mul(self, rhs: &Xform) -> Self::Output { + Point(self.0 * rhs) + } + + type Output = Point; +} + +impl Clone for Point { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Point {} + +impl Display for Point { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +pub struct Rect2d { + tl: Point, + br: Point, +} + +impl Rect2d { + pub fn new(tl: Point, br: Point) -> Rect2d { + Rect2d { tl, br } + } + + pub fn center(&self) -> Point { + (self.tl + self.br) / 2.0 + } + + pub fn tl(&self) -> Point { + self.tl + } + + pub fn br(&self) -> Point { + self.br + } + + pub fn width(&self) -> f64 { + (self.br.x() - self.tl.x()).abs() + } + + pub fn height(&self) -> f64 { + (self.br.y() - self.tl.y()).abs() + } +} + +impl Mul<&Xform> for Rect2d { + fn mul(self, rhs: &Xform) -> Self::Output { + Rect2d { + tl: Point(self.tl.0 * rhs), + br: Point(self.br.0 * rhs), + } + } + + type Output = Rect2d; +} + +pub fn rot(alpha: f64) -> Xform { + let sa = alpha.sin(); + let ca = alpha.cos(); + Xform::new(|position| match position { + (0, 0) => ca, + (1, 1) => ca, + (1, 0) => -sa, + (0, 1) => sa, + (2, 2) => 1f64, + _ => 0f64, + }) +} + +impl Point {} + +pub fn scale(x: f64, y: f64) -> Xform { + Xform::new(|position| match position { + (0, 0) => x, + (1, 1) => y, + (2, 2) => 1f64, + _ => 0f64, + }) +} + +pub fn xlate(x: f64, y: f64) -> Xform { + Xform::new(|position| match position { + (0, 0) => 1f64, + (1, 1) => 1f64, + (2, 2) => 1f64, + (2, 0) => x, + (2, 1) => y, + _ => 0f64, + }) +} + +#[cfg(test)] +mod tests { + + use std::f64::consts::PI; + + use super::Point; + use super::rot; + use super::scale; + use super::xlate; + + #[test] + fn test_xlate() { + let p = Point::new(1.0, 3.0); + let xform = xlate(-1.0, 0.0); + let p2 = p * &xform * &rot(-PI / 2.0) * &xlate(-2.0, 3.0); + assert!(p == p2); + } + + #[test] + fn test_rotate() { + let p = Point::new(0.0, 3.0); + let p2 = p * &rot(-PI / 2.0); + assert!((p2.x() - 3.0).abs() < 1e-3); + assert!((p2.y() - 0.0).abs() < 1e-3); + } + + #[test] + fn test_scale() { + let p = Point::new(1.0, 3.0); + let p2 = p * &scale(2.0, 3.0); + assert!((p2.x() - 2.0).abs() < 1e-3); + assert!((p2.y() - 9.0).abs() < 1e-3); + } +} diff --git a/rdraught-ui/src/main.rs b/rdraught-ui/src/main.rs new file mode 100644 index 0000000..46eea06 --- /dev/null +++ b/rdraught-ui/src/main.rs @@ -0,0 +1,180 @@ +use gdk4::cairo::{Context as CairoContext, Matrix}; +use gtk4::cairo::Error; +use gtk4::prelude::*; +use gtk4::{self as gtk}; +use rdraught::draughts::{self, DraughtsBoard, Piece}; +use rdraught::position::Position; +mod geo2d; +use core::f64::consts::PI; +use geo2d::{Point, Rect2d, Xform, scale, xlate}; +use rsvg::{Handle, HandleExt}; + +const SQUARE_SIZE: f64 = 1.0; + +const CROWN: &'static [u8] = include_bytes!("crown.svg"); + +fn draw_piece(cr: &CairoContext, square: &Rect2d, piece: Piece) -> Result<(), Error> { + if let Piece::NoPiece = piece { + return Ok(()); + } else { + let center = square.center(); + let outer_radius = square.width() * 0.3; + let vertical_scale_factor = 0.8; + let matrix = { + let mut m1 = Matrix::identity(); + m1.translate(0.0, -(center.y() - outer_radius)); + let mut m2 = Matrix::identity(); + m2.scale(1.0, vertical_scale_factor); + let mut m3 = Matrix::identity(); + m3.translate(0.0, center.y() - outer_radius * vertical_scale_factor); + Matrix::multiply(&Matrix::multiply(&m1, &m2), &m3) + }; + cr.save()?; + cr.set_matrix(matrix); + cr.set_source_rgb(0.0, 0.0, 0.0); + let thickness = outer_radius * 0.3; + cr.arc( + center.x(), + center.y() + thickness / 2.0, + outer_radius, + 0.0, + 2.0 * PI, + ); + cr.rectangle( + center.x() - outer_radius, + center.y() - thickness / 2.0, + outer_radius * 2.0, + thickness, + ); + cr.arc( + center.x(), + center.y() - thickness / 2.0, + outer_radius, + 0.0, + 2.0 * PI, + ); + cr.fill().unwrap(); + let (color, crowned) = match piece { + Piece::NoPiece => return Ok(()), + Piece::SimpleRedPawn => ((1.0, 0.0, 0.0), false), + Piece::SimpleWhitePawn => ((1.0, 1.0, 1.0), false), + Piece::CrownedRedPawn => ((1.0, 0.0, 0.0), true), + Piece::CrownedWhitePawn => ((1.0, 0.0, 0.0), true), + }; + let radius = square.width() * 0.275; + cr.set_source_rgb(color.0, color.1, color.2); + cr.arc( + center.x(), + center.y() - thickness / 2.0, + radius, + 0.0, + 2.0 * PI, + ); + cr.fill()?; + if crowned { + let handle = Handle::new_from_data(CROWN).unwrap(); + handle.render_cairo(cr as &cairo::Context); + cr.move_to(center.x(), center.y()); + cr.set_source_rgb(1.0, 1.0, 0.0); + cr.set_font_size(20.0); + cr.show_text("A♔")?; + } + cr.restore()?; + Ok(()) + } +} + +fn on_activate(application: >k::Application) { + // Initialize GTK before using any GTK functions. + if gtk::init().is_err() { + panic!("Failed to initialize GTK."); + } + // Create a new window. + let window = gtk::ApplicationWindow::builder() + .application(application) + .title("Rdraught") + .default_width(800) + .default_height(800) + .build(); + + // Create a DrawingArea widget where we will draw the chessboard. + let drawing_area = gtk::DrawingArea::new(); + // Add the drawing area to the window. + window.set_child(Some(&drawing_area)); + + let draughts_board = DraughtsBoard::default(); + println!("{:?}", draughts_board[Position::new(0, 0)]); + // Get the allocation information for the widget. + let board_width = SQUARE_SIZE * DraughtsBoard::rows() as f64; + let board_height = SQUARE_SIZE * DraughtsBoard::columns() as f64; + let board = Rect2d::new(Point::new(0.0, 0.0), Point::new(board_width, board_height)); + // Set the "draw" function of the drawing area. This callback is called + // whenever GTK needs to redraw this widget (for example, on first display or when resized). + drawing_area.set_draw_func(move |_widget, cr, width, height| { + let screen = Rect2d::new( + Point::new(0.0, 0.0), + Point::new(width as f64, height as f64), + ); + let f = f64::min( + screen.width() / board.width(), + screen.height() / board.height(), + ); + let screen_center = screen.center(); + let board_center = board.center(); + let xform = xlate(-board_center.x(), -board_center.y()) + * scale(f, f) + * xlate(screen_center.x(), screen_center.y()); + //let xlation = screen.center() - board.center(); + //let xform = xform * xlate(xlation.x(), xlation.y()); + + let square_size = SQUARE_SIZE as f64; + + // Loop over rows and columns to draw each chessboard cell. + for row in 0..DraughtsBoard::rows() { + for col in 0..DraughtsBoard::columns() { + let square = Rect2d::new( + board.tl() + Point::new((col as f64) * square_size, (row as f64) * square_size), + board.tl() + + Point::new( + ((col + 1) as f64) * square_size, + ((row + 1) as f64) * square_size, + ), + ); + let square = square * &xform; + // Alternate colors based on the sum of row and column indices. + if (row + col) % 2 == 0 { + cr.set_source_rgb(0.8, 0.8, 0.6); // white + } else { + cr.set_source_rgb(0.4, 0.4, 0.2); // black + } + + // Draw and fill the square. + cr.rectangle( + square.tl().x(), + square.tl().y(), + square.width(), + square.height(), + ); + cr.fill().unwrap(); + draw_piece( + cr, + &square, + draughts_board[Position::new(col as u8, (8 - row - 1) as u8)], + ) + .unwrap(); + } + } + }); + + window.present(); +} + +fn main() { + // Create a new application with the builder pattern + let app = gtk::Application::builder() + .application_id("net.woggioni.rdraught") + .build(); + app.connect_activate(on_activate); + // Run the application + app.run(); +} diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 0000000..b714ae4 --- /dev/null +++ b/src/board.rs @@ -0,0 +1,131 @@ +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)] +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/src/draughts.rs b/src/draughts.rs new file mode 100644 index 0000000..a820618 --- /dev/null +++ b/src/draughts.rs @@ -0,0 +1,462 @@ +use super::board::Board; +use super::board::BoardIteratorRef; +use super::board::RectangularBoard; +use core::iter::Filter; +use core::ops::Index; +use core::ops::IndexMut; +use heapless::Vec; + +use super::position::Position; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Player { + White = 0, + Red = 1, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Piece { + NoPiece = 0, + SimpleRedPawn = 1, + CrownedRedPawn = 3, + SimpleWhitePawn = 2, + CrownedWhitePawn = 4, +} + +impl Piece { + 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() + } +} + +#[derive(Debug, Clone)] +pub struct DraughtsBoard(Board); + +impl Default for DraughtsBoard { + fn default() -> DraughtsBoard { + DraughtsBoard(Board::::new(|(i, j)| { + if i < 3 { + if (i + j) % 2 == 0 { + Piece::SimpleWhitePawn + } else { + Piece::NoPiece + } + } else if i > 4 { + if (i + j) % 2 == 0 { + Piece::SimpleRedPawn + } else { + Piece::NoPiece + } + } else { + Piece::NoPiece + } + })) + } +} + +impl Index for DraughtsBoard { + type Output = Piece; + + fn index(&self, position: Position) -> &Self::Output { + &self.0[position] + } +} + +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 rows() -> usize { + Board::::rows() + } + + pub fn columns() -> usize { + Board::::columns() + } + + pub fn pieces<'a, const PIECES: usize>( + self: &'a Self, + pieces: &'a Vec, + ) -> Filter, impl FnMut(&(usize, usize, Piece)) -> bool> { + self.0.iter().filter(move |(_, _, piece)| { + for p in pieces { + if piece == p { + return true; + } + } + return false; + }) + } + + fn is_position_valid(position: Position) -> bool { + let position = position.to_index(); + return 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 { + return Err(Error::InvalidMove); + } 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(()) + } + } + + 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), + } + } +} + +#[derive(Debug, Clone)] +pub struct DraughtsGame { + board: DraughtsBoard, + next_move: Player, +} + +#[derive(Debug)] +pub enum Error { + WrongPlayer, + NoPiece, + InvalidMove, + InvalidPosition, + NoPieceCaptured, + FirendlyPieceCaptured, + PositionIsOccupied, + MovementInsteadOfCapture, +} + +impl DraughtsGame { + pub fn new() -> DraughtsGame { + DraughtsGame { + board: DraughtsBoard::default(), + next_move: Player::Red, + } + } + + fn next_turn(&mut self) { + match self.next_move { + Player::White => { + self.next_move = Player::Red; + } + Player::Red => { + self.next_move = Player::White; + } + } + } + + fn is_last_row(pos: &Position, player: Player) -> bool { + let row = pos.to_index().0; + match player { + Player::White => row == DraughtsBoard::rows(), + Player::Red => row == 0, + } + } + + pub fn apply_move(&mut self, mv: &Move) -> Result<(), Error> { + let start = mv.get_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); + } + } + Move::Capture { .. } => { + if self.next_move == player { + self.board.apply_move(&mv)?; + // Check if more captures are available for the current piece + for _ in self.board.moves_for_piece(mv.get_end_position(), true) { + 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, + }; + } + Ok(()) + } else { + Err(Error::NoPiece) + } + } +} + +#[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 { + 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), + }, + } + } + + 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, + state: u8, + capture_only: bool, +} + +impl MoveIterator<'_> { + const MOVES: [MoveDirection; 4] = [ + MoveDirection::NE, + MoveDirection::SE, + MoveDirection::SW, + MoveDirection::NW, + ]; + + fn piece(&self) -> Piece { + self.board[self.position] + } + + fn new<'a>( + board: &'a DraughtsBoard, + position: Position, + capture_only: bool, + ) -> MoveIterator<'a> { + MoveIterator { + board, + position, + state: 0u8, + capture_only, + } + } + fn empty_iterator<'a>(board: &'a DraughtsBoard) -> MoveIterator<'a> { + MoveIterator { + board, + position: Position::new(0, 0), + state: 4, + capture_only: false, + } + } +} + +impl<'a> Iterator for MoveIterator<'a> { + type Item = Move; + + fn next(&mut self) -> Option { + let piece = self.piece(); + while self.state < 4 { + let direction = MoveIterator::MOVES[self.state as usize]; + self.state += 1; + if !piece.can_move_backward() && !direction.is_forward(piece).unwrap() { + continue; + } + let movement = Move::Movement { + start: 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]; + match piece_at_next_position { + Piece::NoPiece => { + if self.capture_only { + continue; + } else { + return Some(movement); + } + } + _ => { + let capture = Move::Capture { + start: self.position, + direction, + }; + let next_position = capture.get_end_position(); + if let Piece::NoPiece = self.board[next_position] { + return Some(capture); + } + } + } + } + } + return None; + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9aa9b57 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +#![no_std] +mod board; +pub mod draughts; +pub mod position; + +//use draughts::DraughtsBoard; +//use draughts::Piece; diff --git a/src/position.rs b/src/position.rs new file mode 100644 index 0000000..bb0a4fd --- /dev/null +++ b/src/position.rs @@ -0,0 +1,97 @@ +use core::ops::Add; +use core::ops::Div; +use core::ops::Mul; +use core::ops::Sub; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Position((u8, u8)); + +impl Position { + pub fn to_index(&self) -> (usize, usize) { + (self.0.0 as usize, self.0.1 as usize) + } + + pub fn from_index(index: (usize, usize)) -> Position { + Position((index.0 as u8, index.1 as u8)) + } + + pub fn new(row: u8, column: u8) -> Position { + Position((row, column)) + } +} + +impl Add for Position { + type Output = Position; + + fn add(self, rhs: Position) -> Self::Output { + Position((self.0.0 + rhs.0.0, self.0.1 + rhs.0.1)) + } +} + +impl Sub for Position { + type Output = Position; + + fn sub(self, rhs: Position) -> Self::Output { + Position((self.0.0 - rhs.0.0, self.0.1 - rhs.0.1)) + } +} + +impl Div for Position { + type Output = Position; + + fn div(self, rhs: Position) -> Self::Output { + Position((self.0.0 / rhs.0.0, self.0.1 / rhs.0.1)) + } +} + +impl Mul for Position { + type Output = Position; + + fn mul(self, rhs: Position) -> Self::Output { + Position((self.0.0 * rhs.0.0, self.0.1 * rhs.0.1)) + } +} + +impl Add<(i32, i32)> for Position { + type Output = Position; + + fn add(self, rhs: (i32, i32)) -> Self::Output { + Position(( + (self.0.0 as i32 + rhs.0) as u8, + (self.0.1 as i32 + rhs.1) as u8, + )) + } +} + +impl Sub<(i32, i32)> for Position { + type Output = Position; + + fn sub(self, rhs: (i32, i32)) -> Self::Output { + Position(( + (self.0.0 as i32 - rhs.0) as u8, + (self.0.1 as i32 - rhs.1) as u8, + )) + } +} + +impl Div<(i32, i32)> for Position { + type Output = Position; + + fn div(self, rhs: (i32, i32)) -> Self::Output { + Position(( + (self.0.0 as i32 / rhs.0) as u8, + (self.0.1 as i32 / rhs.1) as u8, + )) + } +} + +impl Mul<(i32, i32)> for Position { + type Output = Position; + + fn mul(self, rhs: (i32, i32)) -> Self::Output { + Position(( + (self.0.0 as i32 * rhs.0) as u8, + (self.0.1 as i32 * rhs.1) as u8, + )) + } +}