support multiple stages for each level, resetting the machine between

This commit is contained in:
Crispy 2024-12-18 21:31:05 +01:00
parent e6437ae9cf
commit 43130b665d
17 changed files with 220 additions and 126 deletions

View file

@ -54,24 +54,29 @@ pub struct Editor {
tool_mirror: MirrorType,
tool_wire: WireType,
input_text_selected: bool,
stage: Option<usize>,
new_blueprint_name: String,
blueprint_name_selected: bool,
sim_speed: u8,
time_since_step: f32,
exit_state: ExitState,
exit_menu: bool,
total_steps: usize,
popup: EndPopup,
score: Option<Score>,
blueprints: Vec<Blueprint>,
selected_blueprint: usize,
blueprint_scroll: usize,
step_time: u128,
max_step_time: u128,
start_time: Instant,
pasting_board: Option<Board>,
/// draw grid, directions and values of marbles
draw_overlay: bool,
undo_history: Vec<Action>,
undo_index: usize,
// debug/profiling
step_time: u128,
max_step_time: u128,
start_time: Instant,
}
#[derive(Debug, Clone)]
@ -124,16 +129,28 @@ pub enum ExitState {
impl Editor {
pub fn new(solution: Solution, level: Level) -> Self {
let mut output_as_text = true;
let mut input_as_text = true;
let mut stage = None;
let mut machine = Machine::new_empty();
if let Some(i) = level.stages().first() {
stage = Some(0);
output_as_text = i.output().is_text();
input_as_text = i.input().is_text();
machine.set_input(i.input().as_bytes().to_owned());
}
Self {
source_board: Board::parse(&solution.board),
machine: Machine::new_empty(level.inputs().to_owned(), 1),
machine,
sim_state: SimState::Editing,
view_offset: Vector2::zero(),
zoom: 1.,
active_tool: Tool::None,
output_as_text: level.output_is_text(),
input_as_text: level.input_is_text(),
output_as_text,
input_as_text,
input_text_selected: false,
stage,
new_blueprint_name: String::new(),
blueprint_name_selected: false,
sim_speed: 3,
@ -148,6 +165,7 @@ impl Editor {
exit_menu: false,
popup: EndPopup::None,
score: solution.score,
total_steps: 0,
blueprints: get_blueprints(),
selected_blueprint: usize::MAX,
blueprint_scroll: 0,
@ -244,17 +262,29 @@ impl Editor {
pos * TILE_TEXTURE_SIZE * self.zoom + self.view_offset
}
fn start_sim(&mut self) {
fn init_sim(&mut self) {
self.max_step_time = 0;
self.total_steps = 0;
self.start_time = Instant::now();
if !self.level.is_sandbox() {
self.stage = Some(0);
}
self.reset_machine();
}
fn reset_machine(&mut self) {
self.machine.reset();
self.machine.set_board(self.source_board.clone());
if let Some(i) = self.stage {
let bytes = self.level.stages()[i].input().as_bytes();
self.machine.set_input(bytes.to_owned());
}
}
fn step_pressed(&mut self) {
match self.sim_state {
SimState::Editing => {
self.start_sim();
self.init_sim();
// self.step();
}
SimState::Running => (),
@ -266,22 +296,31 @@ impl Editor {
fn step(&mut self) {
self.machine.step();
if self.popup != EndPopup::None {
self.popup = EndPopup::Dismissed;
}
if !self.level.outputs().is_empty() && self.popup == EndPopup::None {
if self.level.outputs() == self.machine.output() {
self.popup = EndPopup::Success;
println!("completed in {:?}", self.start_time.elapsed());
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(),
});
} else if !self.level.outputs().starts_with(self.machine.output()) {
self.popup = EndPopup::Failure;
self.sim_state = SimState::Stepping;
if let Some(i) = self.stage {
if self.popup != EndPopup::None {
self.popup = EndPopup::Dismissed;
}
let stage = &self.level.stages()[i];
if self.popup == EndPopup::None {
if stage.output().as_bytes() == self.machine.output() {
if i < self.level.stages().len() {
self.stage = Some(i + 1);
self.total_steps += self.machine.step_count();
self.reset_machine();
} else {
self.popup = EndPopup::Success;
println!("completed in {:?}", self.start_time.elapsed());
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(),
});
}
} else if !stage.output().as_bytes().starts_with(self.machine.output()) {
self.popup = EndPopup::Failure;
self.sim_state = SimState::Stepping;
}
}
}
}
@ -439,7 +478,7 @@ impl Editor {
if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
match self.sim_state {
SimState::Editing => {
self.start_sim();
self.init_sim();
self.sim_state = SimState::Running;
}
SimState::Running => {
@ -654,32 +693,33 @@ impl Editor {
d.draw_text("save", 90, 10, 20, Color::WHITE);
}
if simple_button(d, 150, 4, 32, 32) {
self.undo()
}
let undo_icon = if self.undo_index > 0 {
"undo"
} else {
"undo_disabled"
};
draw_scaled_texture(d, textures.get(undo_icon), 150, 4, 2.);
if self.sim_state == SimState::Editing {
if simple_button(d, 150, 4, 32, 32) {
self.undo()
}
let undo_icon = if self.undo_index > 0 {
"undo"
} else {
"undo_disabled"
};
draw_scaled_texture(d, textures.get(undo_icon), 150, 4, 2.);
if simple_button(d, 186, 4, 32, 32) {
self.redo()
if simple_button(d, 186, 4, 32, 32) {
self.redo()
}
let redo_icon = if self.undo_index < self.undo_history.len() {
"redo"
} else {
"redo_disabled"
};
draw_scaled_texture(d, textures.get(redo_icon), 186, 4, 2.);
}
let redo_icon = if self.undo_index < self.undo_history.len() {
"redo"
} else {
"redo_disabled"
};
draw_scaled_texture(d, textures.get(redo_icon), 186, 4, 2.);
simple_toggle_button(d, &mut self.draw_overlay, 223, 4, 32, 32, 4);
match self.sim_state {
SimState::Editing => {
if simple_button(d, 260, 4, 32, 32) {
self.start_sim();
self.init_sim();
self.sim_state = SimState::Running;
}
draw_scaled_texture(d, textures.get("play"), 260, 4, 2.);
@ -716,9 +756,20 @@ impl Editor {
slider(d, &mut self.sim_speed, 0, MAX_SPEED_POWER, 368, 24, 48, 12);
draw_usize(d, textures, self.machine.step_count(), 420, 4, 9, 2);
if self.stage > Some(0) {
draw_usize(
d,
textures,
self.total_steps + self.machine.step_count(),
420,
44,
9,
2,
);
}
draw_usize(d, textures, self.step_time as usize, 540, 42, 9, 1);
draw_usize(d, textures, self.max_step_time as usize, 540, 60, 9, 1);
draw_usize(d, textures, self.step_time as usize, 260, 42, 9, 1);
draw_usize(d, textures, self.max_step_time as usize, 260, 60, 9, 1);
d.draw_text("input:", 603, 8, 10, Color::WHITE);
if simple_button(d, 600, 20, 35, 15) {
@ -936,32 +987,35 @@ impl Editor {
for (box_index, index) in (output_start..output_end).enumerate() {
let x = output_x + output_cell_width * box_index as i32;
let expected_byte = self.level.outputs().get(index);
let (mut top_color, mut bottom_color) = (BG_LIGHT, BG_MEDIUM);
let real_byte = self.machine.output().get(index);
let (top_color, bottom_color) =
if let Some(stage_index) = self.stage {
let stage = &self.level.stages()[stage_index];
let expected_byte = stage.output().as_bytes().get(index);
if let (Some(&real_byte), Some(&expected_byte)) = (real_byte, expected_byte) {
if expected_byte == real_byte {
(top_color, bottom_color) = if expected_byte == real_byte {
(Color::GREEN, Color::DARKGREEN)
} else {
(Color::RED, Color::DARKRED)
}
} else {
(BG_LIGHT, BG_MEDIUM)
};
d.draw_rectangle(x, y, output_cell_width - 5, 30, top_color);
d.draw_rectangle(x, y + 35, output_cell_width - 5, 30, bottom_color);
if let Some(&expected_byte) = expected_byte {
let top_text = if self.output_as_text
&& (expected_byte.is_ascii_graphic() || expected_byte.is_ascii_whitespace())
{
format!("{:?}", expected_byte as char)
} else {
format!("{expected_byte}")
};
d.draw_text(&top_text, x + 2, y + 5, 20, Color::WHITE);
};
}
d.draw_rectangle(x, y, output_cell_width - 5, 30, top_color);
if let Some(&expected_byte) = expected_byte {
let top_text = if self.output_as_text
&& (expected_byte.is_ascii_graphic() || expected_byte.is_ascii_whitespace())
{
format!("{:?}", expected_byte as char)
} else {
format!("{expected_byte}")
};
d.draw_text(&top_text, x + 2, y + 5, 20, Color::WHITE);
}
}
d.draw_rectangle(x, y + 35, output_cell_width - 5, 30, bottom_color);
if let Some(&real_byte) = real_byte {
let bottom_text = if self.output_as_text
&& (real_byte.is_ascii_graphic() || real_byte.is_ascii_whitespace())

View file

@ -7,10 +7,16 @@ pub struct Level {
name: String,
description: String,
#[serde(default)]
is_sandbox: bool,
init_board: Option<String>,
inputs: IOData,
outputs: IOData,
/// no stages means sandbox
#[serde(default)]
stages: Vec<Stage>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Stage {
input: IOData,
output: IOData,
}
#[derive(Debug, Clone, Deserialize)]
@ -27,6 +33,10 @@ impl IOData {
IOData::Text(t) => t.as_bytes(),
}
}
pub fn is_text(&self) -> bool {
matches!(self, IOData::Text(_))
}
}
impl Level {
@ -47,26 +57,40 @@ impl Level {
}
pub fn is_sandbox(&self) -> bool {
self.is_sandbox
self.stages.is_empty()
}
pub fn init_board(&self) -> Option<String> {
self.init_board.clone()
}
pub fn inputs(&self) -> &[u8] {
self.inputs.as_bytes()
pub fn stages(&self) -> &[Stage] {
&self.stages
}
pub fn outputs(&self) -> &[u8] {
self.outputs.as_bytes()
// pub fn inputs(&self) -> &[u8] {
// self.inputs.as_bytes()
// }
// pub fn outputs(&self) -> &[u8] {
// self.outputs.as_bytes()
// }
// pub fn input_is_text(&self) -> bool {
// matches!(self.inputs, IOData::Text(_))
// }
// pub fn output_is_text(&self) -> bool {
// matches!(self.outputs, IOData::Text(_))
// }
}
impl Stage {
pub fn input(&self) -> &IOData {
&self.input
}
pub fn input_is_text(&self) -> bool {
matches!(self.inputs, IOData::Text(_))
}
pub fn output_is_text(&self) -> bool {
matches!(self.outputs, IOData::Text(_))
pub fn output(&self) -> &IOData {
&self.output
}
}

View file

@ -21,12 +21,12 @@ pub struct Machine {
}
impl Machine {
pub fn new_empty(input: Vec<u8>, width: usize) -> Self {
pub fn new_empty() -> Self {
Self {
board: Board::new_empty(width, width),
board: Board::new_empty(5, 5),
marbles: Vec::new(),
powered: Vec::new(),
input,
input: Vec::new(),
input_index: 0,
output: Vec::new(),
steps: 0,