implement level completion, score storing and a dismissable 'level complete' popup

This commit is contained in:
Crispy 2024-10-06 22:24:37 +02:00
parent 4aa5ed9eec
commit f9b8dba019
7 changed files with 130 additions and 18 deletions

View file

@ -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

View file

@ -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<Score>,
}
#[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<Score> {
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) {

View file

@ -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,

View file

@ -58,6 +58,10 @@ impl Machine {
&self.input
}
pub fn step_count(&self) -> usize {
self.steps
}
pub fn set_input(&mut self, bytes: Vec<u8>) {
self.input_index = self.input_index.min(bytes.len());
self.input = bytes;

View file

@ -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
}

View file

@ -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 {