Compare commits

..

No commits in common. "31c6c4858f1edd3e62aa2de47ceb056d378154a5" and "761b9ba29540463a211a5bf8916dcaa5dedd370c" have entirely different histories.

22 changed files with 336 additions and 324 deletions

Binary file not shown.

Before

(image error) Size: 1.7 KiB

After

(image error) Size: 1.7 KiB

View file

@ -32,7 +32,7 @@
<script src="https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js"></script>
<script>
load("./minesweeper.wasm");
</script>
</script> <!-- Your compiled wasm file -->
<script>
document.addEventListener("contextmenu", (e) => {
e.preventDefault();

View file

@ -1,18 +1,13 @@
mod board_render;
mod highlighter;
pub mod settings_menu;
mod seven_segment;
pub mod texture_store;
mod tile_render;
pub mod top_menu;
use crate::{logic::game_board::ModifyMode, util::Events};
pub mod ui_event;
use self::{
highlighter::Highlighter,
settings_menu::SettingsMenu,
texture_store::TextureStore,
top_menu::{smile::SmileyState, GUITop},
highlighter::Highlighter, settings_menu::SettingsMenu, texture_store::TextureStore, top_menu::GUITop, ui_event::GUIEvents,
};
use macroquad::prelude::*;
#[derive(Default, Copy, Clone, Debug)]
@ -21,21 +16,6 @@ pub enum Language {
English,
Japanese,
}
pub enum GUIEvent {
ClickReset,
OpenSettings,
CloseSettings,
SwitchLanguage(Language),
ClickTile(usize, usize),
ModifyTile(usize, usize),
HighlightTile(usize, usize),
UnHighlightTile(usize, usize),
CreateNewGame(usize, usize, usize),
SetQuestionMode(ModifyMode),
SetSmileyState(SmileyState),
}
#[derive(Debug, Default)]
pub struct UIState {
pub width: usize,
@ -53,13 +33,13 @@ pub struct UIState {
}
impl UIState {
pub fn new(width: usize, height: usize, tile_size: usize, top_offset: usize) -> Self {
Self {
return Self {
width,
height,
tile_size,
top_offset,
..Default::default()
}
};
}
pub fn update_dimensions(&mut self, width: usize, height: usize) {
self.width = width;
@ -87,22 +67,25 @@ 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;
let y = y + self.letterbox.1;
(x, y)
return (x, y);
}
pub fn pixel_screen_scale(&self, x: usize, y: usize) -> (f32, f32) {
let x = x as f32;
let y = y as f32;
(x * self.scale, y * self.scale)
return (x * self.scale, y * self.scale);
}
}
#[derive(Default)]
pub struct GameUI {
pub event_handler: Events<GUIEvent>,
pub event_handler: GUIEvents,
pub highlighter: Highlighter,
pub state: UIState,
pub settings_menu: SettingsMenu,
@ -113,10 +96,10 @@ pub struct GameUI {
impl GameUI {
pub fn new(settings: UIState) -> Self {
let set = settings;
Self {
return Self {
state: set,
..Default::default()
}
};
}
pub fn is_valid_position(&self, x: usize, y: usize) -> bool {
if x < self.state.width && y < self.state.height {
@ -145,6 +128,6 @@ impl GameUI {
if !self.is_valid_position(x, y) {
return None;
}
Some((x, y))
return Some((x, y));
}
}

View file

@ -1,7 +1,7 @@
use crate::{logic::game_board::GameBoard, util::Events};
use crate::logic::game_board::GameBoard;
use macroquad::prelude::*;
use super::{texture_store::TextureStore, GUIEvent, UIState};
use super::{texture_store::TextureStore, ui_event::GUIEvents, UIState};
impl GameBoard {
pub fn render(&self, textures: &TextureStore, settings: &UIState) {
// dbg!(&settings.top_offset, &settings.render_scale);
@ -32,13 +32,13 @@ impl GameBoard {
}
}
}
pub fn events(&self, settings: &UIState, event_handler: &mut Events<GUIEvent>) {
pub fn events(&self, settings: &UIState, event_handler: &mut GUIEvents) {
if settings.mouse_in_minefield && !settings.frozen {
if is_mouse_button_released(MouseButton::Left) {
event_handler.add(GUIEvent::ClickTile(settings.cursor.0, settings.cursor.1))
event_handler.add(super::ui_event::GUIEvent::ClickTile(settings.cursor.0, settings.cursor.1))
}
if is_mouse_button_released(MouseButton::Right) {
event_handler.add(GUIEvent::ModifyTile(settings.cursor.0, settings.cursor.1))
event_handler.add(super::ui_event::GUIEvent::ModifyTile(settings.cursor.0, settings.cursor.1))
}
}
}

View file

@ -5,11 +5,10 @@ use crate::{
game_board::GameBoard,
tile::{TileModifier, TileState},
},
util::Events,
util::{ADJACENT_WITHOUT_CENTER, ADJACENT_WITH_CENTER},
};
use super::{top_menu::smile::SmileyState, GUIEvent, UIState};
use super::{top_menu::smile::SmileyState, ui_event::GUIEvent, ui_event::GUIEvents, UIState};
use macroquad::prelude::*;
use std::default;
@ -26,31 +25,31 @@ 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 {
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)
}
pub fn events(&mut self, ui_state: &UIState, event_handler: &mut GUIEvents, 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 is_mouse_button_released(MouseButton::Left) {
self.reset_highlight(ui_state, event_handler);
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 {
event_handler.add(GUIEvent::SetSmileyState(SmileyState::Chillin));
}
if is_mouse_button_released(MouseButton::Middle) {
self.reset_highlight(ui_state, event_handler);
}
if is_mouse_button_released(MouseButton::Middle) {
self.reset_highlight(ui_state, event_handler);
if !ui_state.frozen {
event_handler.add(GUIEvent::SetSmileyState(SmileyState::Chillin));
}
}
}
fn check_reveal(&self, event_handler: &mut Events<GUIEvent>, interface: &UIState, game_board: &mut GameBoard) {
fn check_reveal(&self, event_handler: &mut GUIEvents, interface: &UIState, game_board: &mut GameBoard) {
let (x, y) = interface.cursor;
if let Some(tile) = game_board.get_tile_mut(x, y) {
let adjacent_mines = tile.adjacent;
@ -88,7 +87,7 @@ impl Highlighter {
}
}
}
pub fn highlight(&mut self, interface: &UIState, event_handler: &mut Events<GUIEvent>) {
pub fn highlight(&mut self, interface: &UIState, event_handler: &mut GUIEvents) {
if interface.frozen {
return;
}
@ -117,10 +116,10 @@ impl Highlighter {
}
}
self.move_highlight(interface, event_handler);
self.move_highlight(&interface, event_handler);
}
fn move_highlight(&mut self, interface: &UIState, event_handler: &mut Events<GUIEvent>) {
fn move_highlight(&mut self, interface: &UIState, event_handler: &mut GUIEvents) {
if let Some((old_x, old_y)) = self.cursor_old {
match self.highlight {
Highlight::None => (),
@ -166,7 +165,7 @@ impl Highlighter {
self.cursor_old = Some(interface.cursor);
}
fn reset_highlight(&mut self, interface: &UIState, event_handler: &mut Events<GUIEvent>) {
fn reset_highlight(&mut self, interface: &UIState, event_handler: &mut GUIEvents) {
if let Some((x, y)) = self.cursor_old {
match self.highlight {
Highlight::None => (),

View file

@ -1,24 +1,16 @@
use crate::{logic::game_board::ModifyMode, util::Events};
use crate::logic::game_board::ModifyMode;
use super::{seven_segment::draw_seven_segment_unscaled, texture_store::TextureStore, GUIEvent, Language, UIState};
use super::{
texture_store::TextureStore,
ui_event::{GUIEvent, GUIEvents},
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,
@ -40,7 +32,7 @@ impl SettingsMenu {
pub fn render(
&mut self,
ui_state: &UIState,
event_handler: &mut Events<GUIEvent>,
event_handler: &mut GUIEvents,
textures: &TextureStore,
skin: &Skin,
exit_button_skin: &Skin,
@ -51,41 +43,46 @@ impl SettingsMenu {
root_ui().window(hash!(), vec2(0., 0.), vec2(screen_width, screen_height), |ui| {
draw_rectangle(0f32, 0f32, screen_width, screen_height, background_color);
ui.push_skin(exit_button_skin);
ui.push_skin(&exit_button_skin);
if widgets::Button::new("").size(vec2(50.0, 50.0)).position(vec2(0f32, 0f32)).ui(ui) {
event_handler.add(GUIEvent::CloseSettings)
}
ui.pop_skin();
ui.push_skin(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,
&textures,
vec2(half_screen_width, 100f32),
"Minefield Width",
String::from("Minefield Width"),
MIN_MINEFIELD_WIDTH,
MAX_MINEFIELD_WIDTH,
);
render_counter(
&mut self.height,
ui,
textures,
&textures,
vec2(half_screen_width, 200f32),
"Minefield Height",
String::from("Minefield Height"),
MIN_MINEFIELD_WIDTH,
MIN_MINEFIELD_HEIGHT,
MAX_MINEFIELD_HEIGHT,
);
render_counter(
&mut self.mines,
ui,
textures,
&textures,
vec2(half_screen_width, 300f32),
"Mines",
String::from("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))
@ -94,9 +91,12 @@ 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))
@ -113,12 +113,14 @@ impl SettingsMenu {
{
event_handler.add(GUIEvent::SwitchLanguage(Language::Japanese));
}
} else if widgets::Button::new("Japanese")
.size(vec2(BUTTON_SIZE, BUTTON_SIZE))
.position(vec2(language_button_x, BUTTON_MENU_Y))
.ui(ui)
{
event_handler.add(GUIEvent::SwitchLanguage(Language::English));
} else {
if widgets::Button::new("Japanese")
.size(vec2(BUTTON_SIZE, BUTTON_SIZE))
.position(vec2(language_button_x, BUTTON_MENU_Y))
.ui(ui)
{
event_handler.add(GUIEvent::SwitchLanguage(Language::English));
}
}
if let ModifyMode::Question = self.board_modify_mode {
if widgets::Button::new("ON")
@ -129,35 +131,50 @@ impl SettingsMenu {
self.board_modify_mode = ModifyMode::Flag;
event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Flag));
}
} else if widgets::Button::new("OFF")
.size(vec2(BUTTON_SIZE, BUTTON_SIZE))
.position(vec2(question_button_x, BUTTON_MENU_Y))
.ui(ui)
{
self.board_modify_mode = ModifyMode::Question;
event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Question));
} else {
if widgets::Button::new("OFF")
.size(vec2(BUTTON_SIZE, BUTTON_SIZE))
.position(vec2(question_button_x, BUTTON_MENU_Y))
.ui(ui)
{
self.board_modify_mode = ModifyMode::Question;
event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Question));
}
}
});
}
}
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) {
fn render_counter(
count: &mut usize,
ui: &mut Ui,
textures: &TextureStore,
position: Vec2,
title: String,
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);
draw_seven_segment_unscaled(ui, textures, &digits, position.x as usize, position.y as usize);
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);
}
if widgets::Button::new("+")
.size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT))
.position(position + vec2(counter_width + COUNTER_BUTTON_MARGIN, BUTTON_OFFSET_HEIGHT))
@ -168,9 +185,11 @@ fn render_counter(count: &mut usize, ui: &mut Ui, textures: &TextureStore, posit
if widgets::Button::new("-")
.size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT))
.position(position - vec2(COUNTER_BUTTON_HEIGHT + COUNTER_BUTTON_MARGIN, -BUTTON_OFFSET_HEIGHT))
.ui(ui) && *count > min
.ui(ui)
{
*count -= 1;
if *count > min {
*count -= 1;
}
}
if widgets::Button::new("++")
.size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT))

View file

@ -1,31 +0,0 @@
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: &[usize], x: usize, y: usize) {
for (n, digit) in val.iter().enumerate() {
let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH, 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: &[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);
}
}

View file

@ -22,20 +22,20 @@ 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,
}
}
pub fn get_tiles(&self) -> &Vec<Texture2D> {
match self.lang {
return match self.lang {
Language::English => &self.english_tiles,
Language::Japanese => &self.japanese_tiles,
}
};
}
}

View file

@ -1,8 +1,7 @@
use crate::logic::tile::{Tile, TileModifier, TileState};
#[repr(usize)]
pub enum TileIndex {
Unknown,
Hidden,
Revealed,
Flag,
Question,
@ -21,46 +20,52 @@ pub enum TileIndex {
}
impl Tile {
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,
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
}
}
}
}

