Add progress bar, Add stats, pretty print

This commit is contained in:
cool-mist
2025-01-11 19:15:49 +05:30
parent 69420922bd
commit b6a6d570e1
7 changed files with 430 additions and 113 deletions
+35 -15
View File
@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::{collections::HashSet, fmt::Display};
use super::{
coord::{at, Coord},
@@ -100,24 +100,26 @@ impl Board {
empty_squares
}
pub(crate) fn print(&self) -> String {
let mut builder: Vec<char> = Vec::new();
pub(crate) fn pretty_print(&self) {
println!("{}", self.print(true));
}
fn print(&self, pretty: bool) -> String {
let mut board_string = String::new();
for rank in 0..4 {
let mut row = String::new();
for file in 0..4 {
match self.cells[file][rank] {
Square::Empty(_) => builder.push('.'),
Square::Occupied(Piece::King, _) => builder.push('K'),
Square::Occupied(Piece::Queen, _) => builder.push('Q'),
Square::Occupied(Piece::Bishop, _) => builder.push('B'),
Square::Occupied(Piece::Knight, _) => builder.push('N'),
Square::Occupied(Piece::Rook, _) => builder.push('R'),
Square::Occupied(Piece::Pawn, _) => builder.push('P'),
}
print_square(&mut row, &self.cells[file][rank], pretty);
}
builder.push('\n');
if pretty {
board_string.push_str(&format!("{:^40}\n", row));
} else {
board_string.push_str(&row);
}
board_string.push('\n');
}
builder.iter().collect::<String>()
board_string
}
fn calc_legal_moves(&mut self) {
@@ -385,6 +387,24 @@ impl Board {
}
}
fn print_square(row: &mut String, square: &Square, pretty: bool) {
let contents = if let Square::Occupied(piece, _) = square {
if pretty {
piece.pretty()
} else {
piece.notation()
}
} else {
"."
};
if pretty {
row.push_str(&format!(" {} ", contents));
} else {
row.push_str(contents);
}
}
#[cfg(test)]
mod tests {
use crate::engine::piece::p;
@@ -395,7 +415,7 @@ mod tests {
macro_rules! validate_board {
($board:expr, $row1:literal, $row2:literal, $row3:literal, $row4:literal) => {
let printed = $board.print();
let printed = $board.print(false);
assert_eq!(
printed,
format!("{}\n{}\n{}\n{}\n", $row1, $row2, $row3, $row4)
+11
View File
@@ -31,6 +31,17 @@ impl Piece {
Piece::Pawn => "P",
}
}
pub(crate) fn pretty(&self) -> &str {
match self {
Piece::King => "",
Piece::Queen => "",
Piece::Bishop => "",
Piece::Knight => "",
Piece::Rook => "",
Piece::Pawn => "",
}
}
}
macro_rules! p {
+91 -13
View File
@@ -1,10 +1,13 @@
use std::{fmt::Display, thread::Builder, time::Duration};
use crate::{
engine::{board::Board, coord::Coord, piece::Piece, square::Square},
solver::{self, solver::Solver},
};
use indicatif::ProgressBar;
use rand::{seq::*, Rng};
pub(crate) fn generate(num_pieces: u32) -> Option<Board> {
pub(crate) fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
let mut rand = rand::thread_rng();
let candidate_pieces = vec![
Piece::Pawn,
@@ -17,33 +20,104 @@ pub(crate) fn generate(num_pieces: u32) -> Option<Board> {
Piece::King,
Piece::Queen,
];
let attempts = 1000;
let attempts: u32 = 1000;
let bar = ProgressBar::new_spinner();
bar.enable_steady_tick(Duration::from_millis(100));
let mut overall_stats = GenerateStats::new(0, 0, 0, 0, None);
for i in 0..attempts {
let board = try_generate(num_pieces, candidate_pieces.clone(), rand.clone());
if let Some(board) = board {
return Some(board);
let stats = try_generate(
num_pieces,
num_solutions,
candidate_pieces.clone(),
rand.clone(),
);
overall_stats.piece_total += stats.piece_total;
overall_stats.piece_success += stats.piece_success;
overall_stats.total += stats.total;
overall_stats.total_millis += stats.total_millis;
overall_stats.board = stats.board;
bar.set_message(format!(
"Generating puzzle.. Elapsed: {}s",
overall_stats.total_millis / 1000
));
if overall_stats.board.is_some() {
return overall_stats;
}
}
None
bar.finish_and_clear();
overall_stats
}
pub(crate) struct GenerateStats {
piece_total: u32,
piece_success: u32,
total: u32,
total_millis: u128,
board: Option<Board>,
}
impl GenerateStats {
fn new(
piece_total: u32,
piece_success: u32,
total: u32,
total_millis: u128,
board: Option<Board>,
) -> Self {
Self {
piece_total,
piece_success,
total,
total_millis,
board,
}
}
pub(crate) fn print_stats(&self) {
let mut stats = String::new();
add_stat(&mut stats, "Total attempts", self.total);
add_stat(&mut stats, "Total pieces placed", self.piece_total);
add_stat(&mut stats, "Success pieces placed", self.piece_success);
add_stat(&mut stats, "Total time (ms)", self.total_millis);
println!("{}", stats);
}
pub(crate) fn board(mut self) -> Option<Board> {
self.board
}
}
fn add_stat<T>(stats: &mut String, name: &str, val: T)
where
T: Display,
{
stats.push_str(&format!("{:>30}:{:>6}\n", name, val));
}
fn try_generate(
num_pieces: u32,
num_solutions: u32,
mut candidate_pieces: Vec<Piece>,
mut rand: rand::prelude::ThreadRng,
) -> Option<Board> {
) -> GenerateStats {
let mut board = Board::new();
let mut piece_total = 0;
let mut piece_success = 0;
let mut now = std::time::Instant::now();
for _ in 0..num_pieces {
let mut placed = false;
let empty_squares = board.empty_squares();
let mut attempts = 15;
while !placed {
if attempts == 0 {
return None;
let elapsed = now.elapsed().as_millis();
return GenerateStats::new(piece_total, piece_success, 1, elapsed, None);
}
attempts -= 1;
piece_total += 1;
let index = rand.gen_range(0..candidate_pieces.len());
let piece = candidate_pieces[index];
@@ -53,6 +127,7 @@ fn try_generate(
let solutions = Solver::new(board.clone()).solve();
if solutions.len() > 0 {
placed = true;
piece_success += 1;
candidate_pieces.remove(index);
continue;
}
@@ -61,10 +136,11 @@ fn try_generate(
}
let solutions = Solver::new(board.clone()).solve();
if solutions.len() > 1 {
None
let elapsed = now.elapsed().as_millis();
if solutions.len() > num_solutions as usize {
GenerateStats::new(piece_total, piece_success, 1, elapsed, None)
} else {
Some(board)
GenerateStats::new(piece_total, piece_success, 1, elapsed, Some(board))
}
}
@@ -77,11 +153,13 @@ mod tests {
#[test]
fn generator_smoke() {
for _ in 0..10 {
let board = generate(5).unwrap();
let gen_stats = generate(5, 5);
let board = gen_stats.board.unwrap();
assert_eq!(board.game_state, GameState::InProgress);
let solutions = Solver::new(board).solve();
assert_eq!(solutions.len(), 1);
assert!(solutions.len() <= 5);
assert!(solutions.len() >= 1);
}
}
}
+26 -20
View File
@@ -16,13 +16,12 @@ use crate::solver::solver::Solver;
fn main() {
let args: Args = argh::from_env();
if args.generate {
let puzzle = generate_puzzle(args.num_pieces);
let puzzle = generate_puzzle(args.num_pieces, args.solutions);
let Some(board) = puzzle else {
return;
};
println!("{}", board.print());
board.pretty_print();
if args.print {
solve_puzzle(board);
}
@@ -32,7 +31,7 @@ fn main() {
println!("Invalid board string");
return;
};
println!("{}", board.print());
board.pretty_print();
solve_puzzle(board);
} else {
println!("Use --help to see available options");
@@ -54,26 +53,29 @@ fn solve_puzzle(board: Board) {
});
}
fn generate_puzzle(num_pieces: Option<u32>) -> Option<Board> {
let start = std::time::Instant::now();
let num_pieces = num_pieces.unwrap_or(5);
let board = generate(num_pieces);
let elapsed = start.elapsed();
fn generate_puzzle(num_pieces: Option<u32>, num_solutions: Option<u32>) -> Option<Board> {
let mut num_pieces = num_pieces.unwrap_or(5);
if num_pieces < 2 {
num_pieces = 2;
}
let Some(board) = board else {
println!(
"Failed to generate a puzzle with {} pieces after {} ms, Try again",
num_pieces,
elapsed.as_millis()
);
let mut num_solutions = num_solutions.unwrap_or(5);
if num_solutions < 1 {
num_solutions = 5;
}
println!(
"Generating a puzzle with {} pieces with a maximum of {} solutions",
num_pieces, num_solutions
);
let gen = generate(num_pieces, num_solutions);
gen.print_stats();
let Some(board) = gen.board() else {
println!("Failed to generate a puzzle, try again");
return None;
};
println!(
"Generated a puzzle with {} pieces after {} ms",
num_pieces,
elapsed.as_millis()
);
Some(board)
}
@@ -89,6 +91,10 @@ struct Args {
/// number of pieces to place on the board while generating a puzzle
num_pieces: Option<u32>,
#[argh(option)]
/// maximum number of solutions allowed for the generated puzzle. atleast 1. defaults to 5
solutions: Option<u32>,
#[argh(switch)]
/// print the solution. When solving a puzzle, this is always set to true
print: bool,