add age
This commit is contained in:
+2
-1
@@ -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",
|
||||
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();
|
||||
|
||||
let Some(puzzle) = gen_result.puzzle() else {
|
||||
|
||||
+17
-8
@@ -2,6 +2,8 @@ pub mod constants;
|
||||
mod draw;
|
||||
mod logic;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{resources::Resources, widgets::*};
|
||||
use macroquad::prelude::*;
|
||||
use sol_lib::generator::Puzzle;
|
||||
@@ -15,30 +17,36 @@ pub struct Game {
|
||||
window_width: f32,
|
||||
|
||||
puzzle: Puzzle,
|
||||
id_text: IdTextInput,
|
||||
id_text_btn: ButtonWidget,
|
||||
reset_btn: ButtonWidget,
|
||||
next_btn: ButtonWidget,
|
||||
|
||||
show_rules: bool,
|
||||
rules_font_size: f32,
|
||||
board: BoardWidget,
|
||||
|
||||
heading_rect: Rect,
|
||||
heading_font_size: f32,
|
||||
heading_text: String,
|
||||
heading: LabelWidget,
|
||||
|
||||
show_rules: bool,
|
||||
rules_text: String,
|
||||
rules_btn_text: String,
|
||||
rules_btn: ButtonWidget,
|
||||
|
||||
game_mode: GameMode,
|
||||
easy_btn: ButtonWidget,
|
||||
medium_btn: ButtonWidget,
|
||||
hard_btn: ButtonWidget,
|
||||
game_mode_btns: HashMap<GameMode, ButtonWidget>,
|
||||
|
||||
pieces_label: LabelWidget,
|
||||
max_age_label: LabelWidget,
|
||||
pieces_counter: CounterWidget,
|
||||
max_age_counter: CounterWidget,
|
||||
generate_btn: ButtonWidget,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GameSettings {
|
||||
pub volume: f32,
|
||||
pub max_moves_per_piece: u32,
|
||||
pub debug: bool,
|
||||
pub num_pieces: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
@@ -46,6 +54,7 @@ pub enum GameMode {
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
Custom,
|
||||
}
|
||||
|
||||
impl Default for GameMode {
|
||||
|
||||
@@ -6,7 +6,7 @@ pub const SCREEN_WIDTH_MIN: f32 = 200.0;
|
||||
pub const SCREEN_WIDTH_MAX: f32 = 10000.0;
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -22,19 +22,24 @@ pub const BUTTON_HEIGHT_MULTIPLIER: f32 = 0.08;
|
||||
pub const BUTTON_WIDTH_MULTIPLIER: f32 = 0.20;
|
||||
|
||||
// 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
|
||||
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
|
||||
pub const TOP_HEADING_OFFSET_MULTIPLIER: f32 = 0.5;
|
||||
|
||||
pub const HEADING_TEXT: &str = "Solitaire Chess";
|
||||
pub const RESET_BUTTON_TEXT: &str = "R";
|
||||
pub const NEXT_BUTTON_TEXT: &str = "N";
|
||||
pub const RESET_BUTTON_TEXT: &str = "RESET";
|
||||
pub const NEXT_BUTTON_TEXT: &str = "NEXT";
|
||||
pub const RULES_BUTTON_TEXT: &str = "RULES";
|
||||
pub const RULES_BUTTON_ALT_TEXT: &str = "CLOSE";
|
||||
pub const EASY_BUTTON_TEXT: &str = "EASY";
|
||||
pub const MEDIUM_BUTTON_TEXT: &str = "MEDIUM";
|
||||
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";
|
||||
|
||||
|
||||
+159
-70
@@ -1,53 +1,66 @@
|
||||
use super::{Game, constants};
|
||||
use crate::widgets::*;
|
||||
use crate::{game::GameMode, widgets::*};
|
||||
use macroquad::{math, prelude::*};
|
||||
|
||||
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 square_width = constants::BOARD_SQUARE_WIDTH_MULTIPLIER * min_dimension;
|
||||
let num_squares = self.board.num_squares;
|
||||
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_rect = Rect::new(board_x, board_y, board_width, board_width);
|
||||
self.board.initialize_drawables(square_width, board_rect);
|
||||
|
||||
self.heading_font_size = constants::HEADING_FONT_SIZE_MULTIPLIER * min_dimension;
|
||||
|
||||
let f = self.heading_font_size.floor() as u16;
|
||||
let dims = measure_text(
|
||||
let heading_font_size = constants::HEADING_FONT_SIZE_MULTIPLIER * min_dimension;
|
||||
let f = heading_font_size.floor() as u16;
|
||||
let heading_text_dims = measure_text(
|
||||
self.heading_text.as_str(),
|
||||
Some(&self.resources.font()),
|
||||
f,
|
||||
1.0,
|
||||
);
|
||||
self.heading_rect = Rect::new(
|
||||
board_x + (board_width - dims.width) / 2.0,
|
||||
board_y - dims.height - constants::TOP_HEADING_OFFSET_MULTIPLIER * square_width,
|
||||
dims.width,
|
||||
dims.height,
|
||||
let heading_rect = Rect::new(
|
||||
(self.window_width - heading_text_dims.width) / 2.0,
|
||||
board_y
|
||||
- heading_text_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
|
||||
let btn_h = constants::BUTTON_HEIGHT_MULTIPLIER * min_dimension;
|
||||
let btn_w = board_width * constants::BUTTON_WIDTH_MULTIPLIER;
|
||||
let btn_w_sq = btn_h;
|
||||
let btn_w = constants::BUTTON_WIDTH_MULTIPLIER * board_width;
|
||||
|
||||
// Bottom row
|
||||
let bottom_row_y =
|
||||
board_width + board_y + constants::BOTTOM_BUTTON_ROW_OFFSET_MULTIPLIER * square_width;
|
||||
self.reset_btn.initialize_drawables(Rect::new(
|
||||
board_x + (square_width - btn_w_sq) / 2.,
|
||||
board_width + board_y + constants::BOTTOM_ROW_OFFSET_MULTIPLIER * square_width;
|
||||
self.rules_btn.initialize_drawables(Rect::new(
|
||||
board_x + (square_width - btn_w) / 2.,
|
||||
bottom_row_y,
|
||||
btn_w_sq,
|
||||
btn_w,
|
||||
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(
|
||||
board_x + btn_next_x_offset,
|
||||
bottom_row_y,
|
||||
btn_w_sq,
|
||||
btn_w,
|
||||
btn_h,
|
||||
));
|
||||
|
||||
@@ -57,77 +70,131 @@ impl Game {
|
||||
btn_w * 2.,
|
||||
btn_h,
|
||||
);
|
||||
self.id_text = IdTextInput::new(id_text_rect);
|
||||
self.id_text_btn.initialize_drawables(id_text_rect);
|
||||
|
||||
// Left column
|
||||
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.,
|
||||
self.rules_font_size = heading_font_size;
|
||||
|
||||
// Right column
|
||||
let right_column_x =
|
||||
board_x + board_width + constants::RIGHT_COLUMN_OFFSET_MULTIPLIER * square_width;
|
||||
self.get_game_mode_button(&GameMode::Easy)
|
||||
.initialize_drawables(Rect::new(
|
||||
right_column_x,
|
||||
board_y + (square_width - btn_h) / 2.,
|
||||
btn_w,
|
||||
btn_h,
|
||||
));
|
||||
|
||||
// Right column
|
||||
let right_column_x =
|
||||
board_x + board_width + constants::BOTTOM_RIGHT_ROW_OFFSET_MULTIPLIER * square_width;
|
||||
self.easy_btn.initialize_drawables(Rect::new(
|
||||
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.medium_btn.initialize_drawables(Rect::new(
|
||||
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.hard_btn.initialize_drawables(Rect::new(
|
||||
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,
|
||||
));
|
||||
|
||||
self.pieces_label.initialize_drawables(
|
||||
Rect::new(
|
||||
right_column_2_x,
|
||||
board_y + (square_width + btn_h) / 2.,
|
||||
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,
|
||||
);
|
||||
|
||||
// Right column 3
|
||||
let right_column_3_x = right_column_2_x + btn_w;
|
||||
self.max_age_label.initialize_drawables(
|
||||
Rect::new(
|
||||
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) {
|
||||
self.update_window_size();
|
||||
|
||||
self.draw_heading();
|
||||
self.board.draw(
|
||||
self.show_rules,
|
||||
self.heading_font_size,
|
||||
&self.resources,
|
||||
&self.settings,
|
||||
);
|
||||
self.id_text.draw(&self.resources, &self.puzzle.board.id);
|
||||
self.draw_board();
|
||||
self.draw_id_text_button();
|
||||
self.draw_buttons();
|
||||
self.draw_setting_controls();
|
||||
self.draw_debug();
|
||||
}
|
||||
|
||||
fn draw_heading(&self) {
|
||||
let f = self.heading_font_size.floor() as u16;
|
||||
let dims = measure_text(
|
||||
self.heading_text.as_str(),
|
||||
Some(&self.resources.font()),
|
||||
f,
|
||||
1.0,
|
||||
);
|
||||
let draw_text_params = TextParams {
|
||||
font_size: f,
|
||||
font: Some(&self.resources.font()),
|
||||
color: BLACK,
|
||||
..Default::default()
|
||||
self.heading.draw(&self.resources);
|
||||
}
|
||||
|
||||
fn draw_board(&mut self) {
|
||||
let params = BoardDrawParams {
|
||||
show_rules: self.show_rules,
|
||||
rules_font_size: self.rules_font_size,
|
||||
};
|
||||
draw_text_ex(
|
||||
self.heading_text.as_str(),
|
||||
self.heading_rect.x,
|
||||
self.heading_rect.y + dims.offset_y,
|
||||
draw_text_params,
|
||||
);
|
||||
|
||||
self.board.draw(¶ms, &self.resources, &self.settings);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -143,26 +210,50 @@ impl Game {
|
||||
&self.resources,
|
||||
);
|
||||
|
||||
self.easy_btn.draw(
|
||||
self.draw_game_mode_button(
|
||||
&GameMode::Easy,
|
||||
&UiColor::Yellow,
|
||||
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,
|
||||
&UiColor::Yellow,
|
||||
&self.resources,
|
||||
);
|
||||
|
||||
self.hard_btn.draw(
|
||||
constants::HARD_BUTTON_TEXT,
|
||||
self.draw_game_mode_button(
|
||||
&GameMode::Hard,
|
||||
&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.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) {
|
||||
@@ -198,8 +289,6 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
|
||||
self.window_height = new_height;
|
||||
self.window_width = new_width;
|
||||
self.initialize_drawables();
|
||||
self.initialize_drawables(new_width, new_height);
|
||||
}
|
||||
}
|
||||
|
||||
+123
-48
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{Game, GameMode, GameSettings, constants};
|
||||
use crate::{
|
||||
resources::{self, *},
|
||||
@@ -15,26 +17,61 @@ impl Game {
|
||||
let game_mode = GameMode::Medium;
|
||||
let settings = GameSettings {
|
||||
volume: constants::VOLUME,
|
||||
max_moves_per_piece: 3,
|
||||
num_pieces: 2,
|
||||
..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 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 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 {
|
||||
puzzle,
|
||||
board,
|
||||
resources,
|
||||
game_mode,
|
||||
settings,
|
||||
heading_text: constants::HEADING_TEXT.to_string(),
|
||||
rules_text: constants::RULES_BUTTON_TEXT.to_string(),
|
||||
medium_btn,
|
||||
game_mode,
|
||||
game_mode_btns,
|
||||
rules_btn_text: constants::RULES_BUTTON_TEXT.to_string(),
|
||||
reset_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) {
|
||||
let (mx, my) = mouse_position();
|
||||
let winput = WidgetInput {
|
||||
mouse_pos: Circle::new(mx, my, 0.0),
|
||||
};
|
||||
|
||||
// CORE GAMEPLAY
|
||||
let (mouse_x, mouse_y) = mouse_position();
|
||||
let mouse_pos = Circle::new(mouse_x, mouse_y, 0.0);
|
||||
@@ -56,6 +93,11 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
|
||||
self.id_text_btn.handle_input(
|
||||
&self.resources.sound(&SoundKind::Click),
|
||||
self.settings.volume,
|
||||
);
|
||||
|
||||
if self
|
||||
.reset_btn
|
||||
.handle_input(
|
||||
@@ -81,50 +123,41 @@ impl Game {
|
||||
}
|
||||
|
||||
// PRESETS
|
||||
let mut clicked_game_mode_btn: Option<&ButtonWidget> = None;
|
||||
if self
|
||||
.easy_btn
|
||||
let mut game_mode_changed = false;
|
||||
for (mode, btn) in &mut self.game_mode_btns {
|
||||
if btn
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Mode),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.game_mode = GameMode::Easy;
|
||||
self.easy_btn.is_active = false;
|
||||
self.medium_btn.is_active = true;
|
||||
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);
|
||||
self.game_mode = *mode;
|
||||
game_mode_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(_) = clicked_game_mode_btn {
|
||||
if game_mode_changed {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -137,7 +170,7 @@ impl Game {
|
||||
.is_clicked
|
||||
{
|
||||
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(),
|
||||
false => constants::RULES_BUTTON_TEXT.to_string(),
|
||||
};
|
||||
@@ -145,6 +178,36 @@ impl Game {
|
||||
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
|
||||
if is_key_released(KeyCode::Escape) {
|
||||
if self.show_rules {
|
||||
@@ -153,7 +216,7 @@ impl Game {
|
||||
self.settings.volume,
|
||||
);
|
||||
self.show_rules = false;
|
||||
self.rules_text = constants::RULES_BUTTON_TEXT.to_string();
|
||||
self.rules_btn_text = constants::RULES_BUTTON_TEXT.to_string();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -175,7 +238,7 @@ impl Game {
|
||||
|
||||
fn reset_game(&mut self, options: ResetOptions) {
|
||||
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;
|
||||
@@ -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 {
|
||||
GameMode::Easy => 3,
|
||||
GameMode::Medium => 5,
|
||||
GameMode::Hard => 7,
|
||||
GameMode::Custom => settings.num_pieces,
|
||||
};
|
||||
|
||||
let generated =
|
||||
generator::generate_weighted_random(piece_count, 100, &MacroquadRandAdapter);
|
||||
let max_moves_per_piece = match mode {
|
||||
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();
|
||||
puzzle.expect("No puzzle was generated")
|
||||
}
|
||||
|
||||
@@ -36,3 +36,4 @@ async fn main() {
|
||||
next_frame().await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+36
-18
@@ -2,13 +2,14 @@ use macroquad::{
|
||||
audio::{self, Sound},
|
||||
prelude::*,
|
||||
};
|
||||
use sol_lib::board::piece::Piece;
|
||||
use sol_lib::board::piece::{Piece, PieceKind};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Resources {
|
||||
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>,
|
||||
|
||||
@@ -52,32 +53,43 @@ pub async fn init() -> Resources {
|
||||
texture_pieces.set_filter(FilterMode::Nearest);
|
||||
build_textures_atlas();
|
||||
|
||||
let mut texture_rects_pieces = HashMap::new();
|
||||
texture_rects_pieces.insert(Piece::Pawn, piece_texture_rect(Piece::Pawn));
|
||||
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));
|
||||
let texture_rects_pieces = generate_texture_rects_pieces(true);
|
||||
let texture_rects_inactive_pieces = generate_texture_rects_pieces(false);
|
||||
|
||||
Resources {
|
||||
texture_pieces: Some(texture_pieces),
|
||||
texture_rects_pieces,
|
||||
texture_rects_inactive_pieces,
|
||||
sounds,
|
||||
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 {
|
||||
Piece::Pawn => 0,
|
||||
Piece::Knight => 1,
|
||||
Piece::Bishop => 2,
|
||||
Piece::Rook => 3,
|
||||
Piece::Queen => 4,
|
||||
Piece::King => 5,
|
||||
PieceKind::Pawn => 0,
|
||||
PieceKind::Knight => 1,
|
||||
PieceKind::Bishop => 2,
|
||||
PieceKind::Rook => 3,
|
||||
PieceKind::Queen => 4,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -93,7 +105,13 @@ pub enum SoundKind {
|
||||
impl Resources {
|
||||
pub fn get_piece_texture<'a>(&'a self, piece: &Piece) -> Texture<'a> {
|
||||
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_rect: *texture_rect,
|
||||
|
||||
+10
-1
@@ -5,7 +5,12 @@ mod button;
|
||||
pub use button::*;
|
||||
|
||||
mod id_text_input;
|
||||
pub use id_text_input::*;
|
||||
|
||||
mod label;
|
||||
pub use label::*;
|
||||
|
||||
mod counter;
|
||||
pub use counter::*;
|
||||
|
||||
use macroquad::{
|
||||
audio::{self, Sound},
|
||||
@@ -13,6 +18,10 @@ use macroquad::{
|
||||
};
|
||||
use quad_snd::PlaySoundParams;
|
||||
|
||||
pub struct WidgetInput {
|
||||
pub mouse_pos: Circle,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UiColor {
|
||||
|
||||
@@ -28,10 +28,6 @@ pub struct BoardInteraction<'a> {
|
||||
}
|
||||
|
||||
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 {
|
||||
let mut board = BoardWidget::default();
|
||||
board.num_squares = num_squares;
|
||||
@@ -71,11 +67,11 @@ impl BoardWidget {
|
||||
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;
|
||||
draw_shadow(&self.board_rect, board_shadow_width);
|
||||
|
||||
if show_rules {
|
||||
if params.show_rules {
|
||||
draw_rectangle(
|
||||
self.board_rect.x,
|
||||
self.board_rect.y,
|
||||
@@ -84,11 +80,13 @@ impl BoardWidget {
|
||||
UiColor::Yellow.to_bg_color(),
|
||||
);
|
||||
|
||||
let font_size = rules_font_size * 0.6;
|
||||
let font_size = params.rules_font_size * 0.4;
|
||||
let rules = "\
|
||||
Every move should be a \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 draw_text_params = TextParams {
|
||||
font_size: font_size as u16,
|
||||
@@ -99,7 +97,7 @@ impl BoardWidget {
|
||||
draw_multiline_text_ex(
|
||||
rules,
|
||||
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,
|
||||
Some(2.),
|
||||
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) {
|
||||
let mut debug_lines = vec![];
|
||||
let (mx, my) = mouse_position();
|
||||
@@ -420,3 +422,8 @@ struct PieceDrawTextureParams<'a> {
|
||||
texture: &'a Texture2D,
|
||||
draw_text_params: DrawTextureParams,
|
||||
}
|
||||
|
||||
pub struct BoardDrawParams {
|
||||
pub show_rules: bool,
|
||||
pub rules_font_size: f32,
|
||||
}
|
||||
|
||||
@@ -66,8 +66,19 @@ impl ButtonWidget {
|
||||
};
|
||||
|
||||
let font = resources.font();
|
||||
let font_size = (0.2 * self.rect.w) as u16;
|
||||
let dims = measure_text(&text, Some(&font), font_size, 1.0);
|
||||
let mut dim_width = self.rect.w;
|
||||
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 text_params = TextParams {
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,73 @@
|
||||
#[allow(dead_code)]
|
||||
|
||||
use crate::{resources::Resources, widgets::*};
|
||||
use macroquad::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct IdTextInput {
|
||||
rect: Rect,
|
||||
text_rect: Rect,
|
||||
copy_box_rect: Rect,
|
||||
paste_box_rect: Rect,
|
||||
shadow_width: f32,
|
||||
color: UiColor,
|
||||
copy_color: UiColor,
|
||||
paste_color: UiColor,
|
||||
}
|
||||
|
||||
pub struct IdTextInputDrawParams<'a> {
|
||||
pub text: &'a str,
|
||||
pub color: UiColor,
|
||||
pub copy_color: UiColor,
|
||||
pub paste_color: UiColor,
|
||||
pub shadow_width: f32,
|
||||
}
|
||||
|
||||
impl IdTextInput {
|
||||
pub fn new(rect: Rect) -> Self {
|
||||
pub fn initialize_state() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn initialize_drawables(&mut self, rect: Rect) {
|
||||
let copy_button_w = 0.25 * rect.w;
|
||||
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(
|
||||
self.rect = rect;
|
||||
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,
|
||||
),
|
||||
paste_box_rect: Rect::new(
|
||||
);
|
||||
self.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) {
|
||||
draw_rect(&self.text_rect, self.color);
|
||||
draw_rect(&self.copy_box_rect, self.copy_color);
|
||||
draw_rect(&self.paste_box_rect, self.paste_color);
|
||||
draw_shadow(&self.rect, self.shadow_width);
|
||||
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_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(
|
||||
&text,
|
||||
params.text,
|
||||
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,
|
||||
TextParams {
|
||||
font: Some(font),
|
||||
font_size: font_size,
|
||||
color: self.color.to_fg_color(),
|
||||
color: params.color.to_fg_color(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, _: &WidgetInput) -> IdTextInputInteraction {
|
||||
IdTextInputInteraction {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IdTextInput {
|
||||
fn default() -> Self {
|
||||
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 {}
|
||||
|
||||
@@ -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
@@ -14,10 +14,10 @@ use std::{
|
||||
use cmove::CMove;
|
||||
use constants::BOARD_SIZE;
|
||||
use errors::SError;
|
||||
use piece::Piece;
|
||||
use piece::PieceKind;
|
||||
use square::{Square, SquarePair};
|
||||
|
||||
use crate::generator::Puzzle;
|
||||
use crate::{board::piece::Piece, generator::Puzzle};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Board {
|
||||
@@ -27,6 +27,7 @@ pub struct Board {
|
||||
pub id: String,
|
||||
pub size: usize,
|
||||
pieces_remaining: u8,
|
||||
max_moves_per_piece: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
@@ -43,8 +44,19 @@ impl Default for BoardState {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoardOptions {
|
||||
pub max_moves_per_piece: u32,
|
||||
}
|
||||
|
||||
impl Board {
|
||||
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 id = Board::encode(cells);
|
||||
Board {
|
||||
@@ -54,6 +66,7 @@ impl Board {
|
||||
pieces_remaining: 0,
|
||||
game_state: BoardState::NotStarted,
|
||||
size: BOARD_SIZE,
|
||||
max_moves_per_piece: options.max_moves_per_piece,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +85,10 @@ impl Board {
|
||||
let mask = 0b111;
|
||||
for i 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;
|
||||
let piece = piece?;
|
||||
board.set(Square::new(i, j, piece));
|
||||
let piece_kind = piece_kind?;
|
||||
board.set(Square::new(i, j, Piece::from_kind(piece_kind)));
|
||||
}
|
||||
}
|
||||
Ok(board)
|
||||
@@ -91,18 +104,18 @@ impl Board {
|
||||
for r in 0..BOARD_SIZE {
|
||||
for f in 0..BOARD_SIZE {
|
||||
let c = chars.next().unwrap();
|
||||
let piece = match c {
|
||||
'K' | 'k' => Piece::King,
|
||||
'Q' | 'q' => Piece::Queen,
|
||||
'B' | 'b' => Piece::Bishop,
|
||||
'N' | 'n' => Piece::Knight,
|
||||
'R' | 'r' => Piece::Rook,
|
||||
'P' | 'p' => Piece::Pawn,
|
||||
let piece_kind = match c {
|
||||
'K' | 'k' => PieceKind::King,
|
||||
'Q' | 'q' => PieceKind::Queen,
|
||||
'B' | 'b' => PieceKind::Bishop,
|
||||
'N' | 'n' => PieceKind::Knight,
|
||||
'R' | 'r' => PieceKind::Rook,
|
||||
'P' | 'p' => PieceKind::Pawn,
|
||||
'.' => continue,
|
||||
_ => 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);
|
||||
}
|
||||
}
|
||||
@@ -137,7 +150,14 @@ impl Board {
|
||||
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.pieces_remaining -= 1;
|
||||
@@ -278,13 +298,17 @@ impl Board {
|
||||
return None;
|
||||
};
|
||||
|
||||
let legal = match piece {
|
||||
Piece::King => self.is_king_legal(&pair),
|
||||
Piece::Queen => self.is_queen_legal(&pair),
|
||||
Piece::Bishop => self.is_bishop_legal(&pair),
|
||||
Piece::Knight => self.is_knight_legal(&pair),
|
||||
Piece::Rook => self.is_rook_legal(&pair),
|
||||
Piece::Pawn => self.is_pawn_legal(&pair),
|
||||
if piece.moves_made >= self.max_moves_per_piece {
|
||||
return None;
|
||||
}
|
||||
|
||||
let legal = match piece.kind {
|
||||
PieceKind::King => self.is_king_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 {
|
||||
@@ -400,26 +424,26 @@ impl Board {
|
||||
|
||||
fn get_piece_encoding(piece: Option<Piece>) -> u8 {
|
||||
match piece {
|
||||
Some(p) => match p {
|
||||
Piece::King => 0b001,
|
||||
Piece::Queen => 0b010,
|
||||
Piece::Rook => 0b011,
|
||||
Piece::Bishop => 0b100,
|
||||
Piece::Knight => 0b101,
|
||||
Piece::Pawn => 0b110,
|
||||
Some(p) => match p.kind {
|
||||
PieceKind::King => 0b001,
|
||||
PieceKind::Queen => 0b010,
|
||||
PieceKind::Rook => 0b011,
|
||||
PieceKind::Bishop => 0b100,
|
||||
PieceKind::Knight => 0b101,
|
||||
PieceKind::Pawn => 0b110,
|
||||
},
|
||||
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 {
|
||||
0b001 => Ok(Some(Piece::King)),
|
||||
0b010 => Ok(Some(Piece::Queen)),
|
||||
0b011 => Ok(Some(Piece::Rook)),
|
||||
0b100 => Ok(Some(Piece::Bishop)),
|
||||
0b101 => Ok(Some(Piece::Knight)),
|
||||
0b110 => Ok(Some(Piece::Pawn)),
|
||||
0b001 => Ok(Some(PieceKind::King)),
|
||||
0b010 => Ok(Some(PieceKind::Queen)),
|
||||
0b011 => Ok(Some(PieceKind::Rook)),
|
||||
0b100 => Ok(Some(PieceKind::Bishop)),
|
||||
0b101 => Ok(Some(PieceKind::Knight)),
|
||||
0b110 => Ok(Some(PieceKind::Pawn)),
|
||||
0b000 => Ok(None),
|
||||
_ => Err(SError::InvalidBoard),
|
||||
}
|
||||
@@ -433,9 +457,9 @@ impl Board {
|
||||
fn get_square_for_display(piece: &Option<Piece>, pretty: bool) -> String {
|
||||
let contents = if let Some(piece) = piece {
|
||||
if pretty {
|
||||
piece.pretty()
|
||||
piece.kind.pretty()
|
||||
} else {
|
||||
piece.notation()
|
||||
piece.kind.notation()
|
||||
}
|
||||
} else {
|
||||
".".to_string()
|
||||
@@ -557,7 +581,7 @@ mod tests {
|
||||
assert!(board.set(sq!("Nb2")).is_none());
|
||||
let existing = board.set(sq!("Pc4"));
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
pub struct CMove {
|
||||
@@ -26,8 +28,8 @@ impl CMove {
|
||||
}
|
||||
|
||||
pub fn notation(&self) -> String {
|
||||
let piece_qualifier = match &self.from_piece {
|
||||
Piece::Pawn => self.from.file_notation(),
|
||||
let piece_qualifier = match &self.from_piece.kind {
|
||||
PieceKind::Pawn => self.from.file_notation(),
|
||||
p => p.notation(),
|
||||
};
|
||||
format!(
|
||||
|
||||
+47
-27
@@ -1,5 +1,25 @@
|
||||
#[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,
|
||||
Queen,
|
||||
Bishop,
|
||||
@@ -8,15 +28,15 @@ pub enum Piece {
|
||||
Pawn,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
impl PieceKind {
|
||||
pub fn parse(piece: &str) -> Option<Self> {
|
||||
match piece {
|
||||
"K" => Some(Piece::King),
|
||||
"Q" => Some(Piece::Queen),
|
||||
"B" => Some(Piece::Bishop),
|
||||
"N" => Some(Piece::Knight),
|
||||
"R" => Some(Piece::Rook),
|
||||
"P" => Some(Piece::Pawn),
|
||||
"K" => Some(PieceKind::King),
|
||||
"Q" => Some(PieceKind::Queen),
|
||||
"B" => Some(PieceKind::Bishop),
|
||||
"N" => Some(PieceKind::Knight),
|
||||
"R" => Some(PieceKind::Rook),
|
||||
"P" => Some(PieceKind::Pawn),
|
||||
"." => None,
|
||||
p => panic!("Invalid piece {}", p),
|
||||
}
|
||||
@@ -24,12 +44,12 @@ impl Piece {
|
||||
|
||||
pub fn notation(&self) -> String {
|
||||
let n = match self {
|
||||
Piece::King => "K",
|
||||
Piece::Queen => "Q",
|
||||
Piece::Bishop => "B",
|
||||
Piece::Knight => "N",
|
||||
Piece::Rook => "R",
|
||||
Piece::Pawn => "P",
|
||||
PieceKind::King => "K",
|
||||
PieceKind::Queen => "Q",
|
||||
PieceKind::Bishop => "B",
|
||||
PieceKind::Knight => "N",
|
||||
PieceKind::Rook => "R",
|
||||
PieceKind::Pawn => "P",
|
||||
};
|
||||
|
||||
n.to_string()
|
||||
@@ -37,12 +57,12 @@ impl Piece {
|
||||
|
||||
pub fn pretty(&self) -> String {
|
||||
let n = match self {
|
||||
Piece::King => "♔",
|
||||
Piece::Queen => "♕",
|
||||
Piece::Bishop => "♗",
|
||||
Piece::Knight => "♘",
|
||||
Piece::Rook => "♖",
|
||||
Piece::Pawn => "♙",
|
||||
PieceKind::King => "♔",
|
||||
PieceKind::Queen => "♕",
|
||||
PieceKind::Bishop => "♗",
|
||||
PieceKind::Knight => "♘",
|
||||
PieceKind::Rook => "♖",
|
||||
PieceKind::Pawn => "♙",
|
||||
};
|
||||
|
||||
n.to_string()
|
||||
@@ -55,17 +75,17 @@ mod tests {
|
||||
|
||||
macro_rules! p {
|
||||
($piece:literal) => {
|
||||
Piece::parse($piece)
|
||||
PieceKind::parse($piece)
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_piece_parse() {
|
||||
assert_eq!(p!("K"), Some(Piece::King));
|
||||
assert_eq!(p!("Q"), Some(Piece::Queen));
|
||||
assert_eq!(p!("B"), Some(Piece::Bishop));
|
||||
assert_eq!(p!("N"), Some(Piece::Knight));
|
||||
assert_eq!(p!("R"), Some(Piece::Rook));
|
||||
assert_eq!(p!("P"), Some(Piece::Pawn));
|
||||
assert_eq!(p!("K"), Some(PieceKind::King));
|
||||
assert_eq!(p!("Q"), Some(PieceKind::Queen));
|
||||
assert_eq!(p!("B"), Some(PieceKind::Bishop));
|
||||
assert_eq!(p!("N"), Some(PieceKind::Knight));
|
||||
assert_eq!(p!("R"), Some(PieceKind::Rook));
|
||||
assert_eq!(p!("P"), Some(PieceKind::Pawn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::board::piece::Piece;
|
||||
|
||||
use super::constants::BOARD_SIZE;
|
||||
use super::piece::Piece;
|
||||
use super::piece::PieceKind;
|
||||
use core::fmt;
|
||||
|
||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||
@@ -29,8 +31,8 @@ impl Square {
|
||||
|
||||
pub fn parse(notation: &str) -> Self {
|
||||
let mut chars = notation.chars();
|
||||
let piece = chars.next().expect("Piece missing");
|
||||
let piece = Piece::parse(&piece.to_string());
|
||||
let piece_kind = chars.next().expect("Piece missing");
|
||||
let piece_kind = PieceKind::parse(&piece_kind.to_string());
|
||||
let file = chars.next().expect("File missing");
|
||||
let file = match file {
|
||||
'a' => 0,
|
||||
@@ -45,6 +47,7 @@ impl Square {
|
||||
panic!("rank should be between 1-{}", BOARD_SIZE);
|
||||
}
|
||||
let rank = BOARD_SIZE - rank;
|
||||
let piece = piece_kind.map(|kind| Piece::new(kind));
|
||||
Square::new(file, rank, piece)
|
||||
}
|
||||
|
||||
@@ -73,7 +76,7 @@ impl Square {
|
||||
if self.piece.is_none() {
|
||||
"".to_string()
|
||||
} else {
|
||||
self.piece.unwrap().notation()
|
||||
self.piece.unwrap().kind.notation()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +134,7 @@ mod tests {
|
||||
let square = Square::parse(¬ation);
|
||||
assert_eq!(square.file, $file);
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
+36
-21
@@ -1,6 +1,10 @@
|
||||
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 {
|
||||
fn gen_range(&self, min: usize, max: usize) -> usize;
|
||||
@@ -24,23 +28,24 @@ pub struct GenerateStats {
|
||||
pub fn generate_weighted_random(
|
||||
num_pieces: u32,
|
||||
num_solutions: u32,
|
||||
max_moves_per_piece: u32,
|
||||
rand: &impl RandomRange,
|
||||
) -> GenerateStats {
|
||||
let candidate_pieces = vec![
|
||||
Piece::Pawn,
|
||||
Piece::Pawn,
|
||||
Piece::Pawn,
|
||||
Piece::Pawn,
|
||||
Piece::Bishop,
|
||||
Piece::Bishop,
|
||||
Piece::Bishop,
|
||||
Piece::Bishop,
|
||||
Piece::Knight,
|
||||
Piece::Knight,
|
||||
Piece::Knight,
|
||||
Piece::Queen,
|
||||
Piece::Rook,
|
||||
Piece::Rook,
|
||||
PieceKind::Pawn,
|
||||
PieceKind::Pawn,
|
||||
PieceKind::Pawn,
|
||||
PieceKind::Pawn,
|
||||
PieceKind::Bishop,
|
||||
PieceKind::Bishop,
|
||||
PieceKind::Bishop,
|
||||
PieceKind::Bishop,
|
||||
PieceKind::Knight,
|
||||
PieceKind::Knight,
|
||||
PieceKind::Knight,
|
||||
PieceKind::Queen,
|
||||
PieceKind::Rook,
|
||||
PieceKind::Rook,
|
||||
];
|
||||
|
||||
if num_pieces > candidate_pieces.len().try_into().unwrap() {
|
||||
@@ -53,7 +58,13 @@ pub fn generate_weighted_random(
|
||||
let attempts: u32 = 1000;
|
||||
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());
|
||||
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_success += stats.piece_success;
|
||||
overall_stats.total += stats.total;
|
||||
@@ -117,10 +128,14 @@ where
|
||||
fn try_generate(
|
||||
num_pieces: u32,
|
||||
num_solutions: u32,
|
||||
max_moves_per_piece: u32,
|
||||
rand: &impl RandomRange,
|
||||
mut candidate_pieces: Vec<Piece>,
|
||||
mut candidate_pieces: Vec<PieceKind>,
|
||||
) -> GenerateStats {
|
||||
let mut board = Board::new();
|
||||
let mut board = Board::create(BoardOptions {
|
||||
max_moves_per_piece,
|
||||
});
|
||||
|
||||
let mut piece_total = 0;
|
||||
let mut piece_success = 0;
|
||||
for _ in 0..num_pieces {
|
||||
@@ -136,10 +151,10 @@ fn try_generate(
|
||||
piece_total += 1;
|
||||
|
||||
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 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());
|
||||
let puzzle = board.solve();
|
||||
if puzzle.solutions.len() > 0 {
|
||||
@@ -186,7 +201,7 @@ mod tests {
|
||||
#[test]
|
||||
fn generator_smoke() {
|
||||
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");
|
||||
assert_eq!(board.game_state, BoardState::InProgress);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user