Added texture and board
This commit is contained in:
@@ -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>,
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SError {
|
||||
pub enum SError {
|
||||
InvalidBoard,
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod texture;
|
||||
@@ -1,3 +0,0 @@
|
||||
pub(crate) fn run() {
|
||||
println!("Running game...");
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod game;
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod generator;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod board;
|
||||
pub mod generator;
|
||||
pub mod solver;
|
||||
+110
-113
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod solver;
|
||||
Reference in New Issue
Block a user