From f9b8dba019d2f4c2bd72904fe82ee791ef0b5d04 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sun, 6 Oct 2024 22:24:37 +0200 Subject: [PATCH] implement level completion, score storing and a dismissable 'level complete' popup --- README.md | 7 ++- src/editor.rs | 104 ++++++++++++++++++++++++++++++++++--- src/level.rs | 2 +- src/main.rs | 4 +- src/marble_engine.rs | 6 ++- src/marble_engine/board.rs | 19 +++++-- src/solution.rs | 6 +-- 7 files changed, 130 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f9cf146..4997eb8 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,14 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble ## todo cleanup: unpowered texture names x_off -> x -input/output display -grow grid automatically while editing sim/speed control gui +(option) display input as numbers +scroll output +default i/o text modes specified per level +grow grid automatically while editing make marble movement not order-dependent (`>ooo <` does not behave symmetrically) blueprints +scroll level list decide on marble data size (u32 or byte?) blueprint rotation diff --git a/src/editor.rs b/src/editor.rs index 33c640b..3f222bb 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -10,7 +10,7 @@ use crate::{ Machine, }, simple_button, - solution::Solution, + solution::{Score, Solution}, text_input, texture_option_button, Textures, }; @@ -38,6 +38,16 @@ pub struct Editor { time_since_step: f32, exit_state: ExitState, exit_menu: bool, + complete_popup: Popup, + fail_popup: Popup, + score: Option, +} + +#[derive(Debug, PartialEq)] +enum Popup { + Start, + Visible, + Dismissed, } #[derive(Debug, Clone)] @@ -90,6 +100,9 @@ impl Editor { level, exit_state: ExitState::Dont, exit_menu: false, + complete_popup: Popup::Start, + fail_popup: Popup::Start, + score: solution.score, } } @@ -105,11 +118,29 @@ impl Editor { &self.source_board } + pub fn score(&self) -> Option { + self.score.clone() + } + fn start_sim(&mut self) { self.machine.reset(); self.machine.set_board(self.source_board.clone()); } + fn step(&mut self) { + self.machine.step(); + if self.level.outputs() == self.machine.output() && self.complete_popup == Popup::Start { + self.complete_popup = Popup::Visible; + self.exit_state = ExitState::Save; + self.sim_state = SimState::Stepping; + self.score = Some(Score { + cycles: self.machine.step_count(), + tiles: self.source_board.count_tiles(), + area: 0, + }); + } + } + fn rotate_tool(&mut self, shift: bool) { match &self.active_tool { Tool::Math => { @@ -155,25 +186,26 @@ impl Editor { } } - pub fn input(&mut self, rl: &RaylibHandle) { + pub fn update(&mut self, rl: &RaylibHandle) { if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) { - self.sim_state = SimState::Editing + self.sim_state = SimState::Editing; + self.complete_popup = Popup::Start; } if self.sim_state == SimState::Running { self.time_since_step += rl.get_frame_time(); if self.time_since_step > 1. / self.sim_speed { self.time_since_step = 0.; - self.machine.step(); + self.step(); } } if rl.is_key_pressed(KeyboardKey::KEY_SPACE) { match self.sim_state { SimState::Editing => { self.start_sim(); - self.machine.step(); + self.step(); } SimState::Running => (), - SimState::Stepping => self.machine.step(), + SimState::Stepping => self.step(), } self.sim_state = SimState::Stepping; } @@ -183,7 +215,10 @@ impl Editor { self.start_sim(); self.sim_state = SimState::Running; } - SimState::Running => self.sim_state = SimState::Editing, + SimState::Running => { + self.sim_state = SimState::Editing; + self.complete_popup = Popup::Start; + } SimState::Stepping => self.sim_state = SimState::Running, } } @@ -241,6 +276,61 @@ impl Editor { self.board_overlay(d, textures); self.draw_bottom_bar(d, textures); self.draw_top_bar(d, textures); + + if self.complete_popup == Popup::Visible { + let width = 310; + let height = 165; + let x = d.get_screen_width() / 2 - width / 2; + let y = d.get_screen_height() / 2 - height / 2; + d.draw_rectangle(x, y, width, height, Color::DIMGRAY); + d.draw_text("Level Complete!", x + 10, y + 10, 30, Color::LIME); + if let Some(score) = &self.score { + d.draw_text("cycles", x + 15, y + 45, 20, Color::WHITE); + d.draw_rectangle(x + 10, y + 70, 90, 30, Color::DARKGREEN); + d.draw_text( + &format!("{}", score.cycles), + x + 15, + y + 75, + 20, + Color::WHITE, + ); + + d.draw_text("tiles", x + 115, y + 45, 20, Color::WHITE); + d.draw_rectangle(x + 110, y + 70, 90, 30, Color::DARKGREEN); + d.draw_text( + &format!("{}", score.tiles), + x + 115, + y + 75, + 20, + Color::WHITE, + ); + + d.draw_text("area", x + 215, y + 45, 20, Color::WHITE); + d.draw_rectangle(x + 210, y + 70, 90, 30, Color::DARKGREEN); + d.draw_text( + &format!("{}", score.area), + x + 215, + y + 75, + 20, + Color::WHITE, + ); + } + if simple_button(d, x + 10, y + 110, 140, 45) { + self.complete_popup = Popup::Dismissed; + } + d.draw_text("continue\nediting", x + 15, y + 115, 20, Color::WHITE); + + if simple_button(d, x + width / 2 + 5, y + 110, 140, 45) { + self.exit_state = ExitState::ExitAndSave; + } + d.draw_text( + "return to\nlevel list", + x + width / 2 + 10, + y + 115, + 20, + Color::WHITE, + ); + } } fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, _textures: &Textures) { diff --git a/src/level.rs b/src/level.rs index 803a42c..912a65c 100644 --- a/src/level.rs +++ b/src/level.rs @@ -23,7 +23,7 @@ impl Level { &self.description } - pub fn init_board(&self)->Option{ + pub fn init_board(&self) -> Option { self.init_board.clone() } diff --git a/src/main.rs b/src/main.rs index 8e9ed0c..37f9e09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,7 @@ impl Game { while !rl.window_should_close() { let mut d = rl.begin_drawing(&thread); if let Some(editor) = &mut self.open_editor { - editor.input(&d); + editor.update(&d); editor.draw(&mut d, &self.textures); match editor.get_exit_state() { ExitState::Dont => (), @@ -70,6 +70,7 @@ impl Game { let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap() [self.selected_solution]; solution.board = editor.source_board().to_string(); + solution.score = editor.score(); solution.save(); self.open_editor = None; } @@ -77,6 +78,7 @@ impl Game { let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap() [self.selected_solution]; solution.board = editor.source_board().to_string(); + solution.score = editor.score(); solution.save(); } ExitState::ExitNoSave => self.open_editor = None, diff --git a/src/marble_engine.rs b/src/marble_engine.rs index cfaa880..97c4b61 100644 --- a/src/marble_engine.rs +++ b/src/marble_engine.rs @@ -17,7 +17,7 @@ pub struct Machine { } impl Machine { - pub fn new_empty(input:Vec,width: usize) -> Self { + pub fn new_empty(input: Vec, width: usize) -> Self { Self { board: Board::new_empty(width, width), marbles: Vec::new(), @@ -58,6 +58,10 @@ impl Machine { &self.input } + pub fn step_count(&self) -> usize { + self.steps + } + pub fn set_input(&mut self, bytes: Vec) { self.input_index = self.input_index.min(bytes.len()); self.input = bytes; diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs index 25ddb2c..9fec522 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/board.rs @@ -63,10 +63,10 @@ impl Board { Board::new(rows) } - pub fn to_string(&self)->String{ + pub fn to_string(&self) -> String { let mut out = String::new(); - for row in &self.rows{ - for tile in row{ + for row in &self.rows { + for tile in row { out.push(tile.to_char()) } out.push('\n'); @@ -91,6 +91,19 @@ impl Board { } } + pub fn count_tiles(&self) -> usize { + let mut sum = 0; + for row in &self.rows { + for tile in row { + match tile { + Tile::Blank | Tile::Block => (), + _ => sum += 1, + } + } + } + sum + } + pub fn in_bounds(&self, p: Pos) -> bool { p.x >= 0 && p.y >= 0 && p.x < self.width as isize && p.y < self.height as isize } diff --git a/src/solution.rs b/src/solution.rs index c91aebd..a4d8b23 100644 --- a/src/solution.rs +++ b/src/solution.rs @@ -19,9 +19,9 @@ pub struct Solution { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Score { - pub cycles: u32, - pub tiles: u32, - pub area: u32, + pub cycles: usize, + pub tiles: usize, + pub area: usize, } impl Solution {