diff --git a/assets/faces.png b/assets/faces.png index 968fdbd..c2bb0dd 100644 Binary files a/assets/faces.png and b/assets/faces.png differ diff --git a/minesweeper.html b/minesweeper.html index 7c72608..1de8186 100644 --- a/minesweeper.html +++ b/minesweeper.html @@ -32,7 +32,7 @@ <script src="https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js"></script> <script> load("./minesweeper.wasm"); - </script> <!-- Your compiled wasm file --> + </script> <script> document.addEventListener("contextmenu", (e) => { e.preventDefault(); diff --git a/src/gui.rs b/src/gui.rs index a33fd9c..e2e418e 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,6 +1,7 @@ mod board_render; mod highlighter; pub mod settings_menu; +mod seven_segment; pub mod texture_store; mod tile_render; pub mod top_menu; @@ -86,9 +87,6 @@ impl UIState { self.letterbox.1 = 0f32; } } -} - -impl UIState { pub fn pixel_screen_offset(&self, x: usize, y: usize) -> (f32, f32) { let (x, y) = self.pixel_screen_scale(x, y); let x = x + self.letterbox.0; diff --git a/src/gui/highlighter.rs b/src/gui/highlighter.rs index 6c00e2a..77d7c28 100644 --- a/src/gui/highlighter.rs +++ b/src/gui/highlighter.rs @@ -26,30 +26,30 @@ pub enum Highlight { Wide, } +// The highlighter is responsible for the selection when a tile is clicked and held either with left or middle click impl Highlighter { pub fn events(&mut self, ui_state: &UIState, event_handler: &mut Events<GUIEvent>, game_board: &mut GameBoard) { - if !ui_state.frozen && ui_state.mouse_in_minefield { - if is_mouse_button_pressed(MouseButton::Left) { - self.highlight = Highlight::Normal; + if !ui_state.frozen { + if ui_state.mouse_in_minefield { + if is_mouse_button_pressed(MouseButton::Left) { + self.highlight = Highlight::Normal; + } + if is_mouse_button_pressed(MouseButton::Middle) { + self.highlight = Highlight::Wide; + self.check_reveal(event_handler, ui_state, game_board) + } } - if is_mouse_button_pressed(MouseButton::Middle) { - self.highlight = Highlight::Wide; - self.check_reveal(event_handler, ui_state, game_board) - } - } - if is_mouse_button_released(MouseButton::Left) { - self.reset_highlight(ui_state, event_handler); - if !ui_state.frozen { + if is_mouse_button_released(MouseButton::Left) { + self.reset_highlight(ui_state, event_handler); event_handler.add(GUIEvent::SetSmileyState(SmileyState::Chillin)); } - } - if is_mouse_button_released(MouseButton::Middle) { - self.reset_highlight(ui_state, event_handler); - if !ui_state.frozen { + if is_mouse_button_released(MouseButton::Middle) { + self.reset_highlight(ui_state, event_handler); event_handler.add(GUIEvent::SetSmileyState(SmileyState::Chillin)); } } } + fn check_reveal(&self, event_handler: &mut Events<GUIEvent>, interface: &UIState, game_board: &mut GameBoard) { let (x, y) = interface.cursor; if let Some(tile) = game_board.get_tile_mut(x, y) { diff --git a/src/gui/settings_menu.rs b/src/gui/settings_menu.rs index 51a2203..97afb60 100644 --- a/src/gui/settings_menu.rs +++ b/src/gui/settings_menu.rs @@ -1,12 +1,24 @@ use crate::{logic::game_board::ModifyMode, util::Events}; -use super::{texture_store::TextureStore, GUIEvent, Language, UIState}; +use super::{seven_segment::draw_seven_segment_unscaled, texture_store::TextureStore, GUIEvent, Language, UIState}; use macroquad::{ hash, prelude::*, ui::{root_ui, widgets, Skin, Ui}, }; +const NEW_GAME_HEIGHT: f32 = 40f32; +const NEW_GAME_WIDTH: f32 = 250f32; +const BUTTON_MENU_WIDTH: f32 = 250f32; +const BUTTON_SIZE: f32 = 100f32; +const BUTTON_MENU_Y: f32 = 400f32; +const BUTTON_MENU_LABEL_HEIGHT: f32 = 20f32; + +const MIN_MINEFIELD_WIDTH: usize = 5; +const MAX_MINEFIELD_WIDTH: usize = 100; +const MIN_MINEFIELD_HEIGHT: usize = 5; +const MAX_MINEFIELD_HEIGHT: usize = 100; + pub struct SettingsMenu { mines: usize, width: usize, @@ -46,16 +58,13 @@ impl SettingsMenu { ui.pop_skin(); ui.push_skin(&skin); let half_screen_width = screen_width * 0.5; - const MIN_MINEFIELD_WIDTH: usize = 5; - const MAX_MINEFIELD_WIDTH: usize = 100; - const MIN_MINEFIELD_HEIGHT: usize = 5; - // const MAX_MINEFIELD_HEIGHT: usize = 100; + render_counter( &mut self.width, ui, &textures, vec2(half_screen_width, 100f32), - String::from("Minefield Width"), + "Minefield Width", MIN_MINEFIELD_WIDTH, MAX_MINEFIELD_WIDTH, ); @@ -64,21 +73,19 @@ impl SettingsMenu { ui, &textures, vec2(half_screen_width, 200f32), - String::from("Minefield Height"), - MIN_MINEFIELD_WIDTH, + "Minefield Height", MIN_MINEFIELD_HEIGHT, + MAX_MINEFIELD_HEIGHT, ); render_counter( &mut self.mines, ui, &textures, vec2(half_screen_width, 300f32), - String::from("Mines"), + "Mines", 1, self.width * self.height - 10, ); - const NEW_GAME_HEIGHT: f32 = 40f32; - const NEW_GAME_WIDTH: f32 = 250f32; if widgets::Button::new("New Game") .size(vec2(NEW_GAME_WIDTH, NEW_GAME_HEIGHT)) .position(vec2((screen_width - NEW_GAME_WIDTH) * 0.5, 0.0)) @@ -87,12 +94,9 @@ impl SettingsMenu { event_handler.add(GUIEvent::CreateNewGame(self.width, self.height, self.mines)); event_handler.add(GUIEvent::CloseSettings); } - const BUTTON_MENU_WIDTH: f32 = 250f32; let language_button_x = (screen_width - BUTTON_MENU_WIDTH) * 0.5; - const BUTTON_SIZE: f32 = 100f32; - const BUTTON_MENU_Y: f32 = 400f32; let question_button_x = (screen_width - BUTTON_MENU_WIDTH) * 0.5 + (BUTTON_MENU_WIDTH - BUTTON_SIZE); - const BUTTON_MENU_LABEL_HEIGHT: f32 = 20f32; + widgets::Label::new("Language") .position(vec2(language_button_x, BUTTON_MENU_Y - BUTTON_MENU_LABEL_HEIGHT)) .size(vec2(BUTTON_SIZE, BUTTON_MENU_LABEL_HEIGHT)) @@ -141,36 +145,23 @@ impl SettingsMenu { } } -fn render_counter( - count: &mut usize, - ui: &mut Ui, - textures: &TextureStore, - position: Vec2, - title: String, - min: usize, - max: usize, -) { +const COUNTER_DIGIT_WIDTH: f32 = 13f32 * 2.0; +const COUNTER_DIGIT_HEIGHT: f32 = 23f32 * 2.0; +const COUNTER_BUTTON_HEIGHT: f32 = 30f32; +const COUNTER_BUTTON_MARGIN: f32 = 10f32; +const BUTTON_OFFSET_HEIGHT: f32 = (COUNTER_DIGIT_HEIGHT - COUNTER_BUTTON_HEIGHT) * 0.5; + +fn render_counter(count: &mut usize, ui: &mut Ui, textures: &TextureStore, position: Vec2, title: &str, min: usize, max: usize) { let digits: Vec<usize> = { let digits = count.to_string(); let digits = format!("{:0>3}", digits); digits.chars().map(|i| (i.to_digit(10u32).unwrap_or(0)) as usize).collect() }; - const COUNTER_DIGIT_WIDTH: f32 = 13f32 * 2.0; - const COUNTER_DIGIT_HEIGHT: f32 = 23f32 * 2.0; - const COUNTER_BUTTON_HEIGHT: f32 = 30f32; - const COUNTER_BUTTON_MARGIN: f32 = 10f32; - const BUTTON_OFFSET_HEIGHT: f32 = (COUNTER_DIGIT_HEIGHT - COUNTER_BUTTON_HEIGHT) * 0.5; - let counter_width = digits.len() as f32 * COUNTER_DIGIT_WIDTH; let position = position - vec2(counter_width * 0.5, 0.0); - for (x, digit) in digits.iter().enumerate() { - let position = vec2(COUNTER_DIGIT_WIDTH * x as f32, 0f32) + position; - widgets::Texture::new(textures.numbers[*digit]) - .size(COUNTER_DIGIT_WIDTH, COUNTER_DIGIT_HEIGHT) - .position(position) - .ui(ui); - } + + draw_seven_segment_unscaled(ui, textures, &digits, position.x as usize, position.y as usize); if widgets::Button::new("+") .size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT)) .position(position + vec2(counter_width + COUNTER_BUTTON_MARGIN, BUTTON_OFFSET_HEIGHT)) diff --git a/src/gui/seven_segment.rs b/src/gui/seven_segment.rs new file mode 100644 index 0000000..2f34a09 --- /dev/null +++ b/src/gui/seven_segment.rs @@ -0,0 +1,31 @@ +use macroquad::{ + prelude::vec2, + ui::{widgets, Ui}, +}; +pub const WIDTH: usize = 13 * 2; +pub const HEIGHT: usize = 23 * 2; + +use super::{texture_store::TextureStore, UIState}; + +pub fn draw_seven_segment(ui_state: &UIState, ui: &mut Ui, textures: &TextureStore, val: &Vec<usize>, x: usize, y: usize) { + for (n, digit) in val.iter().enumerate() { + let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH as usize, HEIGHT); + let (pos_x, pos_y) = ui_state.pixel_screen_offset(n * WIDTH + x, y); + + widgets::Texture::new(textures.numbers[*digit]) + .size(scaled_width, scaled_height) + .position(vec2(pos_x, pos_y)) + .ui(ui); + } +} + +pub fn draw_seven_segment_unscaled(ui: &mut Ui, textures: &TextureStore, val: &Vec<usize>, x: usize, y: usize) { + for (n, digit) in val.iter().enumerate() { + let (pos_x, pos_y) = ((n * WIDTH + x) as f32, y as f32); + + widgets::Texture::new(textures.numbers[*digit]) + .size(WIDTH as f32, HEIGHT as f32) + .position(vec2(pos_x, pos_y)) + .ui(ui); + } +} diff --git a/src/gui/texture_store.rs b/src/gui/texture_store.rs index b005274..02effe7 100644 --- a/src/gui/texture_store.rs +++ b/src/gui/texture_store.rs @@ -22,12 +22,12 @@ impl Default for TextureStore { impl TextureStore { pub fn new() -> Self { Self { - numbers: load_sprites(include_bytes!("../../assets/numbers.png"), [26, 46], 1, 10).expect("Could not load sprites"), - english_tiles: load_sprites(include_bytes!("../../assets/english_32x.png"), [32, 32], 2, 8) + numbers: load_sprites(include_bytes!("../../assets/numbers.png"), (26, 46), 1, 10).expect("Could not load sprites"), + english_tiles: load_sprites(include_bytes!("../../assets/english_32x.png"), (32, 32), 2, 8) .expect("Could not load Tile Sprites"), - japanese_tiles: load_sprites(include_bytes!("../../assets/japanese_32x.png"), [32, 32], 2, 8) + japanese_tiles: load_sprites(include_bytes!("../../assets/japanese_32x.png"), (32, 32), 2, 8) .expect("Could not load Tile Sprites"), - smilies: load_sprites(include_bytes!("../../assets/faces.png"), [48, 48], 1, 5).expect("Could not load face sprites"), + smilies: load_sprites(include_bytes!("../../assets/faces.png"), (48, 48), 1, 5).expect("Could not load face sprites"), cog: Texture2D::from_file_with_format(include_bytes!("../../assets/cog.png"), Some(ImageFormat::Png)), lang: Language::English, } diff --git a/src/gui/tile_render.rs b/src/gui/tile_render.rs index b7a20e1..c5304ce 100644 --- a/src/gui/tile_render.rs +++ b/src/gui/tile_render.rs @@ -1,7 +1,8 @@ use crate::logic::tile::{Tile, TileModifier, TileState}; +#[repr(usize)] pub enum TileIndex { - Hidden, + Unknown, Revealed, Flag, Question, @@ -20,52 +21,46 @@ pub enum TileIndex { } impl Tile { - pub fn render(self, show_all: bool) -> TileIndex { - if self.swept && self.state == TileState::Mine { - return TileIndex::Explosion; - } - if show_all { - if let Some(modifier) = self.modifier { - if modifier == TileModifier::Flagged { - if self.state == TileState::Mine { - return TileIndex::Flag; - } else { - return TileIndex::FalseFlagMine; - } - } - } - if self.state == TileState::Mine { - return TileIndex::RevealedMine; - } - } - if self.swept { - if self.state == TileState::Mine { - TileIndex::Explosion - } else { - match self.adjacent { - 0 => TileIndex::Revealed, - 1 => TileIndex::One, - 2 => TileIndex::Two, - 3 => TileIndex::Three, - 4 => TileIndex::Four, - 5 => TileIndex::Five, - 6 => TileIndex::Six, - 7 => TileIndex::Seven, - 8 => TileIndex::Eight, - _ => TileIndex::RevealedQuestion, - } - } - } else { - if let Some(modif) = self.modifier { - match modif { - TileModifier::Flagged => TileIndex::Flag, - TileModifier::Unsure => TileIndex::Question, - } - } else if self.highlighted { - TileIndex::Revealed - } else { - TileIndex::Hidden - } + pub fn render(self, game_over: bool) -> TileIndex { + // Behold: the match statement from hell! + match ( + self.state, + self.modifier, + self.adjacent, + game_over, + self.swept, + self.highlighted, + ) { + // Has mine, clicked mine: BOOM! + (TileState::Mine, _, _, _, true, _) => TileIndex::Explosion, + // Has mine, has flag, and game is over: True Flag + (TileState::Mine, Some(TileModifier::Flagged), _, true, _, _) => TileIndex::Flag, + // Has flag, is not Mine, and game is over: False flag + (TileState::Empty, Some(TileModifier::Flagged), _, true, _, _) => TileIndex::FalseFlagMine, + // Revealed mine after game is over + (TileState::Mine, _, _, true, _, _) => TileIndex::RevealedMine, + // Revealed tiles with adjacent tile count + (TileState::Empty, _, 0, _, true, _) => TileIndex::Revealed, + (TileState::Empty, _, 1, _, true, _) => TileIndex::One, + (TileState::Empty, _, 2, _, true, _) => TileIndex::Two, + (TileState::Empty, _, 3, _, true, _) => TileIndex::Three, + (TileState::Empty, _, 4, _, true, _) => TileIndex::Four, + (TileState::Empty, _, 5, _, true, _) => TileIndex::Five, + (TileState::Empty, _, 6, _, true, _) => TileIndex::Six, + (TileState::Empty, _, 7, _, true, _) => TileIndex::Seven, + (TileState::Empty, _, 8, _, true, _) => TileIndex::Eight, + // Flag modifier + (_, Some(TileModifier::Flagged), _, _, _, _) => TileIndex::Flag, + // Question mark modifier + (_, Some(TileModifier::Unsure), _, _, _, _) => TileIndex::Question, + // No modifier, not swept, but highlighted + (_, None, _, _, false, true) => TileIndex::Revealed, + // No modifier, Not swept, and not highlighted: Unknown tile + (_, None, _, _, false, false) => TileIndex::Unknown, + // unsigned 8 bit integer has too much range for the adjacent tiles count, creating an invalid state + // from 9 onward. This last clause is to catch if somehow this invalid state occurs, and display + // the invalid tile in that case. + (TileState::Empty, _, 9..=u8::MAX, _, true, _) => TileIndex::RevealedQuestion, } } } diff --git a/src/gui/top_menu/flag_counter.rs b/src/gui/top_menu/flag_counter.rs index 8d22719..26cdfbd 100644 --- a/src/gui/top_menu/flag_counter.rs +++ b/src/gui/top_menu/flag_counter.rs @@ -1,9 +1,9 @@ -use macroquad::{ - prelude::*, - ui::{widgets, Ui}, -}; +use macroquad::ui::Ui; -use crate::gui::texture_store::TextureStore; +use crate::gui::{ + seven_segment::{self, draw_seven_segment}, + texture_store::TextureStore, +}; use super::UIState; @@ -33,18 +33,13 @@ impl GUIFlagCounter { self.old_count = remaining; } - let top = ui_state.top_offset; - const WIDTH: usize = 13 * 2; - const HEIGHT: usize = 23 * 2; - let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH, HEIGHT); - - // let length = self.digits.len() as f32; - for (x, digit) in self.digits.iter().enumerate() { - let (pos_x, pos_y) = ui_state.pixel_screen_offset((x + 2) * WIDTH, (top - HEIGHT) / 2); - widgets::Texture::new(textures.numbers[*digit]) - .size(scaled_width, scaled_height) - .position(vec2(pos_x, pos_y)) - .ui(ui); - } + draw_seven_segment( + ui_state, + ui, + textures, + &self.digits, + seven_segment::WIDTH * 2, + (ui_state.top_offset - seven_segment::HEIGHT) / 2, + ); } } diff --git a/src/gui/top_menu/timer.rs b/src/gui/top_menu/timer.rs index 312a397..72aba10 100644 --- a/src/gui/top_menu/timer.rs +++ b/src/gui/top_menu/timer.rs @@ -1,9 +1,9 @@ -use macroquad::{ - prelude::*, - ui::{widgets, Ui}, -}; +use macroquad::{prelude::*, ui::Ui}; -use crate::gui::texture_store::TextureStore; +use crate::gui::{ + seven_segment::{self, draw_seven_segment}, + texture_store::TextureStore, +}; use super::UIState; @@ -31,18 +31,15 @@ impl GUITimer { let digits: Vec<usize> = time_1.chars().map(|i| (i.to_digit(10u32).unwrap_or(0)) as usize).collect(); self.digits = digits; } - let top = ui_state.top_offset; - const WIDTH: usize = 13 * 2; - const HEIGHT: usize = 23 * 2; - let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH as usize, HEIGHT); let board_width = ui_state.width * ui_state.tile_size; - let length = self.digits.len(); - for (x, digit) in self.digits.iter().enumerate() { - let (pos_x, pos_y) = ui_state.pixel_screen_offset(WIDTH * x + board_width - WIDTH * (length + 2), (top - HEIGHT) / 2); - widgets::Texture::new(textures.numbers[*digit]) - .size(scaled_width, scaled_height) - .position(vec2(pos_x, pos_y)) - .ui(ui); - } + + draw_seven_segment( + ui_state, + ui, + textures, + &self.digits, + board_width - seven_segment::WIDTH * (self.digits.len() + 2), + (ui_state.top_offset - seven_segment::HEIGHT) / 2, + ); } } diff --git a/src/logic/game_board.rs b/src/logic/game_board.rs index 33cd861..df069df 100644 --- a/src/logic/game_board.rs +++ b/src/logic/game_board.rs @@ -159,26 +159,28 @@ impl GameBoard { let mut revealed: usize = 0; while scan_list.len() > 0 { for &scan_location in ADJACENT_WITHOUT_CENTER.iter() { - if let Some(old_tile) = self.get_tile(scan_list[0].0, scan_list[0].1) { - if old_tile.adjacent > 0 { - continue; - } - let x = scan_list[0].0 as isize + scan_location.0; - let y = scan_list[0].1 as isize + scan_location.1; - - if x < 0 || y < 0 { - continue; - } - let y = y as usize; - let x = x as usize; - if let Some(tile) = self.get_tile_mut(x, y) { - if tile.swept { + if let Some((x, y)) = scan_list.front() { + if let Some(old_tile) = self.get_tile(*x, *y) { + if old_tile.adjacent > 0 { continue; } - scan_list.push_back((x, y)); - tile.swept = true; - revealed += 1; - event_handler.add(GameEvent::RevealTile(x, y, tile.clone())); + let x = *x as isize + scan_location.0; + let y = *y as isize + scan_location.1; + + if x < 0 || y < 0 { + continue; + } + let y = y as usize; + let x = x as usize; + if let Some(tile) = self.get_tile_mut(x, y) { + if tile.swept { + continue; + } + scan_list.push_back((x, y)); + tile.swept = true; + revealed += 1; + event_handler.add(GameEvent::RevealTile(x, y, tile.clone())); + } } } } diff --git a/src/sprite_loader.rs b/src/sprite_loader.rs index 3ab86f2..45cc74a 100644 --- a/src/sprite_loader.rs +++ b/src/sprite_loader.rs @@ -2,13 +2,11 @@ use std::error::Error; use image::{load_from_memory, EncodableLayout}; use macroquad::texture::{FilterMode, Texture2D}; -pub fn load_sprites(bytes: &[u8], tile_size: [usize; 2], rows: usize, columns: usize) -> Result<Vec<Texture2D>, Box<dyn Error>> { +pub fn load_sprites(bytes: &[u8], tile_size: (u32, u32), rows: usize, columns: usize) -> Result<Vec<Texture2D>, Box<dyn Error>> { let sprite_sheet = load_from_memory(bytes)?.to_rgba8(); let mut sprite_list: Vec<Texture2D> = vec![]; - let [tile_width, tile_height] = tile_size; - let tile_width = tile_width as u32; - let tile_height = tile_height as u32; + let (tile_width, tile_height) = tile_size; for i in 0..(rows * columns) { let x = (i % columns) as u32; diff --git a/src/util.rs b/src/util.rs index 83523f5..52cb782 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,18 @@ +#[rustfmt::skip] pub const ADJACENT_WITH_CENTER: [(isize, isize); 9] = - [(-1, -1), (0, -1), (1, -1), (-1, 0), (0, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]; + [ + (-1, -1), (0, -1), (1, -1), + (-1, 0), (0, 0), (1, 0), + (-1, 1), (0, 1), (1, 1) + ]; -pub const ADJACENT_WITHOUT_CENTER: [(isize, isize); 8] = [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]; +#[rustfmt::skip] +pub const ADJACENT_WITHOUT_CENTER: [(isize, isize); 8] = + [ + (-1, -1), (0, -1), (1, -1), + (-1, 0), (1, 0), + (-1, 1), (0, 1), (1, 1) + ]; // Event Queue pub struct Events<E> {