support multiple stages for each level, resetting the machine between
This commit is contained in:
parent
e6437ae9cf
commit
43130b665d
17 changed files with 220 additions and 126 deletions
12
README.md
12
README.md
|
@ -4,6 +4,8 @@
|
|||
logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
||||
|
||||
## todo
|
||||
- show level info in editor
|
||||
- comments
|
||||
- accessibility
|
||||
- background colour setting
|
||||
- hotkeys for everything
|
||||
|
@ -11,9 +13,7 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
|||
- make direct power (comparator -> machine) work, (needs storing power direction in machine tiles)
|
||||
- cut selections, copy to system clipboard
|
||||
- timestamps in solutions and blueprints
|
||||
- multiple input/output sets
|
||||
- tooltips
|
||||
- show level info in editor
|
||||
- lock tile types for early levels to make it less overwhelming
|
||||
- display tool variant more clearly (it's not obvious there are more states)
|
||||
- option to use 8-bit marbles?
|
||||
|
@ -60,9 +60,11 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
|||
"sortorder": 5,
|
||||
"name": "Zeroes",
|
||||
"description": "learn how to output data",
|
||||
"init_board": null,
|
||||
"inputs": [],
|
||||
"outputs": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"init_board": "<optional, serialized board>",
|
||||
"stages": [{
|
||||
"input": [],
|
||||
"output": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
}]
|
||||
}
|
||||
```
|
||||
### solution
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
"name": "Zero",
|
||||
"description": "learn how to output data",
|
||||
"init_board": "\n o \n\n I\n\n",
|
||||
"inputs": [],
|
||||
"outputs": [0]
|
||||
"stages": [
|
||||
{
|
||||
"input": [],
|
||||
"output": [
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 12,
|
||||
"name": "Digits",
|
||||
"description": "place digits and use number keys to assign them values",
|
||||
"init_board": null,
|
||||
"inputs": [],
|
||||
"outputs": [4, 8, 16]
|
||||
"stages": [{
|
||||
"input": [],
|
||||
"output": [4, 8, 16]
|
||||
}]
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
"name": "Loop",
|
||||
"description": "repeated output",
|
||||
"init_board": "\n \n o\n\n\n\n ^ \n\n",
|
||||
"inputs": [],
|
||||
"outputs": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
"stages": [{
|
||||
"input": [],
|
||||
"output": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 14,
|
||||
"name": "Copy Cat",
|
||||
"description": "read input and output the same thing",
|
||||
"init_board": null,
|
||||
"inputs": "Hello, world!",
|
||||
"outputs": "Hello, world!"
|
||||
"stages": [{
|
||||
"input": "Hello, world!",
|
||||
"output": "Hello, world!"
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 15,
|
||||
"name": "Odd Cat",
|
||||
"description": "copy only the odd numbers from the input",
|
||||
"init_board": null,
|
||||
"inputs": [112, 92, 51, 79, 112, 96, 84, 59, 195, 208, 137, 196, 68, 204, 82, 148, 251, 56, 105, 38, 63, 204, 240, 220, 180, 54, 211, 17, 82, 17, 181, 43],
|
||||
"outputs": [51, 79, 59, 195, 137, 251, 105, 63, 211, 17, 17, 181, 43]
|
||||
"stages": [{
|
||||
"input": [112, 92, 51, 79, 112, 96, 84, 59, 195, 208, 137, 196, 68, 204, 82, 148, 251, 56, 105, 38, 63, 204, 240, 220, 180, 54, 211, 17, 82, 17, 181, 43],
|
||||
"output": [51, 79, 59, 195, 137, 251, 105, 63, 211, 17, 17, 181, 43]
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 23,
|
||||
"name": "Fives",
|
||||
"description": "count how many fives are in the input",
|
||||
"init_board": null,
|
||||
"inputs": [182, 236, 71, 5, 5, 242, 29, 99, 19, 230, 217, 5, 67, 5, 223, 224, 70, 243, 3, 74, 242, 5, 171, 31, 96, 5, 169, 70, 5, 163, 72, 5, 172, 148, 5, 208, 28, 220, 17, 184, 172, 238, 5, 105, 119, 5, 106, 100, 73, 53, 42, 221, 155, 5, 74, 100, 161, 36, 16, 239, 193, 164, 64, 162, 222, 155, 107, 14, 45, 52, 159, 31, 199, 124, 129, 0],
|
||||
"outputs": [12]
|
||||
"stages": [{
|
||||
"input": [182, 236, 71, 5, 5, 242, 29, 99, 19, 230, 217, 5, 67, 5, 223, 224, 70, 243, 3, 74, 242, 5, 171, 31, 96, 5, 169, 70, 5, 163, 72, 5, 172, 148, 5, 208, 28, 220, 17, 184, 172, 238, 5, 105, 119, 5, 106, 100, 73, 53, 42, 221, 155, 5, 74, 100, 161, 36, 16, 239, 193, 164, 64, 162, 222, 155, 107, 14, 45, 52, 159, 31, 199, 124, 129, 0],
|
||||
"output": [12]
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 22,
|
||||
"name": "Length",
|
||||
"description": "count how many numbers are in the input, until the first zero",
|
||||
"init_board": null,
|
||||
"inputs": [182, 236, 71, 5, 5, 242, 29, 99, 19, 230, 217, 5, 67, 5, 223, 224, 70, 243, 3, 74, 242, 5, 171, 31, 96, 5, 169, 70, 5, 163, 72, 5, 172, 148, 5, 208, 28, 220, 17, 184, 172, 0],
|
||||
"outputs": [41]
|
||||
"stages": [{
|
||||
"input": [182, 236, 71, 5, 5, 242, 29, 99, 19, 230, 217, 5, 67, 5, 223, 224, 70, 243, 3, 74, 242, 5, 171, 31, 96, 5, 169, 70, 5, 163, 72, 5, 172, 148, 5, 208, 28, 220, 17, 184, 172, 0],
|
||||
"output": [41]
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 21,
|
||||
"name": "Null Separation",
|
||||
"description": "output everything after the first zero in the input data",
|
||||
"init_board": null,
|
||||
"inputs": "9834726\u0000Hello, worlg!",
|
||||
"outputs": "Hello, worlg!"
|
||||
"stages": [{
|
||||
"input": "9834726\u0000Hello, worlg!",
|
||||
"output": "Hello, worlg!"
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 24,
|
||||
"name": "Reverse",
|
||||
"description": "read input until zero and output the same thing in reverse",
|
||||
"init_board": null,
|
||||
"inputs": "tnropmi yrev\u0000",
|
||||
"outputs": "very impornt"
|
||||
"stages": [{
|
||||
"input": "tnropmi yrev\u0000",
|
||||
"output": "very impornt"
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 35,
|
||||
"name": "Lowercase",
|
||||
"description": "Convert text to lowercase",
|
||||
"init_board": null,
|
||||
"inputs": "I CraVeD tHE strEnGTH AND CerTAinTy oF STeeL",
|
||||
"outputs": "i craved the strength and certainty of steel"
|
||||
"stages": [{
|
||||
"input": "I CraVeD tHE strEnGTH AND CerTAinTy oF STeeL",
|
||||
"output": "i craved the strength and certainty of steel"
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 31,
|
||||
"name": "Numbers",
|
||||
"description": "Convert input numbers to text, separated by spaces (32)\n'0' = 48, '1' = 49, '2' = 50, and so on",
|
||||
"init_board": null,
|
||||
"inputs": [85, 114, 32, 103, 97, 121, 58, 51],
|
||||
"outputs": "85 114 32 103 97 121 58 51"
|
||||
"stages": [{
|
||||
"input": [85, 114, 32, 103, 97, 121, 58, 51],
|
||||
"output": "85 114 32 103 97 121 58 51"
|
||||
}]
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"sort_order": 33,
|
||||
"name": "Numbers 2",
|
||||
"description": "Convert input numbers from text, separated by spaces (32)\n'0' = 48, '1' = 49, '2' = 50, and so on",
|
||||
"init_board": null,
|
||||
"inputs": "85 114 32 103 97 121 58 51",
|
||||
"outputs": [85, 114, 32, 103, 97, 121, 58, 51]
|
||||
"stages": [{
|
||||
"input": "85 114 32 103 97 121 58 51",
|
||||
"output": [85, 114, 32, 103, 97, 121, 58, 51]
|
||||
}]
|
||||
}
|
|
@ -2,9 +2,5 @@
|
|||
"id": "sandbox",
|
||||
"sort_order": 100000,
|
||||
"name": "Sandbox",
|
||||
"description": "make whatever you want here",
|
||||
"is_sandbox": true,
|
||||
"init_board": null,
|
||||
"inputs": "",
|
||||
"outputs": []
|
||||
"description": "make whatever you want here"
|
||||
}
|
102
src/editor.rs
102
src/editor.rs
|
@ -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,11 +296,18 @@ impl Editor {
|
|||
fn step(&mut self) {
|
||||
self.machine.step();
|
||||
|
||||
if let Some(i) = self.stage {
|
||||
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() {
|
||||
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;
|
||||
|
@ -279,12 +316,14 @@ impl Editor {
|
|||
cycles: self.machine.step_count(),
|
||||
tiles: self.source_board.count_tiles(),
|
||||
});
|
||||
} else if !self.level.outputs().starts_with(self.machine.output()) {
|
||||
}
|
||||
} else if !stage.output().as_bytes().starts_with(self.machine.output()) {
|
||||
self.popup = EndPopup::Failure;
|
||||
self.sim_state = SimState::Stepping;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_tool(&mut self, shift: bool) {
|
||||
if shift {
|
||||
|
@ -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,6 +693,7 @@ impl Editor {
|
|||
d.draw_text("save", 90, 10, 20, Color::WHITE);
|
||||
}
|
||||
|
||||
if self.sim_state == SimState::Editing {
|
||||
if simple_button(d, 150, 4, 32, 32) {
|
||||
self.undo()
|
||||
}
|
||||
|
@ -673,13 +713,13 @@ impl Editor {
|
|||
"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,22 +987,22 @@ 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())
|
||||
|
@ -962,6 +1013,9 @@ impl Editor {
|
|||
};
|
||||
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())
|
||||
|
|
52
src/level.rs
52
src/level.rs
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue