Added texture and board

This commit is contained in:
cool-mist 2025-01-27 00:24:28 +05:30
parent 90a6ae7716
commit a7ada984c5
32 changed files with 664 additions and 276 deletions

View File

@ -2,6 +2,7 @@
name = "sol_chess"
version = "0.1.1"
edition = "2021"
default-run = "sol_chess"
[dependencies]
argh = "0.1.13"

View File

@ -6,14 +6,18 @@ Goal: Generate 'hard' puzzles.
- Install Rust from [here](https://www.rust-lang.org/tools/install).
- Run `cargo install --git https://github.com/cool-mist/sol_chess` to install the tool.
- Run `sol_chess --help` to see the options.
## Usage
- Run `sol_chess` to start a windowed GUI game.
- Run `sol_cli` to start the CLI tool.
## CLI Usage
- Generate a puzzle
```bash
$ sol_chess -g -n 6
$ sol_cli -g -n 6
Generating a puzzle with 6 pieces with a maximum of 5 solutions
Total attempts: 7
Total pieces placed: 71
@ -27,12 +31,17 @@ Generating a puzzle with 6 pieces with a maximum of 5 solutions
♔ . ♘ ♙
. . . .
id: 202859896274992
```
- Solve a puzzle
- Solve a puzzle by ID, or by board string
```bash
$ sol_chess -- --solve N...P.R.K.NP....
$ sol_cli --solve 202859896274992
$ sol_cli --solve-board N...P.R.K.NP....
♘ . . .
♙ . ♖ .
@ -42,39 +51,15 @@ $ sol_chess -- --solve N...P.R.K.NP....
. . . .
id: 202859896274992
Found 3 solutions
1. Rc3 -> a3
2. Ra3 -> a4
3. Ra4 -> a2
4. Ra2 -> c2
5. Rc2 -> d2
```
1. RxNc2
2. RxPd2
3. RxKa2
4. RxPa3
5. RxNa4
- Generate and solve a puzzle
```bash
$ sol_chess -g -n 6 --print
Generating a puzzle with 6 pieces with a maximum of 5 solutions
Total attempts: 4
Total pieces placed: 34
Success pieces placed: 24
Total time (ms): 38
. . ♙ .
♕ . . ♘
. . . .
♗ ♖ . ♘
Found 5 solutions
1. Rb1 -> a1
2. Ra1 -> d1
3. Rd1 -> d3
4. Qa3 -> d3
5. Qd3 -> c4
```
## Heuristics of current algorithm

BIN
assets/pieces.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

106
src/bin/sol_cli.rs Normal file
View File

@ -0,0 +1,106 @@
use argh::FromArgs;
use sol_chess::board::Board;
use sol_chess::generator;
use sol_chess::solver::Solver;
fn main() {
let args: Args = argh::from_env();
if args.generate {
let puzzle = generate_puzzle(args.num_pieces, args.solutions);
let Some(board) = puzzle else {
return;
};
board.pretty_print();
if args.print {
solve_puzzle(board);
}
} else {
let board = if let Some(board_string) = args.solve_board {
Board::from_string(board_string)
} else if let Some(board_id) = args.solve {
Board::from_id(board_id)
} else {
println!("Use --help to see available options");
return;
};
let Ok(board) = board else {
println!("Invalid board string/id");
return;
};
board.pretty_print();
solve_puzzle(board);
}
}
fn solve_puzzle(board: Board) {
let solutions = Solver::new(board).solve();
if solutions.len() == 0 {
println!("No solutions found");
return;
}
println!("Found {} solutions", solutions.len());
let solution = solutions.first().unwrap();
let mut idx = 0;
solution.iter().for_each(|m| {
idx += 1;
println!("{}. {}", idx, m.notation());
});
}
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 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 = generator::generate(num_pieces, num_solutions);
gen.print_stats();
let Some(board) = gen.board() else {
println!("Failed to generate a puzzle, try again");
return None;
};
Some(board)
}
/// Solitaire Chess puzzle generator and solver
/// - v0.0.1 cool-mist
#[derive(FromArgs)]
struct Args {
#[argh(switch, short = 'g')]
/// generate a puzzle
generate: bool,
#[argh(option, short = 'n')]
/// 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,
#[argh(option, short = 's')]
/// the id of the board to solve
solve: Option<u128>,
#[argh(option)]
/// the board to solve in board representation
solve_board: Option<String>,
}

