initial commit

This commit is contained in:
2025-06-20 22:11:47 +08:00
parent 42b1d642ff
commit 8b9d066257
16 changed files with 1525 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

24
Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[workspace.package]
homepage = "https://gitea.woggioni.net/woggioni/rdraught"
authors = ["Walter Oggioni <oggioni.walter@gmail.com>"]
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"]

27
examples/checkers.rs Normal file
View File

@@ -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::<Piece, 2>::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);
});
}

17
rdraught-cli/Cargo.toml Normal file
View File

@@ -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 }

View File

@@ -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(())
}

16
rdraught-pi/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "rdraught-pi"
version = "0.1.0"
edition = "2024"
authors = ["Walter Oggioni <oggioni.walter@gmail.com>"]
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 }

17
rdraught-pi/src/hello.rs Normal file
View File

@@ -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);
}
}

21
rdraught-ui/Cargo.toml Normal file
View File

@@ -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"

View File

@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="270" height="90">
<!-- white king //-->
<g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0)">
<path
d="M 22.5,11.63 L 22.5,6"
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
<path
d="M 20,8 L 25,8"
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
<path
d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"
style="fill:#ffffff; stroke:#000000; stroke-linecap:butt; stroke-linejoin:miter;" />
<path
d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "
style="fill:#ffffff; stroke:#000000;" />
<path
d="M 11.5,30 C 17,27 27,27 32.5,30"
style="fill:none; stroke:#000000;" />
<path
d="M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5"
style="fill:none; stroke:#000000;" />
<path
d="M 11.5,37 C 17,34 27,34 32.5,37"
style="fill:none; stroke:#000000;" />
</g>
<!-- white queen //-->
<g style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(45,0)">
<path
d="M 9 13 A 2 2 0 1 1 5,13 A 2 2 0 1 1 9 13 z"
transform="translate(-1,-1)" />
<path
d="M 9 13 A 2 2 0 1 1 5,13 A 2 2 0 1 1 9 13 z"
transform="translate(15.5,-5.5)" />
<path
d="M 9 13 A 2 2 0 1 1 5,13 A 2 2 0 1 1 9 13 z"
transform="translate(32,-1)" />
<path
d="M 9 13 A 2 2 0 1 1 5,13 A 2 2 0 1 1 9 13 z"
transform="translate(7,-4.5)" />
<path
d="M 9 13 A 2 2 0 1 1 5,13 A 2 2 0 1 1 9 13 z"
transform="translate(24,-4)" />
<path
d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38,14 L 31,25 L 31,11 L 25.5,24.5 L 22.5,9.5 L 19.5,24.5 L 14,10.5 L 14,25 L 7,14 L 9,26 z "
style="stroke-linecap:butt;" />
<path
d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 10.5,36 10.5,36 C 9,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z "
style="stroke-linecap:butt;" />
<path
d="M 11.5,30 C 15,29 30,29 33.5,30"
style="fill:none;" />
<path
d="M 12,33.5 C 18,32.5 27,32.5 33,33.5"
style="fill:none;" />
</g>
<!-- white bishop //-->
<g style="opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(90,0)">
<g style="fill:#ffffff; stroke:#000000; stroke-linecap:butt;">
<path
d="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.646,38.99 6.677,38.97 6,38 C 7.354,36.06 9,36 9,36 z" />
<path
d="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z" />
<path
d="M 25 8 A 2.5 2.5 0 1 1 20,8 A 2.5 2.5 0 1 1 25 8 z" />
</g>
<path
d="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18"
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
</g>
<!-- white knight //-->
<g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(135,0)">
<path
d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"
style="fill:#ffffff; stroke:#000000;" />
<path
d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"
style="fill:#ffffff; stroke:#000000;" />
<path
d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"
style="fill:#000000; stroke:#000000;" />
<path
d="M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z"
transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)"
style="fill:#000000; stroke:#000000;" />
</g>
<!-- white rook //-->
<g style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(180,0)">
<path
d="M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z "
style="stroke-linecap:butt;" />
<path
d="M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z "
style="stroke-linecap:butt;" />
<path
d="M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14"
style="stroke-linecap:butt;" />
<path
d="M 34,14 L 31,17 L 14,17 L 11,14" />
<path
d="M 31,17 L 31,29.5 L 14,29.5 L 14,17"
style="stroke-linecap:butt; stroke-linejoin:miter;" />
<path
d="M 31,29.5 L 32.5,32 L 12.5,32 L 14,29.5" />
<path
d="M 11,14 L 34,14"
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
</g>
<!-- white pawn //-->
<g transform="translate(225,0)">
<path
d="M 22,9 C 19.79,9 18,10.79 18,13 C 18,13.89 18.29,14.71 18.78,15.38 C 16.83,16.5 15.5,18.59 15.5,21 C 15.5,23.03 16.44,24.84 17.91,26.03 C 14.91,27.09 10.5,31.58 10.5,39.5 L 33.5,39.5 C 33.5,31.58 29.09,27.09 26.09,26.03 C 27.56,24.84 28.5,23.03 28.5,21 C 28.5,18.59 27.17,16.5 25.22,15.38 C 25.71,14.71 26,13.89 26,13 C 26,10.79 24.21,9 22,9 z "
style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:nonzero; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" />
</g>
<!-- black king //-->
<g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,45)">
<path
d="M 22.5,11.63 L 22.5,6"
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
<path
d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"
style="fill:#000000;fill-opacity:1; stroke-linecap:butt; stroke-linejoin:miter;" />
<path
d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "
style="fill:#000000; stroke:#000000;" />
<path
d="M 20,8 L 25,8"
style="fill:none; stroke:#000000; stroke-linejoin:miter;" />
<path
d="M 32,29.5 C 32,29.5 40.5,25.5 38.03,19.85 C 34.15,14 25,18 22.5,24.5 L 22.51,26.6 L 22.5,24.5 C 20,18 9.906,14 6.997,19.85 C 4.5,25.5 11.85,28.85 11.85,28.85"
style="fill:none; stroke:#ffffff;" />
<path
d="M 11.5,30 C 17,27 27,27 32.5,30 M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5 M 11.5,37 C 17,34 27,34 32.5,37"
style="fill:none; stroke:#ffffff;" />
</g>
<!-- black queen //-->
<g style="opacity:1; fill:000000; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(45,45)">
<g style="fill:#000000; stroke:none;">
<circle cx="6" cy="12" r="2.75" />
<circle cx="14" cy="9" r="2.75" />
<circle cx="22.5" cy="8" r="2.75" />
<circle cx="31" cy="9" r="2.75" />
<circle cx="39" cy="12" r="2.75" />
</g>
<path
d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z"
style="stroke-linecap:butt; stroke:#000000;" />
<path
d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 10.5,36 10.5,36 C 9,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z"
style="stroke-linecap:butt;" />
<path
d="M 11,38.5 A 35,35 1 0 0 34,38.5"
style="fill:none; stroke:#000000; stroke-linecap:butt;" />
<path
d="M 11,29 A 35,35 1 0 1 34,29"
style="fill:none; stroke:#ffffff;" />
<path
d="M 12.5,31.5 L 32.5,31.5"
style="fill:none; stroke:#ffffff;" />
<path
d="M 11.5,34.5 A 35,35 1 0 0 33.5,34.5"
style="fill:none; stroke:#ffffff;" />
<path
d="M 10.5,37.5 A 35,35 1 0 0 34.5,37.5"
style="fill:none; stroke:#ffffff;" />
</g>
<!-- black bishop //-->
<g style="opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(90,45)">
<g style="fill:#000000; stroke:#000000; stroke-linecap:butt;">
<path
d="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.646,38.99 6.677,38.97 6,38 C 7.354,36.06 9,36 9,36 z" />
<path
d="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z" />
<path
d="M 25 8 A 2.5 2.5 0 1 1 20,8 A 2.5 2.5 0 1 1 25 8 z" />
</g>
<path
d="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18"
style="fill:none; stroke:#ffffff; stroke-linejoin:miter;" />
</g>
<!-- black knight //-->
<g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(135,45)">
<path
d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"
style="fill:#000000; stroke:#000000;" />
<path
d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"
style="fill:#000000; stroke:#000000;" />
<path
d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"
style="fill:#ffffff; stroke:#ffffff;" />
<path
d="M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z"
transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)"
style="fill:#ffffff; stroke:#ffffff;" />
<path
d="M 24.55,10.4 L 24.1,11.85 L 24.6,12 C 27.75,13 30.25,14.49 32.5,18.75 C 34.75,23.01 35.75,29.06 35.25,39 L 35.2,39.5 L 37.45,39.5 L 37.5,39 C 38,28.94 36.62,22.15 34.25,17.66 C 31.88,13.17 28.46,11.02 25.06,10.5 L 24.55,10.4 z "
style="fill:#ffffff; stroke:none;" />
</g>
<!-- black rook //-->
<g style="opacity:1; fill:000000; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(180,45)">
<path
d="M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z "
style="stroke-linecap:butt;" />
<path
d="M 12.5,32 L 14,29.5 L 31,29.5 L 32.5,32 L 12.5,32 z "
style="stroke-linecap:butt;" />
<path
d="M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z "
style="stroke-linecap:butt;" />
<path
d="M 14,29.5 L 14,16.5 L 31,16.5 L 31,29.5 L 14,29.5 z "
style="stroke-linecap:butt;stroke-linejoin:miter;" />
<path
d="M 14,16.5 L 11,14 L 34,14 L 31,16.5 L 14,16.5 z "
style="stroke-linecap:butt;" />
<path
d="M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14 L 11,14 z "
style="stroke-linecap:butt;" />
<path
d="M 12,35.5 L 33,35.5 L 33,35.5"
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
<path
d="M 13,31.5 L 32,31.5"
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
<path
d="M 14,29.5 L 31,29.5"
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
<path
d="M 14,16.5 L 31,16.5"
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
<path
d="M 11,14 L 34,14"
style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />
</g>
<!-- black pawn //-->
<g transform="translate(225,45)">
<path
d="M 22,9 C 19.79,9 18,10.79 18,13 C 18,13.89 18.29,14.71 18.78,15.38 C 16.83,16.5 15.5,18.59 15.5,21 C 15.5,23.03 16.44,24.84 17.91,26.03 C 14.91,27.09 10.5,31.58 10.5,39.5 L 33.5,39.5 C 33.5,31.58 29.09,27.09 26.09,26.03 C 27.56,24.84 28.5,23.03 28.5,21 C 28.5,18.59 27.17,16.5 25.22,15.38 C 25.71,14.71 26,13.89 26,13 C 26,10.79 24.21,9 22,9 z "
style="opacity:1; fill:#000000; fill-opacity:1; fill-rule:nonzero; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

