move to retained mode complete

This commit is contained in:
cool-mist
2026-03-15 17:24:41 +05:30
parent 194136dfc7
commit 82d13cfd66
12 changed files with 544 additions and 540 deletions
Binary file not shown.
+2 -276
View File
@@ -4,11 +4,7 @@ mod logic;
use std::collections::HashMap;
use crate::{
game,
resources::{self, Resources, SoundKind},
widgets::*,
};
use crate::{resources::Resources, widgets::*};
use macroquad::prelude::*;
use sol_lib::{board::BoardState, generator::Puzzle};
@@ -16,14 +12,12 @@ use sol_lib::{board::BoardState, generator::Puzzle};
pub struct Game {
resources: Resources,
// Game state
settings: Settings,
puzzle: Puzzle,
show_rules: bool,
game_mode: GameMode,
game_mode_btns: HashMap<GameMode, WidgetId>,
// Render state
window_height: f32,
window_width: f32,
@@ -44,109 +38,6 @@ pub struct Game {
btn_generate: WidgetId,
}
#[derive(Eq, PartialEq, Copy, Clone, Hash, Default)]
pub struct WidgetId(u64);
pub enum Widget {
Label(LabelWidget),
Counter(CounterWidget),
Button(ButtonWidget),
Board(BoardWidget),
}
impl Widget {
fn draw(&mut self, resources: &Resources, settings: &Settings) {
match self {
Widget::Label(label) => label.draw(&resources),
Widget::Counter(counter) => counter.draw(&resources),
Widget::Button(btn) => btn.draw(&resources),
Widget::Board(board) => board.draw(&resources, &settings),
}
}
fn update(
&mut self,
interaction: &Interaction,
resources: &Resources,
settings: &Settings,
) -> Command {
match self {
Widget::Label(_) => Command::NoInteraction,
Widget::Counter(c) => handle_counter_input(interaction, c, resources, settings),
Widget::Button(b) => handle_button_input(interaction, b, resources, settings),
Widget::Board(_) => Command::NoInteraction,
}
}
}
fn handle_counter_input(
interaction: &Interaction,
counter: &mut CounterWidget,
resources: &Resources,
settings: &Settings,
) -> Command {
let counter_interaction = counter.handle_input(interaction, resources, settings);
let Some(interaction_type) = counter_interaction.interaction_type else {
return Command::NoInteraction;
};
match interaction_type {
CounterInteractionType::Increment => (counter.on_increment)(counter_interaction),
CounterInteractionType::Decrement => (counter.on_decrement)(counter_interaction),
}
}
fn handle_button_input(
interaction: &Interaction,
button: &mut ButtonWidget,
resources: &Resources,
settings: &Settings,
) -> Command {
let clicked = button
.handle_input(interaction, resources, settings)
.clicked;
if clicked {
return (button.on_click)(button);
}
Command::NoInteraction
}
macro_rules! get_widget_fn {
($name:ident, $ret:ty, $variant:path) => {
fn $name(widgets: &mut HashMap<WidgetId, Widget>, id: WidgetId) -> &mut $ret {
match widgets.get_mut(&id) {
Some(w) => match w {
$variant(v) => return v,
_ => panic!("Widget with id {} not found", id.0),
},
None => panic!("Widget with id {} not found", id.0),
}
}
};
}
get_widget_fn!(label, LabelWidget, Widget::Label);
get_widget_fn!(button, ButtonWidget, Widget::Button);
get_widget_fn!(counter, CounterWidget, Widget::Counter);
get_widget_fn!(board, BoardWidget, Widget::Board);
macro_rules! widget_accessor {
($name:ident, $fn:ident) => {
macro_rules! $name {
($this:expr, $widget_id:ident) => {
game::$fn(&mut $this.widgets, $this.$widget_id)
};
}
pub(crate) use $name;
};
}
widget_accessor!(w_button, button);
widget_accessor!(w_label, label);
widget_accessor!(w_counter, counter);
widget_accessor!(w_board, board);
#[derive(Default)]
pub struct Settings {
pub volume: f32,
@@ -169,6 +60,7 @@ impl Default for GameMode {
}
}
#[derive(Copy, Clone)]
pub enum Command {
NoInteraction,
ResetBoard,
@@ -183,169 +75,3 @@ pub enum Command {
HideRules,
Quit,
}
pub async fn new_game() -> Game {
let resources = resources::init().await;
let game_mode = GameMode::Medium;
let settings = Settings {
volume: constants::VOLUME,
max_age: 3,
max_pieces: 2,
..Default::default()
};
let puzzle = logic::generate_new_puzzle(game_mode, &settings);
let game_mode = GameMode::Medium;
let label_heading = LabelWidget::initialize_state(constants::HEADING_TEXT);
let label_pieces = LabelWidget::initialize_state(constants::PIECES_LABEL_TEXT);
let label_max_age = LabelWidget::initialize_state(constants::AGE_LABEL_TEXT);
let show_rules = false;
let mut widgets = HashMap::new();
let label_pieces = add_widget(&mut widgets, Widget::Label(label_pieces));
let label_max_age = add_widget(&mut widgets, Widget::Label(label_max_age));
let label_heading = add_widget(&mut widgets, Widget::Label(label_heading));
let mut w_counter_pieces =
CounterWidget::new(2, 7, settings.max_pieces, false, UiColor::Yellow);
w_counter_pieces.on_increment = |c| Command::UpdateMaxPieceCount(c.current);
w_counter_pieces.on_decrement = |c| Command::UpdateMaxPieceCount(c.current);
let counter_pieces = add_widget(&mut widgets, Widget::Counter(w_counter_pieces));
let mut w_counter_moves = CounterWidget::new(1, 7, settings.max_age, false, UiColor::Yellow);
w_counter_moves.on_increment = |c| Command::UpdateMaxAge(c.current);
w_counter_moves.on_decrement = |c| Command::UpdateMaxAge(c.current);
let counter_moves = add_widget(&mut widgets, Widget::Counter(w_counter_moves));
let btn_reset = add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
true,
UiColor::Yellow,
constants::RESET_BUTTON_TEXT,
SoundKind::Button,
)),
);
let btn_next = add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
false,
UiColor::Green,
constants::NEXT_BUTTON_TEXT,
SoundKind::Button,
)),
);
let btn_rules = add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
true,
UiColor::Brown,
constants::RULES_BUTTON_TEXT,
SoundKind::Button,
)),
);
let btn_generate = add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
false,
UiColor::Pink,
constants::GENERATE_BUTTON_TEXT,
SoundKind::Button,
)),
);
let mut game_mode_btns = HashMap::new();
game_mode_btns.insert(
GameMode::Easy,
add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
true,
UiColor::Yellow,
constants::EASY_BUTTON_TEXT,
SoundKind::Mode,
)),
),
);
game_mode_btns.insert(
GameMode::Medium,
add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
true,
UiColor::Yellow,
constants::MEDIUM_BUTTON_TEXT,
SoundKind::Mode,
)),
),
);
game_mode_btns.insert(
GameMode::Hard,
add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
true,
UiColor::Yellow,
constants::HARD_BUTTON_TEXT,
SoundKind::Mode,
)),
),
);
game_mode_btns.insert(
GameMode::Custom,
add_widget(
&mut widgets,
Widget::Button(ButtonWidget::new(
true,
UiColor::Blue,
constants::CUSTOM_BUTTON_TEXT,
SoundKind::Mode,
)),
),
);
let active_game_mode_btn = game_mode_btns.get(&game_mode).unwrap();
game::button(&mut widgets, *active_game_mode_btn).is_active = false;
let board = add_widget(
&mut widgets,
Widget::Board(BoardWidget::initialize_state(
puzzle.board.size,
puzzle.board.clone(),
)),
);
Game {
puzzle,
board,
resources,
settings,
game_mode,
game_mode_btns,
btn_reset,
btn_next,
btn_rules,
show_rules,
widgets,
counter_pieces,
counter_moves,
btn_generate,
label_pieces,
label_max_age,
label_heading,
..Default::default()
}
}
fn add_widget(widgets: &mut HashMap<WidgetId, Widget>, w: Widget) -> WidgetId {
let id = rand::rand() as u64;
let widget_id = WidgetId(id);
widgets.insert(widget_id, w);
widget_id
}
+69 -59
View File
@@ -1,6 +1,6 @@
use super::{Game, constants};
use crate::{
game::{self, GameMode, w_board, w_button, w_counter, w_label},
game::{self, GameMode},
widgets::*,
};
use macroquad::{math, prelude::*};
@@ -33,92 +33,102 @@ impl Game {
heading_text_dims.width,
heading_text_dims.height,
);
w_label!(self, label_heading).initialize_drawables(
heading_rect,
heading_font_size,
&self.resources,
);
w_label!(self, label_heading).place(heading_rect, heading_font_size, &self.resources);
let board_rect = Rect::new(board_x, board_y, board_width, board_width);
w_board!(self, board).initialize_drawables(
square_width,
board_rect,
heading_font_size * 0.4,
);
w_board!(self, board).place(square_width, board_rect, heading_font_size * 0.4);
// Buttons
let btn_h = constants::BUTTON_HEIGHT_MULTIPLIER * min_dimension;
let btn_w = constants::BUTTON_WIDTH_MULTIPLIER * board_width;
let btn_font_size = heading_font_size * 0.3;
// Bottom row
let bottom_row_y =
board_width + board_y + constants::BOTTOM_ROW_OFFSET_MULTIPLIER * square_width;
w_button!(self, btn_rules).place(Rect::new(
board_x + (square_width - btn_w) / 2.,
bottom_row_y,
btn_w,
btn_h,
));
w_button!(self, btn_rules).place(
Rect::new(
board_x + (square_width - btn_w) / 2.,
bottom_row_y,
btn_w,
btn_h,
),
FontSize::Fixed(btn_font_size),
);
w_button!(self, btn_reset).place(Rect::new(
board_x + square_width + (square_width - btn_w) / 2.,
bottom_row_y,
btn_w,
btn_h,
));
w_button!(self, btn_reset).place(
Rect::new(
board_x + square_width + (square_width - btn_w) / 2.,
bottom_row_y,
btn_w,
btn_h,
),
FontSize::Fixed(btn_font_size),
);
let btn_next_x_offset = (board_width - square_width) + (square_width - btn_w) / 2.;
w_button!(self, btn_next).place(Rect::new(
board_x + btn_next_x_offset,
bottom_row_y,
btn_w,
btn_h,
));
w_button!(self, btn_next).place(
Rect::new(board_x + btn_next_x_offset, bottom_row_y, btn_w, btn_h),
FontSize::Fixed(btn_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).place(Rect::new(
right_column_x,
board_y + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
self.get_game_mode_button(&GameMode::Easy).place(
Rect::new(
right_column_x,
board_y + (square_width - btn_h) / 2.,
btn_w,
btn_h,
),
FontSize::Fixed(btn_font_size),
);
self.get_game_mode_button(&GameMode::Medium)
.place(Rect::new(
self.get_game_mode_button(&GameMode::Medium).place(
Rect::new(
right_column_x,
board_y + square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
),
FontSize::Fixed(btn_font_size),
);
self.get_game_mode_button(&GameMode::Hard).place(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::Hard).place(
Rect::new(
right_column_x,
board_y + 2. * square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
),
FontSize::Fixed(btn_font_size),
);
self.get_game_mode_button(&GameMode::Custom)
.place(Rect::new(
self.get_game_mode_button(&GameMode::Custom).place(
Rect::new(
right_column_x,
board_y + 3. * square_width + (square_width - btn_h) / 2.,
btn_w,
btn_h,
));
),
FontSize::Fixed(btn_font_size),
);
// Right column 2
let right_column_2_x =
right_column_x + btn_w + constants::RIGHT_COLUMN_OFFSET_MULTIPLIER * square_width;
w_button!(self, btn_generate).place(Rect::new(
right_column_2_x,
board_y + board_width - square_width + (square_width - btn_h) / 2.,
2. * btn_w,
btn_h,
));
w_button!(self, btn_generate).place(
Rect::new(
right_column_2_x,
board_y + board_width - square_width + (square_width - btn_h) / 2.,
2. * btn_w,
btn_h,
),
FontSize::Fixed(btn_font_size),
);
w_label!(self, label_pieces).initialize_drawables(
w_label!(self, label_pieces).place(
Rect::new(
right_column_2_x,
board_y + (square_width + btn_h) / 2.,
@@ -129,20 +139,20 @@ impl Game {
&self.resources,
);
w_counter!(self, counter_pieces).initialize_drawables(
w_counter!(self, counter_pieces).place(
Rect::new(
right_column_2_x,
board_y + square_width + (2. * square_width - 2. * btn_h) / 2.,
btn_w,
2. * btn_h,
),
30.,
0.50 * btn_h,
&self.resources,
);
// Right column 3
let right_column_3_x = right_column_2_x + btn_w;
w_label!(self, label_max_age).initialize_drawables(
w_label!(self, label_max_age).place(
Rect::new(
right_column_2_x + btn_w,
board_y + (square_width + btn_h) / 2.,
@@ -152,14 +162,14 @@ impl Game {
0.30 * btn_h,
&self.resources,
);
w_counter!(self, counter_moves).initialize_drawables(
w_counter!(self, counter_moves).place(
Rect::new(
right_column_3_x,
board_y + square_width + (2. * square_width - 2. * btn_h) / 2.,
btn_w,
2. * btn_h,
),
30.,
0.50 * btn_h,
&self.resources,
);
}
+174 -69
View File
@@ -1,7 +1,9 @@
use std::collections::HashMap;
use super::{Game, GameMode, Settings, constants};
use crate::{
game::{self, Command, w_board, w_button, w_counter},
resources::*,
game::{self, Command},
resources::{self, *},
widgets::*,
};
use macroquad::prelude::*;
@@ -11,80 +13,175 @@ use sol_lib::{
};
impl Game {
pub async fn new() -> Self {
let resources = resources::init().await;
let game_mode = GameMode::Medium;
let settings = Settings {
volume: constants::VOLUME,
max_age: 3,
max_pieces: 2,
..Default::default()
};
let puzzle = generate_new_puzzle(game_mode, &settings);
let game_mode = GameMode::Medium;
let show_rules = false;
let mut widgets = HashMap::new();
let w_label_heading = LabelWidget::new(constants::HEADING_TEXT);
let label_heading = add_widget(&mut widgets, Widget::Label(w_label_heading));
let w_label_pieces = LabelWidget::new(constants::PIECES_LABEL_TEXT);
let label_pieces = add_widget(&mut widgets, Widget::Label(w_label_pieces));
let w_label_max_age = LabelWidget::new(constants::AGE_LABEL_TEXT);
let label_max_age = add_widget(&mut widgets, Widget::Label(w_label_max_age));
let mut w_counter_pieces =
CounterWidget::new(2, 7, settings.max_pieces, false, UiColor::Yellow);
w_counter_pieces.on_increment = |c| Command::UpdateMaxPieceCount(c.current);
w_counter_pieces.on_decrement = |c| Command::UpdateMaxPieceCount(c.current);
let counter_pieces = add_widget(&mut widgets, Widget::Counter(w_counter_pieces));
let mut w_counter_moves =
CounterWidget::new(1, 7, settings.max_age, false, UiColor::Yellow);
w_counter_moves.on_increment = |c| Command::UpdateMaxAge(c.current);
w_counter_moves.on_decrement = |c| Command::UpdateMaxAge(c.current);
let counter_moves = add_widget(&mut widgets, Widget::Counter(w_counter_moves));
let mut w_btn_reset = ButtonWidget::new(
true,
UiColor::Yellow,
constants::RESET_BUTTON_TEXT,
SoundKind::Button,
);
w_btn_reset.on_click = |_| Command::ResetBoard;
let btn_reset = add_widget(&mut widgets, Widget::Button(w_btn_reset));
let mut w_btn_next = ButtonWidget::new(
false,
UiColor::Green,
constants::NEXT_BUTTON_TEXT,
SoundKind::Button,
);
w_btn_next.on_click = |_| Command::NextPuzzle;
let btn_next = add_widget(&mut widgets, Widget::Button(w_btn_next));
let mut w_btn_rules = ButtonWidget::new(
true,
UiColor::Brown,
constants::RULES_BUTTON_TEXT,
SoundKind::Button,
);
w_btn_rules.on_click = |_| Command::ToggleRules;
let btn_rules = add_widget(&mut widgets, Widget::Button(w_btn_rules));
let mut w_btn_generate = ButtonWidget::new(
false,
UiColor::Pink,
constants::GENERATE_BUTTON_TEXT,
SoundKind::Button,
);
w_btn_generate.on_click = |_| Command::NewCustomPuzzle;
let btn_generate = add_widget(&mut widgets, Widget::Button(w_btn_generate));
let mut game_mode_btns = HashMap::new();
let mut w_game_mode_easy_btn = ButtonWidget::new(
true,
UiColor::Yellow,
constants::EASY_BUTTON_TEXT,
SoundKind::Mode,
);
w_game_mode_easy_btn.on_click = |_| Command::SwitchGameMode(GameMode::Easy);
game_mode_btns.insert(
GameMode::Easy,
add_widget(&mut widgets, Widget::Button(w_game_mode_easy_btn)),
);
let mut w_game_mode_medium_btn = ButtonWidget::new(
true,
UiColor::Yellow,
constants::MEDIUM_BUTTON_TEXT,
SoundKind::Mode,
);
w_game_mode_medium_btn.on_click = |_| Command::SwitchGameMode(GameMode::Medium);
game_mode_btns.insert(
GameMode::Medium,
add_widget(&mut widgets, Widget::Button(w_game_mode_medium_btn)),
);
let mut w_game_mode_hard_btn = ButtonWidget::new(
true,
UiColor::Yellow,
constants::HARD_BUTTON_TEXT,
SoundKind::Mode,
);
w_game_mode_hard_btn.on_click = |_| Command::SwitchGameMode(GameMode::Hard);
game_mode_btns.insert(
GameMode::Hard,
add_widget(&mut widgets, Widget::Button(w_game_mode_hard_btn)),
);
let mut w_game_mode_custom_btn = ButtonWidget::new(
true,
UiColor::Blue,
constants::CUSTOM_BUTTON_TEXT,
SoundKind::Mode,
);
w_game_mode_custom_btn.on_click = |_| Command::SwitchGameMode(GameMode::Custom);
game_mode_btns.insert(
GameMode::Custom,
add_widget(&mut widgets, Widget::Button(w_game_mode_custom_btn)),
);
let active_game_mode_btn = game_mode_btns.get(&game_mode).unwrap();
game::button(&mut widgets, *active_game_mode_btn).is_active = false;
let mut w_board = BoardWidget::new(puzzle.board.size, puzzle.board.clone());
w_board.on_board_state_changed = |s| Command::UpdateBoard(s.new_board_state);
let board = add_widget(&mut widgets, Widget::Board(w_board));
let mut w_controls = ControlsWidget::new();
w_controls.add(KeyCode::Q, Command::Quit);
w_controls.add(KeyCode::Escape, Command::HideRules);
w_controls.add(KeyCode::D, Command::ToggleDebug);
add_widget(&mut widgets, Widget::Controls(w_controls));
Game {
puzzle,
board,
resources,
settings,
game_mode,
game_mode_btns,
btn_reset,
btn_next,
btn_rules,
show_rules,
widgets,
counter_pieces,
counter_moves,
btn_generate,
label_pieces,
label_max_age,
label_heading,
..Default::default()
}
}
pub fn update(&mut self, interaction: Interaction) {
let command = self.get_command(interaction);
let command = self.map_to_command(interaction);
self.update_state(command);
}
fn get_command(&mut self, interaction: Interaction) -> Command {
fn map_to_command(&mut self, interaction: Interaction) -> Command {
for (_, widget) in &mut self.widgets {
let cmd = widget.update(&interaction, &self.resources, &self.settings);
let ret_cmd = match cmd {
Command::NoInteraction => false,
_ => true,
};
if ret_cmd {
return cmd;
if let Some(command) = widget.update(&interaction, &self.resources, &self.settings) {
return command;
}
}
let board_interaction =
w_board!(self, board).handle_input(&interaction, &self.resources, &self.settings);
if let Some(new_board_state) = board_interaction.new_board_state {
return Command::UpdateBoard(new_board_state);
}
if w_button!(self, btn_reset)
.handle_input(&interaction, &self.resources, &self.settings)
.clicked
{
return Command::ResetBoard;
}
if w_button!(self, btn_next)
.handle_input(&interaction, &self.resources, &self.settings)
.clicked
{
return Command::NextPuzzle;
}
for (mode, btn) in &mut self.game_mode_btns {
if game::button(&mut self.widgets, *btn)
.handle_input(&interaction, &self.resources, &self.settings)
.clicked
{
return Command::SwitchGameMode(*mode);
}
}
if w_button!(self, btn_rules)
.handle_input(&interaction, &self.resources, &self.settings)
.clicked
{
return Command::ToggleRules;
}
if w_button!(self, btn_generate)
.handle_input(&interaction, &self.resources, &self.settings)
.clicked
{
return Command::NewCustomPuzzle;
}
if interaction.keys_released.contains(&KeyCode::Escape) {
return Command::HideRules;
}
if interaction.keys_released.contains(&KeyCode::D) {
return Command::ToggleDebug;
}
if interaction.keys_released.contains(&KeyCode::Q) {
return Command::Quit;
}
return Command::NoInteraction;
}
@@ -177,7 +274,15 @@ impl Game {
}
}
pub fn generate_new_puzzle(mode: GameMode, settings: &Settings) -> Puzzle {
fn add_widget(widgets: &mut HashMap<WidgetId, Widget>, w: Widget) -> WidgetId {
let id = rand::rand() as u64;
let widget_id = WidgetId(id);
widgets.insert(widget_id, w);
widget_id
}
fn generate_new_puzzle(mode: GameMode, settings: &Settings) -> Puzzle {
let piece_count = match mode {
GameMode::Easy => 3,
GameMode::Medium => 5,
+2 -2
View File
@@ -9,7 +9,7 @@ use miniquad::date;
use game::constants;
use crate::widgets::Interaction;
use crate::{game::Game, widgets::Interaction};
fn window_conf() -> Conf {
let window_title = {
@@ -31,7 +31,7 @@ fn window_conf() -> Conf {
async fn main() {
rand::srand(date::now() as u64);
let background_color = Color::from_rgba(196, 195, 208, 255);
let mut game = game::new_game().await;
let mut game = Game::new().await;
loop {
clear_background(background_color);
+134 -8
View File
@@ -1,16 +1,16 @@
use std::collections::{HashMap, HashSet};
mod board;
use std::collections::HashSet;
mod button;
mod controls;
mod counter;
mod label;
pub use board::*;
mod button;
pub use button::*;
mod label;
pub use label::*;
mod counter;
pub use controls::*;
pub use counter::*;
pub use label::*;
use macroquad::{
audio::{self, Sound},
@@ -18,6 +18,11 @@ use macroquad::{
};
use quad_snd::PlaySoundParams;
use crate::{
game::{Command, Settings},
resources::Resources,
};
pub struct Interaction {
pub mouse_pos: Circle,
pub keys_released: HashSet<KeyCode>,
@@ -25,6 +30,127 @@ pub struct Interaction {
pub mouse_released: HashSet<MouseButton>,
}
#[derive(Eq, PartialEq, Copy, Clone, Hash, Default)]
pub struct WidgetId(pub u64);
pub enum Widget {
Label(LabelWidget),
Counter(CounterWidget),
Button(ButtonWidget),
Board(BoardWidget),
Controls(ControlsWidget),
}
impl Widget {
pub fn draw(&mut self, resources: &Resources, settings: &Settings) {
match self {
Widget::Label(label) => label.draw(&resources),
Widget::Counter(counter) => counter.draw(&resources),
Widget::Button(btn) => btn.draw(&resources),
Widget::Board(board) => board.draw(&resources, &settings),
Widget::Controls(_) => (),
}
}
pub fn update(
&mut self,
interaction: &Interaction,
resources: &Resources,
settings: &Settings,
) -> Option<Command> {
match self {
Widget::Label(_) => None,
Widget::Counter(c) => handle_counter_input(interaction, c, resources, settings),
Widget::Button(b) => handle_button_input(interaction, b, resources, settings),
Widget::Board(b) => handle_board_input(interaction, b, resources, settings),
Widget::Controls(c) => c.handle_input(interaction),
}
}
}
fn handle_board_input(
interaction: &Interaction,
b: &mut BoardWidget,
resources: &Resources,
settings: &Settings,
) -> Option<Command> {
let board_interaction = b.handle_input(interaction, resources, settings);
let Some(board_interaction) = board_interaction else {
return None;
};
Some((b.on_board_state_changed)(board_interaction))
}
fn handle_counter_input(
interaction: &Interaction,
counter: &mut CounterWidget,
resources: &Resources,
settings: &Settings,
) -> Option<Command> {
let counter_interaction = counter.handle_input(interaction, resources, settings);
let Some(interaction_type) = counter_interaction.interaction_type else {
return None;
};
let command = match interaction_type {
CounterInteractionType::Increment => (counter.on_increment)(counter_interaction),
CounterInteractionType::Decrement => (counter.on_decrement)(counter_interaction),
};
Some(command)
}
fn handle_button_input(
interaction: &Interaction,
button: &mut ButtonWidget,
resources: &Resources,
settings: &Settings,
) -> Option<Command> {
let Some(ButtonInteraction { clicked: true }) =
button.handle_input(interaction, resources, settings)
else {
return None;
};
return Some((button.on_click)(button));
}
macro_rules! get_widget_fn {
($name:ident, $ret:ty, $variant:path) => {
pub fn $name(widgets: &mut HashMap<WidgetId, Widget>, id: WidgetId) -> &mut $ret {
match widgets.get_mut(&id) {
Some(w) => match w {
$variant(v) => return v,
_ => panic!("Widget with id {} not found", id.0),
},
None => panic!("Widget with id {} not found", id.0),
}
}
};
}
get_widget_fn!(label, LabelWidget, Widget::Label);
get_widget_fn!(button, ButtonWidget, Widget::Button);
get_widget_fn!(counter, CounterWidget, Widget::Counter);
get_widget_fn!(board, BoardWidget, Widget::Board);
macro_rules! widget_accessor {
($name:ident, $fn:ident) => {
macro_rules! $name {
($this:expr, $widget_id:ident) => {
game::$fn(&mut $this.widgets, $this.$widget_id)
};
}
pub(crate) use $name;
};
}
widget_accessor!(w_button, button);
widget_accessor!(w_label, label);
widget_accessor!(w_counter, counter);
widget_accessor!(w_board, board);
#[allow(unused)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UiColor {
+67 -53
View File
@@ -2,7 +2,7 @@ use core::fmt;
use std::fmt::{Display, Formatter};
use crate::{
game::{Settings, constants},
game::{Command, Settings, constants},
resources::{Resources, SoundKind},
widgets::*,
};
@@ -11,12 +11,12 @@ use sol_lib::{
generator::Puzzle,
};
#[derive(Default)]
pub struct BoardWidget {
current_board: Board,
square_width: f32,
pub num_squares: usize, // this is a constant = 4 for now.
pub show_rules: bool,
pub on_board_state_changed: fn(BoardInteraction) -> Command,
current_board: Board,
square_width: f32,
rules_font_size: f32,
board_rect: Rect,
squares: Vec<GameSquare>,
@@ -24,23 +24,18 @@ pub struct BoardWidget {
}
pub struct BoardInteraction {
pub new_board_state: Option<BoardState>,
pub new_board_state: BoardState,
}
impl BoardWidget {
pub fn initialize_state(num_squares: usize, current_board: Board) -> BoardWidget {
pub fn new(num_squares: usize, current_board: Board) -> BoardWidget {
let mut board = BoardWidget::default();
board.num_squares = num_squares;
board.current_board = current_board;
board
}
pub fn initialize_drawables(
&mut self,
square_width: f32,
board_rect: Rect,
rules_font_size: f32,
) {
pub fn place(&mut self, square_width: f32, board_rect: Rect, rules_font_size: f32) {
self.square_width = square_width;
self.board_rect = board_rect;
self.rules_font_size = rules_font_size;
@@ -169,49 +164,18 @@ 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();
let hover_square = self.squares.iter().find(|s| {
let c = Circle::new(mx, my, 0.0);
if c.overlaps_rect(&s.rect) {
return true;
}
return false;
});
debug_lines.push(format!("Game State: {}", self.state));
debug_lines.push(format!("Board State: {}", self.current_board.game_state));
if let Some(hover_square) = hover_square {
debug_lines.push(format!("Hover: [ {}, {} ]", hover_square.i, hover_square.j));
}
self.add_debug_info(debug_lines);
}
fn add_debug_info(&self, lines: Vec<String>) {
let mut y = 20.0;
for line in lines {
draw_text(&line, 10.0, y, 20.0, BLACK);
y += 25.0;
}
}
pub fn handle_input(
&mut self,
interaction: &Interaction,
resources: &Resources,
settings: &Settings,
) -> BoardInteraction {
) -> Option<BoardInteraction> {
let mouse_pos = interaction.mouse_pos;
if mouse_pos.overlaps_rect(&self.board_rect) == false {
return BoardInteraction {
new_board_state: None,
};
return None;
}
let old_board_state = self.current_board.state;
if is_mouse_button_released(MouseButton::Left) {
let current_state = self.state.clone();
let new_state = match current_state {
@@ -247,10 +211,44 @@ impl BoardWidget {
self.state = new_state;
}
let board_state = &self.current_board.game_state;
return BoardInteraction {
new_board_state: Some(*board_state),
};
if old_board_state == self.current_board.state {
return None;
}
let board_state = self.current_board.state;
return Some(BoardInteraction {
new_board_state: board_state,
});
}
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();
let hover_square = self.squares.iter().find(|s| {
let c = Circle::new(mx, my, 0.0);
if c.overlaps_rect(&s.rect) {
return true;
}
return false;
});
debug_lines.push(format!("Game State: {}", self.state));
debug_lines.push(format!("Board State: {}", self.current_board.state));
if let Some(hover_square) = hover_square {
debug_lines.push(format!("Hover: [ {}, {} ]", hover_square.i, hover_square.j));
}
self.add_debug_info(debug_lines);
}
fn add_debug_info(&self, lines: Vec<String>) {
let mut y = 20.0;
for line in lines {
draw_text(&line, 10.0, y, 20.0, BLACK);
y += 25.0;
}
}
pub fn reset(&mut self, puzzle: &Puzzle) {
@@ -337,11 +335,11 @@ impl BoardWidget {
let m = m.expect("legal move should be found");
self.current_board.make_move(m.clone());
if self.current_board.game_state == BoardState::Won
|| self.current_board.game_state == BoardState::Lost
if self.current_board.state == BoardState::Won
|| self.current_board.state == BoardState::Lost
{
self.reset_squares();
if self.current_board.game_state == BoardState::Won {
if self.current_board.state == BoardState::Won {
play_sound_once(resources.sound(&SoundKind::Win), settings.volume);
} else {
play_sound_once(resources.sound(&SoundKind::Loss), settings.volume);
@@ -431,3 +429,19 @@ struct PieceDrawTextureParams<'a> {
texture: &'a Texture2D,
draw_text_params: DrawTextureParams,
}
impl Default for BoardWidget {
fn default() -> Self {
Self {
on_board_state_changed: |_| Command::NoInteraction,
num_squares: Default::default(),
show_rules: Default::default(),
current_board: Default::default(),
square_width: Default::default(),
rules_font_size: Default::default(),
board_rect: Default::default(),
squares: Default::default(),
state: Default::default(),
}
}
}
+33 -21
View File
@@ -12,6 +12,7 @@ pub struct ButtonWidget {
rect: Rect,
shadow_width: f32,
sound: SoundKind,
font_size: FontSize,
pub on_click: fn(&mut ButtonWidget) -> Command,
}
@@ -20,15 +21,17 @@ pub struct ButtonInteraction {
}
impl ButtonInteraction {
fn not_clicked() -> Self {
Self { clicked: false }
}
fn clicked() -> Self {
Self { clicked: true }
}
}
#[derive(Copy, Clone)]
pub enum FontSize {
Fixed(f32),
Fill,
}
impl ButtonWidget {
pub fn new(is_active: bool, color: UiColor, text: impl Into<String>, sound: SoundKind) -> Self {
Self {
@@ -41,8 +44,9 @@ impl ButtonWidget {
}
}
pub fn place(&mut self, rect: Rect) {
pub fn place(&mut self, rect: Rect, font_size: FontSize) {
self.rect = rect;
self.font_size = font_size;
}
pub fn draw(&self, resources: &Resources) {
@@ -86,19 +90,26 @@ impl ButtonWidget {
};
let font = resources.font();
let target_w = self.rect.w * 0.9;
let target_h = self.rect.h;
let mut font_size = 20;
let mut dims = measure_text(&text, Some(&font), font_size, 1.0);
loop {
if dims.width <= target_w && dims.height <= target_h {
break;
let font_size = match self.font_size {
FontSize::Fixed(s) => s as u16,
FontSize::Fill => {
let target_w = self.rect.w * 0.9;
let target_h = self.rect.h;
let mut font_size = 22;
loop {
font_size -= 2;
let dims = measure_text(&text, Some(&font), font_size as u16, 1.0);
if dims.width <= target_w && dims.height <= target_h {
break;
}
}
font_size
}
};
font_size -= 2;
dims = measure_text(&text, Some(&font), font_size as u16, 1.0);
}
let dims = measure_text(&text, Some(&font), font_size, 1.0);
let button_draw_offset = self.get_button_draw_offset();
let text_params = TextParams {
font_size,
@@ -130,17 +141,17 @@ impl ButtonWidget {
interaction: &Interaction,
resources: &Resources,
settings: &Settings,
) -> ButtonInteraction {
) -> Option<ButtonInteraction> {
if !self.is_active {
self.is_down = false;
return ButtonInteraction::not_clicked();
return None;
}
let mouse = interaction.mouse_pos;
if interaction.mouse_pressed.contains(&MouseButton::Left) {
if mouse.overlaps_rect(&self.rect) {
self.is_down = true;
return ButtonInteraction::not_clicked();
return None;
}
}
@@ -150,13 +161,13 @@ impl ButtonWidget {
let volume = settings.volume;
play_sound_once(&click_sound, volume);
self.is_down = false;
return ButtonInteraction::clicked();
return Some(ButtonInteraction::clicked());
}
self.is_down = false;
}
ButtonInteraction::not_clicked()
None
}
}
@@ -170,6 +181,7 @@ impl Default for ButtonWidget {
color: UiColor::Grey,
text: String::new(),
sound: SoundKind::Click,
font_size: FontSize::Fill,
on_click: |_| Command::NoInteraction,
}
}
+31
View File
@@ -0,0 +1,31 @@
use std::collections::HashMap;
use macroquad::input::KeyCode;
use crate::{game::Command, widgets::Interaction};
pub struct ControlsWidget {
key_release_bindings: HashMap<KeyCode, Command>,
}
impl ControlsWidget {
pub fn new() -> Self {
Self {
key_release_bindings: HashMap::new(),
}
}
pub fn add(&mut self, key: KeyCode, command: Command) {
self.key_release_bindings.insert(key, command);
}
pub fn handle_input(&self, interaction: &Interaction) -> Option<Command> {
for (key, command) in &self.key_release_bindings {
if interaction.keys_released.contains(&key) {
return Some(*command);
}
}
None
}
}
+16 -36
View File
@@ -77,12 +77,7 @@ impl CounterWidget {
self.decrement.is_active = is_active;
}
pub fn initialize_drawables(
&mut self,
total_area: Rect,
font_size: f32,
resources: &Resources,
) {
pub fn place(&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;
@@ -94,10 +89,10 @@ impl CounterWidget {
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.place(increment_rect);
self.increment.place(increment_rect, FontSize::Fill);
let decrement_rect = Rect::new(btn_x, decrement_y, btn_w, btn_h);
self.decrement.place(decrement_rect);
self.decrement.place(decrement_rect, FontSize::Fill);
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);
@@ -112,25 +107,27 @@ impl CounterWidget {
resources: &Resources,
settings: &Settings,
) -> CounterWidgetInteraction {
if self
if let Some(interaction) = self
.increment
.handle_input(&interaction, &resources, &settings)
.clicked
{
if self.current < self.max {
self.current += 1;
return CounterWidgetInteraction::increment(self.current);
if interaction.clicked {
if self.current < self.max {
self.current += 1;
return CounterWidgetInteraction::increment(self.current);
}
}
}
if self
if let Some(interaction) = self
.decrement
.handle_input(&interaction, &resources, &settings)
.clicked
{
if self.current > self.min {
self.current -= 1;
return CounterWidgetInteraction::decrement(self.current);
if interaction.clicked {
if self.current > self.min {
self.current -= 1;
return CounterWidgetInteraction::decrement(self.current);
}
}
}
@@ -141,24 +138,7 @@ impl CounterWidget {
self.increment.draw(resources);
self.decrement.draw(resources);
let font = resources.font();
let mut min_dim = self.visual_rect.w.min(self.visual_rect.h);
let mut font_size = self.font_size as u16;
loop {
if min_dim <= self.visual_rect.w * 0.5 {
break;
}
let dims = measure_text(
&self.current.to_string(),
Some(&font),
font_size as u16,
1.0,
);
min_dim = dims.width.min(dims.height);
font_size -= 1;
}
let font_size = self.font_size as u16;
draw_text_ex(
&self.current.to_string(),
self.visual_rect.x + (self.visual_rect.w - self.visual_dims.width) / 2.,
+2 -2
View File
@@ -11,14 +11,14 @@ pub struct LabelWidget {
}
impl LabelWidget {
pub fn initialize_state(text: &str) -> Self {
pub fn new(text: &str) -> Self {
Self {
text: text.to_string(),
..Default::default()
}
}
pub fn initialize_drawables(&mut self, rect: Rect, font_size: f32, resources: &Resources) {
pub fn place(&mut self, rect: Rect, font_size: f32, resources: &Resources) {
self.rect = rect;
self.font_size = font_size;
self.text_dimensions =
+14 -14
View File
@@ -23,7 +23,7 @@ use crate::{board::piece::Piece, generator::Puzzle};
pub struct Board {
pub cells: [[Option<Piece>; BOARD_SIZE]; BOARD_SIZE],
pub legal_moves: HashSet<CMove>,
pub game_state: BoardState,
pub state: BoardState,
pub id: String,
pub size: usize,
pieces_remaining: u8,
@@ -64,7 +64,7 @@ impl Board {
legal_moves: HashSet::new(),
id,
pieces_remaining: 0,
game_state: BoardState::NotStarted,
state: BoardState::NotStarted,
size: BOARD_SIZE,
max_moves_per_piece: options.max_moves_per_piece,
}
@@ -190,7 +190,7 @@ impl Board {
next_move: CMove,
}
if let BoardState::Won = self.game_state {
if let BoardState::Won = self.state {
return Puzzle {
board: self.clone(),
solutions: vec![vec![]],
@@ -223,7 +223,7 @@ impl Board {
let (mut board, mut moves_so_far, next) = (top.board, top.moves_so_far, top.next_move);
board.make_move(next.clone());
match board.game_state {
match board.state {
BoardState::Won => {
moves_so_far.push(next);
solutions.push(moves_so_far);
@@ -374,7 +374,7 @@ impl Board {
}
fn calc_game_state(&mut self) {
self.game_state = if self.pieces_remaining == 0 {
self.state = if self.pieces_remaining == 0 {
BoardState::NotStarted
} else if self.pieces_remaining == 1 {
BoardState::Won
@@ -674,7 +674,7 @@ mod tests {
#[test]
fn test_smoke_puzzle() {
let mut board = Board::new();
assert_eq!(BoardState::NotStarted, board.game_state);
assert_eq!(BoardState::NotStarted, board.state);
assert_eq!(0, board.pieces_remaining);
// K . . .
@@ -682,22 +682,22 @@ mod tests {
// . . R .
// N . . .
board.set(sq!("Ka4"));
assert_eq!(BoardState::Won, board.game_state);
assert_eq!(BoardState::Won, board.state);
board.set(sq!("Pb3"));
board.set(sq!("Rc2"));
board.set(sq!("Na1"));
assert_eq!(BoardState::InProgress, board.game_state);
assert_eq!(BoardState::InProgress, board.state);
assert_eq!(4, board.pieces_remaining);
assert!(board.make_move(mv!("Na1", "Rc2")).is_some());
assert_eq!(3, board.pieces_remaining);
assert_eq!(BoardState::InProgress, board.game_state);
assert_eq!(BoardState::InProgress, board.state);
assert!(board.make_move(mv!("Pb3", "Ka4")).is_some());
assert_eq!(2, board.pieces_remaining);
assert_eq!(BoardState::Lost, board.game_state);
assert_eq!(BoardState::Lost, board.state);
// P . . .
// . . . .
@@ -712,12 +712,12 @@ mod tests {
// . . N .
// P . . .
assert_eq!(4, board.pieces_remaining);
assert_eq!(BoardState::InProgress, board.game_state);
assert_eq!(BoardState::InProgress, board.state);
board.make_move(mv!("Qa3", "Pa4"));
board.make_move(mv!("Nc2", "Pa1"));
assert_eq!(2, board.pieces_remaining);
assert_eq!(BoardState::InProgress, board.game_state);
assert_eq!(BoardState::InProgress, board.state);
// Q . . .
// . . . .
@@ -725,7 +725,7 @@ mod tests {
// N . . .
board.make_move(mv!("Qa4", "Na1"));
assert_eq!(1, board.pieces_remaining);
assert_eq!(BoardState::Won, board.game_state);
assert_eq!(BoardState::Won, board.state);
}
#[test]
@@ -775,7 +775,7 @@ mod tests {
solution
.into_iter()
.for_each(|m| assert!(board.make_move(m).is_some()));
assert_eq!(BoardState::Won, board.game_state);
assert_eq!(BoardState::Won, board.state);
}
}