group levels into chapters

This commit is contained in:
Crispy 2024-12-22 16:39:40 +01:00
parent ff69b967dd
commit 42dfe4fac7
22 changed files with 236 additions and 210 deletions

View file

@ -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<Level>,
levels: Vec<LevelListEntry>,
level_scroll: usize,
solutions: HashMap<String, Vec<Solution>>,
open_editor: Option<Editor>,
@ -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<Level> {
let mut levels = Vec::<Level>::new();
fn get_levels() -> Vec<LevelListEntry> {
let mut chapters = Vec::<Chapter>::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<Level> {
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
}