27
rdraught-ui/src/crown.svg Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
<g style="fill:none; fill-opacity:0; fill-rule:evenodd; stroke:#ffc837; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0)">
<path
d="M 22.5,11.63 L 22.5,6"
style="fill:none; stroke:#ffc837; stroke-linejoin:miter;" />
<path
d="M 20,8 L 25,8"
style="fill:none; stroke:#ffc837; stroke-linejoin:miter;" />
<path
d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"
style="fill:#ffffff; stroke:#ffc837; stroke-linecap:butt; stroke-linejoin:miter;" />
<path
d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "
style="fill:#ffffff; stroke:#ffc837;" />
<path
d="M 11.5,30 C 17,27 27,27 32.5,30"
style="fill:none; stroke:#ffc837;" />
<path
d="M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5"
style="fill:none; stroke:#ffc837;" />
<path
d="M 11.5,37 C 17,34 27,34 32.5,37"
style="fill:none; stroke:#ffc837;" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

211
rdraught-ui/src/geo2d.rs Normal file
View File

@@ -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<f64, 3, 3>;
pub struct Point(SMatrix<f64, 1, 3>);
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<Point> for Point {
fn add(self, rhs: Point) -> Self::Output {
self * &xlate(rhs.x(), rhs.y())
}
type Output = Point;
}
impl Sub<Point> for Point {
fn sub(self, rhs: Point) -> Self::Output {
self * &xlate(-rhs.x(), -rhs.y())
}
type Output = Point;
}
impl Mul<f64> 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<f64> 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);
}
}