View file

@ -7,11 +7,15 @@ pub mod flag_counter;
pub mod smile;
pub mod timer;
use crate::{gui::GUIEvent, logic::Minesweeper, util::Events};
use crate::logic::Minesweeper;
use self::{flag_counter::GUIFlagCounter, smile::GUISmile, timer::GUITimer};
use super::{texture_store::TextureStore, UIState};
use super::{
texture_store::TextureStore,
ui_event::{GUIEvent, GUIEvents},
UIState,
};
use macroquad::prelude::*;
#[derive(Default)]
pub struct GUITop {
@ -25,7 +29,7 @@ impl GUITop {
&mut self,
ui_state: &UIState,
game_logic: &Minesweeper,
event_handler: &mut Events<GUIEvent>,
event_handler: &mut GUIEvents,
textures: &TextureStore,
) {
let background_color = Color::from_rgba(192, 192, 192, 255);
@ -54,7 +58,7 @@ impl GUITop {
const HEIGHT: usize = 35;
let pos_y = (ui_state.top_offset - HEIGHT) / 2;
let pos_x = (13 * 2 * 2 - WIDTH) / 2;
let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH, HEIGHT);
let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH as usize, HEIGHT);
let (pos_x, pos_y) = ui_state.pixel_screen_offset(pos_x, pos_y);
if widgets::Button::new(textures.cog)
.size(vec2(scaled_width, scaled_height))
@ -69,9 +73,9 @@ impl GUITop {
}
}
self.timer.render(ui_state, game_logic.get_time(), ui, textures);
self.smile.render(ui_state, ui, event_handler, textures);
self.flag_counter.render(ui_state, game_logic.board.remaining_flags(), ui, textures);
self.timer.render(&ui_state, game_logic.get_time(), ui, &textures);
self.smile.render(&ui_state, ui, event_handler, &textures);
self.flag_counter.render(&ui_state, game_logic.board.remaining_flags(), ui, &textures);
});
}
}

