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

View file

@ -1,18 +1,13 @@
mod board_render; mod board_render;
mod highlighter; mod highlighter;
pub mod settings_menu; pub mod settings_menu;
mod seven_segment;
pub mod texture_store; pub mod texture_store;
mod tile_render; mod tile_render;
pub mod top_menu; pub mod top_menu;
pub mod ui_event;
use crate::{logic::game_board::ModifyMode, util::Events};
use self::{ use self::{
highlighter::Highlighter, highlighter::Highlighter, settings_menu::SettingsMenu, texture_store::TextureStore, top_menu::GUITop, ui_event::GUIEvents,
settings_menu::SettingsMenu,
texture_store::TextureStore,
top_menu::{smile::SmileyState, GUITop},
}; };
use macroquad::prelude::*; use macroquad::prelude::*;
#[derive(Default, Copy, Clone, Debug)] #[derive(Default, Copy, Clone, Debug)]
@ -21,21 +16,6 @@ pub enum Language {
English, English,
Japanese, 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)] #[derive(Debug, Default)]
pub struct UIState { pub struct UIState {
pub width: usize, pub width: usize,
@ -53,13 +33,13 @@ pub struct UIState {
} }
impl UIState { impl UIState {
pub fn new(width: usize, height: usize, tile_size: usize, top_offset: usize) -> Self { pub fn new(width: usize, height: usize, tile_size: usize, top_offset: usize) -> Self {
Self { return Self {
width, width,
height, height,
tile_size, tile_size,
top_offset, top_offset,
..Default::default() ..Default::default()
} };
} }
pub fn update_dimensions(&mut self, width: usize, height: usize) { pub fn update_dimensions(&mut self, width: usize, height: usize) {
self.width = width; self.width = width;
@ -87,22 +67,25 @@ impl UIState {
self.letterbox.1 = 0f32; self.letterbox.1 = 0f32;
} }
} }
}
impl UIState {
pub fn pixel_screen_offset(&self, x: usize, y: usize) -> (f32, f32) { pub fn pixel_screen_offset(&self, x: usize, y: usize) -> (f32, f32) {
let (x, y) = self.pixel_screen_scale(x, y); let (x, y) = self.pixel_screen_scale(x, y);
let x = x + self.letterbox.0; let x = x + self.letterbox.0;
let y = y + self.letterbox.1; let y = y + self.letterbox.1;
(x, y) return (x, y);
} }
pub fn pixel_screen_scale(&self, x: usize, y: usize) -> (f32, f32) { pub fn pixel_screen_scale(&self, x: usize, y: usize) -> (f32, f32) {
let x = x as f32; let x = x as f32;
let y = y as f32; let y = y as f32;
(x * self.scale, y * self.scale) return (x * self.scale, y * self.scale);
} }
} }
#[derive(Default)] #[derive(Default)]
pub struct GameUI { pub struct GameUI {
pub event_handler: Events<GUIEvent>, pub event_handler: GUIEvents,
pub highlighter: Highlighter, pub highlighter: Highlighter,
pub state: UIState, pub state: UIState,
pub settings_menu: SettingsMenu, pub settings_menu: SettingsMenu,
@ -113,10 +96,10 @@ pub struct GameUI {
impl GameUI { impl GameUI {
pub fn new(settings: UIState) -> Self { pub fn new(settings: UIState) -> Self {
let set = settings; let set = settings;
Self { return Self {
state: set, state: set,
..Default::default() ..Default::default()
} };
} }
pub fn is_valid_position(&self, x: usize, y: usize) -> bool { pub fn is_valid_position(&self, x: usize, y: usize) -> bool {
if x < self.state.width && y < self.state.height { if x < self.state.width && y < self.state.height {
@ -145,6 +128,6 @@ impl GameUI {
if !self.is_valid_position(x, y) { if !self.is_valid_position(x, y) {
return None; 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 macroquad::prelude::*;
use super::{texture_store::TextureStore, GUIEvent, UIState}; use super::{texture_store::TextureStore, ui_event::GUIEvents, UIState};
impl GameBoard { impl GameBoard {
pub fn render(&self, textures: &TextureStore, settings: &UIState) { pub fn render(&self, textures: &TextureStore, settings: &UIState) {
// dbg!(&settings.top_offset, &settings.render_scale); // 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 settings.mouse_in_minefield && !settings.frozen {
if is_mouse_button_released(MouseButton::Left) { 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) { 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, game_board::GameBoard,
tile::{TileModifier, TileState}, tile::{TileModifier, TileState},
}, },
util::Events,
util::{ADJACENT_WITHOUT_CENTER, ADJACENT_WITH_CENTER}, 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 macroquad::prelude::*;
use std::default; use std::default;
@ -26,31 +25,31 @@ pub enum Highlight {
Wide, Wide,
} }
// The highlighter is responsible for the selection when a tile is clicked and held either with left or middle click
impl Highlighter { impl Highlighter {
pub fn events(&mut self, ui_state: &UIState, event_handler: &mut Events<GUIEvent>, game_board: &mut GameBoard) { pub fn events(&mut self, ui_state: &UIState, event_handler: &mut GUIEvents, game_board: &mut GameBoard) {
if !ui_state.frozen { if !ui_state.frozen && ui_state.mouse_in_minefield {
if ui_state.mouse_in_minefield { if is_mouse_button_pressed(MouseButton::Left) {
if is_mouse_button_pressed(MouseButton::Left) { self.highlight = Highlight::Normal;
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_released(MouseButton::Left) { if is_mouse_button_pressed(MouseButton::Middle) {
self.reset_highlight(ui_state, event_handler); 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)); 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)); event_handler.add(GUIEvent::SetSmileyState(SmileyState::Chillin));
} }
} }
} }
fn check_reveal(&self, event_handler: &mut GUIEvents, interface: &UIState, game_board: &mut GameBoard) {
fn check_reveal(&self, event_handler: &mut Events<GUIEvent>, interface: &UIState, game_board: &mut GameBoard) {
let (x, y) = interface.cursor; let (x, y) = interface.cursor;
if let Some(tile) = game_board.get_tile_mut(x, y) { if let Some(tile) = game_board.get_tile_mut(x, y) {
let adjacent_mines = tile.adjacent; 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 { if interface.frozen {
return; 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 { if let Some((old_x, old_y)) = self.cursor_old {
match self.highlight { match self.highlight {
Highlight::None => (), Highlight::None => (),
@ -166,7 +165,7 @@ impl Highlighter {
self.cursor_old = Some(interface.cursor); 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 { if let Some((x, y)) = self.cursor_old {
match self.highlight { match self.highlight {
Highlight::None => (), 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::{ use macroquad::{
hash, hash,
prelude::*, prelude::*,
ui::{root_ui, widgets, Skin, Ui}, 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 { pub struct SettingsMenu {
mines: usize, mines: usize,
width: usize, width: usize,
@ -40,7 +32,7 @@ impl SettingsMenu {
pub fn render( pub fn render(
&mut self, &mut self,
ui_state: &UIState, ui_state: &UIState,
event_handler: &mut Events<GUIEvent>, event_handler: &mut GUIEvents,
textures: &TextureStore, textures: &TextureStore,
skin: &Skin, skin: &Skin,
exit_button_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| { root_ui().window(hash!(), vec2(0., 0.), vec2(screen_width, screen_height), |ui| {
draw_rectangle(0f32, 0f32, screen_width, screen_height, background_color); 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) { if widgets::Button::new("").size(vec2(50.0, 50.0)).position(vec2(0f32, 0f32)).ui(ui) {
event_handler.add(GUIEvent::CloseSettings) event_handler.add(GUIEvent::CloseSettings)
} }
ui.pop_skin(); ui.pop_skin();
ui.push_skin(skin); ui.push_skin(&skin);
let half_screen_width = screen_width * 0.5; 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( render_counter(
&mut self.width, &mut self.width,
ui, ui,
textures, &textures,
vec2(half_screen_width, 100f32), vec2(half_screen_width, 100f32),
"Minefield Width", String::from("Minefield Width"),
MIN_MINEFIELD_WIDTH, MIN_MINEFIELD_WIDTH,
MAX_MINEFIELD_WIDTH, MAX_MINEFIELD_WIDTH,
); );
render_counter( render_counter(
&mut self.height, &mut self.height,
ui, ui,
textures, &textures,
vec2(half_screen_width, 200f32), vec2(half_screen_width, 200f32),
"Minefield Height", String::from("Minefield Height"),
MIN_MINEFIELD_WIDTH,
MIN_MINEFIELD_HEIGHT, MIN_MINEFIELD_HEIGHT,
MAX_MINEFIELD_HEIGHT,
); );
render_counter( render_counter(
&mut self.mines, &mut self.mines,
ui, ui,
textures, &textures,
vec2(half_screen_width, 300f32), vec2(half_screen_width, 300f32),
"Mines", String::from("Mines"),
1, 1,
self.width * self.height - 10, self.width * self.height - 10,
); );
const NEW_GAME_HEIGHT: f32 = 40f32;
const NEW_GAME_WIDTH: f32 = 250f32;
if widgets::Button::new("New Game") if widgets::Button::new("New Game")
.size(vec2(NEW_GAME_WIDTH, NEW_GAME_HEIGHT)) .size(vec2(NEW_GAME_WIDTH, NEW_GAME_HEIGHT))
.position(vec2((screen_width - NEW_GAME_WIDTH) * 0.5, 0.0)) .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::CreateNewGame(self.width, self.height, self.mines));
event_handler.add(GUIEvent::CloseSettings); event_handler.add(GUIEvent::CloseSettings);
} }
const BUTTON_MENU_WIDTH: f32 = 250f32;
let language_button_x = (screen_width - BUTTON_MENU_WIDTH) * 0.5; 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); 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") widgets::Label::new("Language")
.position(vec2(language_button_x, BUTTON_MENU_Y - BUTTON_MENU_LABEL_HEIGHT)) .position(vec2(language_button_x, BUTTON_MENU_Y - BUTTON_MENU_LABEL_HEIGHT))
.size(vec2(BUTTON_SIZE, 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)); event_handler.add(GUIEvent::SwitchLanguage(Language::Japanese));
} }
} else if widgets::Button::new("Japanese") } else {
.size(vec2(BUTTON_SIZE, BUTTON_SIZE)) if widgets::Button::new("Japanese")
.position(vec2(language_button_x, BUTTON_MENU_Y)) .size(vec2(BUTTON_SIZE, BUTTON_SIZE))
.ui(ui) .position(vec2(language_button_x, BUTTON_MENU_Y))
{ .ui(ui)
event_handler.add(GUIEvent::SwitchLanguage(Language::English)); {
event_handler.add(GUIEvent::SwitchLanguage(Language::English));
}
} }
if let ModifyMode::Question = self.board_modify_mode { if let ModifyMode::Question = self.board_modify_mode {
if widgets::Button::new("ON") if widgets::Button::new("ON")
@ -129,35 +131,50 @@ impl SettingsMenu {
self.board_modify_mode = ModifyMode::Flag; self.board_modify_mode = ModifyMode::Flag;
event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Flag)); event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Flag));
} }
} else if widgets::Button::new("OFF") } else {
.size(vec2(BUTTON_SIZE, BUTTON_SIZE)) if widgets::Button::new("OFF")
.position(vec2(question_button_x, BUTTON_MENU_Y)) .size(vec2(BUTTON_SIZE, BUTTON_SIZE))
.ui(ui) .position(vec2(question_button_x, BUTTON_MENU_Y))
{ .ui(ui)
self.board_modify_mode = ModifyMode::Question; {
event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Question)); self.board_modify_mode = ModifyMode::Question;
event_handler.add(GUIEvent::SetQuestionMode(ModifyMode::Question));
}
} }
}); });
} }
} }
const COUNTER_DIGIT_WIDTH: f32 = 13f32 * 2.0; fn render_counter(
const COUNTER_DIGIT_HEIGHT: f32 = 23f32 * 2.0; count: &mut usize,
const COUNTER_BUTTON_HEIGHT: f32 = 30f32; ui: &mut Ui,
const COUNTER_BUTTON_MARGIN: f32 = 10f32; textures: &TextureStore,
const BUTTON_OFFSET_HEIGHT: f32 = (COUNTER_DIGIT_HEIGHT - COUNTER_BUTTON_HEIGHT) * 0.5; position: Vec2,
title: String,
fn render_counter(count: &mut usize, ui: &mut Ui, textures: &TextureStore, position: Vec2, title: &str, min: usize, max: usize) { min: usize,
max: usize,
) {
let digits: Vec<usize> = { let digits: Vec<usize> = {
let digits = count.to_string(); let digits = count.to_string();
let digits = format!("{:0>3}", digits); let digits = format!("{:0>3}", digits);
digits.chars().map(|i| (i.to_digit(10u32).unwrap_or(0)) as usize).collect() 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 counter_width = digits.len() as f32 * COUNTER_DIGIT_WIDTH;
let position = position - vec2(counter_width * 0.5, 0.0); let position = position - vec2(counter_width * 0.5, 0.0);
for (x, digit) in digits.iter().enumerate() {
draw_seven_segment_unscaled(ui, textures, &digits, position.x as usize, position.y as usize); 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("+") if widgets::Button::new("+")
.size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT)) .size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT))
.position(position + vec2(counter_width + COUNTER_BUTTON_MARGIN, BUTTON_OFFSET_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("-") if widgets::Button::new("-")
.size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT)) .size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT))
.position(position - vec2(COUNTER_BUTTON_HEIGHT + COUNTER_BUTTON_MARGIN, -BUTTON_OFFSET_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("++") if widgets::Button::new("++")
.size(vec2(COUNTER_BUTTON_HEIGHT, COUNTER_BUTTON_HEIGHT)) .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 { impl TextureStore {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
numbers: load_sprites(include_bytes!("../../assets/numbers.png"), (26, 46), 1, 10).expect("Could not load sprites"), 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) english_tiles: load_sprites(include_bytes!("../../assets/english_32x.png"), [32, 32], 2, 8)
.expect("Could not load Tile Sprites"), .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"), .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)), cog: Texture2D::from_file_with_format(include_bytes!("../../assets/cog.png"), Some(ImageFormat::Png)),
lang: Language::English, lang: Language::English,
} }
} }
pub fn get_tiles(&self) -> &Vec<Texture2D> { pub fn get_tiles(&self) -> &Vec<Texture2D> {
match self.lang { return match self.lang {
Language::English => &self.english_tiles, Language::English => &self.english_tiles,
Language::Japanese => &self.japanese_tiles, Language::Japanese => &self.japanese_tiles,
} };
} }
} }

View file

@ -1,8 +1,7 @@
use crate::logic::tile::{Tile, TileModifier, TileState}; use crate::logic::tile::{Tile, TileModifier, TileState};
#[repr(usize)]
pub enum TileIndex { pub enum TileIndex {
Unknown, Hidden,
Revealed, Revealed,
Flag, Flag,
Question, Question,
@ -21,46 +20,52 @@ pub enum TileIndex {
} }
impl Tile { impl Tile {
pub fn render(self, game_over: bool) -> TileIndex { pub fn render(self, show_all: bool) -> TileIndex {
// Behold: the match statement from hell! if self.swept && self.state == TileState::Mine {
match ( return TileIndex::Explosion;
self.state, }
self.modifier, if show_all {
self.adjacent, if let Some(modifier) = self.modifier {
game_over, if modifier == TileModifier::Flagged {
self.swept, if self.state == TileState::Mine {
self.highlighted, return TileIndex::Flag;
) { } else {
// Has mine, clicked mine: BOOM! return TileIndex::FalseFlagMine;
(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 if self.state == TileState::Mine {
(TileState::Empty, Some(TileModifier::Flagged), _, true, _, _) => TileIndex::FalseFlagMine, return TileIndex::RevealedMine;
// Revealed mine after game is over }
(TileState::Mine, _, _, true, _, _) => TileIndex::RevealedMine, }
// Revealed tiles with adjacent tile count if self.swept {
(TileState::Empty, _, 0, _, true, _) => TileIndex::Revealed, if self.state == TileState::Mine {
(TileState::Empty, _, 1, _, true, _) => TileIndex::One, TileIndex::Explosion
(TileState::Empty, _, 2, _, true, _) => TileIndex::Two, } else {
(TileState::Empty, _, 3, _, true, _) => TileIndex::Three, match self.adjacent {
(TileState::Empty, _, 4, _, true, _) => TileIndex::Four, 0 => TileIndex::Revealed,
(TileState::Empty, _, 5, _, true, _) => TileIndex::Five, 1 => TileIndex::One,
(TileState::Empty, _, 6, _, true, _) => TileIndex::Six, 2 => TileIndex::Two,
(TileState::Empty, _, 7, _, true, _) => TileIndex::Seven, 3 => TileIndex::Three,
(TileState::Empty, _, 8, _, true, _) => TileIndex::Eight, 4 => TileIndex::Four,
// Flag modifier 5 => TileIndex::Five,
(_, Some(TileModifier::Flagged), _, _, _, _) => TileIndex::Flag, 6 => TileIndex::Six,
// Question mark modifier 7 => TileIndex::Seven,
(_, Some(TileModifier::Unsure), _, _, _, _) => TileIndex::Question, 8 => TileIndex::Eight,
// No modifier, not swept, but highlighted _ => TileIndex::RevealedQuestion,
(_, None, _, _, false, true) => TileIndex::Revealed, }
// No modifier, Not swept, and not highlighted: Unknown tile }
(_, None, _, _, false, false) => TileIndex::Unknown, } else {
// unsigned 8 bit integer has too much range for the adjacent tiles count, creating an invalid state if let Some(modif) = self.modifier {
// from 9 onward. This last clause is to catch if somehow this invalid state occurs, and display match modif {
// the invalid tile in that case. TileModifier::Flagged => TileIndex::Flag,
(TileState::Empty, _, 9..=u8::MAX, _, true, _) => TileIndex::RevealedQuestion, 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 smile;
pub mod timer; 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 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::*; use macroquad::prelude::*;
#[derive(Default)] #[derive(Default)]
pub struct GUITop { pub struct GUITop {
@ -25,7 +29,7 @@ impl GUITop {
&mut self, &mut self,
ui_state: &UIState, ui_state: &UIState,
game_logic: &Minesweeper, game_logic: &Minesweeper,
event_handler: &mut Events<GUIEvent>, event_handler: &mut GUIEvents,
textures: &TextureStore, textures: &TextureStore,
) { ) {
let background_color = Color::from_rgba(192, 192, 192, 255); let background_color = Color::from_rgba(192, 192, 192, 255);
@ -54,7 +58,7 @@ impl GUITop {
const HEIGHT: usize = 35; const HEIGHT: usize = 35;
let pos_y = (ui_state.top_offset - HEIGHT) / 2; let pos_y = (ui_state.top_offset - HEIGHT) / 2;
let pos_x = (13 * 2 * 2 - WIDTH) / 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); let (pos_x, pos_y) = ui_state.pixel_screen_offset(pos_x, pos_y);
if widgets::Button::new(textures.cog) if widgets::Button::new(textures.cog)
.size(vec2(scaled_width, scaled_height)) .size(vec2(scaled_width, scaled_height))
@ -69,9 +73,9 @@ impl GUITop {
} }
} }
self.timer.render(ui_state, game_logic.get_time(), ui, textures); self.timer.render(&ui_state, game_logic.get_time(), ui, &textures);
self.smile.render(ui_state, ui, event_handler, textures); self.smile.render(&ui_state, ui, event_handler, &textures);
self.flag_counter.render(ui_state, game_logic.board.remaining_flags(), ui, 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 macroquad::{
prelude::*,
use crate::gui::{ ui::{widgets, Ui},
seven_segment::{self, draw_seven_segment},
texture_store::TextureStore,
}; };
use crate::gui::texture_store::TextureStore;
use super::UIState; use super::UIState;
pub struct GUIFlagCounter { pub struct GUIFlagCounter {
@ -33,13 +33,18 @@ impl GUIFlagCounter {
self.old_count = remaining; self.old_count = remaining;
} }
draw_seven_segment( let top = ui_state.top_offset;
ui_state, const WIDTH: usize = 13 * 2;
ui, const HEIGHT: usize = 23 * 2;
textures, let (scaled_width, scaled_height) = ui_state.pixel_screen_scale(WIDTH, HEIGHT);
&self.digits,
seven_segment::WIDTH * 2, // let length = self.digits.len() as f32;
(ui_state.top_offset - seven_segment::HEIGHT) / 2, 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}, ui::{widgets, Ui},
}; };
use crate::{ use crate::gui::{
gui::{texture_store::TextureStore, GUIEvent}, texture_store::TextureStore,
util::Events, ui_event::{GUIEvent, GUIEvents},
}; };
use super::UIState; use super::UIState;
@ -34,7 +34,7 @@ const WIDTH: usize = 70;
const HEIGHT: usize = 70; const HEIGHT: usize = 70;
impl GUISmile { 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_height = ui_state.top_offset;
let top_width = ui_state.width * ui_state.tile_size; let top_width = ui_state.width * ui_state.tile_size;
let pos_x = (top_width - HEIGHT) / 2; let pos_x = (top_width - HEIGHT) / 2;

View file

@ -1,10 +1,10 @@
use macroquad::{prelude::*, ui::Ui}; use macroquad::{
prelude::*,
use crate::gui::{ ui::{widgets, Ui},
seven_segment::{self, draw_seven_segment},
texture_store::TextureStore,
}; };
use crate::gui::texture_store::TextureStore;
use super::UIState; use super::UIState;
pub struct GUITimer { 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(); let digits: Vec<usize> = time_1.chars().map(|i| (i.to_digit(10u32).unwrap_or(0)) as usize).collect();
self.digits = digits; 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 board_width = ui_state.width * ui_state.tile_size;
let length = self.digits.len();
draw_seven_segment( for (x, digit) in self.digits.iter().enumerate() {
ui_state, let (pos_x, pos_y) = ui_state.pixel_screen_offset(WIDTH * x + board_width - WIDTH * (length + 2), (top - HEIGHT) / 2);
ui, widgets::Texture::new(textures.numbers[*digit])
textures, .size(scaled_width, scaled_height)
&self.digits, .position(vec2(pos_x, pos_y))
board_width - seven_segment::WIDTH * (self.digits.len() + 2), .ui(ui);
(ui_state.top_offset - seven_segment::HEIGHT) / 2, }
);
} }
} }

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 game_board;
pub mod tile; pub mod tile;
mod timer; mod timer;
use crate::util::Events; use self::{
events::{Events, GameEvent},
use self::{tile::Tile, timer::Timer}; timer::Timer,
};
use game_board::GameBoard; use game_board::GameBoard;
use std::error::Error; use std::error::Error;
@ -16,25 +17,13 @@ pub enum GameState {
GameOver, GameOver,
Victory, 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)] #[derive(Default)]
pub struct Minesweeper { pub struct Minesweeper {
pub board: GameBoard, pub board: GameBoard,
pub events: Events<GameEvent>,
pub state: GameState, pub state: GameState,
timer: Timer, timer: Timer,
pub events: Events,
} }
impl Minesweeper { impl Minesweeper {
@ -82,7 +71,7 @@ impl Minesweeper {
self.board.modify(x, y, &mut self.events) self.board.modify(x, y, &mut self.events)
} }
pub fn get_time(&self) -> Option<f64> { pub fn get_time(&self) -> Option<f64> {
self.timer.elapsed() return self.timer.elapsed();
} }
pub fn highlight(&mut self, x: usize, y: usize) { pub fn highlight(&mut self, x: usize, y: usize) {
if self.state == GameState::Playing || self.state == GameState::Empty { 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 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 let Some(&tile) = &self.get_tile(x, y) {
if tile.swept { if tile.swept {
return; return;
@ -109,11 +109,11 @@ impl GameBoard {
self.flags -= 1; self.flags -= 1;
match self.modify_mode { match self.modify_mode {
ModifyMode::Flag => { ModifyMode::Flag => {
event_handler.add(GameEvent::FlagTile(x, y, tile)); event_handler.add(GameEvent::FlagTile(x, y, tile.clone()));
None None
} }
ModifyMode::Question => { ModifyMode::Question => {
event_handler.add(GameEvent::QuestionTile(x, y, tile)); event_handler.add(GameEvent::QuestionTile(x, y, tile.clone()));
Some(TileModifier::Unsure) Some(TileModifier::Unsure)
} }
@ -123,7 +123,7 @@ impl GameBoard {
} }
} else { } else {
self.flags += 1; self.flags += 1;
event_handler.add(GameEvent::FlagTile(x, y, tile)); event_handler.add(GameEvent::FlagTile(x, y, tile.clone()));
Some(TileModifier::Flagged) Some(TileModifier::Flagged)
}; };
if let Some(tile) = self.get_tile_mut(x, y) { 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 { if let BoardState::Ungenerated = self.state {
self.generate(x, y); self.generate(x, y);
} }
let tile = self.tiles[x][y]; let &tile = &self.tiles[x][y];
if tile.modifier.is_some() { if let Some(_) = tile.modifier {
return None; return None;
} }
if tile.swept { if tile.swept {
@ -145,10 +145,10 @@ impl GameBoard {
} }
self.tiles[x][y].swept = true; self.tiles[x][y].swept = true;
self.revealed_tiles += 1; 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 { 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())); event_handler.add(GameEvent::GameEnd(self.clone()));
return Some(GameState::GameOver); return Some(GameState::GameOver);
@ -157,30 +157,28 @@ impl GameBoard {
let mut scan_list = VecDeque::from([(x, y)]); let mut scan_list = VecDeque::from([(x, y)]);
let mut revealed: usize = 0; let mut revealed: usize = 0;
while !scan_list.is_empty() { while scan_list.len() > 0 {
for &scan_location in ADJACENT_WITHOUT_CENTER.iter() { for &scan_location in ADJACENT_WITHOUT_CENTER.iter() {
if let Some((x, y)) = scan_list.front() { if let Some(old_tile) = self.get_tile(scan_list[0].0, scan_list[0].1) {
if let Some(old_tile) = self.get_tile(*x, *y) { if old_tile.adjacent > 0 {
if old_tile.adjacent > 0 { continue;
continue; }
} let x = scan_list[0].0 as isize + scan_location.0;
let x = *x as isize + scan_location.0; let y = scan_list[0].1 as isize + scan_location.1;
let y = *y 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; continue;
} }
let y = y as usize; scan_list.push_back((x, y));
let x = x as usize; tile.swept = true;
if let Some(tile) = self.get_tile_mut(x, y) { revealed += 1;
if tile.swept { event_handler.add(GameEvent::RevealTile(x, y, tile.clone()));
continue;
}
scan_list.push_back((x, y));
tile.swept = true;
revealed += 1;
event_handler.add(GameEvent::RevealTile(x, y, *tile));
}
} }
} }
} }
@ -226,7 +224,7 @@ impl GameBoard {
let y = macroquad::rand::gen_range(0, height); let y = macroquad::rand::gen_range(0, height);
let mut tile = &mut self.tiles[x][y]; let mut tile = &mut self.tiles[x][y];
if tile.state == TileState::Mine || tile.safe { if tile.state == TileState::Mine || tile.safe == true {
continue; continue;
} }

View file

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

View file

@ -26,7 +26,11 @@ impl Timer {
if let TimerState::Frozen = self.state { if let TimerState::Frozen = self.state {
return Some(self.old); 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) { pub fn stop(&mut self) {
self.old = self.elapsed().unwrap_or(0f64); self.old = self.elapsed().unwrap_or(0f64);

View file

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

View file

@ -2,11 +2,13 @@ use std::error::Error;
use image::{load_from_memory, EncodableLayout}; use image::{load_from_memory, EncodableLayout};
use macroquad::texture::{FilterMode, Texture2D}; 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 sprite_sheet = load_from_memory(bytes)?.to_rgba8();
let mut sprite_list: Vec<Texture2D> = vec![]; 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) { for i in 0..(rows * columns) {
let x = (i % columns) as u32; let x = (i % columns) as u32;

View file

@ -1,44 +1,4 @@
#[rustfmt::skip]
pub const ADJACENT_WITH_CENTER: [(isize, isize); 9] = 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)];
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),
}
}
}