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

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  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),
}
}
}