remove recursion in solver
This commit is contained in:
@@ -15,15 +15,15 @@ mod texture;
|
|||||||
|
|
||||||
use button::Button;
|
use button::Button;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
use sol_chess::board::{Board, BoardState};
|
use sol_chess::{board::{Board, BoardState}, generator::Puzzle};
|
||||||
use sound::Sounds;
|
use sound::Sounds;
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
// The generated puzzle. We keep a copy of this to reset the game.
|
// The generated puzzle. We keep a copy of this to reset the game.
|
||||||
original_board: Board,
|
puzzle: Puzzle,
|
||||||
|
|
||||||
// What is shown to the user
|
// What is shown to the user
|
||||||
board: Board,
|
current_board: Board,
|
||||||
|
|
||||||
// Constants througout the game
|
// Constants througout the game
|
||||||
texture_res: Texture2D,
|
texture_res: Texture2D,
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ impl Game {
|
|||||||
color,
|
color,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(p) = &self.board.cells[square.i][square.j] {
|
if let Some(p) = &self.current_board.cells[square.i][square.j] {
|
||||||
let offset = (square.rect.w - sprite_size) / 2.0;
|
let offset = (square.rect.w - sprite_size) / 2.0;
|
||||||
let dtp = PieceTexture::for_piece(*p, sprite_size);
|
let dtp = PieceTexture::for_piece(*p, sprite_size);
|
||||||
if !square.is_source {
|
if !square.is_source {
|
||||||
@@ -267,7 +267,7 @@ impl Game {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(selected_square) = selected_square {
|
if let Some(selected_square) = selected_square {
|
||||||
if let Some(p) = self.board.cells[selected_square.i][selected_square.j] {
|
if let Some(p) = self.current_board.cells[selected_square.i][selected_square.j] {
|
||||||
let dtp = PieceTexture::for_piece(p, sprite_size);
|
let dtp = PieceTexture::for_piece(p, sprite_size);
|
||||||
draw_texture_ex(
|
draw_texture_ex(
|
||||||
&self.texture_res,
|
&self.texture_res,
|
||||||
@@ -306,7 +306,7 @@ impl Game {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
debug_lines.push(format!("Game State: {}", self.state));
|
debug_lines.push(format!("Game State: {}", self.state));
|
||||||
debug_lines.push(format!("Board State: {}", self.board.game_state));
|
debug_lines.push(format!("Board State: {}", self.current_board.game_state));
|
||||||
if let Some(hover_square) = hover_square {
|
if let Some(hover_square) = hover_square {
|
||||||
debug_lines.push(format!("Hover: [ {}, {} ]", hover_square.i, hover_square.j));
|
debug_lines.push(format!("Hover: [ {}, {} ]", hover_square.i, hover_square.j));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
constants, sound::Sounds, Board, BoardState, ButtonAction, Game, GameMode, GameSquare,
|
constants, sound::Sounds, BoardState, ButtonAction, Game, GameMode, GameSquare, GameState,
|
||||||
GameState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
use sol_chess::{
|
use sol_chess::{
|
||||||
board,
|
board,
|
||||||
generator::{self, RandomRange},
|
generator::{self, Puzzle, RandomRange},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@@ -139,7 +138,7 @@ impl Game {
|
|||||||
let mut selected = None;
|
let mut selected = None;
|
||||||
for square in &mut self.squares {
|
for square in &mut self.squares {
|
||||||
if mouse.overlaps_rect(&square.rect) {
|
if mouse.overlaps_rect(&square.rect) {
|
||||||
if let Some(_) = self.board.cells[square.i][square.j] {
|
if let Some(_) = self.current_board.cells[square.i][square.j] {
|
||||||
selected = Some((square.i, square.j));
|
selected = Some((square.i, square.j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ impl Game {
|
|||||||
if let Some((i, j)) = selected {
|
if let Some((i, j)) = selected {
|
||||||
self.get(i, j).is_source = true;
|
self.get(i, j).is_source = true;
|
||||||
let mut target_squares = vec![];
|
let mut target_squares = vec![];
|
||||||
for m in self.board.legal_moves.iter() {
|
for m in self.current_board.legal_moves.iter() {
|
||||||
if m.from.file == i && m.from.rank == j {
|
if m.from.file == i && m.from.rank == j {
|
||||||
target_squares.push((m.to.file, m.to.rank));
|
target_squares.push((m.to.file, m.to.rank));
|
||||||
}
|
}
|
||||||
@@ -179,7 +178,7 @@ impl Game {
|
|||||||
let mut selected = None;
|
let mut selected = None;
|
||||||
for square in &mut self.squares {
|
for square in &mut self.squares {
|
||||||
if mouse.overlaps_rect(&square.rect) {
|
if mouse.overlaps_rect(&square.rect) {
|
||||||
if let Some(_) = self.board.cells[square.i][square.j] {
|
if let Some(_) = self.current_board.cells[square.i][square.j] {
|
||||||
selected = Some((square.i, square.j));
|
selected = Some((square.i, square.j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,17 +201,18 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if is_legal {
|
if is_legal {
|
||||||
let m = self.board.legal_moves.iter().find(|m| {
|
let m = self.current_board.legal_moves.iter().find(|m| {
|
||||||
m.from.file == s_x && m.from.rank == s_y && m.to.file == x && m.to.rank == y
|
m.from.file == s_x && m.from.rank == s_y && m.to.file == x && m.to.rank == y
|
||||||
});
|
});
|
||||||
|
|
||||||
let m = m.expect("legal move should be found");
|
let m = m.expect("legal move should be found");
|
||||||
self.board.make_move(m.clone());
|
self.current_board.make_move(m.clone());
|
||||||
|
|
||||||
if self.board.game_state == BoardState::Won || self.board.game_state == BoardState::Lost
|
if self.current_board.game_state == BoardState::Won
|
||||||
|
|| self.current_board.game_state == BoardState::Lost
|
||||||
{
|
{
|
||||||
self.reset_squares();
|
self.reset_squares();
|
||||||
if self.board.game_state == BoardState::Won {
|
if self.current_board.game_state == BoardState::Won {
|
||||||
let next_btn = self
|
let next_btn = self
|
||||||
.gp_btns
|
.gp_btns
|
||||||
.get_mut(&ButtonAction::Next)
|
.get_mut(&ButtonAction::Next)
|
||||||
@@ -237,7 +237,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.board = self.original_board.clone();
|
self.current_board = self.puzzle.board.clone();
|
||||||
self.reset_squares();
|
self.reset_squares();
|
||||||
|
|
||||||
let next_button = self
|
let next_button = self
|
||||||
@@ -251,9 +251,9 @@ impl Game {
|
|||||||
|
|
||||||
fn next_puzzle(&mut self) {
|
fn next_puzzle(&mut self) {
|
||||||
self.reset();
|
self.reset();
|
||||||
let board = Game::generate_puzzle(self.game_mode);
|
let puzzle = Game::generate_puzzle(self.game_mode);
|
||||||
self.original_board = board.clone();
|
self.current_board = puzzle.board.clone();
|
||||||
self.board = board;
|
self.puzzle = puzzle;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_squares(&mut self) {
|
fn reset_squares(&mut self) {
|
||||||
@@ -265,25 +265,27 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_puzzle(mode: GameMode) -> Board {
|
fn generate_puzzle(mode: GameMode) -> Puzzle {
|
||||||
let piece_count = match mode {
|
let piece_count = match mode {
|
||||||
GameMode::Easy => 3,
|
GameMode::Easy => 3,
|
||||||
GameMode::Medium => 5,
|
GameMode::Medium => 5,
|
||||||
GameMode::Hard => 7,
|
GameMode::Hard => 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
let generate = generator::generate(piece_count, 100, &MacroquadRandAdapter);
|
let generated = generator::generate(piece_count, 100, &MacroquadRandAdapter);
|
||||||
generate.board().expect("No puzzle was generated")
|
let puzzle = generated.puzzle();
|
||||||
|
puzzle.expect("No puzzle was generated")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_game(texture_res: Texture2D, sounds: Sounds, font: Font) -> Self {
|
pub fn new_game(texture_res: Texture2D, sounds: Sounds, font: Font) -> Self {
|
||||||
let num_squares: usize = board::constants::BOARD_SIZE;
|
let num_squares: usize = board::constants::BOARD_SIZE;
|
||||||
let game_mode = GameMode::Medium;
|
let game_mode = GameMode::Medium;
|
||||||
let board = Game::generate_puzzle(game_mode);
|
let puzzle = Game::generate_puzzle(game_mode);
|
||||||
|
let current_board = puzzle.board.clone();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
original_board: board.clone(),
|
puzzle,
|
||||||
board,
|
current_board,
|
||||||
board_rect: Rect::new(0., 0., 0., 0.),
|
board_rect: Rect::new(0., 0., 0., 0.),
|
||||||
squares: Vec::new(),
|
squares: Vec::new(),
|
||||||
heading_rect: Rect::new(0., 0., 0., 0.),
|
heading_rect: Rect::new(0., 0., 0., 0.),
|
||||||
|
|||||||
+33
-27
@@ -1,8 +1,8 @@
|
|||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
|
|
||||||
|
use sol_chess::board::cmove::CMove;
|
||||||
use sol_chess::board::Board;
|
use sol_chess::board::Board;
|
||||||
use sol_chess::generator::{self, RandomRange};
|
use sol_chess::generator::{self, Puzzle, RandomRange};
|
||||||
use sol_chess::solver::Solver;
|
|
||||||
|
|
||||||
// Learn how to specify a different dependency for this binary
|
// Learn how to specify a different dependency for this binary
|
||||||
struct MacroquadRngTodo;
|
struct MacroquadRngTodo;
|
||||||
@@ -14,37 +14,40 @@ impl RandomRange for MacroquadRngTodo {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Args = argh::from_env();
|
let args: Args = argh::from_env();
|
||||||
|
|
||||||
if args.generate {
|
if args.generate {
|
||||||
let puzzle = generate_puzzle(args.num_pieces, args.solutions);
|
let puzzle = generate_puzzle(args.num_pieces, args.solutions);
|
||||||
let Some(board) = puzzle else {
|
let Some(puzzle) = puzzle else {
|
||||||
|
println!("Failed to generate a puzzle, try adjusting the generation parameters");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
board.pretty_print();
|
|
||||||
if args.print {
|
if args.print {
|
||||||
solve_puzzle(board);
|
print_solutions(puzzle);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let board = if let Some(board_string) = args.solve_board {
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
let puzzle = board.solve();
|
||||||
|
print_solutions(puzzle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve_puzzle(board: Board) {
|
fn print_solutions(puzzle: Puzzle) {
|
||||||
let solutions = Solver::new(board).solve();
|
puzzle.board.pretty_print();
|
||||||
|
let solutions = puzzle.solutions;
|
||||||
if solutions.len() == 0 {
|
if solutions.len() == 0 {
|
||||||
println!("No solutions found");
|
println!("No solutions found");
|
||||||
return;
|
return;
|
||||||
@@ -55,10 +58,13 @@ fn solve_puzzle(board: Board) {
|
|||||||
idx += 1;
|
idx += 1;
|
||||||
println!("{}. {}", idx, m.notation());
|
println!("{}. {}", idx, m.notation());
|
||||||
});
|
});
|
||||||
println!("There are atleast {} solutions to this puzzle", solutions.len());
|
println!(
|
||||||
|
"There are atleast {} solutions to this puzzle",
|
||||||
|
solutions.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_puzzle(num_pieces: Option<u32>, num_solutions: Option<u32>) -> Option<Board> {
|
fn generate_puzzle(num_pieces: Option<u32>, num_solutions: Option<u32>) -> Option<Puzzle> {
|
||||||
let mut num_pieces = num_pieces.unwrap_or(5);
|
let mut num_pieces = num_pieces.unwrap_or(5);
|
||||||
if num_pieces < 2 {
|
if num_pieces < 2 {
|
||||||
num_pieces = 2;
|
num_pieces = 2;
|
||||||
@@ -76,12 +82,12 @@ fn generate_puzzle(num_pieces: Option<u32>, num_solutions: Option<u32>) -> Optio
|
|||||||
let gen = generator::generate(num_pieces, num_solutions, &MacroquadRngTodo);
|
let gen = generator::generate(num_pieces, num_solutions, &MacroquadRngTodo);
|
||||||
gen.print_stats();
|
gen.print_stats();
|
||||||
|
|
||||||
let Some(board) = gen.board() else {
|
let Some(puzzle) = gen.puzzle() else {
|
||||||
println!("Failed to generate a puzzle, try again");
|
println!("Failed to generate a puzzle, try again");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(board)
|
Some(puzzle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Solitaire Chess puzzle generator and solver
|
/// Solitaire Chess puzzle generator and solver
|
||||||
|
|||||||
+104
-3
@@ -17,7 +17,7 @@ use errors::SError;
|
|||||||
use piece::Piece;
|
use piece::Piece;
|
||||||
use square::{Square, SquarePair};
|
use square::{Square, SquarePair};
|
||||||
|
|
||||||
use crate::util;
|
use crate::generator::Puzzle;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
@@ -54,7 +54,7 @@ impl Board {
|
|||||||
let mut board_id_bytes = [0; 8];
|
let mut board_id_bytes = [0; 8];
|
||||||
board_id_bytes.copy_from_slice(board_id.as_bytes());
|
board_id_bytes.copy_from_slice(board_id.as_bytes());
|
||||||
let mut working_bytes_slice = [0; 6];
|
let mut working_bytes_slice = [0; 6];
|
||||||
util::b64_decode_48(&board_id_bytes, &mut working_bytes_slice);
|
b64_decode_exact_48(&board_id_bytes, &mut working_bytes_slice);
|
||||||
|
|
||||||
let mut working_bytes = [0; 8];
|
let mut working_bytes = [0; 8];
|
||||||
working_bytes[2..].copy_from_slice(&working_bytes_slice);
|
working_bytes[2..].copy_from_slice(&working_bytes_slice);
|
||||||
@@ -155,6 +155,68 @@ impl Board {
|
|||||||
println!("{:^40}\n", format!("id: {}", self.id));
|
println!("{:^40}\n", format!("id: {}", self.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn solve(&self) -> Puzzle {
|
||||||
|
struct StackItem {
|
||||||
|
board: Board,
|
||||||
|
moves_so_far: Vec<CMove>,
|
||||||
|
next_move: CMove,
|
||||||
|
}
|
||||||
|
|
||||||
|
if let BoardState::Won = self.game_state {
|
||||||
|
return Puzzle {
|
||||||
|
board: self.clone(),
|
||||||
|
solutions: vec![vec![]],
|
||||||
|
solved: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
for mv in &self.legal_moves {
|
||||||
|
let item = StackItem {
|
||||||
|
board: self.clone(),
|
||||||
|
moves_so_far: vec![],
|
||||||
|
next_move: mv.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
stack.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut solutions = Vec::new();
|
||||||
|
loop {
|
||||||
|
let top = stack.pop();
|
||||||
|
let Some(top) = top else {
|
||||||
|
let solved = solutions.len() > 0;
|
||||||
|
return Puzzle {
|
||||||
|
board: self.clone(),
|
||||||
|
solutions,
|
||||||
|
solved,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut board, mut moves_so_far, next) = (top.board, top.moves_so_far, top.next_move);
|
||||||
|
board.make_move(next.clone());
|
||||||
|
match board.game_state {
|
||||||
|
BoardState::Won => {
|
||||||
|
moves_so_far.push(next);
|
||||||
|
solutions.push(moves_so_far);
|
||||||
|
}
|
||||||
|
BoardState::InProgress => {
|
||||||
|
moves_so_far.push(next);
|
||||||
|
for mv in &board.legal_moves {
|
||||||
|
let item = StackItem {
|
||||||
|
board: board.clone(),
|
||||||
|
moves_so_far: moves_so_far.clone(),
|
||||||
|
next_move: mv.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
stack.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn encode(cells: [[Option<Piece>; BOARD_SIZE]; BOARD_SIZE]) -> String {
|
fn encode(cells: [[Option<Piece>; BOARD_SIZE]; BOARD_SIZE]) -> String {
|
||||||
let mut res: u64 = 0;
|
let mut res: u64 = 0;
|
||||||
|
|
||||||
@@ -168,7 +230,7 @@ impl Board {
|
|||||||
|
|
||||||
let mut id_bytes = [0; 6];
|
let mut id_bytes = [0; 6];
|
||||||
id_bytes.copy_from_slice(&res.to_be_bytes()[2..]);
|
id_bytes.copy_from_slice(&res.to_be_bytes()[2..]);
|
||||||
util::b64_encode_48(&id_bytes)
|
b64_encode_exact_48(&id_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print(&self, pretty: bool) -> String {
|
fn print(&self, pretty: bool) -> String {
|
||||||
@@ -391,6 +453,45 @@ impl Display for BoardState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn b64_encode_exact_48(input: &[u8; 6]) -> String {
|
||||||
|
let mut output = [0 as char; 8];
|
||||||
|
for (byte_chunk, output_slice) in input.chunks_exact(3).zip(output.chunks_exact_mut(4)) {
|
||||||
|
let byte1 = byte_chunk[0];
|
||||||
|
let byte2 = byte_chunk[1];
|
||||||
|
let byte3 = byte_chunk[2];
|
||||||
|
|
||||||
|
output_slice[0] = lookup((byte1 & 0b1111_1100) >> 2);
|
||||||
|
output_slice[1] = lookup((byte1 & 0b0000_0011) << 4 | (byte2 & 0b1111_0000) >> 4);
|
||||||
|
output_slice[2] = lookup((byte2 & 0b0000_1111) << 2 | (byte3 & 0b1100_0000) >> 6);
|
||||||
|
output_slice[3] = lookup(byte3 & 0b0011_1111);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b64_decode_exact_48(input: &[u8; 8], output: &mut [u8; 6]) {
|
||||||
|
for (char_chunk, output_slice) in input.chunks_exact(4).zip(output.chunks_exact_mut(3)) {
|
||||||
|
let char_1 = reverse_lookup(char_chunk[0] as char);
|
||||||
|
let char_2 = reverse_lookup(char_chunk[1] as char);
|
||||||
|
let char_3 = reverse_lookup(char_chunk[2] as char);
|
||||||
|
let char_4 = reverse_lookup(char_chunk[3] as char);
|
||||||
|
|
||||||
|
output_slice[0] = (char_1 << 2) | (char_2 >> 4);
|
||||||
|
output_slice[1] = (char_2 << 4) | (char_3 >> 2);
|
||||||
|
output_slice[2] = (char_3 << 6) | char_4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALPHABET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||||
|
|
||||||
|
fn lookup(idx: u8) -> char {
|
||||||
|
ALPHABET.chars().nth(idx as usize).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reverse_lookup(c: char) -> u8 {
|
||||||
|
ALPHABET.chars().position(|x| x == c).unwrap() as u8
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+40
-19
@@ -1,9 +1,6 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::{
|
use crate::board::{cmove::CMove, piece::Piece, Board};
|
||||||
board::{piece::Piece, Board},
|
|
||||||
solver::Solver,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait RandomRange {
|
pub trait RandomRange {
|
||||||
fn gen_range(&self, min: usize, max: usize) -> usize;
|
fn gen_range(&self, min: usize, max: usize) -> usize;
|
||||||
@@ -35,7 +32,7 @@ pub fn generate(num_pieces: u32, num_solutions: u32, rand: &impl RandomRange) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
let attempts: u32 = 1000;
|
let attempts: u32 = 1000;
|
||||||
let mut overall_stats = GenerateStats::new(0, 0, 0, None);
|
let mut overall_stats = GenerateStats::new(0, 0, 0, None, vec![]);
|
||||||
for _ in 0..attempts {
|
for _ in 0..attempts {
|
||||||
let stats = try_generate(num_pieces, num_solutions, rand, candidate_pieces.clone());
|
let stats = try_generate(num_pieces, num_solutions, rand, candidate_pieces.clone());
|
||||||
overall_stats.piece_total += stats.piece_total;
|
overall_stats.piece_total += stats.piece_total;
|
||||||
@@ -50,20 +47,34 @@ pub fn generate(num_pieces: u32, num_solutions: u32, rand: &impl RandomRange) ->
|
|||||||
overall_stats
|
overall_stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Puzzle {
|
||||||
|
pub board: Board,
|
||||||
|
pub solutions: Vec<Vec<CMove>>,
|
||||||
|
pub solved: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GenerateStats {
|
pub struct GenerateStats {
|
||||||
piece_total: u32,
|
piece_total: u32,
|
||||||
piece_success: u32,
|
piece_success: u32,
|
||||||
total: u32,
|
total: u32,
|
||||||
board: Option<Board>,
|
board: Option<Board>,
|
||||||
|
solutions: Vec<Vec<CMove>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateStats {
|
impl GenerateStats {
|
||||||
fn new(piece_total: u32, piece_success: u32, total: u32, board: Option<Board>) -> Self {
|
fn new(
|
||||||
|
piece_total: u32,
|
||||||
|
piece_success: u32,
|
||||||
|
total: u32,
|
||||||
|
board: Option<Board>,
|
||||||
|
solutions: Vec<Vec<CMove>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
piece_total,
|
piece_total,
|
||||||
piece_success,
|
piece_success,
|
||||||
total,
|
total,
|
||||||
board,
|
board,
|
||||||
|
solutions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +87,18 @@ impl GenerateStats {
|
|||||||
println!("{}", stats);
|
println!("{}", stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn board(self) -> Option<Board> {
|
pub fn puzzle(self) -> Option<Puzzle> {
|
||||||
self.board
|
let Some(board) = self.board else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let solved = self.solutions.len() > 0;
|
||||||
|
|
||||||
|
Some(Puzzle {
|
||||||
|
board,
|
||||||
|
solutions: self.solutions,
|
||||||
|
solved,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +124,7 @@ fn try_generate(
|
|||||||
let mut attempts = 15;
|
let mut attempts = 15;
|
||||||
while !placed {
|
while !placed {
|
||||||
if attempts == 0 {
|
if attempts == 0 {
|
||||||
return GenerateStats::new(piece_total, piece_success, 1, None);
|
return GenerateStats::new(piece_total, piece_success, 1, None, vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
attempts -= 1;
|
attempts -= 1;
|
||||||
@@ -115,8 +136,8 @@ fn try_generate(
|
|||||||
let mut random_square = empty_squares[square_index].clone();
|
let mut random_square = empty_squares[square_index].clone();
|
||||||
random_square.piece = Some(piece);
|
random_square.piece = Some(piece);
|
||||||
board.set(random_square.clone());
|
board.set(random_square.clone());
|
||||||
let solutions = Solver::new(board.clone()).solve();
|
let puzzle = board.solve();
|
||||||
if solutions.len() > 0 {
|
if puzzle.solutions.len() > 0 {
|
||||||
placed = true;
|
placed = true;
|
||||||
piece_success += 1;
|
piece_success += 1;
|
||||||
candidate_pieces.remove(index);
|
candidate_pieces.remove(index);
|
||||||
@@ -128,17 +149,17 @@ fn try_generate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let solutions = Solver::new(board.clone()).solve();
|
let puzzle = board.solve();
|
||||||
if solutions.len() > num_solutions as usize {
|
if puzzle.solutions.len() > num_solutions as usize {
|
||||||
GenerateStats::new(piece_total, piece_success, 1, None)
|
GenerateStats::new(piece_total, piece_success, 1, None, vec![])
|
||||||
} else {
|
} else {
|
||||||
GenerateStats::new(piece_total, piece_success, 1, Some(board))
|
GenerateStats::new(piece_total, piece_success, 1, Some(puzzle.board), puzzle.solutions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{board::BoardState, solver::Solver};
|
use crate::board::BoardState;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -158,9 +179,9 @@ mod tests {
|
|||||||
let board = gen_stats.board.expect("No puzzle was generated");
|
let board = gen_stats.board.expect("No puzzle was generated");
|
||||||
assert_eq!(board.game_state, BoardState::InProgress);
|
assert_eq!(board.game_state, BoardState::InProgress);
|
||||||
|
|
||||||
let solutions = Solver::new(board).solve();
|
let puzzle = board.solve();
|
||||||
assert!(solutions.len() <= 5);
|
assert!(puzzle.solutions.len() <= 5);
|
||||||
assert!(solutions.len() >= 1);
|
assert!(puzzle.solutions.len() >= 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
pub mod solver;
|
pub mod solver;
|
||||||
mod util;
|
|
||||||
|
|||||||
+62
-109
@@ -1,109 +1,62 @@
|
|||||||
use crate::board::{
|
// #[cfg(test)]
|
||||||
cmove::CMove,
|
// mod tests {
|
||||||
{Board, BoardState},
|
// use super::*;
|
||||||
};
|
// use crate::board::{square::Square, Board};
|
||||||
|
//
|
||||||
pub struct Solver {
|
// macro_rules! sq {
|
||||||
pub board: Board,
|
// ($sq:literal) => {
|
||||||
moves: Vec<CMove>,
|
// Square::parse($sq)
|
||||||
}
|
// };
|
||||||
|
// }
|
||||||
impl Solver {
|
//
|
||||||
pub fn new(board: Board) -> Solver {
|
// #[test]
|
||||||
Solver {
|
// fn solver_smoke() {
|
||||||
board,
|
// let mut board = Board::new();
|
||||||
moves: vec![],
|
// // . R . .
|
||||||
}
|
// // R . . P
|
||||||
}
|
// // B . B N
|
||||||
|
// // P . N .
|
||||||
fn clone(&self, m: CMove) -> Self {
|
//
|
||||||
let mut moves = self.moves.clone();
|
// board.set(sq!("Pa1"));
|
||||||
let mut board = self.board.clone();
|
// board.set(sq!("Ba2"));
|
||||||
moves.push(m.clone());
|
// board.set(sq!("Ra3"));
|
||||||
board.make_move(m);
|
// board.set(sq!("Rb4"));
|
||||||
Solver { board, moves }
|
// board.set(sq!("Nc1"));
|
||||||
}
|
// board.set(sq!("Bc2"));
|
||||||
|
// board.set(sq!("Nd2"));
|
||||||
pub fn solve(&self) -> Vec<Vec<CMove>> {
|
// board.set(sq!("Pd3"));
|
||||||
let mut solutions = Vec::new();
|
//
|
||||||
if let BoardState::Won = self.board.game_state {
|
// let solver = Solver::new(board.clone());
|
||||||
solutions.push(self.moves.clone());
|
// let solutions = solver.solve();
|
||||||
return solutions;
|
//
|
||||||
}
|
// for solution in solutions {
|
||||||
|
// let mut board = board.clone();
|
||||||
let BoardState::InProgress = self.board.game_state else {
|
// solution
|
||||||
return solutions;
|
// .into_iter()
|
||||||
};
|
// .for_each(|m| assert!(board.make_move(m).is_some()));
|
||||||
|
// assert_eq!(BoardState::Won, board.game_state);
|
||||||
self.board.legal_moves.iter().for_each(|m| {
|
// }
|
||||||
let solver = self.clone(m.clone());
|
// }
|
||||||
let more_solutions = solver.solve();
|
//
|
||||||
solutions.extend(more_solutions);
|
// #[test]
|
||||||
});
|
// fn solver_smoke_no_solution() {
|
||||||
|
// // . R . .
|
||||||
solutions
|
// // R . . .
|
||||||
}
|
// // B . B N
|
||||||
}
|
// // P . N .
|
||||||
|
//
|
||||||
#[cfg(test)]
|
// let mut board = Board::new();
|
||||||
mod tests {
|
// board.set(sq!("Pa1"));
|
||||||
use super::*;
|
// board.set(sq!("Ba2"));
|
||||||
use crate::board::{square::Square, Board};
|
// board.set(sq!("Ra3"));
|
||||||
|
// board.set(sq!("Rb4"));
|
||||||
macro_rules! sq {
|
// board.set(sq!("Nc1"));
|
||||||
($sq:literal) => {
|
// board.set(sq!("Bc2"));
|
||||||
Square::parse($sq)
|
// board.set(sq!("Nd2"));
|
||||||
};
|
//
|
||||||
}
|
// let solver = Solver::new(board.clone());
|
||||||
|
// let solutions = solver.solve();
|
||||||
#[test]
|
//
|
||||||
fn solver_smoke() {
|
// assert_eq!(0, solutions.len());
|
||||||
let mut board = Board::new();
|
// }
|
||||||
// . R . .
|
// }
|
||||||
// R . . P
|
|
||||||
// B . B N
|
|
||||||
// P . N .
|
|
||||||
|
|
||||||
board.set(sq!("Pa1"));
|
|
||||||
board.set(sq!("Ba2"));
|
|
||||||
board.set(sq!("Ra3"));
|
|
||||||
board.set(sq!("Rb4"));
|
|
||||||
board.set(sq!("Nc1"));
|
|
||||||
board.set(sq!("Bc2"));
|
|
||||||
board.set(sq!("Nd2"));
|
|
||||||
board.set(sq!("Pd3"));
|
|
||||||
|
|
||||||
let solver = Solver::new(board.clone());
|
|
||||||
let solutions = solver.solve();
|
|
||||||
|
|
||||||
for solution in solutions {
|
|
||||||
let mut board = board.clone();
|
|
||||||
solution
|
|
||||||
.into_iter()
|
|
||||||
.for_each(|m| assert!(board.make_move(m).is_some()));
|
|
||||||
assert_eq!(BoardState::Won, board.game_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn solver_smoke_no_solution() {
|
|
||||||
// . R . .
|
|
||||||
// R . . .
|
|
||||||
// B . B N
|
|
||||||
// P . N .
|
|
||||||
|
|
||||||
let mut board = Board::new();
|
|
||||||
board.set(sq!("Pa1"));
|
|
||||||
board.set(sq!("Ba2"));
|
|
||||||
board.set(sq!("Ra3"));
|
|
||||||
board.set(sq!("Rb4"));
|
|
||||||
board.set(sq!("Nc1"));
|
|
||||||
board.set(sq!("Bc2"));
|
|
||||||
board.set(sq!("Nd2"));
|
|
||||||
|
|
||||||
let solver = Solver::new(board.clone());
|
|
||||||
let solutions = solver.solve();
|
|
||||||
|
|
||||||
assert_eq!(0, solutions.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
pub(crate) fn b64_encode_48(input: &[u8; 6]) -> String {
|
|
||||||
let mut output = [0 as char; 8];
|
|
||||||
for (byte_chunk, output_slice) in input.chunks_exact(3).zip(output.chunks_exact_mut(4)) {
|
|
||||||
let byte1 = byte_chunk[0];
|
|
||||||
let byte2 = byte_chunk[1];
|
|
||||||
let byte3 = byte_chunk[2];
|
|
||||||
|
|
||||||
output_slice[0] = lookup((byte1 & 0b1111_1100) >> 2);
|
|
||||||
output_slice[1] = lookup((byte1 & 0b0000_0011) << 4 | (byte2 & 0b1111_0000) >> 4);
|
|
||||||
output_slice[2] = lookup((byte2 & 0b0000_1111) << 2 | (byte3 & 0b1100_0000) >> 6);
|
|
||||||
output_slice[3] = lookup(byte3 & 0b0011_1111);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.iter().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn b64_decode_48(input: &[u8; 8], output: &mut [u8; 6]) {
|
|
||||||
for (char_chunk, output_slice) in input.chunks_exact(4).zip(output.chunks_exact_mut(3)) {
|
|
||||||
let char_1 = reverse_lookup(char_chunk[0] as char);
|
|
||||||
let char_2 = reverse_lookup(char_chunk[1] as char);
|
|
||||||
let char_3 = reverse_lookup(char_chunk[2] as char);
|
|
||||||
let char_4 = reverse_lookup(char_chunk[3] as char);
|
|
||||||
|
|
||||||
output_slice[0] = (char_1 << 2) | (char_2 >> 4);
|
|
||||||
output_slice[1] = (char_2 << 4) | (char_3 >> 2);
|
|
||||||
output_slice[2] = (char_3 << 6) | char_4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ALPHABET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
||||||
|
|
||||||
fn lookup(idx: u8) -> char {
|
|
||||||
ALPHABET.chars().nth(idx as usize).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reverse_lookup(c: char) -> u8 {
|
|
||||||
ALPHABET.chars().position(|x| x == c).unwrap() as u8
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user