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 ## todo
cleanup: unpowered texture names x_off -> x cleanup: unpowered texture names x_off -> x
input/output display
grow grid automatically while editing
sim/speed control gui 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) make marble movement not order-dependent (`>ooo <` does not behave symmetrically)
blueprints blueprints
scroll level list
decide on marble data size (u32 or byte?) decide on marble data size (u32 or byte?)
blueprint rotation blueprint rotation

View file

@ -10,7 +10,7 @@ use crate::{
Machine, Machine,
}, },
simple_button, simple_button,
solution::Solution, solution::{Score, Solution},
text_input, texture_option_button, Textures, text_input, texture_option_button, Textures,
}; };
@ -38,6 +38,16 @@ pub struct Editor {
time_since_step: f32, time_since_step: f32,
exit_state: ExitState, exit_state: ExitState,
exit_menu: bool, exit_menu: bool,
complete_popup: Popup,
fail_popup: Popup,
score: Option<Score>,
}
#[derive(Debug, PartialEq)]
enum Popup {
Start,
Visible,
Dismissed,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -90,6 +100,9 @@ impl Editor {
level, level,
exit_state: ExitState::Dont, exit_state: ExitState::Dont,
exit_menu: false, exit_menu: false,
complete_popup: Popup::Start,
fail_popup: Popup::Start,
score: solution.score,
} }
} }
@ -105,11 +118,29 @@ impl Editor {
&self.source_board &self.source_board
} }
pub fn score(&self) -> Option<Score> {
self.score.clone()
}
fn start_sim(&mut self) { fn start_sim(&mut self) {
self.machine.reset(); self.machine.reset();
self.machine.set_board(self.source_board.clone()); 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) { fn rotate_tool(&mut self, shift: bool) {
match &self.active_tool { match &self.active_tool {
Tool::Math => { 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) { 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 { if self.sim_state == SimState::Running {
self.time_since_step += rl.get_frame_time(); self.time_since_step += rl.get_frame_time();
if self.time_since_step > 1. / self.sim_speed { if self.time_since_step > 1. / self.sim_speed {
self.time_since_step = 0.; self.time_since_step = 0.;
self.machine.step(); self.step();
} }
} }
if rl.is_key_pressed(KeyboardKey::KEY_SPACE) { if rl.is_key_pressed(KeyboardKey::KEY_SPACE) {
match self.sim_state { match self.sim_state {
SimState::Editing => { SimState::Editing => {
self.start_sim(); self.start_sim();
self.machine.step(); self.step();
} }
SimState::Running => (), SimState::Running => (),
SimState::Stepping => self.machine.step(), SimState::Stepping => self.step(),
} }
self.sim_state = SimState::Stepping; self.sim_state = SimState::Stepping;
} }
@ -183,7 +215,10 @@ impl Editor {
self.start_sim(); self.start_sim();
self.sim_state = SimState::Running; 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, SimState::Stepping => self.sim_state = SimState::Running,
} }
} }
@ -241,6 +276,61 @@ impl Editor {
self.board_overlay(d, textures); self.board_overlay(d, textures);
self.draw_bottom_bar(d, textures); self.draw_bottom_bar(d, textures);
self.draw_top_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) { fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, _textures: &Textures) {

View file

@ -23,7 +23,7 @@ impl Level {
&self.description &self.description
} }
pub fn init_board(&self)->Option<String>{ pub fn init_board(&self) -> Option<String> {
self.init_board.clone() self.init_board.clone()
} }

View file

@ -62,7 +62,7 @@ impl Game {
while !rl.window_should_close() { while !rl.window_should_close() {
let mut d = rl.begin_drawing(&thread); let mut d = rl.begin_drawing(&thread);
if let Some(editor) = &mut self.open_editor { if let Some(editor) = &mut self.open_editor {
editor.input(&d); editor.update(&d);
editor.draw(&mut d, &self.textures); editor.draw(&mut d, &self.textures);
match editor.get_exit_state() { match editor.get_exit_state() {
ExitState::Dont => (), ExitState::Dont => (),
@ -70,6 +70,7 @@ impl Game {
let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap() let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap()
[self.selected_solution]; [self.selected_solution];
solution.board = editor.source_board().to_string(); solution.board = editor.source_board().to_string();
solution.score = editor.score();
solution.save(); solution.save();
self.open_editor = None; self.open_editor = None;
} }
@ -77,6 +78,7 @@ impl Game {
let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap() let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap()
[self.selected_solution]; [self.selected_solution];
solution.board = editor.source_board().to_string(); solution.board = editor.source_board().to_string();
solution.score = editor.score();
solution.save(); solution.save();
} }
ExitState::ExitNoSave => self.open_editor = None, ExitState::ExitNoSave => self.open_editor = None,

View file

@ -17,7 +17,7 @@ pub struct Machine {
} }
impl Machine { impl Machine {
pub fn new_empty(input:Vec<u8>,width: usize) -> Self { pub fn new_empty(input: Vec<u8>, width: usize) -> Self {
Self { Self {
board: Board::new_empty(width, width), board: Board::new_empty(width, width),
marbles: Vec::new(), marbles: Vec::new(),
@ -58,6 +58,10 @@ impl Machine {
&self.input &self.input
} }
pub fn step_count(&self) -> usize {
self.steps
}
pub fn set_input(&mut self, bytes: Vec<u8>) { pub fn set_input(&mut self, bytes: Vec<u8>) {
self.input_index = self.input_index.min(bytes.len()); self.input_index = self.input_index.min(bytes.len());
self.input = bytes; self.input = bytes;

View file

@ -63,10 +63,10 @@ impl Board {
Board::new(rows) Board::new(rows)
} }
pub fn to_string(&self)->String{ pub fn to_string(&self) -> String {
let mut out = String::new(); let mut out = String::new();
for row in &self.rows{ for row in &self.rows {
for tile in row{ for tile in row {
out.push(tile.to_char()) out.push(tile.to_char())
} }
out.push('\n'); 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 { 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 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Score { pub struct Score {
pub cycles: u32, pub cycles: usize,
pub tiles: u32, pub tiles: usize,
pub area: u32, pub area: usize,
} }
impl Solution { impl Solution {