180
rdraught-ui/src/main.rs Normal file
View File

@@ -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: &gtk::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();
}

131
src/board.rs Normal file
View File

@@ -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<TYPE, const ROWS: usize, const COLUMNS: usize> {
data: [[TYPE; COLUMNS]; ROWS],
}
impl<TYPE, const ROWS: usize, const COLUMNS: usize> RectangularBoard
for Board<TYPE, ROWS, COLUMNS>
{
fn rows() -> usize {
ROWS
}
fn columns() -> usize {
COLUMNS
}
}
impl<TYPE, const ROWS: usize, const COLUMNS: usize> Default for Board<TYPE, ROWS, COLUMNS>
where
TYPE: Default,
{
fn default() -> Self {
Self::new(|_| TYPE::default())
}
}
impl<TYPE, const ROWS: usize, const COLUMNS: usize> IntoIterator for Board<TYPE, ROWS, COLUMNS> {
type Item = (usize, usize, TYPE);
type IntoIter = BoardIterator<TYPE, ROWS, COLUMNS>;
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<Flatten<core::slice::Iter<'a, [TYPE; COLUMNS]>>>,
}
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::Item> {
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<TYPE, ROWS, COLUMNS>
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<TYPE, const ROWS: usize, const COLUMNS: usize> Board<TYPE, ROWS, COLUMNS> {
pub(crate) fn new<INITIALIZER>(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<TYPE, const ROWS: usize, const COLUMNS: usize> {
it: Enumerate<Flatten<core::array::IntoIter<[TYPE; COLUMNS], ROWS>>>,
}
impl<TYPE, const ROWS: usize, const COLUMNS: usize> Iterator
for BoardIterator<TYPE, ROWS, COLUMNS>
{
fn next(&mut self) -> Option<Self::Item> {
self.it
.next()
.map(|(index, value)| (index / COLUMNS, index % COLUMNS, value))
}
type Item = (usize, usize, TYPE);
}
impl<TYPE, const ROWS: usize, const COLUMNS: usize> Index<Position> for Board<TYPE, ROWS, COLUMNS> {
type Output = TYPE;
fn index(&self, position: Position) -> &Self::Output {
let index = position.to_index();
&self.data[index.1][index.0]
}
}
impl<TYPE, const ROWS: usize, const COLUMNS: usize> IndexMut<Position>
for Board<TYPE, ROWS, COLUMNS>
{
fn index_mut(&mut self, position: Position) -> &mut Self::Output {
let index = position.to_index();
&mut self.data[index.1][index.0]
}
}

462
src/draughts.rs Normal file
View File

@@ -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<Player> {
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<Piece, 8, 8>);
impl Default for DraughtsBoard {
fn default() -> DraughtsBoard {
DraughtsBoard(Board::<Piece, 8usize, 8usize>::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<Position> for DraughtsBoard {
type Output = Piece;
fn index(&self, position: Position) -> &Self::Output {
&self.0[position]
}
}
impl IndexMut<Position> for DraughtsBoard {
fn index_mut(&mut self, position: Position) -> &mut Self::Output {
&mut self.0[position]
}
}
impl RectangularBoard for DraughtsBoard {
fn rows() -> usize {
Board::<Piece, 8, 8>::rows()
}
fn columns() -> usize {
Board::<Piece, 8, 8>::columns()
}
}
impl DraughtsBoard {
pub fn rows() -> usize {
Board::<Piece, 8, 8>::rows()
}
pub fn columns() -> usize {
Board::<Piece, 8, 8>::columns()
}
pub fn pieces<'a, const PIECES: usize>(
self: &'a Self,
pieces: &'a Vec<Piece, PIECES>,
) -> Filter<BoardIteratorRef<'a, Piece, 8, 8>, 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<bool, Error> {
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<Self::Item> {
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;
}
}

7
src/lib.rs Normal file
View File

@@ -0,0 +1,7 @@
#![no_std]
mod board;
pub mod draughts;
pub mod position;
//use draughts::DraughtsBoard;
//use draughts::Piece;

97
src/position.rs Normal file
View File

@@ -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<Position> 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<Position> 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<Position> 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<Position> 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,
))
}
}