This commit is contained in:
cool-mist
2026-01-26 20:00:50 +05:30
parent dc61d6d956
commit 280150b0b0
18 changed files with 779 additions and 325 deletions
+62 -38
View File
@@ -14,10 +14,10 @@ use std::{
use cmove::CMove;
use constants::BOARD_SIZE;
use errors::SError;
use piece::Piece;
use piece::PieceKind;
use square::{Square, SquarePair};
use crate::generator::Puzzle;
use crate::{board::piece::Piece, generator::Puzzle};
#[derive(Clone, Default)]
pub struct Board {
@@ -27,6 +27,7 @@ pub struct Board {
pub id: String,
pub size: usize,
pieces_remaining: u8,
max_moves_per_piece: u32,
}
#[derive(PartialEq, Eq, Debug, Clone)]
@@ -43,8 +44,19 @@ impl Default for BoardState {
}
}
pub struct BoardOptions {
pub max_moves_per_piece: u32,
}
impl Board {
pub fn new() -> Self {
let options = BoardOptions {
max_moves_per_piece: 2,
};
Board::create(options)
}
pub fn create(options: BoardOptions) -> Self {
let cells = [[None; BOARD_SIZE]; BOARD_SIZE];
let id = Board::encode(cells);
Board {
@@ -54,6 +66,7 @@ impl Board {
pieces_remaining: 0,
game_state: BoardState::NotStarted,
size: BOARD_SIZE,
max_moves_per_piece: options.max_moves_per_piece,
}
}
@@ -72,10 +85,10 @@ impl Board {
let mask = 0b111;
for i in (0..BOARD_SIZE).rev() {
for j in (0..BOARD_SIZE).rev() {
let piece = Board::get_piece_from_encoding((working & mask) as u8);
let piece_kind = Board::get_piece_from_encoding((working & mask) as u8);
working = working >> 3;
let piece = piece?;
board.set(Square::new(i, j, piece));
let piece_kind = piece_kind?;
board.set(Square::new(i, j, Piece::from_kind(piece_kind)));
}
}
Ok(board)
@@ -91,18 +104,18 @@ impl Board {
for r in 0..BOARD_SIZE {
for f in 0..BOARD_SIZE {
let c = chars.next().unwrap();
let piece = match c {
'K' | 'k' => Piece::King,
'Q' | 'q' => Piece::Queen,
'B' | 'b' => Piece::Bishop,
'N' | 'n' => Piece::Knight,
'R' | 'r' => Piece::Rook,
'P' | 'p' => Piece::Pawn,
let piece_kind = match c {
'K' | 'k' => PieceKind::King,
'Q' | 'q' => PieceKind::Queen,
'B' | 'b' => PieceKind::Bishop,
'N' | 'n' => PieceKind::Knight,
'R' | 'r' => PieceKind::Rook,
'P' | 'p' => PieceKind::Pawn,
'.' => continue,
_ => return Err(SError::InvalidBoard),
};
let square = Square::new(f, r, Some(piece));
let square = Square::new(f, r, Some(Piece::new(piece_kind)));
board.set(square);
}
}
@@ -137,7 +150,14 @@ impl Board {
return None;
}
let from_piece = mem::replace(&mut self.cells[mv.from.file][mv.from.rank], None);
let mut from_piece = mem::replace(&mut self.cells[mv.from.file][mv.from.rank], None);
if let Some(p) = &mut from_piece {
p.moves_made += 1;
if p.moves_made >= self.max_moves_per_piece {
p.active = false;
}
}
self.cells[mv.to.file][mv.to.rank] = from_piece;
self.pieces_remaining -= 1;
@@ -278,13 +298,17 @@ impl Board {
return None;
};
let legal = match piece {
Piece::King => self.is_king_legal(&pair),
Piece::Queen => self.is_queen_legal(&pair),
Piece::Bishop => self.is_bishop_legal(&pair),
Piece::Knight => self.is_knight_legal(&pair),
Piece::Rook => self.is_rook_legal(&pair),
Piece::Pawn => self.is_pawn_legal(&pair),
if piece.moves_made >= self.max_moves_per_piece {
return None;
}
let legal = match piece.kind {
PieceKind::King => self.is_king_legal(&pair),
PieceKind::Queen => self.is_queen_legal(&pair),
PieceKind::Bishop => self.is_bishop_legal(&pair),
PieceKind::Knight => self.is_knight_legal(&pair),
PieceKind::Rook => self.is_rook_legal(&pair),
PieceKind::Pawn => self.is_pawn_legal(&pair),
};
if legal {
@@ -400,26 +424,26 @@ impl Board {
fn get_piece_encoding(piece: Option<Piece>) -> u8 {
match piece {
Some(p) => match p {
Piece::King => 0b001,
Piece::Queen => 0b010,
Piece::Rook => 0b011,
Piece::Bishop => 0b100,
Piece::Knight => 0b101,
Piece::Pawn => 0b110,
Some(p) => match p.kind {
PieceKind::King => 0b001,
PieceKind::Queen => 0b010,
PieceKind::Rook => 0b011,
PieceKind::Bishop => 0b100,
PieceKind::Knight => 0b101,
PieceKind::Pawn => 0b110,
},
None => 0b000,
}
}
fn get_piece_from_encoding(encoding: u8) -> Result<Option<Piece>, SError> {
fn get_piece_from_encoding(encoding: u8) -> Result<Option<PieceKind>, SError> {
match encoding {
0b001 => Ok(Some(Piece::King)),
0b010 => Ok(Some(Piece::Queen)),
0b011 => Ok(Some(Piece::Rook)),
0b100 => Ok(Some(Piece::Bishop)),
0b101 => Ok(Some(Piece::Knight)),
0b110 => Ok(Some(Piece::Pawn)),
0b001 => Ok(Some(PieceKind::King)),
0b010 => Ok(Some(PieceKind::Queen)),
0b011 => Ok(Some(PieceKind::Rook)),
0b100 => Ok(Some(PieceKind::Bishop)),
0b101 => Ok(Some(PieceKind::Knight)),
0b110 => Ok(Some(PieceKind::Pawn)),
0b000 => Ok(None),
_ => Err(SError::InvalidBoard),
}
@@ -433,9 +457,9 @@ impl Board {
fn get_square_for_display(piece: &Option<Piece>, pretty: bool) -> String {
let contents = if let Some(piece) = piece {
if pretty {
piece.pretty()
piece.kind.pretty()
} else {
piece.notation()
piece.kind.notation()
}
} else {
".".to_string()
@@ -557,7 +581,7 @@ mod tests {
assert!(board.set(sq!("Nb2")).is_none());
let existing = board.set(sq!("Pc4"));
assert!(existing.is_some());
assert_eq!(Piece::Knight, existing.unwrap());
assert_eq!(PieceKind::Knight, existing.unwrap().kind);
validate_board!(board, "..PP", "..B.", "QN..", "K..R");
}
+5 -3
View File
@@ -1,4 +1,6 @@
use super::{piece::Piece, square::Square};
use crate::board::piece::Piece;
use super::{piece::PieceKind, square::Square};
#[derive(PartialEq, Hash, Eq, Clone)]
pub struct CMove {
@@ -26,8 +28,8 @@ impl CMove {
}
pub fn notation(&self) -> String {
let piece_qualifier = match &self.from_piece {
Piece::Pawn => self.from.file_notation(),
let piece_qualifier = match &self.from_piece.kind {
PieceKind::Pawn => self.from.file_notation(),
p => p.notation(),
};
format!(
+47 -27
View File
@@ -1,5 +1,25 @@
#[derive(Clone, Eq, Hash, Copy, Debug, PartialEq)]
pub enum Piece {
pub struct Piece {
pub kind: PieceKind,
pub moves_made: u32,
pub active: bool,
}
impl Piece {
pub fn new(kind: PieceKind) -> Self {
Self {
kind,
moves_made: 0,
active: true,
}
}
pub fn from_kind(kind: Option<PieceKind>) -> Option<Self> {
kind.map(|k| Piece::new(k))
}
}
#[derive(Clone, Eq, Hash, Copy, Debug, PartialEq)]
pub enum PieceKind {
King,
Queen,
Bishop,
@@ -8,15 +28,15 @@ pub enum Piece {
Pawn,
}
impl Piece {
impl PieceKind {
pub fn parse(piece: &str) -> Option<Self> {
match piece {
"K" => Some(Piece::King),
"Q" => Some(Piece::Queen),
"B" => Some(Piece::Bishop),
"N" => Some(Piece::Knight),
"R" => Some(Piece::Rook),
"P" => Some(Piece::Pawn),
"K" => Some(PieceKind::King),
"Q" => Some(PieceKind::Queen),
"B" => Some(PieceKind::Bishop),
"N" => Some(PieceKind::Knight),
"R" => Some(PieceKind::Rook),
"P" => Some(PieceKind::Pawn),
"." => None,
p => panic!("Invalid piece {}", p),
}
@@ -24,12 +44,12 @@ impl Piece {
pub fn notation(&self) -> String {
let n = match self {
Piece::King => "K",
Piece::Queen => "Q",
Piece::Bishop => "B",
Piece::Knight => "N",
Piece::Rook => "R",
Piece::Pawn => "P",
PieceKind::King => "K",
PieceKind::Queen => "Q",
PieceKind::Bishop => "B",
PieceKind::Knight => "N",
PieceKind::Rook => "R",
PieceKind::Pawn => "P",
};
n.to_string()
@@ -37,12 +57,12 @@ impl Piece {
pub fn pretty(&self) -> String {
let n = match self {
Piece::King => "",
Piece::Queen => "",
Piece::Bishop => "",
Piece::Knight => "",
Piece::Rook => "",
Piece::Pawn => "",
PieceKind::King => "",
PieceKind::Queen => "",
PieceKind::Bishop => "",
PieceKind::Knight => "",
PieceKind::Rook => "",
PieceKind::Pawn => "",
};
n.to_string()
@@ -55,17 +75,17 @@ mod tests {
macro_rules! p {
($piece:literal) => {
Piece::parse($piece)
PieceKind::parse($piece)
};
}
#[test]
fn test_piece_parse() {
assert_eq!(p!("K"), Some(Piece::King));
assert_eq!(p!("Q"), Some(Piece::Queen));
assert_eq!(p!("B"), Some(Piece::Bishop));
assert_eq!(p!("N"), Some(Piece::Knight));
assert_eq!(p!("R"), Some(Piece::Rook));
assert_eq!(p!("P"), Some(Piece::Pawn));
assert_eq!(p!("K"), Some(PieceKind::King));
assert_eq!(p!("Q"), Some(PieceKind::Queen));
assert_eq!(p!("B"), Some(PieceKind::Bishop));
assert_eq!(p!("N"), Some(PieceKind::Knight));
assert_eq!(p!("R"), Some(PieceKind::Rook));
assert_eq!(p!("P"), Some(PieceKind::Pawn));
}
}
+8 -5
View File
@@ -1,5 +1,7 @@
use crate::board::piece::Piece;
use super::constants::BOARD_SIZE;
use super::piece::Piece;
use super::piece::PieceKind;
use core::fmt;
#[derive(Clone, Eq, Hash, PartialEq)]
@@ -29,8 +31,8 @@ impl Square {
pub fn parse(notation: &str) -> Self {
let mut chars = notation.chars();
let piece = chars.next().expect("Piece missing");
let piece = Piece::parse(&piece.to_string());
let piece_kind = chars.next().expect("Piece missing");
let piece_kind = PieceKind::parse(&piece_kind.to_string());
let file = chars.next().expect("File missing");
let file = match file {
'a' => 0,
@@ -45,6 +47,7 @@ impl Square {
panic!("rank should be between 1-{}", BOARD_SIZE);
}
let rank = BOARD_SIZE - rank;
let piece = piece_kind.map(|kind| Piece::new(kind));
Square::new(file, rank, piece)
}
@@ -73,7 +76,7 @@ impl Square {
if self.piece.is_none() {
"".to_string()
} else {
self.piece.unwrap().notation()
self.piece.unwrap().kind.notation()
}
}
}
@@ -131,7 +134,7 @@ mod tests {
let square = Square::parse(&notation);
assert_eq!(square.file, $file);
assert_eq!(square.rank, $rank);
assert_eq!(square.piece, Some(Piece::King));
assert_eq!(square.piece.unwrap().kind, PieceKind::King);
assert_eq!(square.notation(), notation);
};
}
+36 -21
View File
@@ -1,6 +1,10 @@
use std::fmt::Display;
use crate::board::{Board, cmove::CMove, piece::Piece};
use crate::board::{
Board, BoardOptions,
cmove::CMove,
piece::{Piece, PieceKind},
};
pub trait RandomRange {
fn gen_range(&self, min: usize, max: usize) -> usize;
@@ -24,23 +28,24 @@ pub struct GenerateStats {
pub fn generate_weighted_random(
num_pieces: u32,
num_solutions: u32,
max_moves_per_piece: u32,
rand: &impl RandomRange,
) -> GenerateStats {
let candidate_pieces = vec![
Piece::Pawn,
Piece::Pawn,
Piece::Pawn,
Piece::Pawn,
Piece::Bishop,
Piece::Bishop,
Piece::Bishop,
Piece::Bishop,
Piece::Knight,
Piece::Knight,
Piece::Knight,
Piece::Queen,
Piece::Rook,
Piece::Rook,
PieceKind::Pawn,
PieceKind::Pawn,
PieceKind::Pawn,
PieceKind::Pawn,
PieceKind::Bishop,
PieceKind::Bishop,
PieceKind::Bishop,
PieceKind::Bishop,
PieceKind::Knight,
PieceKind::Knight,
PieceKind::Knight,
PieceKind::Queen,
PieceKind::Rook,
PieceKind::Rook,
];
if num_pieces > candidate_pieces.len().try_into().unwrap() {
@@ -53,7 +58,13 @@ pub fn generate_weighted_random(
let attempts: u32 = 1000;
let mut overall_stats = GenerateStats::new(0, 0, 0, None, vec![]);
for _ in 0..attempts {
let stats = try_generate(num_pieces, num_solutions, rand, candidate_pieces.clone());
let stats = try_generate(
num_pieces,
num_solutions,
max_moves_per_piece,
rand,
candidate_pieces.clone(),
);
overall_stats.piece_total += stats.piece_total;
overall_stats.piece_success += stats.piece_success;
overall_stats.total += stats.total;
@@ -117,10 +128,14 @@ where
fn try_generate(
num_pieces: u32,
num_solutions: u32,
max_moves_per_piece: u32,
rand: &impl RandomRange,
mut candidate_pieces: Vec<Piece>,
mut candidate_pieces: Vec<PieceKind>,
) -> GenerateStats {
let mut board = Board::new();
let mut board = Board::create(BoardOptions {
max_moves_per_piece,
});
let mut piece_total = 0;
let mut piece_success = 0;
for _ in 0..num_pieces {
@@ -136,10 +151,10 @@ fn try_generate(
piece_total += 1;
let index = rand.gen_range(0, candidate_pieces.len());
let piece = candidate_pieces[index];
let piece_kind = candidate_pieces[index];
let square_index = rand.gen_range(0, empty_squares.len());
let mut random_square = empty_squares[square_index].clone();
random_square.piece = Some(piece);
random_square.piece = Some(Piece::new(piece_kind));
board.set(random_square.clone());
let puzzle = board.solve();
if puzzle.solutions.len() > 0 {
@@ -186,7 +201,7 @@ mod tests {
#[test]
fn generator_smoke() {
for _ in 0..10 {
let gen_stats = generate_weighted_random(5, 5, &TestRandom);
let gen_stats = generate_weighted_random(5, 5, 10, &TestRandom);
let board = gen_stats.board.expect("No puzzle was generated");
assert_eq!(board.game_state, BoardState::InProgress);