group levels into chapters
This commit is contained in:
parent
ff69b967dd
commit
42dfe4fac7
22 changed files with 236 additions and 210 deletions
101
src/main.rs
101
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<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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue