add age
This commit is contained in:
+62
-38
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¬ation);
|
||||
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
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user