remove recursion in solver
This commit is contained in:
@@ -15,15 +15,15 @@ mod texture;
|
||||
|
||||
use button::Button;
|
||||
use macroquad::prelude::*;
|
||||
use sol_chess::board::{Board, BoardState};
|
||||
use sol_chess::{board::{Board, BoardState}, generator::Puzzle};
|
||||
use sound::Sounds;
|
||||
|
||||
pub struct 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
|
||||
board: Board,
|
||||
current_board: Board,
|
||||
|
||||
// Constants througout the game
|
||||
texture_res: Texture2D,
|
||||
|
||||
@@ -249,7 +249,7 @@ impl Game {
|
||||
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 dtp = PieceTexture::for_piece(*p, sprite_size);
|
||||
if !square.is_source {
|
||||
@@ -267,7 +267,7 @@ impl Game {
|
||||
});
|
||||
|
||||
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);
|
||||
draw_texture_ex(
|
||||
&self.texture_res,
|
||||
@@ -306,7 +306,7 @@ impl Game {
|
||||
return false;
|
||||
});
|
||||
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 {
|
||||
debug_lines.push(format!("Hover: [ {}, {} ]", hover_square.i, hover_square.j));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
use super::{
|
||||
constants, sound::Sounds, Board, BoardState, ButtonAction, Game, GameMode, GameSquare,
|
||||
GameState,
|
||||
constants, sound::Sounds, BoardState, ButtonAction, Game, GameMode, GameSquare, GameState,
|
||||
};
|
||||
|
||||
use macroquad::prelude::*;
|
||||
use sol_chess::{
|
||||
board,
|
||||
generator::{self, RandomRange},
|
||||
generator::{self, Puzzle, RandomRange},
|
||||
};
|
||||
|
||||
impl Game {
|
||||
@@ -139,7 +138,7 @@ impl Game {
|
||||
let mut selected = None;
|
||||
for square in &mut self.squares {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -148,7 +147,7 @@ impl Game {
|
||||
if let Some((i, j)) = selected {
|
||||
self.get(i, j).is_source = true;
|
||||
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 {
|
||||
target_squares.push((m.to.file, m.to.rank));
|
||||
}
|
||||
@@ -179,7 +178,7 @@ impl Game {
|
||||
let mut selected = None;
|
||||
for square in &mut self.squares {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -202,17 +201,18 @@ impl Game {
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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();
|
||||
if self.board.game_state == BoardState::Won {
|
||||
if self.current_board.game_state == BoardState::Won {
|
||||
let next_btn = self
|
||||
.gp_btns
|
||||
.get_mut(&ButtonAction::Next)
|
||||
@@ -237,7 +237,7 @@ impl Game {
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.board = self.original_board.clone();
|
||||
self.current_board = self.puzzle.board.clone();
|
||||
self.reset_squares();
|
||||
|
||||
let next_button = self
|
||||
@@ -251,9 +251,9 @@ impl Game {
|
||||
|
||||
fn next_puzzle(&mut self) {
|
||||
self.reset();
|
||||
let board = Game::generate_puzzle(self.game_mode);
|
||||
self.original_board = board.clone();
|
||||
self.board = board;
|
||||
let puzzle = Game::generate_puzzle(self.game_mode);
|
||||
self.current_board = puzzle.board.clone();
|
||||
self.puzzle = puzzle;
|
||||
}
|
||||
|
||||
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 {
|
||||
GameMode::Easy => 3,
|
||||
GameMode::Medium => 5,
|
||||
GameMode::Hard => 7,
|
||||
};
|
||||
|
||||
let generate = generator::generate(piece_count, 100, &MacroquadRandAdapter);
|
||||
generate.board().expect("No puzzle was generated")
|
||||
let generated = generator::generate(piece_count, 100, &MacroquadRandAdapter);
|
||||
let puzzle = generated.puzzle();
|
||||
puzzle.expect("No puzzle was generated")
|
||||
}
|
||||
|
||||
pub fn new_game(texture_res: Texture2D, sounds: Sounds, font: Font) -> Self {
|
||||
let num_squares: usize = board::constants::BOARD_SIZE;
|
||||
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 {
|
||||
original_board: board.clone(),
|
||||
board,
|
||||
puzzle,
|
||||
current_board,
|
||||
board_rect: Rect::new(0., 0., 0., 0.),
|
||||
squares: Vec::new(),
|
||||
heading_rect: Rect::new(0., 0., 0., 0.),
|
||||
|
||||
+33
-27
@@ -1,8 +1,8 @@
|
||||
use argh::FromArgs;
|
||||
|
||||
use sol_chess::board::cmove::CMove;
|
||||
use sol_chess::board::Board;
|
||||
use sol_chess::generator::{self, RandomRange};
|
||||
use sol_chess::solver::Solver;
|
||||
use sol_chess::generator::{self, Puzzle, RandomRange};
|
||||
|
||||
// Learn how to specify a different dependency for this binary
|
||||
struct MacroquadRngTodo;
|
||||
@@ -14,37 +14,40 @@ impl RandomRange for MacroquadRngTodo {
|
||||
|
||||
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 {
|
||||
let Some(puzzle) = puzzle else {
|
||||
println!("Failed to generate a puzzle, try adjusting the generation parameters");
|
||||
return;
|
||||
};
|
||||
|
||||
board.pretty_print();
|
||||
if args.print {
|
||||
solve_puzzle(board);
|
||||
print_solutions(puzzle);
|
||||
}
|
||||
} 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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
let solutions = Solver::new(board).solve();
|
||||
fn print_solutions(puzzle: Puzzle) {
|
||||
puzzle.board.pretty_print();
|
||||
let solutions = puzzle.solutions;
|
||||
if solutions.len() == 0 {
|
||||
println!("No solutions found");
|
||||
return;
|
||||
@@ -55,10 +58,13 @@ fn solve_puzzle(board: Board) {
|
||||
idx += 1;
|
||||
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);
|
||||
if 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);
|
||||
gen.print_stats();
|
||||
|
||||
let Some(board) = gen.board() else {
|
||||
let Some(puzzle) = gen.puzzle() else {
|
||||
println!("Failed to generate a puzzle, try again");
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(board)
|
||||
Some(puzzle)
|
||||
}
|
||||
|
||||
/// Solitaire Chess puzzle generator and solver
|
||||
|
||||
+104
-3
@@ -17,7 +17,7 @@ use errors::SError;
|
||||
use piece::Piece;
|
||||
use square::{Square, SquarePair};
|
||||
|
||||
use crate::util;
|
||||
use crate::generator::Puzzle;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Board {
|
||||
@@ -54,7 +54,7 @@ impl Board {
|
||||
let mut board_id_bytes = [0; 8];
|
||||
board_id_bytes.copy_from_slice(board_id.as_bytes());
|
||||
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];
|
||||
working_bytes[2..].copy_from_slice(&working_bytes_slice);
|
||||
@@ -155,6 +155,68 @@ impl Board {
|
||||
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 {
|
||||
let mut res: u64 = 0;
|
||||
|
||||
@@ -168,7 +230,7 @@ impl Board {
|
||||
|
||||
let mut id_bytes = [0; 6];
|
||||
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 {
|
||||
@@ -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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+40
-19
@@ -1,9 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
board::{piece::Piece, Board},
|
||||
solver::Solver,
|
||||
};
|
||||
use crate::board::{cmove::CMove, piece::Piece, Board};
|
||||
|
||||
pub trait RandomRange {
|
||||
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 mut overall_stats = GenerateStats::new(0, 0, 0, None);
|
||||
let mut overall_stats = GenerateStats::new(0, 0, 0, None, vec![]);
|
||||
for _ in 0..attempts {
|
||||
let stats = try_generate(num_pieces, num_solutions, rand, candidate_pieces.clone());
|
||||
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
|
||||
}
|
||||
|
||||
pub struct Puzzle {
|
||||
pub board: Board,
|
||||
pub solutions: Vec<Vec<CMove>>,
|
||||
pub solved: bool,
|
||||
}
|
||||
|
||||
pub struct GenerateStats {
|
||||
piece_total: u32,
|
||||
piece_success: u32,
|
||||
total: u32,
|
||||
board: Option<Board>,
|
||||
solutions: Vec<Vec<CMove>>,
|
||||
}
|
||||
|
||||
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 {
|
||||
piece_total,
|
||||
piece_success,
|
||||
total,
|
||||
board,
|
||||
solutions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +87,18 @@ impl GenerateStats {
|
||||
println!("{}", stats);
|
||||
}
|
||||
|
||||
pub fn board(self) -> Option<Board> {
|
||||
self.board
|
||||
pub fn puzzle(self) -> Option<Puzzle> {
|
||||
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;
|
||||
while !placed {
|
||||
if attempts == 0 {
|
||||
return GenerateStats::new(piece_total, piece_success, 1, None);
|
||||
return GenerateStats::new(piece_total, piece_success, 1, None, vec![]);
|
||||
}
|
||||
|
||||
attempts -= 1;
|
||||
@@ -115,8 +136,8 @@ fn try_generate(
|
||||
let mut random_square = empty_squares[square_index].clone();
|
||||
random_square.piece = Some(piece);
|
||||
board.set(random_square.clone());
|
||||
let solutions = Solver::new(board.clone()).solve();
|
||||
if solutions.len() > 0 {
|
||||
let puzzle = board.solve();
|
||||
if puzzle.solutions.len() > 0 {
|
||||
placed = true;
|
||||
piece_success += 1;
|
||||
candidate_pieces.remove(index);
|
||||
@@ -128,17 +149,17 @@ fn try_generate(
|
||||
}
|
||||
}
|
||||
|
||||
let solutions = Solver::new(board.clone()).solve();
|
||||
if solutions.len() > num_solutions as usize {
|
||||
GenerateStats::new(piece_total, piece_success, 1, None)
|
||||
let puzzle = board.solve();
|
||||
if puzzle.solutions.len() > num_solutions as usize {
|
||||
GenerateStats::new(piece_total, piece_success, 1, None, vec![])
|
||||
} else {
|
||||
GenerateStats::new(piece_total, piece_success, 1, Some(board))
|
||||
GenerateStats::new(piece_total, piece_success, 1, Some(puzzle.board), puzzle.solutions)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{board::BoardState, solver::Solver};
|
||||
use crate::board::BoardState;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -158,9 +179,9 @@ mod tests {
|
||||
let board = gen_stats.board.expect("No puzzle was generated");
|
||||
assert_eq!(board.game_state, BoardState::InProgress);
|
||||
|
||||
let solutions = Solver::new(board).solve();
|
||||
assert!(solutions.len() <= 5);
|
||||
assert!(solutions.len() >= 1);
|
||||
let puzzle = board.solve();
|
||||
assert!(puzzle.solutions.len() <= 5);
|
||||
assert!(puzzle.solutions.len() >= 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod board;
|
||||
pub mod generator;
|
||||
pub mod solver;
|
||||
mod util;
|
||||
|
||||
+62
-109
@@ -1,109 +1,62 @@
|
||||
use crate::board::{
|
||||
cmove::CMove,
|
||||
{Board, BoardState},
|
||||
};
|
||||
|
||||
pub struct Solver {
|
||||
pub board: Board,
|
||||
moves: Vec<CMove>,
|
||||
}
|
||||
|
||||
impl Solver {
|
||||
pub fn new(board: Board) -> Solver {
|
||||
Solver {
|
||||
board,
|
||||
moves: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn clone(&self, m: CMove) -> Self {
|
||||
let mut moves = self.moves.clone();
|
||||
let mut board = self.board.clone();
|
||||
moves.push(m.clone());
|
||||
board.make_move(m);
|
||||
Solver { board, moves }
|
||||
}
|
||||
|
||||
pub fn solve(&self) -> Vec<Vec<CMove>> {
|
||||
let mut solutions = Vec::new();
|
||||
if let BoardState::Won = self.board.game_state {
|
||||
solutions.push(self.moves.clone());
|
||||
return solutions;
|
||||
}
|
||||
|
||||
let BoardState::InProgress = self.board.game_state else {
|
||||
return solutions;
|
||||
};
|
||||
|
||||
self.board.legal_moves.iter().for_each(|m| {
|
||||
let solver = self.clone(m.clone());
|
||||
let more_solutions = solver.solve();
|
||||
solutions.extend(more_solutions);
|
||||
});
|
||||
|
||||
solutions
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::board::{square::Square, Board};
|
||||
|
||||
macro_rules! sq {
|
||||
($sq:literal) => {
|
||||
Square::parse($sq)
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solver_smoke() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::board::{square::Square, Board};
|
||||
//
|
||||
// macro_rules! sq {
|
||||
// ($sq:literal) => {
|
||||
// Square::parse($sq)
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn solver_smoke() {
|
||||
// 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