separate logic state and draw state
This commit is contained in:
+99
-222
@@ -1,107 +1,80 @@
|
||||
use super::{Game, GameSquare, constants};
|
||||
use crate::{resources::*, widgets::*};
|
||||
use core::fmt;
|
||||
use super::{Game, GameMode, GameSettings, constants};
|
||||
use crate::{
|
||||
resources::{self, *},
|
||||
widgets::*,
|
||||
};
|
||||
use macroquad::prelude::*;
|
||||
use sol_lib::{
|
||||
board::BoardState,
|
||||
generator::{self, Puzzle, RandomRange},
|
||||
};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum GameState {
|
||||
SelectSource(Option<(usize, usize)>),
|
||||
SelectTarget((usize, usize)),
|
||||
GameOver((usize, usize)),
|
||||
}
|
||||
|
||||
impl Default for GameState {
|
||||
fn default() -> Self {
|
||||
GameState::SelectSource(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum GameMode {
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
}
|
||||
|
||||
impl Default for GameMode {
|
||||
fn default() -> Self {
|
||||
GameMode::Medium
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GameState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
GameState::SelectSource(Some(x)) => write!(f, "Select Source [ {}, {} ]", x.0, x.1),
|
||||
GameState::SelectSource(None) => write!(f, "Select Source [ ]"),
|
||||
GameState::SelectTarget(x) => write!(f, "Select Target [ {}, {} ]", x.0, x.1),
|
||||
GameState::GameOver(x) => write!(f, "Game Over [ {}, {} ]", x.0, x.1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn get(&mut self, i: usize, j: usize) -> &mut GameSquare {
|
||||
&mut self.squares[i * self.num_squares + j]
|
||||
pub async fn initialize_state() -> Self {
|
||||
let resources = resources::init().await;
|
||||
let game_mode = GameMode::Medium;
|
||||
let settings = GameSettings {
|
||||
volume: constants::VOLUME,
|
||||
..Default::default()
|
||||
};
|
||||
let puzzle = Game::generate_puzzle(game_mode);
|
||||
let board = BoardWidget::initialize_state(puzzle.board.size, puzzle.board.clone());
|
||||
let medium_btn = ButtonWidget::initialize_state(false);
|
||||
let next_btn = ButtonWidget::initialize_state(false);
|
||||
Self {
|
||||
puzzle,
|
||||
board,
|
||||
resources,
|
||||
game_mode,
|
||||
settings,
|
||||
heading_text: constants::HEADING_TEXT.to_string(),
|
||||
rules_text: constants::RULES_BUTTON_TEXT.to_string(),
|
||||
medium_btn,
|
||||
next_btn,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_input(&mut self) {
|
||||
// CORE GAMEPLAY
|
||||
let (mouse_x, mouse_y) = mouse_position();
|
||||
let mouse_pos = Circle::new(mouse_x, mouse_y, 0.0);
|
||||
if is_mouse_button_released(MouseButton::Left) {
|
||||
let current_state = self.state.clone();
|
||||
let new_state = match current_state {
|
||||
GameState::SelectSource(previous_target) => {
|
||||
self.handle_select_source(mouse_pos, previous_target)
|
||||
}
|
||||
GameState::SelectTarget(source) => {
|
||||
let next = self.handle_select_target(mouse_pos, source);
|
||||
if let GameState::SelectTarget(_) = next {
|
||||
self.reset_squares();
|
||||
GameState::SelectSource(None)
|
||||
} else {
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
GameState::GameOver(previous_target) => GameState::GameOver(previous_target),
|
||||
let board_interaction = self
|
||||
.board
|
||||
.handle_input(mouse_pos, &self.resources, &self.settings);
|
||||
if board_interaction.interacted {
|
||||
let Some(board_state) = board_interaction.board_state else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.state = new_state;
|
||||
}
|
||||
|
||||
if is_mouse_button_pressed(MouseButton::Left) {
|
||||
let current_state = self.state.clone();
|
||||
let new_state = match current_state {
|
||||
GameState::SelectSource(previous_target) => {
|
||||
self.handle_select_source(mouse_pos, previous_target)
|
||||
match board_state {
|
||||
BoardState::Won => {
|
||||
self.next_btn.is_active = true;
|
||||
}
|
||||
GameState::SelectTarget(source) => GameState::SelectTarget(source),
|
||||
GameState::GameOver(previous_target) => GameState::GameOver(previous_target),
|
||||
};
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.state = new_state;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self
|
||||
if self
|
||||
.reset_btn
|
||||
.handle_input(&self.resources.sound(&SoundKind::Button), self.volume))
|
||||
.is_clicked
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Button),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self
|
||||
if self
|
||||
.next_btn
|
||||
.handle_input(&self.resources.sound(&SoundKind::Button), self.volume))
|
||||
.is_clicked
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Button),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.next_puzzle();
|
||||
return;
|
||||
@@ -109,30 +82,39 @@ impl Game {
|
||||
|
||||
// PRESETS
|
||||
let mut clicked_game_mode_btn: Option<&ButtonWidget> = None;
|
||||
if (self
|
||||
if self
|
||||
.easy_btn
|
||||
.handle_input(&self.resources.sound(&SoundKind::Mode), self.volume))
|
||||
.is_clicked
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Mode),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.game_mode = GameMode::Easy;
|
||||
self.easy_btn.is_active = false;
|
||||
self.medium_btn.is_active = true;
|
||||
self.hard_btn.is_active = true;
|
||||
clicked_game_mode_btn = Some(&self.easy_btn);
|
||||
} else if (self
|
||||
} else if self
|
||||
.medium_btn
|
||||
.handle_input(&self.resources.sound(&SoundKind::Mode), self.volume))
|
||||
.is_clicked
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Mode),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.game_mode = GameMode::Medium;
|
||||
self.easy_btn.is_active = true;
|
||||
self.medium_btn.is_active = false;
|
||||
self.hard_btn.is_active = true;
|
||||
clicked_game_mode_btn = Some(&self.medium_btn);
|
||||
} else if (self
|
||||
} else if self
|
||||
.hard_btn
|
||||
.handle_input(&self.resources.sound(&SoundKind::Mode), self.volume))
|
||||
.is_clicked
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Mode),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.game_mode = GameMode::Hard;
|
||||
self.easy_btn.is_active = true;
|
||||
@@ -146,10 +128,13 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self
|
||||
if self
|
||||
.rules_btn
|
||||
.handle_input(&self.resources.sound(&SoundKind::Button), self.volume))
|
||||
.is_clicked
|
||||
.handle_input(
|
||||
&self.resources.sound(&SoundKind::Button),
|
||||
self.settings.volume,
|
||||
)
|
||||
.is_clicked
|
||||
{
|
||||
self.show_rules = !self.show_rules;
|
||||
self.rules_text = match self.show_rules {
|
||||
@@ -163,16 +148,19 @@ impl Game {
|
||||
// KEY INPUTS
|
||||
if is_key_released(KeyCode::Escape) {
|
||||
if self.show_rules {
|
||||
play_sound_once(&self.resources.sound(&SoundKind::Button), self.volume);
|
||||
play_sound_once(
|
||||
&self.resources.sound(&SoundKind::Button),
|
||||
self.settings.volume,
|
||||
);
|
||||
self.show_rules = false;
|
||||
self.rules_text = constants::RESET_BUTTON_TEXT.to_string();
|
||||
self.rules_text = constants::RULES_BUTTON_TEXT.to_string();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if is_key_released(KeyCode::D) {
|
||||
self.debug = !self.debug;
|
||||
self.settings.debug = !self.settings.debug;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,123 +169,24 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_select_source(
|
||||
&mut self,
|
||||
mouse_pos: Circle,
|
||||
previous_target: Option<(usize, usize)>,
|
||||
) -> GameState {
|
||||
self.reset_squares();
|
||||
let mut selected = None;
|
||||
for square in &mut self.squares {
|
||||
if mouse_pos.overlaps_rect(&square.rect) {
|
||||
if let Some(_) = self.current_board.cells[square.i][square.j] {
|
||||
selected = Some((square.i, square.j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((i, j)) = selected {
|
||||
self.get(i, j).is_source = true;
|
||||
let mut target_squares = vec![];
|
||||
for m in self.current_board.legal_moves.iter() {
|
||||
if m.from.file == i && m.from.rank == j {
|
||||
target_squares.push((m.to.file, m.to.rank));
|
||||
}
|
||||
}
|
||||
|
||||
for (i, j) in target_squares {
|
||||
self.get(i, j).is_target = true;
|
||||
}
|
||||
|
||||
return GameState::SelectTarget(selected.unwrap());
|
||||
}
|
||||
|
||||
if let Some((i, j)) = previous_target {
|
||||
self.get(i, j).is_previous_target = true;
|
||||
}
|
||||
|
||||
return GameState::SelectSource(None);
|
||||
}
|
||||
|
||||
fn handle_select_target(&mut self, mouse_pos: Circle, source: (usize, usize)) -> GameState {
|
||||
let mut selected = None;
|
||||
for square in &mut self.squares {
|
||||
if mouse_pos.overlaps_rect(&square.rect) {
|
||||
if let Some(_) = self.current_board.cells[square.i][square.j] {
|
||||
selected = Some((square.i, square.j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (s_x, s_y) = source;
|
||||
let Some((x, y)) = selected else {
|
||||
self.get(s_x, s_y).is_source = true;
|
||||
return GameState::SelectTarget(source);
|
||||
};
|
||||
|
||||
if x == s_x && y == s_y {
|
||||
self.get(s_x, s_y).is_source = true;
|
||||
return GameState::SelectTarget(source);
|
||||
}
|
||||
|
||||
let mut is_legal = false;
|
||||
if self.get(x, y).is_target {
|
||||
is_legal = true;
|
||||
}
|
||||
|
||||
if is_legal {
|
||||
let m = self.current_board.legal_moves.iter().find(|m| {
|
||||
m.from.file == s_x && m.from.rank == s_y && m.to.file == x && m.to.rank == y
|
||||
});
|
||||
|
||||
let m = m.expect("legal move should be found");
|
||||
self.current_board.make_move(m.clone());
|
||||
|
||||
if self.current_board.game_state == BoardState::Won
|
||||
|| self.current_board.game_state == BoardState::Lost
|
||||
{
|
||||
self.reset_squares();
|
||||
if self.current_board.game_state == BoardState::Won {
|
||||
self.next_btn.is_active = true;
|
||||
play_sound_once(&self.resources.sound(&SoundKind::Win), self.volume);
|
||||
} else {
|
||||
play_sound_once(&self.resources.sound(&SoundKind::Loss), self.volume);
|
||||
}
|
||||
|
||||
return GameState::GameOver((x, y));
|
||||
}
|
||||
|
||||
self.reset_squares();
|
||||
self.get(x, y).is_target = true;
|
||||
play_sound_once(&self.resources.sound(&SoundKind::Click), self.volume);
|
||||
return GameState::SelectSource(Some((x, y)));
|
||||
}
|
||||
|
||||
self.reset_squares();
|
||||
return GameState::SelectSource(None);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.current_board = self.puzzle.board.clone();
|
||||
self.reset_game(Default::default());
|
||||
}
|
||||
|
||||
fn reset_game(&mut self, options: ResetOptions) {
|
||||
if options.create_new_puzzle {
|
||||
self.puzzle = Game::generate_puzzle(self.game_mode);
|
||||
}
|
||||
|
||||
self.next_btn.is_active = false;
|
||||
self.state = GameState::SelectSource(None);
|
||||
self.reset_squares();
|
||||
self.board.reset(&self.puzzle);
|
||||
}
|
||||
|
||||
fn next_puzzle(&mut self) {
|
||||
self.reset();
|
||||
let puzzle = Game::generate_puzzle(self.game_mode);
|
||||
self.current_board = puzzle.board.clone();
|
||||
self.puzzle = puzzle;
|
||||
}
|
||||
|
||||
fn reset_squares(&mut self) {
|
||||
for i in 0..self.num_squares {
|
||||
for j in 0..self.num_squares {
|
||||
self.get(i, j).is_source = false;
|
||||
self.get(i, j).is_target = false;
|
||||
}
|
||||
}
|
||||
self.reset_game(ResetOptions {
|
||||
create_new_puzzle: true,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn generate_puzzle(mode: GameMode) -> Puzzle {
|
||||
@@ -312,23 +201,11 @@ impl Game {
|
||||
let puzzle = generated.puzzle();
|
||||
puzzle.expect("No puzzle was generated")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_game(resources: Resources) -> Self {
|
||||
let game_mode = GameMode::Medium;
|
||||
let puzzle = Game::generate_puzzle(game_mode);
|
||||
let current_board = puzzle.board.clone();
|
||||
let num_squares: usize = current_board.size;
|
||||
Self {
|
||||
puzzle,
|
||||
current_board,
|
||||
resources,
|
||||
game_mode,
|
||||
num_squares,
|
||||
heading_text: constants::HEADING_TEXT.to_string(),
|
||||
volume: constants::VOLUME,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct ResetOptions {
|
||||
create_new_puzzle: bool,
|
||||
}
|
||||
|
||||
struct MacroquadRandAdapter;
|
||||
|
||||
Reference in New Issue
Block a user