View File

@ -1,22 +1,22 @@
use std::{
collections::{HashMap, HashSet},
fmt::Display,
mem,
};
pub mod cmove;
mod constants;
pub mod errors;
pub mod piece;
pub mod square;
use super::{
cmove::CMove,
constants::BOARD_SIZE,
errors::SError,
piece::Piece,
square::{Square, SquarePair},
};
use std::{collections::HashSet, mem};
use cmove::CMove;
use constants::BOARD_SIZE;
use errors::SError;
use piece::Piece;
use square::{Square, SquarePair};
#[derive(Clone)]
pub(crate) struct Board {
pub(crate) cells: [[Option<Piece>; BOARD_SIZE]; BOARD_SIZE],
pub(crate) legal_moves: HashSet<CMove>,
pub(crate) game_state: GameState,
pub struct Board {
pub cells: [[Option<Piece>; BOARD_SIZE]; BOARD_SIZE],
pub legal_moves: HashSet<CMove>,
pub game_state: GameState,
pieces_remaining: u8,
}
@ -29,7 +29,7 @@ pub enum GameState {
}
impl Board {
pub(crate) fn new() -> Self {
pub fn new() -> Self {
Board {
cells: [[None; BOARD_SIZE]; BOARD_SIZE],
legal_moves: HashSet::new(),
@ -38,7 +38,7 @@ impl Board {
}
}
pub(crate) fn from_id(board_id: u128) -> Result<Self, SError> {
pub fn from_id(board_id: u128) -> Result<Self, SError> {
let mut board = Board::new();
let mut working = board_id;
for i in (0..BOARD_SIZE).rev() {
@ -53,14 +53,12 @@ impl Board {
Ok(board)
}
pub(crate) fn from_string(board_string: String) -> Result<Self, SError> {
pub fn from_string(board_string: String) -> Result<Self, SError> {
if board_string.chars().count() != 16 {
return Err(SError::InvalidBoard);
}
let mut board = Board::new();
let mut file = 0;
let mut rank = 0;
let mut chars = board_string.chars();
for r in 0..BOARD_SIZE {
for f in 0..BOARD_SIZE {
@ -83,7 +81,7 @@ impl Board {
Ok(board)
}
pub(crate) fn set(&mut self, square: Square) -> Option<Piece> {
pub fn set(&mut self, square: Square) -> Option<Piece> {
let new_is_occuppied = square.piece.is_some();
let existing = mem::replace(&mut self.cells[square.file][square.rank], square.piece);
@ -101,7 +99,7 @@ impl Board {
existing
}
pub(crate) fn make_move(&mut self, mv: CMove) -> Option<CMove> {
pub fn make_move(&mut self, mv: CMove) -> Option<CMove> {
if !self.legal_moves.contains(&mv) {
println!("Invalid move - {}", mv.notation());
println!("Legal moves - ");
@ -112,14 +110,14 @@ impl Board {
}
let from_piece = mem::replace(&mut self.cells[mv.from.file][mv.from.rank], None);
mem::replace(&mut self.cells[mv.to.file][mv.to.rank], from_piece);
self.cells[mv.to.file][mv.to.rank] = from_piece;
self.pieces_remaining -= 1;
self.board_state_changed();
Some(mv)
}
pub(crate) fn empty_squares(&self) -> Vec<Square> {
pub fn empty_squares(&self) -> Vec<Square> {
let mut empty_squares = Vec::new();
for file in 0..BOARD_SIZE {
for rank in 0..BOARD_SIZE {
@ -131,12 +129,12 @@ impl Board {
empty_squares
}
pub(crate) fn pretty_print(&self) {
pub fn pretty_print(&self) {
println!("{}", self.print(true));
println!("{:^40}\n", format!("id: {}", self.id()));
}
pub(crate) fn id(&self) -> u128 {
pub fn id(&self) -> u128 {
let mut res: u128 = 0;
for i in 0..BOARD_SIZE {
@ -256,8 +254,6 @@ impl Board {
return false;
}
}
true
}
fn calc_game_state(&mut self) {
@ -356,12 +352,20 @@ fn get_square_for_display(piece: &Option<Piece>, pretty: bool) -> String {
#[cfg(test)]
mod tests {
use crate::engine::cmove::mv;
use crate::engine::piece::p;
use crate::engine::square::sq;
use super::*;
macro_rules! sq {
($sq:literal) => {
Square::parse($sq)
};
}
macro_rules! mv {
($from:literal, $to:literal) => {{
CMove::new(sq!($from), sq!($to))
}};
}
macro_rules! validate_board {
($board:expr, $row1:literal, $row2:literal, $row3:literal, $row4:literal) => {
let printed = $board.print(false);

View File

@ -1,19 +1,19 @@
use super::{board::Board, piece::Piece, square::Square};
use super::{piece::Piece, square::Square};
#[derive(PartialEq, Hash, Eq, Clone)]
pub(crate) struct CMove {
pub(crate) from_piece: Piece,
pub(crate) from: Square,
pub(crate) to_piece: Piece,
pub(crate) to: Square,
pub struct CMove {
pub from_piece: Piece,
pub from: Square,
pub to_piece: Piece,
pub to: Square,
// Used to disambiguate when looking at notation
disambig: String,
}
impl CMove {
pub(crate) fn new(from: Square, to: Square) -> Self {
let qualifier = String::from("");
pub fn new(from: Square, to: Square) -> Self {
let disambig = String::from("");
let from_piece = from.piece.expect("Trying to move a blank");
let to_piece = to.piece.expect("Trying to capture a blank");
CMove {
@ -21,11 +21,11 @@ impl CMove {
from,
to_piece,
to,
disambig: "".to_string(),
disambig
}
}
pub(crate) fn notation(&self) -> String {
pub fn notation(&self) -> String {
let piece_qualifier = match &self.from_piece {
Piece::Pawn => self.from.file_notation(),
p => p.notation(),
@ -38,11 +38,3 @@ impl CMove {
)
}
}
macro_rules! mv {
($from:literal, $to:literal) => {{
CMove::new(sq!($from), sq!($to))
}};
}
pub(crate) use mv;

View File

@ -1,4 +1,4 @@
#[derive(Debug)]
pub(crate) enum SError {
pub enum SError {
InvalidBoard,
}

View File

@ -1,5 +1,5 @@
#[derive(Clone, Eq, Hash, Copy, Debug, PartialEq)]
pub(crate) enum Piece {
pub enum Piece {
King,
Queen,
Bishop,
@ -9,7 +9,7 @@ pub(crate) enum Piece {
}
impl Piece {
pub(crate) fn parse(piece: &str) -> Option<Self> {
pub fn parse(piece: &str) -> Option<Self> {
match piece {
"K" => Some(Piece::King),
"Q" => Some(Piece::Queen),
@ -22,7 +22,7 @@ impl Piece {
}
}
pub(crate) fn notation(&self) -> String {
pub fn notation(&self) -> String {
let n = match self {
Piece::King => "K",
Piece::Queen => "Q",
@ -35,7 +35,7 @@ impl Piece {
n.to_string()
}
pub(crate) fn pretty(&self) -> String {
pub fn pretty(&self) -> String {
let n = match self {
Piece::King => "",
Piece::Queen => "",
@ -49,18 +49,16 @@ impl Piece {
}
}
macro_rules! p {
($piece:literal) => {
Piece::parse($piece)
};
}
pub(crate) use p;
#[cfg(test)]
mod tests {
use super::*;
macro_rules! p {
($piece:literal) => {
Piece::parse($piece)
};
}
#[test]
fn test_piece_parse() {
assert_eq!(p!("K"), Some(Piece::King));

View File

@ -1,34 +1,33 @@
use crate::engine::constants::BOARD_SIZE;
use super::constants::BOARD_SIZE;
use super::piece::Piece;
use core::fmt;
#[derive(Clone, Eq, Hash, PartialEq)]
pub(crate) struct Square {
pub struct Square {
// a = 0, b = 1, c = 2, d = 3 and so on.
pub(crate) file: usize,
pub file: usize,
// 1 = 0, 2 = 1, 3 = 2, 4 = 3 and so on.
pub(crate) rank: usize,
pub rank: usize,
pub(crate) piece: Option<Piece>,
pub piece: Option<Piece>,
}
pub(crate) struct SquarePair {
pub(crate) start: Square,
pub(crate) end: Square,
pub(crate) dx: usize,
pub(crate) dy: usize,
pub(crate) x_dir: i8,
pub(crate) y_dir: i8,
pub struct SquarePair {
pub start: Square,
pub end: Square,
pub dx: usize,
pub dy: usize,
pub x_dir: i8,
pub y_dir: i8,
}
impl Square {
pub(crate) fn new(file: usize, rank: usize, piece: Option<Piece>) -> Self {
pub fn new(file: usize, rank: usize, piece: Option<Piece>) -> Self {
Square { file, rank, piece }
}
pub(crate) fn parse(notation: &str) -> Self {
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());
@ -49,15 +48,15 @@ impl Square {
Square::new(file, rank, piece)
}
pub(crate) fn file_notation(&self) -> String {
pub fn file_notation(&self) -> String {
String::from("abcd".chars().nth(self.file).unwrap())
}
pub(crate) fn rank_notation(&self) -> String {
pub fn rank_notation(&self) -> String {
format!("{}", BOARD_SIZE - self.rank)
}
pub(crate) fn notation(&self) -> String {
pub fn notation(&self) -> String {
format!(
"{}{}{}",
self.piece_notation(),
@ -66,7 +65,7 @@ impl Square {
)
}
pub(crate) fn is_occupied(&self) -> bool {
pub fn is_occupied(&self) -> bool {
self.piece.is_some()
}
@ -80,7 +79,7 @@ impl Square {
}
impl SquarePair {
pub(crate) fn new(start: Square, end: Square) -> Self {
pub fn new(start: Square, end: Square) -> Self {
let mut dx = 0;
let mut dy = 0;
let mut x_dir = 0;
@ -111,19 +110,11 @@ impl SquarePair {
}
}
pub(crate) fn is_different(&self) -> bool {
pub fn is_different(&self) -> bool {
self.dx != 0 || self.dy != 0
}
}
macro_rules! sq {
($sq:literal) => {
Square::parse($sq)
};
}
pub(crate) use sq;
impl fmt::Debug for Square {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({},{})", self.notation(), self.file, self.rank)

View File

@ -1,6 +0,0 @@
pub(crate) mod constants;
pub(crate) mod errors;
pub(crate) mod board;
pub(crate) mod square;
pub(crate) mod cmove;
pub(crate) mod piece;

1
src/game.rs Normal file
View File

@ -0,0 +1 @@
pub mod texture;

View File

@ -1,3 +0,0 @@
pub(crate) fn run() {
println!("Running game...");
}

View File

@ -1 +0,0 @@
pub(crate) mod game;

45
src/game/texture.rs Normal file
View File

@ -0,0 +1,45 @@
use macroquad::prelude::*;
use sol_chess::board::piece::Piece;
pub struct PieceTexture {
x: f32,
y: f32,
w: f32,
h: f32,
}
impl PieceTexture {
fn new(x: u32, y: u32) -> Self {
Self {
x: x as f32 * 128.0,
y: y as f32 * 128.0,
w: 128.0,
h: 128.0,
}
}
pub fn for_piece(piece: Piece, sprite_size: f32) -> DrawTextureParams {
let index = match piece {
Piece::Pawn => 0,
Piece::Knight => 1,
Piece::Bishop => 2,
Piece::Rook => 3,
Piece::Queen => 4,
Piece::King => 5,
};
let color = 0;
let texture_rect = PieceTexture::new(index, color);
DrawTextureParams {
source: Some(Rect::new(
texture_rect.x,
texture_rect.y,
texture_rect.w,
texture_rect.h,
)),
dest_size: Some(Vec2::new(sprite_size, sprite_size)),
..DrawTextureParams::default()
}
}
}

View File

@ -1,14 +1,14 @@
use std::{fmt::Display, thread::Builder, time::Duration};
use std::{fmt::Display, time::Duration};
use crate::{
engine::{board::Board, piece::Piece, square::Square},
solver::{self, solver::Solver},
board::{piece::Piece, Board},
solver::Solver,
};
use indicatif::ProgressBar;
use rand::{seq::*, Rng};
pub(crate) fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
let mut rand = rand::thread_rng();
pub fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
let rand = rand::thread_rng();
let candidate_pieces = vec![
Piece::Pawn,
Piece::Pawn,
@ -16,8 +16,14 @@ pub(crate) fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
Piece::Pawn,
Piece::Bishop,
Piece::Bishop,
Piece::Bishop,
Piece::Bishop,
Piece::Knight,
Piece::Knight,
Piece::Knight,
Piece::Queen,
Piece::Rook,
Piece::Rook,
];
if num_pieces > candidate_pieces.len().try_into().unwrap() {
@ -31,7 +37,7 @@ pub(crate) fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
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 {
for _ in 0..attempts {
let stats = try_generate(
num_pieces,
num_solutions,
@ -56,7 +62,7 @@ pub(crate) fn generate(num_pieces: u32, num_solutions: u32) -> GenerateStats {
overall_stats
}
pub(crate) struct GenerateStats {
pub struct GenerateStats {
piece_total: u32,
piece_success: u32,
total: u32,
@ -81,7 +87,7 @@ impl GenerateStats {
}
}
pub(crate) fn print_stats(&self) {
pub 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);
@ -91,7 +97,7 @@ impl GenerateStats {
println!("{}", stats);
}
pub(crate) fn board(mut self) -> Option<Board> {
pub fn board(self) -> Option<Board> {
self.board
}
}
@ -112,7 +118,7 @@ fn try_generate(
let mut board = Board::new();
let mut piece_total = 0;
let mut piece_success = 0;
let mut now = std::time::Instant::now();
let now = std::time::Instant::now();
for _ in 0..num_pieces {
let mut placed = false;
let empty_squares = board.empty_squares();
@ -155,7 +161,7 @@ fn try_generate(
#[cfg(test)]
mod tests {
use crate::{engine::board::GameState, solver::solver::Solver};
use crate::{board::GameState, solver::Solver};
use super::*;

View File

@ -1 +0,0 @@
pub(crate) mod generator;

3
src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod board;
pub mod generator;
pub mod solver;

View File

@ -1,125 +1,122 @@
#[allow(unused)]
mod engine;
use game::texture::PieceTexture;
use macroquad::prelude::*;
use sol_chess::board::{square::Square, Board};
#[allow(unused)]
mod solver;
#[allow(unused)]
mod generator;
#[allow(unused)]
mod game;
use argh::FromArgs;
use engine::board::Board;
#[macroquad::main("Solitaire Chess")]
async fn main() {
let background_color = Color::from_rgba(196, 195, 208, 255);
let game = init().await;
loop {
clear_background(background_color);
game.draw();
next_frame().await
}
}
use crate::game::game as sol_chess_game;
use crate::generator::generator::generate;
use crate::solver::solver::Solver;
async fn init() -> Game {
set_pc_assets_folder("./assets");
let texture_res = load_texture("pieces.png").await.unwrap();
texture_res.set_filter(FilterMode::Nearest);
build_textures_atlas();
let mut board = Board::new();
board.set(Square::parse("Pa4"));
board.set(Square::parse("Pa3"));
board.set(Square::parse("Na2"));
board.set(Square::parse("Na1"));
board.set(Square::parse("Bb4"));
board.set(Square::parse("Bb3"));
board.set(Square::parse("Rb2"));
board.set(Square::parse("Rb1"));
board.set(Square::parse("Kc4"));
board.set(Square::parse("Kc3"));
board.set(Square::parse("Qc2"));
board.set(Square::parse("Qc1"));
fn main() {
let args: Args = argh::from_env();
let square_width = 128.0;
let num_squares = 4;
let x = (screen_width() - (square_width * num_squares as f32)) / 2.0;
let y = (screen_height() - (square_width * num_squares as f32)) / 2.0;
let game = Game::new(board, x, y, square_width, num_squares, texture_res);
if args.game {
sol_chess_game::run();
} else if args.generate {
let puzzle = generate_puzzle(args.num_pieces, args.solutions);
let Some(board) = puzzle else {
return;
};
game
}
board.pretty_print();
if args.print {
solve_puzzle(board);
struct Game {
board: Board,
squares: Vec<GameSquare>,
texture_res: Texture2D,
num_squares: usize,
}
impl Game {
fn new(
board: Board,
x: f32,
y: f32,
square_width: f32,
num_squares: usize,
texture_res: Texture2D,
) -> Self {
let dark = Color::from_rgba(83, 104, 120, 255);
let light = Color::from_rgba(190, 190, 190, 255);
let mut rects = Vec::new();
for i in 0..num_squares {
for j in 0..num_squares {
let x_eff = x + (i as f32 * square_width);
let y_eff = y + (j as f32 * square_width);
let rect = Rect::new(x_eff, y_eff, square_width, square_width);
let color = match (i + j) % 2 {
1 => dark,
_ => light,
};
rects.push(GameSquare { rect, color, i, j });
}
}
} else {
let board = if let Some(board_string) = args.solve_board {
Board::from_string(board_string)
} else if let Some(board_id) = args.solve {
Board::from_id(board_id)
} else {
println!("Use --help to see available options");
return;
};
let Ok(board) = board else {
println!("Invalid board string/id");
return;
};
board.pretty_print();
solve_puzzle(board);
Self {
board,
squares: rects,
num_squares,
texture_res,
}
}
fn get(&mut self, i: usize, j: usize) -> &mut GameSquare {
&mut self.squares[i * self.num_squares + j]
}
fn draw(&self) {
let sprite_size = 100.0;
self.squares.iter().for_each(|square| {
draw_rectangle(
square.rect.x,
square.rect.y,
square.rect.w,
square.rect.h,
square.color,
);
if let Some(p) = &self.board.cells[square.i][square.j] {
let offset = (square.rect.w - sprite_size) / 2.0;
let dtp = PieceTexture::for_piece(*p, sprite_size);
draw_texture_ex(
&self.texture_res,
square.rect.x + offset,
square.rect.y + offset,
WHITE,
dtp,
);
}
});
}
}
fn solve_puzzle(board: Board) {
let solutions = Solver::new(board).solve();
if solutions.len() == 0 {
println!("No solutions found");
return;
}
println!("Found {} solutions", solutions.len());
let solution = solutions.first().unwrap();
let mut idx = 0;
solution.iter().for_each(|m| {
idx += 1;
println!("{}. {}", idx, m.notation());
});
}
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 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;
};
Some(board)
}
/// Solitaire Chess puzzle generator and solver
/// - v0.0.1 cool-mist
#[derive(FromArgs)]
struct Args {
#[argh(switch, short = 'g')]
/// generate a puzzle
generate: bool,
#[argh(switch)]
/// run the game
game: bool,
#[argh(option, short = 'n')]
/// 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,
#[argh(option, short = 's')]
/// the id of the board to solve
solve: Option<u128>,
#[argh(option)]
/// the board to solve in board representation
solve_board: Option<String>,
struct GameSquare {
rect: Rect,
color: Color,
i: usize,
j: usize,
}

View File

@ -1,15 +1,15 @@
use crate::engine::{
board::{Board, GameState},
use crate::board::{
cmove::CMove,
{Board, GameState},
};
pub(crate) struct Solver {
pub(crate) board: Board,
pub struct Solver {
pub board: Board,
moves: Vec<CMove>,
}
impl Solver {
pub(crate) fn new(board: Board) -> Solver {
pub fn new(board: Board) -> Solver {
Solver {
board,
moves: vec![],
@ -24,7 +24,7 @@ impl Solver {
Solver { board, moves }
}
pub(crate) fn solve(&self) -> Vec<Vec<CMove>> {
pub fn solve(&self) -> Vec<Vec<CMove>> {
let mut solutions = Vec::new();
if let GameState::Won = self.board.game_state {
solutions.push(self.moves.clone());
@ -36,7 +36,7 @@ impl Solver {
};
self.board.legal_moves.iter().for_each(|m| {
let mut solver = self.clone(m.clone());
let solver = self.clone(m.clone());
let more_solutions = solver.solve();
solutions.extend(more_solutions);
});
@ -47,12 +47,14 @@ impl Solver {
#[cfg(test)]
mod tests {
use crate::engine::{
piece::{p, Piece},
square::{sq, Square},
};
use super::*;
use crate::board::{square::Square, Board};
macro_rules! sq {
($sq:literal) => {
Square::parse($sq)
};
}
#[test]
fn solver_smoke() {

View File

@ -1 +0,0 @@
pub(crate) mod solver;

View File

@ -0,0 +1,26 @@
*********** Game 1 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 328
Total pieces placed: 3363
Success pieces placed: 2296
Total time (ms): 1840
♗ ♗ ♙ ♘
. . . ♖
. . ♙ .
. . . ♙
id: 140771860875974
Found 1 solutions
1. BxPc2
2. BxPd1
3. RxBd1
4. RxNd4
5. RxPc4
6. RxBb4

View File

@ -0,0 +1,26 @@
*********** Game 10 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 43
Total pieces placed: 407
Success pieces placed: 301
Total time (ms): 238
♙ . ♖ .
. . ♘ ♙
. ♙ ♘ .
♗ . . .
id: 211381923512704
Found 4 solutions
1. BxPb2
2. NxPa4
3. RxNc2
4. RxBb2
5. NxRb2
6. NxPd3

View File

@ -0,0 +1,26 @@
*********** Game 2 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 47
Total pieces placed: 473
Success pieces placed: 329
Total time (ms): 279
. . . ♗
♘ . ♗ .
♙ . . .
. ♘ ♖ ♗
id: 25288852387844
Found 4 solutions
1. RxBd1
2. BxBc3
3. RxNb1
4. NxRb1
5. NxBc3
6. NxPa2

View File

@ -0,0 +1,26 @@
*********** Game 3 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 22
Total pieces placed: 239
Success pieces placed: 154
Total time (ms): 160
♗ . ♖ ♙
. . . ♙
. . . ♙
. ♙ . ♗
id: 140737595313588
Found 5 solutions
1. RxBa4
2. RxPd4
3. RxPd3
4. RxPd2
5. RxBd1
6. RxPb1

View File

@ -0,0 +1,26 @@
*********** Game 4 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 200
Total pieces placed: 2059
Success pieces placed: 1388
Total time (ms): 929
♙ ♘ . ♗
. ♖ ♙ ♘
. . . .
. . ♙ .
id: 211152405031232
Found 1 solutions
1. RxNb4
2. RxPa4
3. RxBd4
4. RxNd3
5. RxPc3
6. RxPc1

View File

@ -0,0 +1,26 @@
*********** Game 5 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 74
Total pieces placed: 771
Success pieces placed: 512
Total time (ms): 437
♗ ♙ . ♘
. ♖ . ♘
. . . .
. ♗ ♗ .
id: 140792316316480
Found 4 solutions
1. BxRb3
2. BxNd3
3. NxBb3
4. NxBc1
5. NxBd3
6. NxPb4

View File

@ -0,0 +1,26 @@
*********** Game 6 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 1
Total pieces placed: 9
Success pieces placed: 7
Total time (ms): 0
. ♙ . .
. . . .
♗ . ♗ ♗
♖ ♙ . ♘
id: 2456822087717
Found 5 solutions
1. RxBa2
2. RxBc2
3. RxBd2
4. RxNd1
5. RxPb1
6. RxPb4

View File

@ -0,0 +1,26 @@
*********** Game 7 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 22
Total pieces placed: 230
Success pieces placed: 154
Total time (ms): 109
♖ . . ♙
. . . ♗
♗ ♙ ♘ .
. . ♖ .
id: 107752945007872
Found 2 solutions
1. RxNc2
2. RxPb2
3. RxBa2
4. RxRa4
5. RxPd4
6. RxBd3

View File

@ -0,0 +1,26 @@
*********** Game 8 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 63
Total pieces placed: 639
Success pieces placed: 441
Total time (ms): 345
. . ♖ .
. ♙ . .
♙ . ♗ .
♗ ♙ . ♗
id: 3579962327044
Found 5 solutions
1. BxBc2
2. RxBc2
3. RxPa2
4. RxBa1
5. RxPb1
6. RxPb3

View File

@ -0,0 +1,26 @@
*********** Game 9 ************
Generating a puzzle with 7 pieces with a maximum of 5 solutions
Total attempts: 250
Total pieces placed: 2557
Success pieces placed: 1750
Total time (ms): 1298
. . . .
♘ ♘ . ♙
. ♙ . ♗
♙ . ♘ .
id: 22408723452320
Found 1 solutions
1. BxNc1
2. NxBc1
3. NxPd3
4. NxPb2
5. axNb2
6. bxNa3

View File

@ -0,0 +1,9 @@
if [ ! -d games ]; then
mkdir games
fi
for i in {1..10}; do
echo "*********** Game $i ************" >> games/$i.txt
echo "" >> games/$i.txt
sol_cli -g -n 7 --print >> games/$i.txt
done