From 42dfe4fac7dbf932e08de51f8cc0d907d3c596af Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sun, 22 Dec 2024 16:39:40 +0100 Subject: [PATCH] group levels into chapters --- build.rs | 9 ++- levels/01_intro/01_output.json | 11 --- levels/01_intro/02_digits.json | 10 --- levels/01_intro/03_loop.json | 11 --- levels/01_intro/04_copy_input.json | 16 ----- levels/01_intro/05_copy_odd.json | 13 ---- levels/02_lists/count_fives.json | 13 ---- levels/02_lists/list_length.json | 13 ---- levels/02_lists/null_separation.json | 13 ---- levels/02_lists/reverse_input.json | 13 ---- levels/03_text/ascii_to_lower.json | 13 ---- levels/03_text/output_decimal.json | 13 ---- levels/03_text/parse_decimal.json | 13 ---- levels/chapter_01.json | 62 ++++++++++++++++ levels/chapter_02.json | 53 ++++++++++++++ levels/chapter_03.json | 41 +++++++++++ levels/sandbox.json | 12 ++-- src/editor.rs | 1 - src/level.rs | 11 +-- src/main.rs | 101 +++++++++++++++------------ src/marble_engine.rs | 2 +- src/theme.rs | 2 +- 22 files changed, 236 insertions(+), 210 deletions(-) delete mode 100644 levels/01_intro/01_output.json delete mode 100644 levels/01_intro/02_digits.json delete mode 100644 levels/01_intro/03_loop.json delete mode 100644 levels/01_intro/04_copy_input.json delete mode 100644 levels/01_intro/05_copy_odd.json delete mode 100644 levels/02_lists/count_fives.json delete mode 100644 levels/02_lists/list_length.json delete mode 100644 levels/02_lists/null_separation.json delete mode 100644 levels/02_lists/reverse_input.json delete mode 100644 levels/03_text/ascii_to_lower.json delete mode 100644 levels/03_text/output_decimal.json delete mode 100644 levels/03_text/parse_decimal.json create mode 100644 levels/chapter_01.json create mode 100644 levels/chapter_02.json create mode 100644 levels/chapter_03.json diff --git a/build.rs b/build.rs index dd69605..75a2430 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,9 @@ use std::{fs::File, io::Write}; -fn main(){ +fn main() { let version = concat!("v", env!("CARGO_PKG_VERSION")); - File::create("version.txt").unwrap().write_all(version.as_bytes()).unwrap(); -} \ No newline at end of file + File::create("version.txt") + .unwrap() + .write_all(version.as_bytes()) + .unwrap(); +} diff --git a/levels/01_intro/01_output.json b/levels/01_intro/01_output.json deleted file mode 100644 index 8a51782..0000000 --- a/levels/01_intro/01_output.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "output", - "sort_order": 11, - "name": "Zero", - "description": "learn how to output data", - "init_board": "\n o \n\n I\n\n", - "stages": [{ - "input": [], - "output": [0] - }] -} \ No newline at end of file diff --git a/levels/01_intro/02_digits.json b/levels/01_intro/02_digits.json deleted file mode 100644 index d84bdbd..0000000 --- a/levels/01_intro/02_digits.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "digits", - "sort_order": 12, - "name": "Digits", - "description": "place digits and use number keys to assign them values", - "stages": [{ - "input": [], - "output": [4, 8, 16] - }] -} \ No newline at end of file diff --git a/levels/01_intro/03_loop.json b/levels/01_intro/03_loop.json deleted file mode 100644 index 3fb5c57..0000000 --- a/levels/01_intro/03_loop.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "loop", - "sort_order": 13, - "name": "Loop", - "description": "repeated output", - "init_board": "\n \n o\n\n\n\n ^ \n\n", - "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] - }] -} \ No newline at end of file diff --git a/levels/01_intro/04_copy_input.json b/levels/01_intro/04_copy_input.json deleted file mode 100644 index 1281381..0000000 --- a/levels/01_intro/04_copy_input.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "copy_input", - "sort_order": 14, - "name": "Copy Cat", - "description": "read input and output the same thing", - "stages": [{ - "input": "Hello, world!", - "output": "Hello, world!" - },{ - "input": "Meow!", - "output": "Meow!" - },{ - "input": "there really isn't much point to more than 2 stages, but here we are", - "output": "there really isn't much point to more than 2 stages, but here we are" - }] -} \ No newline at end of file diff --git a/levels/01_intro/05_copy_odd.json b/levels/01_intro/05_copy_odd.json deleted file mode 100644 index f57e8c6..0000000 --- a/levels/01_intro/05_copy_odd.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "copy_odd", - "sort_order": 15, - "name": "Odd Cat", - "description": "copy only the odd numbers from the input", - "stages": [{ - "input": [1, 2, 3, 4, 5, 6, 7], - "output": [1, 3, 5, 7] - },{ - "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] - }] -} \ No newline at end of file diff --git a/levels/02_lists/count_fives.json b/levels/02_lists/count_fives.json deleted file mode 100644 index 395177f..0000000 --- a/levels/02_lists/count_fives.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "count_fives", - "sort_order": 23, - "name": "Fives", - "description": "count how many fives are in the input", - "stages": [{ - "input": [6, 5, 5, 3, 5, 0], - "output": [3] - },{ - "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] -}] -} \ No newline at end of file diff --git a/levels/02_lists/list_length.json b/levels/02_lists/list_length.json deleted file mode 100644 index c254b2b..0000000 --- a/levels/02_lists/list_length.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "list_length", - "sort_order": 22, - "name": "Length", - "description": "count how many numbers are in the input, until the first zero", - "stages": [{ - "input": [1, 87, 9, 0], - "output": [3] - },{ - "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] - }] -} \ No newline at end of file diff --git a/levels/02_lists/null_separation.json b/levels/02_lists/null_separation.json deleted file mode 100644 index e455b0d..0000000 --- a/levels/02_lists/null_separation.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "null_separation", - "sort_order": 21, - "name": "Null Separation", - "description": "output everything after the first zero in the input data", - "stages": [{ - "input": "9834726\u0000Hello, worlg!", - "output": "Hello, worlg!" - },{ - "input": "aonmbgoirf\u0000this is just to make sure you don't hardcode the output for a better score", - "output": "this is just to make sure you don't hardcode the output for a better score" - }] -} \ No newline at end of file diff --git a/levels/02_lists/reverse_input.json b/levels/02_lists/reverse_input.json deleted file mode 100644 index 9b6eb1e..0000000 --- a/levels/02_lists/reverse_input.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "reverse_input", - "sort_order": 24, - "name": "Reverse", - "description": "read input until zero and output the same thing in reverse", - "stages": [{ - "input": "woem\u0000", - "output": "meow" - },{ - "input": "tnropmi yrev\u0000", - "output": "very impornt" - }] -} \ No newline at end of file diff --git a/levels/03_text/ascii_to_lower.json b/levels/03_text/ascii_to_lower.json deleted file mode 100644 index 401de96..0000000 --- a/levels/03_text/ascii_to_lower.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "ascii_to_lower", - "sort_order": 35, - "name": "Lowercase", - "description": "Convert text to lowercase", - "stages": [{ - "input": "FROM THE MOMENT I UNDERSTOOD THE WEAKNESS OF MY FLESH, IT DISGUSTED ME", - "output": "from the moment i understood the weakness of my flesh, it disgusted me" - },{ - "input": "I CraVeD tHE strEnGTH AND CerTAinTy oF STeeL", - "output": "i craved the strength and certainty of steel" - }] -} \ No newline at end of file diff --git a/levels/03_text/output_decimal.json b/levels/03_text/output_decimal.json deleted file mode 100644 index 6cc2f5d..0000000 --- a/levels/03_text/output_decimal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "output_decimal", - "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", - "stages": [{ - "input": [8, 7, 1], - "output": "8 7 1" - },{ - "input": [85, 114, 32, 103, 97, 121, 58, 51], - "output": "85 114 32 103 97 121 58 51" - }] -} \ No newline at end of file diff --git a/levels/03_text/parse_decimal.json b/levels/03_text/parse_decimal.json deleted file mode 100644 index fea5a42..0000000 --- a/levels/03_text/parse_decimal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "parse_decimal", - "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", - "stages": [{ - "input": "1 2 3", - "output": [1, 2, 3] - },{ - "input": "85 114 32 103 97 121 58 51", - "output": [85, 114, 32, 103, 97, 121, 58, 51] - }] -} \ No newline at end of file diff --git a/levels/chapter_01.json b/levels/chapter_01.json new file mode 100644 index 0000000..18bbef2 --- /dev/null +++ b/levels/chapter_01.json @@ -0,0 +1,62 @@ +{ + "title": "1. Introduction", + "levels": [ + { + "id": "output", + "name": "Zero", + "description": "learn how to output data", + "init_board": "\n o \n\n I\n\n", + "stages": [{ + "input": [], + "output": [0] + }] + }, + { + "id": "digits", + "name": "Digits", + "description": "place digits and use number keys to assign them values", + "stages": [{ + "input": [], + "output": [4, 8, 16] + }] + }, + { + "id": "loop", + "name": "Loop", + "description": "repeated output", + "init_board": "\n \n o\n\n\n\n ^ \n\n", + "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] + }] + }, + { + "id": "copy_input", + "name": "Copy Cat", + "description": "read input and output the same thing", + "stages": [{ + "input": "Hello, world!", + "output": "Hello, world!" + },{ + "input": "Meow!", + "output": "Meow!" + },{ + "input": "there really isn't much point to more than 2 stages, but here we are", + "output": "there really isn't much point to more than 2 stages, but here we are" + }] + }, + { + "id": "copy_odd", + "sort_order": 15, + "name": "Odd Cat", + "description": "copy only the odd numbers from the input", + "stages": [{ + "input": [1, 2, 3, 4, 5, 6, 7], + "output": [1, 3, 5, 7] + },{ + "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] + }] + } + ] +} \ No newline at end of file diff --git a/levels/chapter_02.json b/levels/chapter_02.json new file mode 100644 index 0000000..b414bc9 --- /dev/null +++ b/levels/chapter_02.json @@ -0,0 +1,53 @@ +{ + "title": "2. List machines", + "levels": [ + { + "id": "null_separation", + "name": "Null Separation", + "description": "output everything after the first zero in the input data", + "stages": [{ + "input": "9834726\u0000Hello, worlg!", + "output": "Hello, worlg!" + },{ + "input": "aonmbgoirf\u0000this is just to make sure you don't hardcode the output for a better score", + "output": "this is just to make sure you don't hardcode the output for a better score" + }] + }, + { + "id": "count_fives", + "name": "Fives", + "description": "count how many fives are in the input", + "stages": [{ + "input": [6, 5, 5, 3, 5, 0], + "output": [3] + },{ + "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] + }] + }, + { + "id": "list_length", + "name": "Length", + "description": "count how many numbers are in the input, until the first zero", + "stages": [{ + "input": [1, 87, 9, 0], + "output": [3] + },{ + "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] + }] + }, + { + "id": "reverse_input", + "name": "Reverse", + "description": "read input until zero and output the same thing in reverse", + "stages": [{ + "input": "woem\u0000", + "output": "meow" + },{ + "input": "tnropmi yrev\u0000", + "output": "very impornt" + }] + } + ] +} \ No newline at end of file diff --git a/levels/chapter_03.json b/levels/chapter_03.json new file mode 100644 index 0000000..61d4da6 --- /dev/null +++ b/levels/chapter_03.json @@ -0,0 +1,41 @@ +{ + "title": "3. Text processing", + "levels": [ + { + "id": "ascii_to_lower", + "name": "Lowercase", + "description": "Convert text to lowercase", + "stages": [{ + "input": "FROM THE MOMENT I UNDERSTOOD THE WEAKNESS OF MY FLESH, IT DISGUSTED ME", + "output": "from the moment i understood the weakness of my flesh, it disgusted me" + },{ + "input": "I CraVeD tHE strEnGTH AND CerTAinTy oF STeeL", + "output": "i craved the strength and certainty of steel" + }] + }, + { + "id": "output_decimal", + "name": "Numbers", + "description": "Convert input numbers to text, separated by spaces (32)\n'0' = 48, '1' = 49, '2' = 50, and so on", + "stages": [{ + "input": [8, 7, 1], + "output": "8 7 1" + },{ + "input": [85, 114, 32, 103, 97, 121, 58, 51], + "output": "85 114 32 103 97 121 58 51" + }] + }, + { + "id": "parse_decimal", + "name": "Numbers 2", + "description": "Convert input numbers from text, separated by spaces (32)\n'0' = 48, '1' = 49, '2' = 50, and so on", + "stages": [{ + "input": "1 2 3", + "output": [1, 2, 3] + },{ + "input": "85 114 32 103 97 121 58 51", + "output": [85, 114, 32, 103, 97, 121, 58, 51] + }] + } + ] +} \ No newline at end of file diff --git a/levels/sandbox.json b/levels/sandbox.json index cebfb5e..a6ed038 100644 --- a/levels/sandbox.json +++ b/levels/sandbox.json @@ -1,6 +1,10 @@ { - "id": "sandbox", - "sort_order": 100000, - "name": "Sandbox", - "description": "make whatever you want here" + "title": "Sandbox", + "levels": [ + { + "id": "sandbox", + "name": "Sandbox", + "description": "make whatever you want here" + } + ] } \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index 165b2fa..bacdfa3 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -281,7 +281,6 @@ impl Editor { match self.sim_state { SimState::Editing => { self.init_sim(); - // self.step(); } SimState::Running => (), SimState::Stepping => self.step(), diff --git a/src/level.rs b/src/level.rs index 1d268cf..48dee3b 100644 --- a/src/level.rs +++ b/src/level.rs @@ -1,9 +1,14 @@ use serde::Deserialize; +#[derive(Debug, Deserialize)] +pub struct Chapter { + pub title: String, + pub levels: Vec, +} + #[derive(Debug, Clone, Deserialize)] pub struct Level { id: String, - sort_order: i32, name: String, description: String, #[serde(default)] @@ -44,10 +49,6 @@ impl Level { &self.id } - pub fn sort_order(&self) -> i32 { - self.sort_order - } - pub fn name(&self) -> &str { &self.name } diff --git a/src/main.rs b/src/main.rs index a96f7da..a999f71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,11 +11,11 @@ mod level; mod marble_engine; mod solution; mod theme; -mod util; mod ui; +mod util; use editor::{Editor, ExitState}; -use level::Level; +use level::{Chapter, Level}; use solution::Solution; use theme::*; use ui::{simple_button, simple_option_button, text_input, ShapedText}; @@ -26,7 +26,7 @@ const TITLE_TEXT: &str = concat!("Marble Machinations v", env!("CARGO_PKG_VERSIO pub const TILE_TEXTURE_SIZE: f32 = 16.0; struct Game { - levels: Vec, + levels: Vec, level_scroll: usize, solutions: HashMap>, open_editor: Option, @@ -37,6 +37,12 @@ struct Game { level_desc_text: ShapedText, } +#[derive(Debug)] +enum LevelListEntry { + Level(Level), + ChapterTitle(String), +} + fn main() { let (mut rl, thread) = raylib::init().resizable().title(TITLE_TEXT).build(); rl.set_target_fps(60); @@ -58,12 +64,7 @@ impl Game { let levels = get_levels(); let solutions = get_solutions(); - let mut selected_solution = 0; - - // select the last solution of the first level, if there is one - if let Some(s) = levels.first().and_then(|l| solutions.get(l.id())) { - selected_solution = s.len().saturating_sub(1); - } + let selected_solution = 0; Self { levels, @@ -145,42 +146,50 @@ impl Game { width: level_list_width as f32 - 10., height: level_entry_height as f32 - 5., }; - if clicked - && bounds.check_collision_point_rec(mouse_pos) - && self.selected_level != index - { - self.editing_solution_name = false; - self.selected_level = index; - self.selected_solution = 0; - // select the last solution of the level, if there is one - if let Some(solutions) = self.solutions.get(level.id()) { - self.selected_solution = solutions.len().saturating_sub(1); + match level { + LevelListEntry::ChapterTitle(title) => { + d.draw_rectangle_rec(bounds, BG_DARK); + d.draw_text(title, 10, y, 30, FG_CHAPTER_TITLE); } - } - d.draw_rectangle_rec(bounds, widget_bg(self.selected_level == index)); + LevelListEntry::Level(level) => { + if clicked + && bounds.check_collision_point_rec(mouse_pos) + && self.selected_level != index + { + self.editing_solution_name = false; + self.selected_level = index; + self.selected_solution = 0; + // select the last solution of the level, if there is one + if let Some(solutions) = self.solutions.get(level.id()) { + self.selected_solution = solutions.len().saturating_sub(1); + } + } + d.draw_rectangle_rec(bounds, widget_bg(self.selected_level == index)); - let mut title_color = Color::WHITE; - if let Some(solutions) = self.solutions.get(level.id()) { - if solutions.iter().any(|s| s.score.is_some()) { - title_color = Color::LIGHTGREEN; + let mut title_color = Color::WHITE; + if let Some(solutions) = self.solutions.get(level.id()) { + if solutions.iter().any(|s| s.score.is_some()) { + title_color = Color::LIGHTGREEN; + } + } + d.draw_text(level.name(), 10, y, 30, title_color); + let solution_count = self + .solutions + .get(level.id()) + .map(Vec::len) + .unwrap_or_default(); + let subtext = format!("solutions: {solution_count}"); + let subtext_color = if solution_count > 0 { + Color::GOLD + } else { + Color::LIGHTGRAY + }; + d.draw_text(&subtext, 10, y + 30, 20, subtext_color); } } - d.draw_text(level.name(), 10, y, 30, title_color); - let solution_count = self - .solutions - .get(level.id()) - .map(Vec::len) - .unwrap_or_default(); - let subtext = format!("solutions: {solution_count}"); - let subtext_color = if solution_count > 0 { - Color::GOLD - } else { - Color::LIGHTGRAY - }; - d.draw_text(&subtext, 10, y + 30, 20, subtext_color); } - if let Some(level) = self.levels.get(self.selected_level) { + if let Some(LevelListEntry::Level(level)) = self.levels.get(self.selected_level) { d.draw_text(level.name(), level_list_width + 10, 10, 40, Color::CYAN); d.draw_text(level.id(), level_list_width + 10, 50, 10, Color::GRAY); @@ -280,15 +289,15 @@ impl Game { } } -fn get_levels() -> Vec { - let mut levels = Vec::::new(); +fn get_levels() -> Vec { + let mut chapters = Vec::::new(); let mut add_level = |path| { let l = read_to_string(path) .ok() .as_deref() .and_then(|s| serde_json::from_str(s).ok()); if let Some(level) = l { - levels.push(level); + chapters.push(level); } }; for d in read_dir("levels").unwrap().flatten() { @@ -300,7 +309,13 @@ fn get_levels() -> Vec { add_level(d.path()); } } - levels.sort_unstable_by_key(Level::sort_order); + chapters.sort_unstable_by_key(|c| c.title.clone()); + + let mut levels = Vec::new(); + for c in chapters { + levels.push(LevelListEntry::ChapterTitle(c.title)); + levels.extend(c.levels.into_iter().map(LevelListEntry::Level)); + } levels } diff --git a/src/marble_engine.rs b/src/marble_engine.rs index a373f30..402842b 100644 --- a/src/marble_engine.rs +++ b/src/marble_engine.rs @@ -7,7 +7,7 @@ use board::Board; use pos::*; use tile::*; -use crate::{ ui::draw_usize_small, Textures, TILE_TEXTURE_SIZE}; +use crate::{ui::draw_usize_small, Textures, TILE_TEXTURE_SIZE}; #[derive(Debug)] pub struct Machine { diff --git a/src/theme.rs b/src/theme.rs index fd2875c..d14c097 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -9,7 +9,7 @@ pub const BG_LIGHT: Color = gray(64); pub const BG_WIDGET: Color = gray(64); pub const BG_WIDGET_ACTIVE: Color = rgb(80, 120, 180); pub const FG_MARBLE_VALUE: Color = rgb(255, 80, 40); -pub const FG_TOGGLE_ENABLED: Color = gray(200); +pub const FG_CHAPTER_TITLE: Color = rgb(255, 160, 40); pub const fn widget_bg(highlight: bool) -> Color { if highlight {