marble-machinations/src/main.rs

283 lines
7.3 KiB
Rust
Raw Normal View History

2024-10-06 12:39:36 +02:00
use std::{
collections::HashMap,
fs::{read_dir, read_to_string},
};
2024-10-03 22:59:49 +02:00
use raylib::prelude::*;
mod blueprint;
2024-10-06 00:24:11 +02:00
mod editor;
2024-10-06 12:39:36 +02:00
mod level;
2024-10-03 22:59:49 +02:00
mod marble_engine;
2024-10-06 12:39:36 +02:00
mod solution;
2024-10-04 21:20:53 +02:00
mod util;
2024-10-06 12:39:36 +02:00
2024-10-06 16:00:12 +02:00
use editor::{Editor, ExitState};
2024-10-06 12:39:36 +02:00
use level::Level;
use solution::Solution;
2024-10-04 21:20:53 +02:00
use util::*;
2024-10-06 12:39:36 +02:00
struct Game {
levels: Vec<Level>,
solutions: HashMap<String, Vec<Solution>>,
open_editor: Option<Editor>,
textures: Textures,
selected_level: usize,
selected_solution: usize,
2024-10-06 13:12:05 +02:00
editing_solution_name: bool,
2024-10-06 12:39:36 +02:00
}
2024-10-03 22:59:49 +02:00
fn main() {
2024-10-04 01:21:52 +02:00
let (mut rl, thread) = raylib::init()
.resizable()
.title("good window title")
.build();
2024-10-03 22:59:49 +02:00
rl.set_target_fps(60);
rl.set_window_min_size(640, 480);
rl.set_mouse_cursor(MouseCursor::MOUSE_CURSOR_CROSSHAIR);
rl.set_exit_key(None);
rl.set_trace_log(TraceLogLevel::LOG_WARNING);
2024-10-03 22:59:49 +02:00
2024-10-06 12:39:36 +02:00
let mut game = Game::new(&mut rl, &thread);
game.run(&mut rl, &thread);
}
impl Game {
fn new(rl: &mut RaylibHandle, thread: &RaylibThread) -> Self {
let mut textures = Textures::default();
2024-10-06 23:37:21 +02:00
textures.load_dir("assets", rl, thread);
textures.load_dir("assets/tiles", rl, thread);
2024-10-07 13:16:26 +02:00
textures.load_dir("assets/digits", rl, thread);
2024-10-06 12:39:36 +02:00
Self {
levels: get_levels(),
2024-10-06 16:29:45 +02:00
solutions: get_solutions(),
2024-10-06 12:39:36 +02:00
open_editor: None,
textures,
selected_level: 0,
selected_solution: 0,
2024-10-06 13:12:05 +02:00
editing_solution_name: false,
2024-10-06 12:39:36 +02:00
}
}
2024-10-05 15:19:27 +02:00
2024-10-06 12:39:36 +02:00
fn run(&mut self, rl: &mut RaylibHandle, thread: &RaylibThread) {
while !rl.window_should_close() {
2024-10-06 23:37:21 +02:00
let mut d = rl.begin_drawing(thread);
2024-10-06 12:39:36 +02:00
if let Some(editor) = &mut self.open_editor {
editor.update(&d);
2024-10-06 12:39:36 +02:00
editor.draw(&mut d, &self.textures);
2024-10-06 16:00:12 +02:00
match editor.get_exit_state() {
ExitState::Dont => (),
ExitState::ExitAndSave => {
2024-10-06 16:29:45 +02:00
let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap()
[self.selected_solution];
solution.board = editor.source_board().to_string();
solution.score = editor.score();
2024-10-06 16:29:45 +02:00
solution.save();
2024-10-06 16:00:12 +02:00
self.open_editor = None;
}
2024-10-06 16:29:45 +02:00
ExitState::Save => {
let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap()
[self.selected_solution];
solution.board = editor.source_board().to_string();
solution.score = editor.score();
2024-10-06 16:29:45 +02:00
solution.save();
}
ExitState::ExitNoSave => self.open_editor = None,
2024-10-06 16:00:12 +02:00
}
2024-10-06 12:39:36 +02:00
} else {
self.draw(&mut d);
}
}
}
2024-10-04 01:21:52 +02:00
2024-10-06 12:39:36 +02:00
fn draw(&mut self, d: &mut RaylibDrawHandle) {
d.clear_background(Color::new(64, 64, 64, 255));
let level_list_width = (d.get_screen_width() / 3).min(400);
2024-10-06 12:39:36 +02:00
let screen_height = d.get_screen_height();
d.draw_rectangle(0, 0, level_list_width, screen_height, Color::GRAY);
let clicked = d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT);
let mouse_pos = d.get_mouse_position();
for (i, level) in self.levels.iter().enumerate() {
let level_entry_height = 65;
2024-10-06 12:39:36 +02:00
let y = 10 + i as i32 * level_entry_height;
let bounds = Rectangle {
x: 5.,
y: y as f32 - 5.,
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 != i {
self.selected_solution = 0;
2024-10-06 13:12:05 +02:00
self.editing_solution_name = false;
2024-10-06 12:39:36 +02:00
self.selected_level = i;
}
if self.selected_level == i {
d.draw_rectangle_rec(bounds, Color::DARKCYAN);
}
2024-10-06 23:44:29 +02:00
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);
2024-10-06 12:39:36 +02:00
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);
2024-10-06 12:39:36 +02:00
}
if let Some(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);
let mut y = 70;
for line in level.description().lines() {
d.draw_text(line, level_list_width + 10, y, 20, Color::WHITE);
y += 30;
}
2024-10-06 12:39:36 +02:00
if let Some(solutions) = self.solutions.get_mut(level.id()) {
let solution_entry_height = 40;
let entry_width = 200;
let mut solution_y = y;
2024-10-06 12:39:36 +02:00
for (solution_index, solution) in solutions.iter().enumerate() {
2024-10-06 13:12:05 +02:00
if simple_option_button(
2024-10-06 12:39:36 +02:00
d,
level_list_width + 10,
solution_y,
entry_width,
2024-10-06 12:39:36 +02:00
solution_entry_height,
solution_index,
&mut self.selected_solution,
2024-10-06 13:12:05 +02:00
) {
self.editing_solution_name = false;
}
2024-10-06 12:39:36 +02:00
let name_color = if solution.score.is_some() {
Color::LIME
} else {
Color::ORANGE
};
d.draw_text(
&solution.name,
level_list_width + 15,
solution_y + 5,
20,
name_color,
);
2024-10-06 12:39:36 +02:00
d.draw_text(
&solution.score_text(),
level_list_width + 15,
solution_y + 25,
2024-10-06 12:39:36 +02:00
10,
Color::WHITE,
);
solution_y += solution_entry_height + 10;
2024-10-06 12:39:36 +02:00
}
if simple_button(d, level_list_width + 10, solution_y, entry_width, 30) {
2024-10-06 12:39:36 +02:00
let n = solutions.len();
2024-10-07 14:16:50 +02:00
self.selected_solution = solutions.len();
2024-10-06 23:37:21 +02:00
solutions.push(Solution::new(level, n));
2024-10-06 12:39:36 +02:00
}
d.draw_text(
"new solution",
level_list_width + 15,
solution_y + 5,
2024-10-06 12:39:36 +02:00
20,
Color::WHITE,
);
2024-10-06 13:12:05 +02:00
if let Some(solution) = solutions.get_mut(self.selected_solution) {
2024-10-07 18:43:24 +02:00
let column_x = level_list_width + entry_width + 20;
let bounds = Rectangle::new(column_x as f32, y as f32, 220., 30.);
2024-10-06 13:12:05 +02:00
text_input(
d,
bounds,
&mut solution.name,
&mut self.editing_solution_name,
2024-10-08 22:24:41 +02:00
24,
true,
2024-10-06 13:12:05 +02:00
);
2024-10-07 18:43:24 +02:00
d.draw_text(solution.id(), column_x, y + 35, 10, Color::GRAY);
2024-10-06 16:00:12 +02:00
2024-10-07 18:43:24 +02:00
if simple_button(d, column_x, y + 50, 220, 30) {
let mut editor = Editor::new(solution.clone(), level.clone());
editor.center_view(d);
self.open_editor = Some(editor);
2024-10-06 16:00:12 +02:00
}
2024-10-07 18:43:24 +02:00
d.draw_text("edit", column_x + 5, y + 55, 20, Color::WHITE);
2024-10-06 13:12:05 +02:00
}
2024-10-06 12:39:36 +02:00
} else {
self.solutions.insert(level.id().to_owned(), Vec::new());
}
}
}
}
fn get_levels() -> Vec<Level> {
let mut levels = Vec::<Level>::new();
let mut add_level = |path| {
let l = read_to_string(path)
2024-10-06 12:39:36 +02:00
.ok()
.as_deref()
2024-10-06 23:37:21 +02:00
.and_then(|s| serde_json::from_str(s).ok());
2024-10-06 12:39:36 +02:00
if let Some(level) = l {
levels.push(level);
}
};
for d in read_dir("levels").unwrap().flatten() {
if d.path().is_dir() {
for d in read_dir(d.path()).unwrap().flatten() {
add_level(d.path());
}
} else {
add_level(d.path());
}
2024-10-05 20:22:18 +02:00
}
2024-10-07 13:16:26 +02:00
levels.sort_by_key(Level::sort_order);
2024-10-06 12:39:36 +02:00
levels
2024-10-05 20:22:18 +02:00
}
2024-10-06 16:29:45 +02:00
fn get_solutions() -> HashMap<String, Vec<Solution>> {
let mut by_level = HashMap::new();
2024-10-06 16:29:45 +02:00
let solution_dir = userdata_dir().join("solutions");
if let Ok(dir_contents) = read_dir(solution_dir) {
for dir in dir_contents.flatten() {
if dir.path().is_dir() {
let level_name = dir.file_name().to_string_lossy().to_string();
let mut solutions = Vec::new();
if let Ok(files) = read_dir(dir.path()) {
for file in files.flatten() {
let s = read_to_string(file.path())
.ok()
.as_deref()
2024-10-06 23:37:21 +02:00
.and_then(|s| serde_json::from_str(s).ok());
2024-10-06 16:29:45 +02:00
if let Some(solution) = s {
solutions.push(solution)
}
}
by_level.insert(level_name, solutions);
2024-10-06 16:29:45 +02:00
}
}
}
}
by_level
2024-10-06 16:29:45 +02:00
}