solution saving and loading

This commit is contained in:
Crispy 2024-10-06 16:29:45 +02:00
parent c4381ac1a1
commit 0c2d241745
6 changed files with 72 additions and 21 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/user

View file

@ -4,7 +4,6 @@
logic mostly like https://git.crispypin.cc/CrispyPin/marble logic mostly like https://git.crispypin.cc/CrispyPin/marble
## todo ## todo
solution saving & loading
cleanup: unpowered texture names x_off -> x cleanup: unpowered texture names x_off -> x
input/output display input/output display
grow grid automatically while editing grow grid automatically while editing

View file

@ -64,6 +64,7 @@ enum SimState {
pub enum ExitState { pub enum ExitState {
Dont, Dont,
ExitAndSave, ExitAndSave,
Save,
ExitNoSave, ExitNoSave,
} }
@ -100,18 +101,10 @@ impl Editor {
&self.level.id() &self.level.id()
} }
pub fn save(&mut self) {
todo!()
}
pub fn source_board(&self) -> &Board { pub fn source_board(&self) -> &Board {
&self.source_board &self.source_board
} }
pub fn load_board(&mut self, board: Board) {
self.source_board = board;
}
fn start_sim(&mut self) { fn start_sim(&mut self) {
self.machine.reset(); self.machine.reset();
self.machine.set_board(self.source_board.clone()); self.machine.set_board(self.source_board.clone());
@ -278,6 +271,10 @@ impl Editor {
self.exit_menu = true; self.exit_menu = true;
} }
d.draw_text("exit", 10, 10, 20, Color::WHITE); d.draw_text("exit", 10, 10, 20, Color::WHITE);
if simple_button(d, 90, 5, 80, 30) {
self.exit_state = ExitState::Save;
}
d.draw_text("save", 95, 10, 20, Color::WHITE);
} }
} }

View file

@ -13,7 +13,6 @@ mod util;
use editor::{Editor, ExitState}; use editor::{Editor, ExitState};
use level::Level; use level::Level;
use marble_engine::board::Board;
use solution::Solution; use solution::Solution;
use util::*; use util::*;
@ -40,8 +39,6 @@ fn main() {
let mut game = Game::new(&mut rl, &thread); let mut game = Game::new(&mut rl, &thread);
game.run(&mut rl, &thread); game.run(&mut rl, &thread);
// let board = Board::parse(&read_to_string("boards/adder.mbl").unwrap());
// game.load_board(board);
} }
impl Game { impl Game {
@ -52,7 +49,7 @@ impl Game {
Self { Self {
levels: get_levels(), levels: get_levels(),
solutions: HashMap::new(), solutions: get_solutions(),
open_editor: None, open_editor: None,
textures, textures,
selected_level: 0, selected_level: 0,
@ -69,13 +66,20 @@ impl Game {
editor.draw(&mut d, &self.textures); editor.draw(&mut d, &self.textures);
match editor.get_exit_state() { match editor.get_exit_state() {
ExitState::Dont => (), ExitState::Dont => (),
ExitState::ExitNoSave => self.open_editor = None,
ExitState::ExitAndSave => { ExitState::ExitAndSave => {
self.solutions.get_mut(editor.level_id()).unwrap() let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap()
[self.selected_solution] [self.selected_solution];
.board = editor.source_board().to_string(); solution.board = editor.source_board().to_string();
solution.save();
self.open_editor = None; self.open_editor = None;
} }
ExitState::Save => {
let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap()
[self.selected_solution];
solution.board = editor.source_board().to_string();
solution.save();
}
ExitState::ExitNoSave => self.open_editor = None,
} }
} else { } else {
self.draw(&mut d); self.draw(&mut d);
@ -227,3 +231,33 @@ fn get_levels() -> Vec<Level> {
levels.sort_by(|a, b| a.id().cmp(b.id())); levels.sort_by(|a, b| a.id().cmp(b.id()));
levels levels
} }
fn get_solutions() -> HashMap<String, Vec<Solution>> {
let mut levels = HashMap::new();
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()
.map(|s| serde_json::from_str(s).ok())
.flatten();
if let Some(solution) = s {
solutions.push(solution)
}
}
levels.insert(level_name, solutions);
}
}
}
}
levels
}

View file

@ -1,13 +1,19 @@
use std::{
fs::{self, File},
io::Write,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::level::Level; use crate::{level::Level, userdata_dir};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Solution { pub struct Solution {
// solution_id: String, solution_id: String,
level_id: String, // redundant? level_id: String, // redundant?
pub name: String, pub name: String,
pub board: String, pub board: String,
#[serde(default)]
pub score: Option<Score>, pub score: Option<Score>,
} }
@ -21,7 +27,7 @@ pub struct Score {
impl Solution { impl Solution {
pub fn new(level: &Level, number: usize) -> Self { pub fn new(level: &Level, number: usize) -> Self {
Self { Self {
// solution_id: format!("solution_{number}"), solution_id: format!("solution_{number}"),
level_id: level.id().to_owned(), level_id: level.id().to_owned(),
name: format!("Unnamed {number}"), name: format!("Unnamed {number}"),
board: level board: level
@ -32,6 +38,16 @@ impl Solution {
} }
} }
pub fn save(&self) {
let dir = userdata_dir().join("solutions").join(&self.level_id);
fs::create_dir_all(&dir).unwrap();
let path = dir.join(&format!("{}.json", &self.solution_id));
let json = serde_json::to_string_pretty(self).unwrap();
let mut file = File::create(path).unwrap();
file.write_all(json.as_bytes()).unwrap();
}
pub fn level_id(&self) -> &str { pub fn level_id(&self) -> &str {
&self.level_id &self.level_id
} }

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, fs::read_dir}; use std::{collections::HashMap, fs::read_dir, path::PathBuf};
use raylib::prelude::*; use raylib::prelude::*;
@ -188,3 +188,7 @@ pub fn shrink_rec(rec: Rectangle, a: f32) -> Rectangle {
height: rec.height - a * 2., height: rec.height - a * 2.,
} }
} }
pub fn userdata_dir()->PathBuf{
PathBuf::from("user")
}