From be9585d5c221ed9b7bf0f17ba6bd7244ab1577ca Mon Sep 17 00:00:00 2001 From: cool-mist Date: Sun, 5 Jan 2025 00:23:38 +0530 Subject: [PATCH] Add as command line tool --- Cargo.lock | 59 ++++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 54 ++++++++++++++++++++++++ src/engine/board.rs | 30 +++++++++++++ src/engine/move.rs | 6 ++- src/generator/generator.rs | 3 +- src/main.rs | 86 ++++++++++++++++++++++++++++++++------ 7 files changed, 224 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e7ae11..55b87e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,38 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "argh" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" +dependencies = [ + "argh_derive", + "argh_shared", + "rust-fuzzy-search", +] + +[[package]] +name = "argh_derive" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" +dependencies = [ + "serde", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -88,10 +120,37 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rust-fuzzy-search" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sol_chess" version = "0.1.0" dependencies = [ + "argh", "rand", ] diff --git a/Cargo.toml b/Cargo.toml index 815756c..eb59820 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +argh = "0.1.13" rand = "0.8.5" diff --git a/README.md b/README.md index 439b984..d52ea01 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,60 @@ Goal: Generate 'hard' puzzles. +## Install + +- 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 + +- Generate a puzzle + +```bash +$ sol_chess -g -n 6 +Generated a puzzle with 6 pieces after 330 ms +PP.. +..PB +.K.. +.N.. +``` + +- Solve a puzzle + +```bash +$ sol_chess -- --solve PP....PB.K...N.. +PP.. +..PB +.K.. +.N.. + +Found 1 solutions +1. Nb1 -> c3 +2. Nc3 -> a4 +3. Na4 -> b2 +4. Nb2 -> d3 +5. Nd3 -> b4 +``` + +- Generate and solve a puzzle + +```bash +$ sol_chess -g -n 6 --print +Generated a puzzle with 6 pieces after 933 ms +.P.N +B.R. +.K.. +..N. + +Found 1 solutions +1. Ba3 -> b4 +2. Bb4 -> c3 +3. Bc3 -> d4 +4. Bd4 -> b2 +5. Bb2 -> c1 +``` + ## Heuristics of current algorithm 1. About 6-7 pieces on the board. diff --git a/src/engine/board.rs b/src/engine/board.rs index 97735b2..a7de1bf 100644 --- a/src/engine/board.rs +++ b/src/engine/board.rs @@ -353,6 +353,36 @@ impl Board { fn create_move(start: &Square, target: Square) -> Move { Move::new(start.clone(), target) } + + pub(crate) fn from_string(board_string: String) -> Option { + if board_string.chars().count() != 16 { + return None; + } + + let mut board = Board::new(); + let mut file = 0; + let mut rank = 0; + let mut chars = board_string.chars(); + for r in 0..4 { + for f in 0..4 { + let c = chars.next().unwrap(); + let piece = match c { + 'K' => Piece::King, + 'Q' => Piece::Queen, + 'B' => Piece::Bishop, + 'N' => Piece::Knight, + 'R' => Piece::Rook, + 'P' => Piece::Pawn, + '.' => continue, + _ => return None, + }; + + let square = Square::Occupied(piece, Coord::new(f, r)); + board.set(square); + } + } + Some(board) + } } #[cfg(test)] diff --git a/src/engine/move.rs b/src/engine/move.rs index 73fe287..35e465b 100644 --- a/src/engine/move.rs +++ b/src/engine/move.rs @@ -12,7 +12,11 @@ impl Move { } pub(crate) fn notation(&self) -> String { - format!("{} -> {}", self.from.notation(), self.to.notation()) + format!( + "{} -> {}", + self.from.notation(), + self.to.coord_ref().notation + ) } } diff --git a/src/generator/generator.rs b/src/generator/generator.rs index 62bbabe..e7ca521 100644 --- a/src/generator/generator.rs +++ b/src/generator/generator.rs @@ -4,7 +4,7 @@ use crate::{ }; use rand::{seq::*, Rng}; -pub(crate) fn generate() -> Option { +pub(crate) fn generate(num_pieces: u32) -> Option { let mut rand = rand::thread_rng(); let candidate_pieces = vec![ Piece::Pawn, @@ -17,7 +17,6 @@ pub(crate) fn generate() -> Option { Piece::King, Piece::Queen, ]; - let num_pieces = 7; let attempts = 1000; for i in 0..attempts { let board = try_generate(num_pieces, candidate_pieces.clone(), rand.clone()); diff --git a/src/main.rs b/src/main.rs index 4655118..77cf715 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,26 +7,44 @@ mod solver; #[allow(unused)] mod generator; +use argh::FromArgs; +use engine::board::Board; + use crate::generator::generator::generate; use crate::solver::solver::Solver; fn main() { - let start = std::time::Instant::now(); - let board = generate(); - let elapsed = start.elapsed(); + let args: Args = argh::from_env(); + if args.generate { + let puzzle = generate_puzzle(args.num_pieces); + let Some(board) = puzzle else { + return; + }; - println!("Generated a problem in {} ms", elapsed.as_millis()); + println!("{}", board.print()); - let Some(board) = board else { - println!( - "Failed to generate a board after {} ms, Try again", - elapsed.as_millis() - ); - return; - }; + if args.print { + solve_puzzle(board); + } + } else if let Some(board_string) = args.solve { + let board = Board::from_string(board_string); + let Some(board) = board else { + println!("Invalid board string"); + return; + }; + println!("{}", board.print()); + solve_puzzle(board); + } else { + println!("Use --help to see available options"); + } +} - println!("{}", board.print()); +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; @@ -35,3 +53,47 @@ fn main() { println!("{}. {}", idx, m.notation()); }); } + +fn generate_puzzle(num_pieces: Option) -> Option { + let start = std::time::Instant::now(); + let num_pieces = num_pieces.unwrap_or(5); + let board = generate(num_pieces); + let elapsed = start.elapsed(); + + let Some(board) = board else { + println!( + "Failed to generate a puzzle with {} pieces after {} ms, Try again", + num_pieces, + elapsed.as_millis() + ); + return None; + }; + + println!( + "Generated a puzzle with {} pieces after {} ms", + num_pieces, + elapsed.as_millis() + ); + 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, + + #[argh(switch)] + /// print the solution. When solving a puzzle, this is always set to true + print: bool, + + #[argh(option, short = 's')] + /// the board to solve + solve: Option, +}