This commit is contained in:
cool-mist
2026-01-26 20:00:50 +05:30
parent dc61d6d956
commit 280150b0b0
18 changed files with 779 additions and 325 deletions
+2 -1
View File
@@ -72,7 +72,8 @@ fn generate_puzzle(num_pieces: Option<u32>, num_solutions: Option<u32>) -> Optio
"Generating a puzzle with {} pieces with a maximum of {} solutions", "Generating a puzzle with {} pieces with a maximum of {} solutions",
num_pieces, num_solutions num_pieces, num_solutions
); );
let gen_result = generator::generate_weighted_random(num_pieces, num_solutions, &RandRngImpl); let gen_result =
generator::generate_weighted_random(num_pieces, num_solutions, num_pieces, &RandRngImpl);
gen_result.print_stats(); gen_result.print_stats();
let Some(puzzle) = gen_result.puzzle() else { let Some(puzzle) = gen_result.puzzle() else {
+17 -8
View File
@@ -2,6 +2,8 @@ pub mod constants;
mod draw; mod draw;
mod logic; mod logic;
use std::collections::HashMap;
use crate::{resources::Resources, widgets::*}; use crate::{resources::Resources, widgets::*};
use macroquad::prelude::*; use macroquad::prelude::*;
use sol_lib::generator::Puzzle; use sol_lib::generator::Puzzle;
@@ -15,30 +17,36 @@ pub struct Game {
window_width: f32, window_width: f32,
puzzle: Puzzle, puzzle: Puzzle,
id_text: IdTextInput, id_text_btn: ButtonWidget,
reset_btn: ButtonWidget, reset_btn: ButtonWidget,
next_btn: ButtonWidget, next_btn: ButtonWidget,
show_rules: bool,
rules_font_size: f32,
board: BoardWidget, board: BoardWidget,
heading_rect: Rect,
heading_font_size: f32,
heading_text: String, heading_text: String,
heading: LabelWidget,
show_rules: bool, rules_btn_text: String,
rules_text: String,
rules_btn: ButtonWidget, rules_btn: ButtonWidget,
game_mode: GameMode, game_mode: GameMode,
easy_btn: ButtonWidget, game_mode_btns: HashMap<GameMode, ButtonWidget>,
medium_btn: ButtonWidget,
hard_btn: ButtonWidget, pieces_label: LabelWidget,
max_age_label: LabelWidget,
pieces_counter: CounterWidget,
max_age_counter: CounterWidget,
generate_btn: ButtonWidget,
} }
#[derive(Default)] #[derive(Default)]
pub struct GameSettings { pub struct GameSettings {
pub volume: f32, pub volume: f32,
pub max_moves_per_piece: u32,
pub debug: bool, pub debug: bool,
pub num_pieces: u32,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@@ -46,6 +54,7 @@ pub enum GameMode {
Easy, Easy,
Medium, Medium,
Hard, Hard,
Custom,
} }
impl Default for GameMode { impl Default for GameMode {
+10 -5
View File
@@ -6,7 +6,7 @@ pub const SCREEN_WIDTH_MIN: f32 = 200.0;
pub const SCREEN_WIDTH_MAX: f32 = 10000.0; pub const SCREEN_WIDTH_MAX: f32 = 10000.0;
// How big the square should be relative to the screen size, between 0 and 1 // How big the square should be relative to the screen size, between 0 and 1
pub const BOARD_SQUARE_WIDTH_MULTIPLIER: f32 = 0.15; pub const BOARD_SQUARE_WIDTH_MULTIPLIER: f32 = 0.11;
pub const BOARD_SHADOW_MULTIPLIER: f32 = 0.1; pub const BOARD_SHADOW_MULTIPLIER: f32 = 0.1;
@@ -22,19 +22,24 @@ pub const BUTTON_HEIGHT_MULTIPLIER: f32 = 0.08;
pub const BUTTON_WIDTH_MULTIPLIER: f32 = 0.20; pub const BUTTON_WIDTH_MULTIPLIER: f32 = 0.20;
// Gap between the bottom of the board and the first row of buttons // Gap between the bottom of the board and the first row of buttons
pub const BOTTOM_BUTTON_ROW_OFFSET_MULTIPLIER: f32 = 0.3; pub const BOTTOM_ROW_OFFSET_MULTIPLIER: f32 = 0.3;
// Gap between the right of the board and the first row of buttons // Gap between the right of the board and the first row of buttons
pub const BOTTOM_RIGHT_ROW_OFFSET_MULTIPLIER: f32 = 0.3; pub const RIGHT_COLUMN_OFFSET_MULTIPLIER: f32 = 0.3;
// Gap between the top of the board and the heading // Gap between the top of the board and the heading
pub const TOP_HEADING_OFFSET_MULTIPLIER: f32 = 0.5; pub const TOP_HEADING_OFFSET_MULTIPLIER: f32 = 0.5;
pub const HEADING_TEXT: &str = "Solitaire Chess"; pub const HEADING_TEXT: &str = "Solitaire Chess";
pub const RESET_BUTTON_TEXT: &str = "R"; pub const RESET_BUTTON_TEXT: &str = "RESET";
pub const NEXT_BUTTON_TEXT: &str = "N"; pub const NEXT_BUTTON_TEXT: &str = "NEXT";
pub const RULES_BUTTON_TEXT: &str = "RULES"; pub const RULES_BUTTON_TEXT: &str = "RULES";
pub const RULES_BUTTON_ALT_TEXT: &str = "CLOSE"; pub const RULES_BUTTON_ALT_TEXT: &str = "CLOSE";
pub const EASY_BUTTON_TEXT: &str = "EASY"; pub const EASY_BUTTON_TEXT: &str = "EASY";
pub const MEDIUM_BUTTON_TEXT: &str = "MEDIUM"; pub const MEDIUM_BUTTON_TEXT: &str = "MEDIUM";
pub const HARD_BUTTON_TEXT: &str = "HARD"; pub const HARD_BUTTON_TEXT: &str = "HARD";
pub const CUSTOM_BUTTON_TEXT: &str = "CUSTOM";
pub const GENERATE_BUTTON_TEXT: &str = "GENERATE";
pub const PIECES_LABEL_TEXT: &str = "Pieces";
pub const AGE_LABEL_TEXT: &str = " Age";
+173 -84
View File
@@ -1,53 +1,66 @@
use super::{Game, constants}; use super::{Game, constants};
use crate::widgets::*; use crate::{game::GameMode, widgets::*};
use macroquad::{math, prelude::*}; use macroquad::{math, prelude::*};
impl Game { impl Game {
fn initialize_drawables(&mut self) { fn initialize_drawables(&mut self, new_width: f32, new_height: f32) {
self.window_height = new_height;
self.window_width = new_width;
let min_dimension = f32::min(self.window_height, self.window_width); let min_dimension = f32::min(self.window_height, self.window_width);
let square_width = constants::BOARD_SQUARE_WIDTH_MULTIPLIER * min_dimension; let square_width = constants::BOARD_SQUARE_WIDTH_MULTIPLIER * min_dimension;
let num_squares = self.board.num_squares; let num_squares = self.board.num_squares;
let board_width = square_width * num_squares as f32; let board_width = square_width * num_squares as f32;
let board_x = (self.window_width - board_width) / 2.0; let board_x = (self.window_width * 0.6 - board_width) / 2.0;
let board_y = (self.window_height - board_width) / 2.0; let board_y = (self.window_height - board_width) / 2.0;
let board_rect = Rect::new(board_x, board_y, board_width, board_width); let board_rect = Rect::new(board_x, board_y, board_width, board_width);
self.board.initialize_drawables(square_width, board_rect); self.board.initialize_drawables(square_width, board_rect);
self.heading_font_size = constants::HEADING_FONT_SIZE_MULTIPLIER * min_dimension; let heading_font_size = constants::HEADING_FONT_SIZE_MULTIPLIER * min_dimension;
let f = heading_font_size.floor() as u16;
let f = self.heading_font_size.floor() as u16; let heading_text_dims = measure_text(
let dims = measure_text(
self.heading_text.as_str(), self.heading_text.as_str(),
Some(&self.resources.font()), Some(&self.resources.font()),
f, f,
1.0, 1.0,
); );
self.heading_rect = Rect::new( let heading_rect = Rect::new(
board_x + (board_width - dims.width) / 2.0, (self.window_width - heading_text_dims.width) / 2.0,
board_y - dims.height - constants::TOP_HEADING_OFFSET_MULTIPLIER * square_width, board_y
dims.width, - heading_text_dims.height
dims.height, - constants::TOP_HEADING_OFFSET_MULTIPLIER * square_width,
heading_text_dims.width,
heading_text_dims.height,
); );
self.heading
.initialize_drawables(heading_rect, heading_font_size, &self.resources);
// Buttons // Buttons
let btn_h = constants::BUTTON_HEIGHT_MULTIPLIER * min_dimension; let btn_h = constants::BUTTON_HEIGHT_MULTIPLIER * min_dimension;
let btn_w = board_width * constants::BUTTON_WIDTH_MULTIPLIER; let btn_w = constants::BUTTON_WIDTH_MULTIPLIER * board_width;
let btn_w_sq = btn_h;
// Bottom row // Bottom row
let bottom_row_y = let bottom_row_y =
board_width + board_y + constants::BOTTOM_BUTTON_ROW_OFFSET_MULTIPLIER * square_width; board_width + board_y + constants::BOTTOM_ROW_OFFSET_MULTIPLIER * square_width;
self.reset_btn.initialize_drawables(Rect::new( self.rules_btn.initialize_drawables(Rect::new(
board_x + (square_width - btn_w_sq) / 2., board_x + (square_width - btn_w) / 2.,
bottom_row_y, bottom_row_y,
btn_w_sq, btn_w,
btn_h, btn_h,
)); ));
let btn_next_x_offset = (board_width - square_width) + (square_width - btn_w_sq) / 2.;
self.reset_btn.initialize_drawables(Rect::new(
board_x + square_width + (square_width - btn_w) / 2.,
bottom_row_y,
btn_w,
btn_h,
));
let btn_next_x_offset = (board_width - square_width) + (square_width - btn_w) / 2.;
self.next_btn.initialize_drawables(Rect::new( self.next_btn.initialize_drawables(Rect::new(
board_x + btn_next_x_offset, board_x + btn_next_x_offset,
bottom_row_y, bottom_row_y,
btn_w_sq, btn_w,
btn_h, btn_h,
)); ));
@@ -57,77 +70,131 @@ impl Game {
btn_w * 2., btn_w * 2.,
btn_h, btn_h,
); );
self.id_text = IdTextInput::new(id_text_rect); self.id_text_btn.initialize_drawables(id_text_rect);
// Left column self.rules_font_size = heading_font_size;
let left_column_x =
board_x - constants::BOTTOM_RIGHT_ROW_OFFSET_MULTIPLIER * square_width - btn_w;
self.rules_btn.initialize_drawables(Rect::new(
left_column_x,
board_y + board_rect.h - square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
// Right column // Right column
let right_column_x = let right_column_x =
board_x + board_width + constants::BOTTOM_RIGHT_ROW_OFFSET_MULTIPLIER * square_width; board_x + board_width + constants::RIGHT_COLUMN_OFFSET_MULTIPLIER * square_width;
self.easy_btn.initialize_drawables(Rect::new( self.get_game_mode_button(&GameMode::Easy)
right_column_x, .initialize_drawables(Rect::new(
board_y + square_width + (square_width - btn_h) / 2., right_column_x,
btn_w, board_y + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
self.get_game_mode_button(&GameMode::Medium)
.initialize_drawables(Rect::new(
right_column_x,
board_y + square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
self.get_game_mode_button(&GameMode::Hard)
.initialize_drawables(Rect::new(
right_column_x,
board_y + 2. * square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
self.get_game_mode_button(&GameMode::Custom)
.initialize_drawables(Rect::new(
right_column_x,
board_y + 3. * square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
// Right column 2
let right_column_2_x =
right_column_x + btn_w + constants::RIGHT_COLUMN_OFFSET_MULTIPLIER * square_width;
self.generate_btn.initialize_drawables(Rect::new(
right_column_2_x,
board_y + board_width - square_width + (square_width - btn_h) / 2.,
2. * btn_w,
btn_h, btn_h,
)); ));
self.medium_btn.initialize_drawables(Rect::new( self.pieces_label.initialize_drawables(
right_column_x, Rect::new(
board_y + 2. * square_width + (square_width - btn_h) / 2., right_column_2_x,
btn_w, board_y + (square_width + btn_h) / 2.,
btn_h, btn_w,
)); btn_h,
),
0.30 * btn_h,
&self.resources,
);
self.pieces_counter.initialize_drawables(
Rect::new(
right_column_2_x,
board_y + square_width + (2. * square_width - 2. * btn_h) / 2.,
btn_w,
2. * btn_h,
),
30.,
&self.resources,
);
self.hard_btn.initialize_drawables(Rect::new( // Right column 3
right_column_x, let right_column_3_x = right_column_2_x + btn_w;
board_y + 3. * square_width + (square_width - btn_h) / 2., self.max_age_label.initialize_drawables(
btn_w, Rect::new(
btn_h, right_column_2_x + btn_w,
)); board_y + (square_width + btn_h) / 2.,
btn_w,
btn_h,
),
0.30 * btn_h,
&self.resources,
);
self.max_age_counter.initialize_drawables(
Rect::new(
right_column_3_x,
board_y + square_width + (2. * square_width - 2. * btn_h) / 2.,
btn_w,
2. * btn_h,
),
30.,
&self.resources,
);
} }
pub fn draw(&mut self) { pub fn draw(&mut self) {
self.update_window_size(); self.update_window_size();
self.draw_heading(); self.draw_heading();
self.board.draw( self.draw_board();
self.show_rules, self.draw_id_text_button();
self.heading_font_size,
&self.resources,
&self.settings,
);
self.id_text.draw(&self.resources, &self.puzzle.board.id);
self.draw_buttons(); self.draw_buttons();
self.draw_setting_controls();
self.draw_debug(); self.draw_debug();
} }
fn draw_heading(&self) { fn draw_heading(&self) {
let f = self.heading_font_size.floor() as u16; self.heading.draw(&self.resources);
let dims = measure_text( }
self.heading_text.as_str(),
Some(&self.resources.font()), fn draw_board(&mut self) {
f, let params = BoardDrawParams {
1.0, show_rules: self.show_rules,
); rules_font_size: self.rules_font_size,
let draw_text_params = TextParams {
font_size: f,
font: Some(&self.resources.font()),
color: BLACK,
..Default::default()
}; };
draw_text_ex(
self.heading_text.as_str(), self.board.draw(&params, &self.resources, &self.settings);
self.heading_rect.x, }
self.heading_rect.y + dims.offset_y,
draw_text_params, fn get_game_mode_button<'a>(&'a mut self, mode: &GameMode) -> &'a mut ButtonWidget {
); self.game_mode_btns.get_mut(mode).unwrap()
}
fn draw_game_mode_button(&mut self, mode: &GameMode, color: &UiColor, text: &str) {
let btn = self.game_mode_btns.get_mut(mode).unwrap();
btn.draw(text, color, &self.resources);
} }
fn draw_buttons(&mut self) { fn draw_buttons(&mut self) {
@@ -143,26 +210,50 @@ impl Game {
&self.resources, &self.resources,
); );
self.easy_btn.draw( self.draw_game_mode_button(
&GameMode::Easy,
&UiColor::Yellow,
constants::EASY_BUTTON_TEXT, constants::EASY_BUTTON_TEXT,
&UiColor::Yellow,
&self.resources,
); );
self.medium_btn.draw( self.draw_game_mode_button(
&GameMode::Medium,
&UiColor::Yellow,
constants::MEDIUM_BUTTON_TEXT, constants::MEDIUM_BUTTON_TEXT,
&UiColor::Yellow,
&self.resources,
); );
self.hard_btn.draw( self.draw_game_mode_button(
constants::HARD_BUTTON_TEXT, &GameMode::Hard,
&UiColor::Yellow, &UiColor::Yellow,
constants::HARD_BUTTON_TEXT,
);
self.draw_game_mode_button(
&GameMode::Custom,
&UiColor::Blue,
constants::CUSTOM_BUTTON_TEXT,
);
self.generate_btn.draw(
constants::GENERATE_BUTTON_TEXT,
&UiColor::Pink,
&self.resources, &self.resources,
); );
self.rules_btn self.rules_btn
.draw(&self.rules_text, &UiColor::Brown, &self.resources); .draw(&self.rules_btn_text, &UiColor::Brown, &self.resources);
}
fn draw_setting_controls(&mut self) {
self.pieces_label.draw(&self.resources);
self.pieces_counter.draw(&self.resources);
self.max_age_label.draw(&self.resources);
self.max_age_counter.draw(&self.resources);
}
fn draw_id_text_button(&self) {
// self.id_text_btn
// .draw(&self.puzzle.board.id, &UiColor::Yellow, &self.resources);
} }
fn draw_debug(&self) { fn draw_debug(&self) {
@@ -198,8 +289,6 @@ impl Game {
return; return;
} }
self.window_height = new_height; self.initialize_drawables(new_width, new_height);
self.window_width = new_width;
self.initialize_drawables();
} }
} }
+130 -55
View File
@@ -1,3 +1,5 @@
use std::collections::HashMap;
use super::{Game, GameMode, GameSettings, constants}; use super::{Game, GameMode, GameSettings, constants};
use crate::{ use crate::{
resources::{self, *}, resources::{self, *},
@@ -15,26 +17,61 @@ impl Game {
let game_mode = GameMode::Medium; let game_mode = GameMode::Medium;
let settings = GameSettings { let settings = GameSettings {
volume: constants::VOLUME, volume: constants::VOLUME,
max_moves_per_piece: 3,
num_pieces: 2,
..Default::default() ..Default::default()
}; };
let puzzle = Game::generate_puzzle(game_mode); let puzzle = Game::generate_puzzle(game_mode, &settings);
let board = BoardWidget::initialize_state(puzzle.board.size, puzzle.board.clone()); let board = BoardWidget::initialize_state(puzzle.board.size, puzzle.board.clone());
let medium_btn = ButtonWidget::initialize_state(false); let mut game_mode_btns = HashMap::new();
let game_mode = GameMode::Medium;
game_mode_btns.insert(GameMode::Easy, ButtonWidget::initialize_state(true));
game_mode_btns.insert(GameMode::Medium, ButtonWidget::initialize_state(true));
game_mode_btns.insert(GameMode::Hard, ButtonWidget::initialize_state(true));
game_mode_btns.insert(GameMode::Custom, ButtonWidget::initialize_state(true));
let reset_btn = ButtonWidget::initialize_state(true);
let next_btn = ButtonWidget::initialize_state(false); let next_btn = ButtonWidget::initialize_state(false);
let rules_btn = ButtonWidget::initialize_state(true);
let generate_btn = ButtonWidget::initialize_state(false);
let id_text_btn = ButtonWidget::initialize_state(true);
let heading = LabelWidget::initialize_state(constants::HEADING_TEXT);
let pieces_label = LabelWidget::initialize_state(constants::PIECES_LABEL_TEXT);
let max_age_label = LabelWidget::initialize_state(constants::AGE_LABEL_TEXT);
let show_rules = false;
let pieces_counter = CounterWidget::initialize_state(2, 7, settings.num_pieces, false);
let moves_counter = CounterWidget::initialize_state(1, 7, settings.max_moves_per_piece, false);
game_mode_btns.get_mut(&game_mode).unwrap().is_active = false;
Self { Self {
puzzle, puzzle,
board, board,
resources, resources,
game_mode,
settings, settings,
heading_text: constants::HEADING_TEXT.to_string(), game_mode,
rules_text: constants::RULES_BUTTON_TEXT.to_string(), game_mode_btns,
medium_btn, rules_btn_text: constants::RULES_BUTTON_TEXT.to_string(),
reset_btn,
next_btn, next_btn,
..Self::default() rules_btn,
id_text_btn,
show_rules,
heading_text: constants::HEADING_TEXT.to_string(),
heading,
pieces_counter,
max_age_counter: moves_counter,
generate_btn,
pieces_label,
max_age_label,
..Default::default()
} }
} }
pub fn handle_input(&mut self) { pub fn handle_input(&mut self) {
let (mx, my) = mouse_position();
let winput = WidgetInput {
mouse_pos: Circle::new(mx, my, 0.0),
};
// CORE GAMEPLAY // CORE GAMEPLAY
let (mouse_x, mouse_y) = mouse_position(); let (mouse_x, mouse_y) = mouse_position();
let mouse_pos = Circle::new(mouse_x, mouse_y, 0.0); let mouse_pos = Circle::new(mouse_x, mouse_y, 0.0);
@@ -56,6 +93,11 @@ impl Game {
return; return;
} }
self.id_text_btn.handle_input(
&self.resources.sound(&SoundKind::Click),
self.settings.volume,
);
if self if self
.reset_btn .reset_btn
.handle_input( .handle_input(
@@ -81,50 +123,41 @@ impl Game {
} }
// PRESETS // PRESETS
let mut clicked_game_mode_btn: Option<&ButtonWidget> = None; let mut game_mode_changed = false;
if self for (mode, btn) in &mut self.game_mode_btns {
.easy_btn if btn
.handle_input( .handle_input(
&self.resources.sound(&SoundKind::Mode), &self.resources.sound(&SoundKind::Mode),
self.settings.volume, self.settings.volume,
) )
.is_clicked .is_clicked
{ {
self.game_mode = GameMode::Easy; self.game_mode = *mode;
self.easy_btn.is_active = false; game_mode_changed = true;
self.medium_btn.is_active = true; break;
self.hard_btn.is_active = true; }
clicked_game_mode_btn = Some(&self.easy_btn);
} else if self
.medium_btn
.handle_input(
&self.resources.sound(&SoundKind::Mode),
self.settings.volume,
)
.is_clicked
{
self.game_mode = GameMode::Medium;
self.easy_btn.is_active = true;
self.medium_btn.is_active = false;
self.hard_btn.is_active = true;
clicked_game_mode_btn = Some(&self.medium_btn);
} else if self
.hard_btn
.handle_input(
&self.resources.sound(&SoundKind::Mode),
self.settings.volume,
)
.is_clicked
{
self.game_mode = GameMode::Hard;
self.easy_btn.is_active = true;
self.medium_btn.is_active = true;
self.hard_btn.is_active = false;
clicked_game_mode_btn = Some(&self.hard_btn);
} }
if let Some(_) = clicked_game_mode_btn { if game_mode_changed {
self.next_puzzle(); for (mode, btn) in &mut self.game_mode_btns {
if *mode == self.game_mode {
btn.is_active = false;
} else {
btn.is_active = true;
}
}
if self.game_mode != GameMode::Custom {
self.generate_btn.is_active = false;
self.pieces_counter.set_active(false);
self.max_age_counter.set_active(false);
self.next_puzzle();
} else {
self.generate_btn.is_active = true;
self.pieces_counter.set_active(true);
self.max_age_counter.set_active(true);
}
return; return;
} }
@@ -137,7 +170,7 @@ impl Game {
.is_clicked .is_clicked
{ {
self.show_rules = !self.show_rules; self.show_rules = !self.show_rules;
self.rules_text = match self.show_rules { self.rules_btn_text = match self.show_rules {
true => constants::RULES_BUTTON_ALT_TEXT.to_string(), true => constants::RULES_BUTTON_ALT_TEXT.to_string(),
false => constants::RULES_BUTTON_TEXT.to_string(), false => constants::RULES_BUTTON_TEXT.to_string(),
}; };
@@ -145,6 +178,36 @@ impl Game {
return; return;
} }
let pieces_counter_interaction =
self.pieces_counter
.handle_input(&winput, &self.resources, &self.settings);
if pieces_counter_interaction.interacted {
self.settings.num_pieces = pieces_counter_interaction.current;
return;
}
let moves_counter_interaction =
self.max_age_counter
.handle_input(&winput, &self.resources, &self.settings);
if moves_counter_interaction.interacted {
self.settings.max_moves_per_piece = moves_counter_interaction.current;
return;
}
if self
.generate_btn
.handle_input(
&self.resources.sound(&SoundKind::Button),
self.settings.volume,
)
.is_clicked
{
if self.game_mode == GameMode::Custom {
self.next_puzzle();
}
return;
}
// KEY INPUTS // KEY INPUTS
if is_key_released(KeyCode::Escape) { if is_key_released(KeyCode::Escape) {
if self.show_rules { if self.show_rules {
@@ -153,7 +216,7 @@ impl Game {
self.settings.volume, self.settings.volume,
); );
self.show_rules = false; self.show_rules = false;
self.rules_text = constants::RULES_BUTTON_TEXT.to_string(); self.rules_btn_text = constants::RULES_BUTTON_TEXT.to_string();
} }
return; return;
@@ -175,7 +238,7 @@ impl Game {
fn reset_game(&mut self, options: ResetOptions) { fn reset_game(&mut self, options: ResetOptions) {
if options.create_new_puzzle { if options.create_new_puzzle {
self.puzzle = Game::generate_puzzle(self.game_mode); self.puzzle = Game::generate_puzzle(self.game_mode, &self.settings);
} }
self.next_btn.is_active = false; self.next_btn.is_active = false;
@@ -189,15 +252,27 @@ impl Game {
}); });
} }
fn generate_puzzle(mode: GameMode) -> Puzzle { fn generate_puzzle(mode: GameMode, settings: &GameSettings) -> 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,
GameMode::Custom => settings.num_pieces,
}; };
let generated = let max_moves_per_piece = match mode {
generator::generate_weighted_random(piece_count, 100, &MacroquadRandAdapter); GameMode::Easy => 2,
GameMode::Medium => 2,
GameMode::Hard => 2,
GameMode::Custom => settings.max_moves_per_piece,
};
let generated = generator::generate_weighted_random(
piece_count,
100,
max_moves_per_piece,
&MacroquadRandAdapter,
);
let puzzle = generated.puzzle(); let puzzle = generated.puzzle();
puzzle.expect("No puzzle was generated") puzzle.expect("No puzzle was generated")
} }
+1
View File
@@ -36,3 +36,4 @@ async fn main() {
next_frame().await next_frame().await
} }
} }
+36 -18
View File
@@ -2,13 +2,14 @@ use macroquad::{
audio::{self, Sound}, audio::{self, Sound},
prelude::*, prelude::*,
}; };
use sol_lib::board::piece::Piece; use sol_lib::board::piece::{Piece, PieceKind};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Default)] #[derive(Default)]
pub struct Resources { pub struct Resources {
texture_pieces: Option<Texture2D>, texture_pieces: Option<Texture2D>,
texture_rects_pieces: HashMap<Piece, Rect>, texture_rects_pieces: HashMap<PieceKind, Rect>,
texture_rects_inactive_pieces: HashMap<PieceKind, Rect>,
sounds: HashMap<SoundKind, Sound>, sounds: HashMap<SoundKind, Sound>,
@@ -52,32 +53,43 @@ pub async fn init() -> Resources {
texture_pieces.set_filter(FilterMode::Nearest); texture_pieces.set_filter(FilterMode::Nearest);
build_textures_atlas(); build_textures_atlas();
let mut texture_rects_pieces = HashMap::new(); let texture_rects_pieces = generate_texture_rects_pieces(true);
texture_rects_pieces.insert(Piece::Pawn, piece_texture_rect(Piece::Pawn)); let texture_rects_inactive_pieces = generate_texture_rects_pieces(false);
texture_rects_pieces.insert(Piece::Knight, piece_texture_rect(Piece::Knight));
texture_rects_pieces.insert(Piece::Bishop, piece_texture_rect(Piece::Bishop));
texture_rects_pieces.insert(Piece::Rook, piece_texture_rect(Piece::Rook));
texture_rects_pieces.insert(Piece::Queen, piece_texture_rect(Piece::Queen));
texture_rects_pieces.insert(Piece::King, piece_texture_rect(Piece::King));
Resources { Resources {
texture_pieces: Some(texture_pieces), texture_pieces: Some(texture_pieces),
texture_rects_pieces, texture_rects_pieces,
texture_rects_inactive_pieces,
sounds, sounds,
font: Some(font), font: Some(font),
} }
} }
fn piece_texture_rect(piece: Piece) -> Rect { fn generate_texture_rects_pieces(active: bool) -> HashMap<PieceKind, Rect> {
let mut texture_rects_pieces = HashMap::new();
texture_rects_pieces.insert(PieceKind::Pawn, piece_texture_rect(PieceKind::Pawn, active));
texture_rects_pieces.insert(PieceKind::Knight, piece_texture_rect(PieceKind::Knight, active));
texture_rects_pieces.insert(PieceKind::Bishop, piece_texture_rect(PieceKind::Bishop, active));
texture_rects_pieces.insert(PieceKind::Rook, piece_texture_rect(PieceKind::Rook, active));
texture_rects_pieces.insert(PieceKind::Queen, piece_texture_rect(PieceKind::Queen, active));
texture_rects_pieces.insert(PieceKind::King, piece_texture_rect(PieceKind::King, active));
texture_rects_pieces
}
fn piece_texture_rect(piece: PieceKind, active: bool) -> Rect {
let index = match piece { let index = match piece {
Piece::Pawn => 0, PieceKind::Pawn => 0,
Piece::Knight => 1, PieceKind::Knight => 1,
Piece::Bishop => 2, PieceKind::Bishop => 2,
Piece::Rook => 3, PieceKind::Rook => 3,
Piece::Queen => 4, PieceKind::Queen => 4,
Piece::King => 5, PieceKind::King => 5,
};
let color = match active {
true => 1,
false => 0,
}; };
let color = 0;
Rect::new(index as f32 * 128.0, color as f32 * 128.0, 128.0, 128.0) Rect::new(index as f32 * 128.0, color as f32 * 128.0, 128.0, 128.0)
} }
@@ -93,7 +105,13 @@ pub enum SoundKind {
impl Resources { impl Resources {
pub fn get_piece_texture<'a>(&'a self, piece: &Piece) -> Texture<'a> { pub fn get_piece_texture<'a>(&'a self, piece: &Piece) -> Texture<'a> {
let texture = self.texture_pieces.as_ref().unwrap(); let texture = self.texture_pieces.as_ref().unwrap();
let texture_rect = self.texture_rects_pieces.get(piece).unwrap(); let texture_rects_lookup = if piece.active {
&self.texture_rects_pieces
} else {
&self.texture_rects_inactive_pieces
};
let texture_rect = texture_rects_lookup.get(&piece.kind).unwrap();
Texture { Texture {
texture: texture, texture: texture,
texture_rect: *texture_rect, texture_rect: *texture_rect,
+10 -1
View File
@@ -5,7 +5,12 @@ mod button;
pub use button::*; pub use button::*;
mod id_text_input; mod id_text_input;
pub use id_text_input::*;
mod label;
pub use label::*;
mod counter;
pub use counter::*;
use macroquad::{ use macroquad::{
audio::{self, Sound}, audio::{self, Sound},
@@ -13,6 +18,10 @@ use macroquad::{
}; };
use quad_snd::PlaySoundParams; use quad_snd::PlaySoundParams;
pub struct WidgetInput {
pub mouse_pos: Circle,
}
#[allow(unused)] #[allow(unused)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UiColor { pub enum UiColor {
+16 -9
View File
@@ -28,10 +28,6 @@ pub struct BoardInteraction<'a> {
} }
impl BoardWidget { impl BoardWidget {
fn get(&mut self, i: usize, j: usize) -> &mut GameSquare {
&mut self.squares[i * self.num_squares + j]
}
pub fn initialize_state(num_squares: usize, current_board: Board) -> BoardWidget { pub fn initialize_state(num_squares: usize, current_board: Board) -> BoardWidget {
let mut board = BoardWidget::default(); let mut board = BoardWidget::default();
board.num_squares = num_squares; board.num_squares = num_squares;
@@ -71,11 +67,11 @@ impl BoardWidget {
self.squares = rects; self.squares = rects;
} }
pub fn draw(&self, show_rules: bool, rules_font_size: f32, resources: &Resources, settings: &GameSettings) { pub fn draw(&self, params: &BoardDrawParams, resources: &Resources, settings: &GameSettings) {
let board_shadow_width = constants::BOARD_SHADOW_MULTIPLIER * self.square_width; let board_shadow_width = constants::BOARD_SHADOW_MULTIPLIER * self.square_width;
draw_shadow(&self.board_rect, board_shadow_width); draw_shadow(&self.board_rect, board_shadow_width);
if show_rules { if params.show_rules {
draw_rectangle( draw_rectangle(
self.board_rect.x, self.board_rect.x,
self.board_rect.y, self.board_rect.y,
@@ -84,11 +80,13 @@ impl BoardWidget {
UiColor::Yellow.to_bg_color(), UiColor::Yellow.to_bg_color(),
); );
let font_size = rules_font_size * 0.6; let font_size = params.rules_font_size * 0.4;
let rules = "\ let rules = "\
Every move should be a \n\ Every move should be a \n\
capture. Win when only \n\ capture. Win when only \n\
one piece is left.\n"; one piece is left.\n\
Age: Each piece can only \n\
move 'age' times";
let measurement = measure_text(rules, Some(resources.font()), font_size as u16, 1.0); let measurement = measure_text(rules, Some(resources.font()), font_size as u16, 1.0);
let draw_text_params = TextParams { let draw_text_params = TextParams {
font_size: font_size as u16, font_size: font_size as u16,
@@ -99,7 +97,7 @@ impl BoardWidget {
draw_multiline_text_ex( draw_multiline_text_ex(
rules, rules,
self.board_rect.x + 0.5 * self.square_width, self.board_rect.x + 0.5 * self.square_width,
self.board_rect.y + 0.5 * (self.board_rect.h - measurement.height) self.board_rect.y + 0.4 * (self.board_rect.h - measurement.height)
- 2. * measurement.offset_y, - 2. * measurement.offset_y,
Some(2.), Some(2.),
draw_text_params, draw_text_params,
@@ -161,6 +159,10 @@ impl BoardWidget {
} }
} }
fn get(&mut self, i: usize, j: usize) -> &mut GameSquare {
&mut self.squares[i * self.num_squares + j]
}
fn draw_debug(&self) { fn draw_debug(&self) {
let mut debug_lines = vec![]; let mut debug_lines = vec![];
let (mx, my) = mouse_position(); let (mx, my) = mouse_position();
@@ -420,3 +422,8 @@ struct PieceDrawTextureParams<'a> {
texture: &'a Texture2D, texture: &'a Texture2D,
draw_text_params: DrawTextureParams, draw_text_params: DrawTextureParams,
} }
pub struct BoardDrawParams {
pub show_rules: bool,
pub rules_font_size: f32,
}
+13 -2
View File
@@ -66,8 +66,19 @@ impl ButtonWidget {
}; };
let font = resources.font(); let font = resources.font();
let font_size = (0.2 * self.rect.w) as u16; let mut dim_width = self.rect.w;
let dims = measure_text(&text, Some(&font), font_size, 1.0); let mut font_size = 30.0;
let mut dims = measure_text(&text, Some(&font), font_size as u16, 1.0);
loop {
if dim_width <= self.rect.w * 0.8 {
break;
}
dims = measure_text(&text, Some(&font), font_size as u16, 1.0);
dim_width = dims.width;
font_size -= 2.0;
}
let button_draw_offset = self.get_button_draw_offset(); let button_draw_offset = self.get_button_draw_offset();
let text_params = TextParams { let text_params = TextParams {
+127
View File
@@ -0,0 +1,127 @@
use crate::{
game::GameSettings,
resources::{Resources, SoundKind},
widgets::*,
};
use macroquad::prelude::*;
#[derive(Default)]
pub struct CounterWidget {
visual_rect: Rect,
increment: ButtonWidget,
decrement: ButtonWidget,
min: u32,
max: u32,
current: u32,
font_size: f32,
visual_dims: TextDimensions,
increment_dims: TextDimensions,
decrement_dims: TextDimensions,
is_active: bool,
}
pub struct CounterWidgetInteraction {
pub current: u32,
pub interacted: bool,
}
impl CounterWidget {
pub fn initialize_state(min: u32, max: u32, current: u32, is_active: bool) -> Self {
Self {
min,
max,
current,
is_active,
..Default::default()
}
}
pub fn set_active(&mut self, is_active: bool) {
self.is_active = is_active;
self.increment.is_active = is_active;
self.decrement.is_active = is_active;
}
pub fn initialize_drawables(
&mut self,
total_area: Rect,
font_size: f32,
resources: &Resources,
) {
let margin_y = 0.05 * total_area.h;
let btn_h = total_area.h * 0.3;
let btn_w = total_area.w * 0.9;
let btn_x = total_area.x + (total_area.w - btn_w) / 2.;
let increment_y = total_area.y + margin_y;
let visual_y = increment_y + btn_h + margin_y;
let decrement_y = visual_y + btn_h + margin_y;
self.visual_rect = Rect::new(btn_x, visual_y, btn_w, btn_h);
let increment_rect = Rect::new(btn_x, increment_y, btn_w, btn_h);
self.increment = ButtonWidget::initialize_state(self.is_active);
self.increment.initialize_drawables(increment_rect);
let decrement_rect = Rect::new(btn_x, decrement_y, btn_w, btn_h);
self.decrement = ButtonWidget::initialize_state(self.is_active);
self.decrement.initialize_drawables(decrement_rect);
self.increment_dims = measure_text("+", Some(resources.font()), font_size as u16, 1.0);
self.decrement_dims = measure_text("-", Some(resources.font()), font_size as u16, 1.0);
self.visual_dims = measure_text("0", Some(resources.font()), font_size as u16, 1.0);
self.font_size = font_size;
}
pub fn handle_input(
&mut self,
_: &WidgetInput,
resources: &Resources,
settings: &GameSettings,
) -> CounterWidgetInteraction {
let mut interacted = false;
if self
.increment
.handle_input(resources.sound(&SoundKind::Click), settings.volume)
.is_clicked
{
if self.current < self.max {
self.current += 1;
interacted = true;
}
}
if self
.decrement
.handle_input(resources.sound(&SoundKind::Click), settings.volume)
.is_clicked
{
if self.current > self.min {
self.current -= 1;
interacted = true;
}
}
return CounterWidgetInteraction {
current: self.current,
interacted,
};
}
pub fn draw(&mut self, resources: &Resources) {
self.increment.draw("+", &UiColor::Yellow, resources);
self.decrement.draw("-", &UiColor::Yellow, resources);
draw_text_ex(
&self.current.to_string(),
self.visual_rect.x + (self.visual_rect.w - self.visual_dims.width) / 2.,
self.visual_rect.y + (self.visual_rect.h + self.font_size) / 2.,
TextParams {
font: Some(resources.font()),
font_size: self.font_size as u16,
color: UiColor::Yellow.to_fg_color(),
..Default::default()
},
);
}
}
+44 -48
View File
@@ -1,77 +1,73 @@
#[allow(dead_code)]
use crate::{resources::Resources, widgets::*}; use crate::{resources::Resources, widgets::*};
use macroquad::prelude::*; use macroquad::prelude::*;
#[derive(Default)]
pub struct IdTextInput { pub struct IdTextInput {
rect: Rect, rect: Rect,
text_rect: Rect, text_rect: Rect,
copy_box_rect: Rect, copy_box_rect: Rect,
paste_box_rect: Rect, paste_box_rect: Rect,
shadow_width: f32, }
color: UiColor,
copy_color: UiColor, pub struct IdTextInputDrawParams<'a> {
paste_color: UiColor, pub text: &'a str,
pub color: UiColor,
pub copy_color: UiColor,
pub paste_color: UiColor,
pub shadow_width: f32,
} }
impl IdTextInput { impl IdTextInput {
pub fn new(rect: Rect) -> Self { pub fn initialize_state() -> Self {
let copy_button_w = 0.25 * rect.w; Default::default()
let paste_button_h = 0.5 * rect.h;
Self {
rect,
text_rect: Rect::new(rect.x, rect.y, rect.w - copy_button_w, rect.h),
copy_box_rect: Rect::new(
rect.x + (rect.w - copy_button_w),
rect.y,
copy_button_w,
paste_button_h,
),
paste_box_rect: Rect::new(
rect.x + (rect.w - copy_button_w),
rect.y + paste_button_h,
copy_button_w,
paste_button_h,
),
shadow_width: 3.0,
color: UiColor::Yellow,
copy_color: UiColor::Blue,
paste_color: UiColor::Pink,
}
} }
pub fn draw(&self, resources: &Resources, text: &str) { pub fn initialize_drawables(&mut self, rect: Rect) {
draw_rect(&self.text_rect, self.color); let copy_button_w = 0.25 * rect.w;
draw_rect(&self.copy_box_rect, self.copy_color); let paste_button_h = 0.5 * rect.h;
draw_rect(&self.paste_box_rect, self.paste_color); self.rect = rect;
draw_shadow(&self.rect, self.shadow_width); self.text_rect = Rect::new(rect.x, rect.y, rect.w - copy_button_w, rect.h);
self.copy_box_rect = Rect::new(
rect.x + (rect.w - copy_button_w),
rect.y,
copy_button_w,
paste_button_h,
);
self.paste_box_rect = Rect::new(
rect.x + (rect.w - copy_button_w),
rect.y + paste_button_h,
copy_button_w,
paste_button_h,
);
}
pub fn draw(&self, params: &IdTextInputDrawParams, resources: &Resources) {
draw_rect(&self.text_rect, params.color);
draw_rect(&self.copy_box_rect, params.copy_color);
draw_rect(&self.paste_box_rect, params.paste_color);
draw_shadow(&self.rect, params.shadow_width);
let font = resources.font(); let font = resources.font();
let font_size = (0.15 * self.text_rect.w) as u16; let font_size = (0.15 * self.text_rect.w) as u16;
let text_dims = measure_text(text, Some(font), font_size, 1.0); let text_dims = measure_text(params.text, Some(font), font_size, 1.0);
draw_text_ex( draw_text_ex(
&text, params.text,
self.rect.x + (self.text_rect.w - text_dims.width) / 2., self.rect.x + (self.text_rect.w - text_dims.width) / 2.,
self.rect.y + (self.text_rect.h - text_dims.height) / 2. + text_dims.offset_y, self.rect.y + (self.text_rect.h - text_dims.height) / 2. + text_dims.offset_y,
TextParams { TextParams {
font: Some(font), font: Some(font),
font_size: font_size, font_size: font_size,
color: self.color.to_fg_color(), color: params.color.to_fg_color(),
..Default::default() ..Default::default()
}, },
); );
} }
}
impl Default for IdTextInput { pub fn handle_input(&self, _: &WidgetInput) -> IdTextInputInteraction {
fn default() -> Self { IdTextInputInteraction {}
Self {
rect: Default::default(),
text_rect: Default::default(),
copy_box_rect: Default::default(),
paste_box_rect: Default::default(),
shadow_width: Default::default(),
color: UiColor::Grey,
copy_color: UiColor::Grey,
paste_color: UiColor::Grey,
}
} }
} }
pub struct IdTextInputInteraction {}
+42
View File
@@ -0,0 +1,42 @@
use macroquad::prelude::*;
use crate::resources::Resources;
#[derive(Default)]
pub struct LabelWidget {
rect: Rect,
font_size: f32,
text_dimensions: TextDimensions,
text: String,
}
impl LabelWidget {
pub fn initialize_state(text: &str) -> Self {
Self {
text: text.to_string(),
..Default::default()
}
}
pub fn initialize_drawables(&mut self, rect: Rect, font_size: f32, resources: &Resources) {
self.rect = rect;
self.font_size = font_size;
self.text_dimensions = measure_text(&self.text, Some(resources.font()), font_size as u16, 1.0);
}
pub fn draw(&self, resources: &Resources) {
let draw_text_params = TextParams {
font: Some(&resources.font()),
font_size: self.font_size as u16,
color: BLACK,
..Default::default()
};
draw_text_ex(
&self.text,
self.rect.x,
self.rect.y + self.text_dimensions.offset_y,
draw_text_params,
);
}
}
+62 -38
View File
@@ -14,10 +14,10 @@ use std::{
use cmove::CMove; use cmove::CMove;
use constants::BOARD_SIZE; use constants::BOARD_SIZE;
use errors::SError; use errors::SError;
use piece::Piece; use piece::PieceKind;
use square::{Square, SquarePair}; use square::{Square, SquarePair};
use crate::generator::Puzzle; use crate::{board::piece::Piece, generator::Puzzle};
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Board { pub struct Board {
@@ -27,6 +27,7 @@ pub struct Board {
pub id: String, pub id: String,
pub size: usize, pub size: usize,
pieces_remaining: u8, pieces_remaining: u8,
max_moves_per_piece: u32,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
@@ -43,8 +44,19 @@ impl Default for BoardState {
} }
} }
pub struct BoardOptions {
pub max_moves_per_piece: u32,
}
impl Board { impl Board {
pub fn new() -> Self { pub fn new() -> Self {
let options = BoardOptions {
max_moves_per_piece: 2,
};
Board::create(options)
}
pub fn create(options: BoardOptions) -> Self {
let cells = [[None; BOARD_SIZE]; BOARD_SIZE]; let cells = [[None; BOARD_SIZE]; BOARD_SIZE];
let id = Board::encode(cells); let id = Board::encode(cells);
Board { Board {
@@ -54,6 +66,7 @@ impl Board {
pieces_remaining: 0, pieces_remaining: 0,
game_state: BoardState::NotStarted, game_state: BoardState::NotStarted,
size: BOARD_SIZE, size: BOARD_SIZE,
max_moves_per_piece: options.max_moves_per_piece,
} }
} }
@@ -72,10 +85,10 @@ impl Board {
let mask = 0b111; let mask = 0b111;
for i in (0..BOARD_SIZE).rev() { for i in (0..BOARD_SIZE).rev() {
for j in (0..BOARD_SIZE).rev() { for j in (0..BOARD_SIZE).rev() {
let piece = Board::get_piece_from_encoding((working & mask) as u8); let piece_kind = Board::get_piece_from_encoding((working & mask) as u8);
working = working >> 3; working = working >> 3;
let piece = piece?; let piece_kind = piece_kind?;
board.set(Square::new(i, j, piece)); board.set(Square::new(i, j, Piece::from_kind(piece_kind)));
} }
} }
Ok(board) Ok(board)
@@ -91,18 +104,18 @@ impl Board {
for r in 0..BOARD_SIZE { for r in 0..BOARD_SIZE {
for f in 0..BOARD_SIZE { for f in 0..BOARD_SIZE {
let c = chars.next().unwrap(); let c = chars.next().unwrap();
let piece = match c { let piece_kind = match c {
'K' | 'k' => Piece::King, 'K' | 'k' => PieceKind::King,
'Q' | 'q' => Piece::Queen, 'Q' | 'q' => PieceKind::Queen,
'B' | 'b' => Piece::Bishop, 'B' | 'b' => PieceKind::Bishop,
'N' | 'n' => Piece::Knight, 'N' | 'n' => PieceKind::Knight,
'R' | 'r' => Piece::Rook, 'R' | 'r' => PieceKind::Rook,
'P' | 'p' => Piece::Pawn, 'P' | 'p' => PieceKind::Pawn,
'.' => continue, '.' => continue,
_ => return Err(SError::InvalidBoard), _ => return Err(SError::InvalidBoard),
}; };
let square = Square::new(f, r, Some(piece)); let square = Square::new(f, r, Some(Piece::new(piece_kind)));
board.set(square); board.set(square);
} }
} }
@@ -137,7 +150,14 @@ impl Board {
return None; return None;
} }
let from_piece = mem::replace(&mut self.cells[mv.from.file][mv.from.rank], None); let mut from_piece = mem::replace(&mut self.cells[mv.from.file][mv.from.rank], None);
if let Some(p) = &mut from_piece {
p.moves_made += 1;
if p.moves_made >= self.max_moves_per_piece {
p.active = false;
}
}
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.pieces_remaining -= 1;
@@ -278,13 +298,17 @@ impl Board {
return None; return None;
}; };
let legal = match piece { if piece.moves_made >= self.max_moves_per_piece {
Piece::King => self.is_king_legal(&pair), return None;
Piece::Queen => self.is_queen_legal(&pair), }
Piece::Bishop => self.is_bishop_legal(&pair),
Piece::Knight => self.is_knight_legal(&pair), let legal = match piece.kind {
Piece::Rook => self.is_rook_legal(&pair), PieceKind::King => self.is_king_legal(&pair),
Piece::Pawn => self.is_pawn_legal(&pair), PieceKind::Queen => self.is_queen_legal(&pair),
PieceKind::Bishop => self.is_bishop_legal(&pair),
PieceKind::Knight => self.is_knight_legal(&pair),
PieceKind::Rook => self.is_rook_legal(&pair),
PieceKind::Pawn => self.is_pawn_legal(&pair),
}; };
if legal { if legal {
@@ -400,26 +424,26 @@ impl Board {
fn get_piece_encoding(piece: Option<Piece>) -> u8 { fn get_piece_encoding(piece: Option<Piece>) -> u8 {
match piece { match piece {
Some(p) => match p { Some(p) => match p.kind {
Piece::King => 0b001, PieceKind::King => 0b001,
Piece::Queen => 0b010, PieceKind::Queen => 0b010,
Piece::Rook => 0b011, PieceKind::Rook => 0b011,
Piece::Bishop => 0b100, PieceKind::Bishop => 0b100,
Piece::Knight => 0b101, PieceKind::Knight => 0b101,
Piece::Pawn => 0b110, PieceKind::Pawn => 0b110,
}, },
None => 0b000, None => 0b000,
} }
} }
fn get_piece_from_encoding(encoding: u8) -> Result<Option<Piece>, SError> { fn get_piece_from_encoding(encoding: u8) -> Result<Option<PieceKind>, SError> {
match encoding { match encoding {
0b001 => Ok(Some(Piece::King)), 0b001 => Ok(Some(PieceKind::King)),
0b010 => Ok(Some(Piece::Queen)), 0b010 => Ok(Some(PieceKind::Queen)),
0b011 => Ok(Some(Piece::Rook)), 0b011 => Ok(Some(PieceKind::Rook)),
0b100 => Ok(Some(Piece::Bishop)), 0b100 => Ok(Some(PieceKind::Bishop)),
0b101 => Ok(Some(Piece::Knight)), 0b101 => Ok(Some(PieceKind::Knight)),
0b110 => Ok(Some(Piece::Pawn)), 0b110 => Ok(Some(PieceKind::Pawn)),
0b000 => Ok(None), 0b000 => Ok(None),
_ => Err(SError::InvalidBoard), _ => Err(SError::InvalidBoard),
} }
@@ -433,9 +457,9 @@ impl Board {
fn get_square_for_display(piece: &Option<Piece>, pretty: bool) -> String { fn get_square_for_display(piece: &Option<Piece>, pretty: bool) -> String {
let contents = if let Some(piece) = piece { let contents = if let Some(piece) = piece {
if pretty { if pretty {
piece.pretty() piece.kind.pretty()
} else { } else {
piece.notation() piece.kind.notation()
} }
} else { } else {
".".to_string() ".".to_string()
@@ -557,7 +581,7 @@ mod tests {
assert!(board.set(sq!("Nb2")).is_none()); assert!(board.set(sq!("Nb2")).is_none());
let existing = board.set(sq!("Pc4")); let existing = board.set(sq!("Pc4"));
assert!(existing.is_some()); assert!(existing.is_some());
assert_eq!(Piece::Knight, existing.unwrap()); assert_eq!(PieceKind::Knight, existing.unwrap().kind);
validate_board!(board, "..PP", "..B.", "QN..", "K..R"); validate_board!(board, "..PP", "..B.", "QN..", "K..R");
} }
+5 -3
View File
@@ -1,4 +1,6 @@
use super::{piece::Piece, square::Square}; use crate::board::piece::Piece;
use super::{piece::PieceKind, square::Square};
#[derive(PartialEq, Hash, Eq, Clone)] #[derive(PartialEq, Hash, Eq, Clone)]
pub struct CMove { pub struct CMove {
@@ -26,8 +28,8 @@ impl CMove {
} }
pub fn notation(&self) -> String { pub fn notation(&self) -> String {
let piece_qualifier = match &self.from_piece { let piece_qualifier = match &self.from_piece.kind {
Piece::Pawn => self.from.file_notation(), PieceKind::Pawn => self.from.file_notation(),
p => p.notation(), p => p.notation(),
}; };
format!( format!(
+47 -27
View File
@@ -1,5 +1,25 @@
#[derive(Clone, Eq, Hash, Copy, Debug, PartialEq)] #[derive(Clone, Eq, Hash, Copy, Debug, PartialEq)]
pub enum Piece { pub struct Piece {
pub kind: PieceKind,
pub moves_made: u32,
pub active: bool,
}
impl Piece {
pub fn new(kind: PieceKind) -> Self {
Self {
kind,
moves_made: 0,
active: true,
}
}
pub fn from_kind(kind: Option<PieceKind>) -> Option<Self> {
kind.map(|k| Piece::new(k))
}
}
#[derive(Clone, Eq, Hash, Copy, Debug, PartialEq)]
pub enum PieceKind {
King, King,
Queen, Queen,
Bishop, Bishop,
@@ -8,15 +28,15 @@ pub enum Piece {
Pawn, Pawn,
} }
impl Piece { impl PieceKind {
pub fn parse(piece: &str) -> Option<Self> { pub fn parse(piece: &str) -> Option<Self> {
match piece { match piece {
"K" => Some(Piece::King), "K" => Some(PieceKind::King),
"Q" => Some(Piece::Queen), "Q" => Some(PieceKind::Queen),
"B" => Some(Piece::Bishop), "B" => Some(PieceKind::Bishop),
"N" => Some(Piece::Knight), "N" => Some(PieceKind::Knight),
"R" => Some(Piece::Rook), "R" => Some(PieceKind::Rook),
"P" => Some(Piece::Pawn), "P" => Some(PieceKind::Pawn),
"." => None, "." => None,
p => panic!("Invalid piece {}", p), p => panic!("Invalid piece {}", p),
} }
@@ -24,12 +44,12 @@ impl Piece {
pub fn notation(&self) -> String { pub fn notation(&self) -> String {
let n = match self { let n = match self {
Piece::King => "K", PieceKind::King => "K",
Piece::Queen => "Q", PieceKind::Queen => "Q",
Piece::Bishop => "B", PieceKind::Bishop => "B",
Piece::Knight => "N", PieceKind::Knight => "N",
Piece::Rook => "R", PieceKind::Rook => "R",
Piece::Pawn => "P", PieceKind::Pawn => "P",
}; };
n.to_string() n.to_string()
@@ -37,12 +57,12 @@ impl Piece {
pub fn pretty(&self) -> String { pub fn pretty(&self) -> String {
let n = match self { let n = match self {
Piece::King => "", PieceKind::King => "",
Piece::Queen => "", PieceKind::Queen => "",
Piece::Bishop => "", PieceKind::Bishop => "",
Piece::Knight => "", PieceKind::Knight => "",
Piece::Rook => "", PieceKind::Rook => "",
Piece::Pawn => "", PieceKind::Pawn => "",
}; };
n.to_string() n.to_string()
@@ -55,17 +75,17 @@ mod tests {
macro_rules! p { macro_rules! p {
($piece:literal) => { ($piece:literal) => {
Piece::parse($piece) PieceKind::parse($piece)
}; };
} }
#[test] #[test]
fn test_piece_parse() { fn test_piece_parse() {
assert_eq!(p!("K"), Some(Piece::King)); assert_eq!(p!("K"), Some(PieceKind::King));
assert_eq!(p!("Q"), Some(Piece::Queen)); assert_eq!(p!("Q"), Some(PieceKind::Queen));
assert_eq!(p!("B"), Some(Piece::Bishop)); assert_eq!(p!("B"), Some(PieceKind::Bishop));
assert_eq!(p!("N"), Some(Piece::Knight)); assert_eq!(p!("N"), Some(PieceKind::Knight));
assert_eq!(p!("R"), Some(Piece::Rook)); assert_eq!(p!("R"), Some(PieceKind::Rook));
assert_eq!(p!("P"), Some(Piece::Pawn)); assert_eq!(p!("P"), Some(PieceKind::Pawn));
} }
} }
+8 -5
View File
@@ -1,5 +1,7 @@
use crate::board::piece::Piece;
use super::constants::BOARD_SIZE; use super::constants::BOARD_SIZE;
use super::piece::Piece; use super::piece::PieceKind;
use core::fmt; use core::fmt;
#[derive(Clone, Eq, Hash, PartialEq)] #[derive(Clone, Eq, Hash, PartialEq)]
@@ -29,8 +31,8 @@ impl Square {
pub fn parse(notation: &str) -> Self { pub fn parse(notation: &str) -> Self {
let mut chars = notation.chars(); let mut chars = notation.chars();
let piece = chars.next().expect("Piece missing"); let piece_kind = chars.next().expect("Piece missing");
let piece = Piece::parse(&piece.to_string()); let piece_kind = PieceKind::parse(&piece_kind.to_string());
let file = chars.next().expect("File missing"); let file = chars.next().expect("File missing");
let file = match file { let file = match file {
'a' => 0, 'a' => 0,
@@ -45,6 +47,7 @@ impl Square {
panic!("rank should be between 1-{}", BOARD_SIZE); panic!("rank should be between 1-{}", BOARD_SIZE);
} }
let rank = BOARD_SIZE - rank; let rank = BOARD_SIZE - rank;
let piece = piece_kind.map(|kind| Piece::new(kind));
Square::new(file, rank, piece) Square::new(file, rank, piece)
} }
@@ -73,7 +76,7 @@ impl Square {
if self.piece.is_none() { if self.piece.is_none() {
"".to_string() "".to_string()
} else { } else {
self.piece.unwrap().notation() self.piece.unwrap().kind.notation()
} }
} }
} }
@@ -131,7 +134,7 @@ mod tests {
let square = Square::parse(&notation); let square = Square::parse(&notation);
assert_eq!(square.file, $file); assert_eq!(square.file, $file);
assert_eq!(square.rank, $rank); assert_eq!(square.rank, $rank);
assert_eq!(square.piece, Some(Piece::King)); assert_eq!(square.piece.unwrap().kind, PieceKind::King);
assert_eq!(square.notation(), notation); assert_eq!(square.notation(), notation);
}; };
} }
+36 -21
View File
@@ -1,6 +1,10 @@
use std::fmt::Display; use std::fmt::Display;
use crate::board::{Board, cmove::CMove, piece::Piece}; use crate::board::{
Board, BoardOptions,
cmove::CMove,
piece::{Piece, PieceKind},
};
pub trait RandomRange { pub trait RandomRange {
fn gen_range(&self, min: usize, max: usize) -> usize; fn gen_range(&self, min: usize, max: usize) -> usize;
@@ -24,23 +28,24 @@ pub struct GenerateStats {
pub fn generate_weighted_random( pub fn generate_weighted_random(
num_pieces: u32, num_pieces: u32,
num_solutions: u32, num_solutions: u32,
max_moves_per_piece: u32,
rand: &impl RandomRange, rand: &impl RandomRange,
) -> GenerateStats { ) -> GenerateStats {
let candidate_pieces = vec![ let candidate_pieces = vec![
Piece::Pawn, PieceKind::Pawn,
Piece::Pawn, PieceKind::Pawn,
Piece::Pawn, PieceKind::Pawn,
Piece::Pawn, PieceKind::Pawn,
Piece::Bishop, PieceKind::Bishop,
Piece::Bishop, PieceKind::Bishop,
Piece::Bishop, PieceKind::Bishop,
Piece::Bishop, PieceKind::Bishop,
Piece::Knight, PieceKind::Knight,
Piece::Knight, PieceKind::Knight,
Piece::Knight, PieceKind::Knight,
Piece::Queen, PieceKind::Queen,
Piece::Rook, PieceKind::Rook,
Piece::Rook, PieceKind::Rook,
]; ];
if num_pieces > candidate_pieces.len().try_into().unwrap() { if num_pieces > candidate_pieces.len().try_into().unwrap() {
@@ -53,7 +58,13 @@ pub fn generate_weighted_random(
let attempts: u32 = 1000; let attempts: u32 = 1000;
let mut overall_stats = GenerateStats::new(0, 0, 0, None, vec![]); 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,
max_moves_per_piece,
rand,
candidate_pieces.clone(),
);
overall_stats.piece_total += stats.piece_total; overall_stats.piece_total += stats.piece_total;
overall_stats.piece_success += stats.piece_success; overall_stats.piece_success += stats.piece_success;
overall_stats.total += stats.total; overall_stats.total += stats.total;
@@ -117,10 +128,14 @@ where
fn try_generate( fn try_generate(
num_pieces: u32, num_pieces: u32,
num_solutions: u32, num_solutions: u32,
max_moves_per_piece: u32,
rand: &impl RandomRange, rand: &impl RandomRange,
mut candidate_pieces: Vec<Piece>, mut candidate_pieces: Vec<PieceKind>,
) -> GenerateStats { ) -> GenerateStats {
let mut board = Board::new(); let mut board = Board::create(BoardOptions {
max_moves_per_piece,
});
let mut piece_total = 0; let mut piece_total = 0;
let mut piece_success = 0; let mut piece_success = 0;
for _ in 0..num_pieces { for _ in 0..num_pieces {
@@ -136,10 +151,10 @@ fn try_generate(
piece_total += 1; piece_total += 1;
let index = rand.gen_range(0, candidate_pieces.len()); let index = rand.gen_range(0, candidate_pieces.len());
let piece = candidate_pieces[index]; let piece_kind = candidate_pieces[index];
let square_index = rand.gen_range(0, empty_squares.len()); let square_index = rand.gen_range(0, empty_squares.len());
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::new(piece_kind));
board.set(random_square.clone()); board.set(random_square.clone());
let puzzle = board.solve(); let puzzle = board.solve();
if puzzle.solutions.len() > 0 { if puzzle.solutions.len() > 0 {
@@ -186,7 +201,7 @@ mod tests {
#[test] #[test]
fn generator_smoke() { fn generator_smoke() {
for _ in 0..10 { for _ in 0..10 {
let gen_stats = generate_weighted_random(5, 5, &TestRandom); let gen_stats = generate_weighted_random(5, 5, 10, &TestRandom);
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);