View file

@ -1,10 +1,10 @@
use macroquad::ui::Ui;
use crate::gui::{
seven_segment::{self, draw_seven_segment},
texture_store::TextureStore,
use macroquad::{
prelude::*,
ui::{widgets, Ui},
};
use crate::gui::texture_store::TextureStore;
use super::UIState;
pub struct GUIFlagCounter {
@ -33,13 +33,18 @@ impl GUIFlagCounter {
self.old_count = remaining;
}
draw_seven_segment(
ui_state,
ui,
textures,
&self.digits,
seven_segment::WIDTH * 2,
(ui_state.top_offset - seven_segment::HEIGHT) / 2,
);
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);
}
}
}

View file

@ -3,9 +3,9 @@ use macroquad::{
ui::{widgets, Ui},
};
use crate::{
gui::{texture_store::TextureStore, GUIEvent},
util::Events,
use crate::gui::{
texture_store::TextureStore,
ui_event::{GUIEvent, GUIEvents},
};
use super::UIState;
@ -34,7 +34,7 @@ const WIDTH: usize = 70;
const HEIGHT: usize = 70;
impl GUISmile {
pub fn render(&mut self, ui_state: &UIState, ui: &mut Ui, event_handler: &mut Events<GUIEvent>, textures: &TextureStore) {
pub fn render(&mut self, ui_state: &UIState, ui: &mut Ui, event_handler: &mut GUIEvents, textures: &TextureStore) {
let top_height = ui_state.top_offset;
let top_width = ui_state.width * ui_state.tile_size;
let pos_x = (top_width - HEIGHT) / 2;

View file

@ -1,10 +1,10 @@
use macroquad::{prelude::*, ui::Ui};
use crate::gui::{
seven_segment::{self, draw_seven_segment},
texture_store::TextureStore,
use macroquad::{
prelude::*,
ui::{widgets, Ui},
};
use crate::gui::texture_store::TextureStore;
use super::UIState;
pub struct GUITimer {
@ -31,15 +31,18 @@ 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;
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,
);
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);
}
}
}

37
src/gui/ui_event.rs Normal file
View file

@ -0,0 +1,37 @@
use crate::logic::game_board::ModifyMode;
use super::{top_menu::smile::SmileyState, Language};
pub enum GUIEvent {
ClickReset,
OpenSettings,
CloseSettings,
SwitchLanguage(Language),
ClickTile(usize, usize),
ModifyTile(usize, usize),
HighlightTile(usize, usize),
UnHighlightTile(usize, usize),
CreateNewGame(usize, usize, usize),
SetQuestionMode(ModifyMode),
SetSmileyState(SmileyState),
}
#[derive(Default)]
pub struct GUIEvents {
events: Vec<GUIEvent>,
}
impl GUIEvents {
pub fn add(&mut self, event: GUIEvent) {
self.events.push(event);
}
pub fn next(&mut self) -> Option<GUIEvent> {
if self.events.len() > 0 {
self.events.pop()
} else {
None
}
}
pub fn clear(&mut self) {
self.events.clear();
}
}

View file

@ -1,10 +1,11 @@
// pub mod events;
pub mod events;
pub mod game_board;
pub mod tile;
mod timer;
use crate::util::Events;
use self::{tile::Tile, timer::Timer};
use self::{
events::{Events, GameEvent},
timer::Timer,
};
use game_board::GameBoard;
use std::error::Error;
@ -16,25 +17,13 @@ pub enum GameState {
GameOver,
Victory,
}
pub enum GameEvent {
Lose(usize, usize, Tile),
RevealTile(usize, usize, Tile),
FlagTile(usize, usize, Tile),
QuestionTile(usize, usize, Tile),
SweepDone,
SweepBegin,
InitDone,
Win,
Reset,
GameEnd(GameBoard),
}
#[derive(Default)]
pub struct Minesweeper {
pub board: GameBoard,
pub events: Events<GameEvent>,
pub state: GameState,
timer: Timer,
pub events: Events,
}
impl Minesweeper {
@ -82,7 +71,7 @@ impl Minesweeper {
self.board.modify(x, y, &mut self.events)
}
pub fn get_time(&self) -> Option<f64> {
self.timer.elapsed()
return self.timer.elapsed();
}
pub fn highlight(&mut self, x: usize, y: usize) {
if self.state == GameState::Playing || self.state == GameState::Empty {

34
src/logic/events.rs Normal file
View file

@ -0,0 +1,34 @@
use super::game_board::GameBoard;
use super::tile::Tile;
pub enum GameEvent {
Lose(usize, usize, Tile),
RevealTile(usize, usize, Tile),
FlagTile(usize, usize, Tile),
QuestionTile(usize, usize, Tile),
SweepDone,
SweepBegin,
InitDone,
Win,
Reset,
GameEnd(GameBoard),
}
#[derive(Default)]
pub struct Events {
events: Vec<GameEvent>,
}
impl Events {
pub fn add(&mut self, event: GameEvent) {
self.events.push(event);
}
pub fn next(&mut self) -> Option<GameEvent> {
if self.events.len() > 0 {
self.events.pop()
} else {
None
}
}
pub fn clear(&mut self) {
self.events.clear();
}
}

View file

@ -98,7 +98,7 @@ impl GameBoard {
self.mines as isize - self.flags as isize
}
pub fn modify(&mut self, x: usize, y: usize, event_handler: &mut Events<GameEvent>) {
pub fn modify(&mut self, x: usize, y: usize, event_handler: &mut Events) {
if let Some(&tile) = &self.get_tile(x, y) {
if tile.swept {
return;
@ -109,11 +109,11 @@ impl GameBoard {
self.flags -= 1;
match self.modify_mode {
ModifyMode::Flag => {
event_handler.add(GameEvent::FlagTile(x, y, tile));
event_handler.add(GameEvent::FlagTile(x, y, tile.clone()));
None
}
ModifyMode::Question => {
event_handler.add(GameEvent::QuestionTile(x, y, tile));
event_handler.add(GameEvent::QuestionTile(x, y, tile.clone()));
Some(TileModifier::Unsure)
}
@ -123,7 +123,7 @@ impl GameBoard {
}
} else {
self.flags += 1;
event_handler.add(GameEvent::FlagTile(x, y, tile));
event_handler.add(GameEvent::FlagTile(x, y, tile.clone()));
Some(TileModifier::Flagged)
};
if let Some(tile) = self.get_tile_mut(x, y) {
@ -132,12 +132,12 @@ impl GameBoard {
}
}
pub fn sweep(&mut self, x: usize, y: usize, event_handler: &mut Events<GameEvent>) -> Option<GameState> {
pub fn sweep(&mut self, x: usize, y: usize, event_handler: &mut Events) -> Option<GameState> {
if let BoardState::Ungenerated = self.state {
self.generate(x, y);
}
let tile = self.tiles[x][y];
if tile.modifier.is_some() {
let &tile = &self.tiles[x][y];
if let Some(_) = tile.modifier {
return None;
}
if tile.swept {
@ -145,10 +145,10 @@ impl GameBoard {
}
self.tiles[x][y].swept = true;
self.revealed_tiles += 1;
event_handler.add(GameEvent::RevealTile(x, y, self.tiles[x][y]));
event_handler.add(GameEvent::RevealTile(x, y, self.tiles[x][y].clone()));
if tile.state == TileState::Mine {
event_handler.add(GameEvent::Lose(x, y, tile));
event_handler.add(GameEvent::Lose(x, y, tile.clone()));
event_handler.add(GameEvent::GameEnd(self.clone()));
return Some(GameState::GameOver);
@ -157,30 +157,28 @@ impl GameBoard {
let mut scan_list = VecDeque::from([(x, y)]);
let mut revealed: usize = 0;
while !scan_list.is_empty() {
while scan_list.len() > 0 {
for &scan_location in ADJACENT_WITHOUT_CENTER.iter() {
if let Some((x, y)) = scan_list.front() {
if let Some(old_tile) = self.get_tile(*x, *y) {
if old_tile.adjacent > 0 {
continue;
}
let x = *x as isize + scan_location.0;
let y = *y as isize + scan_location.1;
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 {
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;
}
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));
}
scan_list.push_back((x, y));
tile.swept = true;
revealed += 1;
event_handler.add(GameEvent::RevealTile(x, y, tile.clone()));
}
}
}
@ -226,7 +224,7 @@ impl GameBoard {
let y = macroquad::rand::gen_range(0, height);
let mut tile = &mut self.tiles[x][y];
if tile.state == TileState::Mine || tile.safe {
if tile.state == TileState::Mine || tile.safe == true {
continue;
}

View file

@ -27,7 +27,7 @@ impl Tile {
}
}
pub fn highlight(&mut self) {
if !self.swept {
if self.swept == false {
self.highlighted = true;
}
}

View file

@ -26,7 +26,11 @@ impl Timer {
if let TimerState::Frozen = self.state {
return Some(self.old);
}
self.start_time.map(|time| get_time() - time)
if let Some(time) = self.start_time {
Some(get_time() - time)
} else {
None
}
}
pub fn stop(&mut self) {
self.old = self.elapsed().unwrap_or(0f64);

View file

@ -1,6 +1,7 @@
use gui::top_menu::smile::SmileyState;
use gui::{GUIEvent, GameUI, UIState};
use logic::{GameEvent, Minesweeper};
use gui::ui_event::*;
use gui::{GameUI, UIState};
use logic::{events::GameEvent, Minesweeper};
use macroquad::{
prelude::*,
ui::{root_ui, Skin},
@ -12,8 +13,8 @@ mod sprite_loader;
mod util;
fn main() {
let width = 30 * 32;
let height = 16 * 32 + 100;
let width = (30 * 32) as i32;
let height = (16 * 32) as i32 + 100;
Window::from_config(
Conf {
sample_count: 2,

View file

@ -2,11 +2,13 @@ use std::error::Error;
use image::{load_from_memory, EncodableLayout};
use macroquad::texture::{FilterMode, Texture2D};
pub fn load_sprites(bytes: &[u8], tile_size: (u32, u32), rows: usize, columns: usize) -> Result<Vec<Texture2D>, Box<dyn Error>> {
pub fn load_sprites(bytes: &[u8], tile_size: [usize; 2], 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_height] = tile_size;
let tile_width = tile_width as u32;
let tile_height = tile_height as u32;
for i in 0..(rows * columns) {
let x = (i % columns) as u32;

View file

@ -1,44 +1,4 @@
#[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)];
#[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> {
events: Vec<E>,
}
impl<E> Events<E> {
pub fn add(&mut self, event: E) {
self.events.push(event);
}
pub fn next(&mut self) -> Option<E> {
if !self.events.is_empty() {
self.events.pop()
} else {
None
}
}
pub fn clear(&mut self) {
self.events.clear();
}
}
impl<E> Default for Events<E> {
fn default() -> Self {
Self {
events: Vec::<E>::with_capacity(10),
}
}
}
pub const ADJACENT_WITHOUT_CENTER: [(isize, isize); 8] = [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)];