From 5c48b531f6066b675b9a5ef870772addc9d3f9e3 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Fri, 28 Mar 2025 23:06:27 +0100 Subject: [PATCH] add comment storage to boards --- .gitignore | 2 +- src/benchmark.rs | 8 +- src/blueprint.rs | 22 +---- src/board.rs | 104 ++++++++++++++++++++++++ src/editor.rs | 41 +++++----- src/level.rs | 6 +- src/lib.rs | 1 + src/main.rs | 7 +- src/marble_engine.rs | 88 ++++++++++---------- src/marble_engine/{board.rs => grid.rs} | 40 ++++++--- src/marble_engine/pos.rs | 3 +- src/solution.rs | 6 +- tests/main.rs | 4 +- 13 files changed, 219 insertions(+), 113 deletions(-) create mode 100644 src/board.rs rename src/marble_engine/{board.rs => grid.rs} (86%) diff --git a/.gitignore b/.gitignore index a768d82..ab75dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target -/user +/user* *.zip version.txt diff --git a/src/benchmark.rs b/src/benchmark.rs index d9305ab..4d41e28 100644 --- a/src/benchmark.rs +++ b/src/benchmark.rs @@ -1,10 +1,6 @@ use std::time::Instant; -use marble_machinations::{ - level::Level, - marble_engine::{board::Board, Machine}, - solution::Solution, -}; +use marble_machinations::{level::Level, marble_engine::Machine, solution::Solution}; fn main() { aoc_2024_1a(); @@ -23,7 +19,7 @@ fn benchmark(level: &str, solution: &str) { let solution: Solution = serde_json::from_str(solution).unwrap(); let cycle_count = solution.score.unwrap().cycles; let mut machine = Machine::new_empty(); - machine.set_board(Board::parse(&solution.board)); + machine.set_grid(solution.board.grid); let start_time = Instant::now(); for (n, stage) in level.stages().iter().enumerate() { machine.set_input(stage.input().as_bytes().to_owned()); diff --git a/src/blueprint.rs b/src/blueprint.rs index 2419eeb..bb3eaaf 100644 --- a/src/blueprint.rs +++ b/src/blueprint.rs @@ -6,24 +6,21 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{marble_engine::board::Board, util::userdata_dir}; +use crate::{board::Board, util::userdata_dir}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Blueprint { id: usize, pub name: String, - pub board: String, - #[serde(skip, default)] - tile_board: Option, + pub board: Board, } impl Blueprint { - pub fn new(content: &Board, id: usize) -> Self { + pub fn new(board: Board, id: usize) -> Self { Self { id, name: format!("Blueprint {id}"), - board: content.serialize(), - tile_board: Some(content.clone()), + board, } } @@ -31,17 +28,6 @@ impl Blueprint { self.id } - pub fn convert_board(&mut self) -> &Board { - if self.tile_board.is_none() { - self.tile_board = Some(Board::parse(&self.board)); - } - self.tile_board.as_ref().unwrap() - } - - pub fn get_board(&self) -> Option<&Board> { - self.tile_board.as_ref() - } - fn path(&self) -> PathBuf { let dir = userdata_dir().join("blueprints"); fs::create_dir_all(&dir).unwrap(); diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 0000000..7cfe928 --- /dev/null +++ b/src/board.rs @@ -0,0 +1,104 @@ +use serde::{Deserialize, Serialize}; + +use crate::marble_engine::{ + grid::{Grid, ResizeDeltas}, + pos::Pos, + tile::Tile, +}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Comment { + text: String, + pos: Pos, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(from = "CompatBoard")] +pub struct Board { + pub grid: Grid, + pub comments: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +enum CompatBoard { + V1(String), + V2 { grid: Grid, comments: Vec }, +} + +impl From for Board { + fn from(value: CompatBoard) -> Self { + match value { + CompatBoard::V1(string) => Self { + grid: Grid::parse(&string), + comments: Vec::new(), + }, + CompatBoard::V2 { grid, comments } => Self { grid, comments }, + } + } +} + +impl Default for Board { + fn default() -> Self { + Self { + grid: Grid::new_single(Tile::BLANK), + comments: Vec::new(), + } + } +} + +impl Board { + pub fn new(grid: Grid) -> Self { + Self { + grid, + comments: Vec::new(), + } + } + + pub fn single_tile(tile: Tile) -> Self { + Self { + grid: Grid::new_single(tile), + comments: Vec::new(), + } + } + + pub fn get_rect(&self, pos: Pos, width: usize, height: usize) -> Self { + // TODO filter for comments in the area + let comments = self.comments.clone(); + Self { + grid: self.grid.get_rect(pos, width, height), + comments, + } + } + + pub fn paste_board(&mut self, pos: Pos, board: &Board) { + // TODO remove overlapping comments + self.grid.paste_grid(pos, &board.grid); + self.comments.extend_from_slice(&board.comments); + } + + pub fn grow(&mut self, deltas: &ResizeDeltas) { + self.grid.grow(deltas); + // TODO adjust comments + } + + pub fn shrink(&mut self, deltas: &ResizeDeltas) { + self.grid.shrink(deltas); + // TODO adjust comments + } + + pub fn from_user_str(source: &str) -> Self { + serde_json::from_str(source).unwrap_or_else(|_| Self { + grid: Grid::parse(source), + comments: Vec::new(), + }) + } + + pub fn width(&self) -> usize { + self.grid.width() + } + + pub fn height(&self) -> usize { + self.grid.height() + } +} diff --git a/src/editor.rs b/src/editor.rs index 644ffb4..21d7352 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -9,8 +9,9 @@ use raylib::prelude::*; use crate::{ blueprint::Blueprint, + board::Board, level::Level, - marble_engine::{board::*, pos::*, tile::*, Machine}, + marble_engine::{grid::*, pos::*, tile::*, Machine}, solution::*, theme::*, ui::*, @@ -141,7 +142,7 @@ impl Editor { info_text.set_text(level.description()); Self { - source_board: Board::parse(&solution.board), + source_board: solution.board.clone(), machine, sim_state: SimState::Editing, view_offset: Vector2::zero(), @@ -280,7 +281,7 @@ impl Editor { fn reset_machine(&mut self) { self.machine.reset(); - self.machine.set_board(self.source_board.clone()); + self.machine.set_grid(self.source_board.grid.clone()); if let Some(i) = self.stage { let bytes = self.level.stages()[i].input().as_bytes(); self.machine.set_input(bytes.to_owned()); @@ -320,7 +321,7 @@ impl Editor { self.sim_state = SimState::Stepping; self.score = Some(Score { cycles: self.total_steps + self.machine.step_count(), - tiles: self.source_board.count_tiles(), + tiles: self.source_board.grid.count_tiles(), }); } } else if !stage.output().as_bytes().starts_with(self.machine.output()) { @@ -397,7 +398,7 @@ impl Editor { fn save_blueprint(&mut self, selection: (Pos, Pos)) { let board = self.get_selected_as_board(selection); let id = get_free_id(&self.blueprints, Blueprint::id); - let mut blueprint = Blueprint::new(&board, id); + let mut blueprint = Blueprint::new(board, id); if !self.new_blueprint_name.is_empty() { blueprint.name.clone_from(&self.new_blueprint_name); } @@ -442,7 +443,7 @@ impl Editor { } fn set_tile(&mut self, pos: Pos, tile: Tile) { - self.set_area(pos, Board::new_single(tile)); + self.set_area(pos, Board::single_tile(tile)); } pub fn update(&mut self, rl: &RaylibHandle) { @@ -548,7 +549,7 @@ impl Editor { if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) { if rl.is_key_pressed(KeyboardKey::KEY_V) { if let Ok(text) = rl.get_clipboard_text() { - let b = Board::parse(&text); + let b = Board::from_user_str(&text); self.pasting_board = Some(b); } } else if rl.is_key_pressed(KeyboardKey::KEY_Z) { @@ -563,17 +564,16 @@ impl Editor { fn draw_board(&self, d: &mut RaylibDrawHandle, textures: &Textures) { if self.sim_state == SimState::Editing { self.source_board + .grid .draw(d, textures, self.view_offset, self.zoom); } else { if self.machine.debug_subticks.is_empty() { self.machine - .board() + .grid() .draw(d, textures, self.view_offset, self.zoom); } else { let subframe = &self.machine.debug_subticks[self.machine.subtick_index]; - subframe - .board - .draw(d, textures, self.view_offset, self.zoom); + subframe.grid.draw(d, textures, self.view_offset, self.zoom); if let Some(pos) = subframe.pos { let p = self.pos_to_screen(pos.to_vec()); d.draw_texture_ex(textures.get("selection"), p, 0., self.zoom, Color::ORANGE); @@ -1037,8 +1037,10 @@ impl Editor { if simple_button((d, &self.mouse), 232, y, 40, 40) { let min = selection.0.min(selection.1); let max = selection.0.max(selection.1); - let board = - Board::new_empty((max.x - min.x) as usize + 1, (max.y - min.y) as usize + 1); + let board = Board::new(Grid::new_empty( + (max.x - min.x) as usize + 1, + (max.y - min.y) as usize + 1, + )); self.set_area(min, board); } draw_scaled_texture(d, textures.get("eraser"), 236, y + 4, 2.); @@ -1262,7 +1264,8 @@ impl Editor { offset.x -= offset.x.rem(tile_size); offset.y -= offset.y.rem(tile_size); offset += view_offset; - board.draw(d, textures, offset, self.zoom); + board.grid.draw(d, textures, offset, self.zoom); + // TODO draw comments if self.mouse.left_click() { let tile_pos = (self.mouse.pos() - self.view_offset) / tile_size; let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor()); @@ -1355,7 +1358,7 @@ impl Editor { Tool::Mirror => self.set_tile(pos, Tile::Mirror(self.tool_mirror)), Tool::Digits(_pos) => { self.active_tool = Tool::Digits(Some(pos)); - let tile = self.source_board.get_or_blank(pos); + let tile = self.source_board.grid.get_or_blank(pos); if !matches!(tile, Tile::Open(OpenTile::Digit(_), _)) { self.set_tile(pos, Tile::Open(OpenTile::Digit(0), Claim::Free)); } @@ -1363,8 +1366,7 @@ impl Editor { Tool::Blueprint => { if self.mouse.pos().x > SIDEBAR_WIDTH as f32 { if let Some(bp) = self.blueprints.get(self.selected_blueprint) { - let board = bp.get_board().unwrap().clone(); - self.set_area(pos, board); + self.set_area(pos, bp.board.clone()); } } } @@ -1390,7 +1392,7 @@ impl Editor { } } if let Tool::Blueprint = self.active_tool { - if let Some(bp) = self.blueprints.get_mut(self.selected_blueprint) { + if let Some(bp) = self.blueprints.get(self.selected_blueprint) { let view_offset = Vector2::new( self.view_offset.x.rem(tile_size), self.view_offset.y.rem(tile_size), @@ -1399,7 +1401,8 @@ impl Editor { offset.x -= offset.x.rem(tile_size); offset.y -= offset.y.rem(tile_size); offset += view_offset; - bp.convert_board().draw(d, textures, offset, self.zoom); + bp.board.grid.draw(d, textures, offset, self.zoom); + // TODO draw comments } if self.mouse.pos().x < SIDEBAR_WIDTH as f32 { if self.mouse.scroll() == Some(Scroll::Down) diff --git a/src/level.rs b/src/level.rs index 48dee3b..0ef9b12 100644 --- a/src/level.rs +++ b/src/level.rs @@ -1,5 +1,7 @@ use serde::Deserialize; +use crate::board::Board; + #[derive(Debug, Deserialize)] pub struct Chapter { pub title: String, @@ -12,7 +14,7 @@ pub struct Level { name: String, description: String, #[serde(default)] - init_board: Option, + init_board: Option, /// no stages means sandbox #[serde(default)] stages: Vec, @@ -61,7 +63,7 @@ impl Level { self.stages.is_empty() } - pub fn init_board(&self) -> Option { + pub fn init_board(&self) -> Option { self.init_board.clone() } diff --git a/src/lib.rs b/src/lib.rs index 294ea50..2d6997e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod blueprint; +pub mod board; pub mod editor; pub mod level; pub mod marble_engine; diff --git a/src/main.rs b/src/main.rs index e613aba..24092cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,6 @@ impl Game { let levels = get_levels(); let solutions = get_solutions(); - let selected_solution = 0; Self { levels, @@ -65,7 +64,7 @@ impl Game { open_editor: None, textures, selected_level: 0, - selected_solution, + selected_solution: 0, delete_solution: None, editing_solution_name: false, level_desc_text: ShapedText::new(20), @@ -83,7 +82,7 @@ impl Game { ExitState::ExitAndSave => { let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap() [self.selected_solution]; - solution.board = editor.source_board().serialize(); + solution.board = editor.source_board().clone(); solution.score = editor.score(); solution.save(); self.open_editor = None; @@ -91,7 +90,7 @@ impl Game { ExitState::Save => { let solution = &mut self.solutions.get_mut(editor.level_id()).unwrap() [self.selected_solution]; - solution.board = editor.source_board().serialize(); + solution.board = editor.source_board().clone(); solution.score = editor.score(); solution.save(); } diff --git a/src/marble_engine.rs b/src/marble_engine.rs index 36ebf83..89aad50 100644 --- a/src/marble_engine.rs +++ b/src/marble_engine.rs @@ -1,16 +1,16 @@ use raylib::prelude::*; -pub mod board; +pub mod grid; pub mod pos; pub mod tile; use crate::{theme::TILE_TEXTURE_SIZE, ui::draw_usize_small, util::Textures}; -use board::Board; +use grid::Grid; use pos::*; use tile::*; #[derive(Debug)] pub struct Machine { - board: Board, + grid: Grid, marbles: Vec, powered: Vec, input: Vec, @@ -23,14 +23,14 @@ pub struct Machine { #[derive(Debug)] pub struct DebugSubTick { - pub board: Board, + pub grid: Grid, pub pos: Option, } impl Machine { pub fn new_empty() -> Self { Self { - board: Board::new_empty(5, 5), + grid: Grid::new_empty(5, 5), marbles: Vec::new(), powered: Vec::new(), input: Vec::new(), @@ -51,14 +51,14 @@ impl Machine { self.subtick_index = 0; } - pub fn set_board(&mut self, board: Board) { + pub fn set_grid(&mut self, board: Grid) { self.marbles = board.get_marbles(); self.powered.clear(); - self.board = board; + self.grid = board; } - pub fn board(&self) -> &Board { - &self.board + pub fn grid(&self) -> &Grid { + &self.grid } pub fn output(&self) -> &[u8] { @@ -93,7 +93,7 @@ impl Machine { for marble in &self.marbles { let x = marble.x; let y = marble.y; - if let Some(tile) = self.board.get(*marble) { + if let Some(tile) = self.grid.get(*marble) { let px = x as i32 * tile_size + offset.x as i32; let py = y as i32 * tile_size + offset.y as i32; if let Tile::Marble { value, dir } = tile { @@ -114,7 +114,7 @@ impl Machine { self.subtick_index = 0; self.debug_subticks.clear(); self.debug_subticks.push(DebugSubTick { - board: self.board.clone(), + grid: self.grid.clone(), pos: None, }); } @@ -124,7 +124,7 @@ impl Machine { let mut new_marbles = Vec::new(); // activate all powered machines for &pos in &self.powered { - match self.board.get_mut(pos) { + match self.grid.get_mut(pos) { Some(Tile::Powerable(PTile::Comparator(_), board_power_state)) => { // already handled at the power propagation stage (end of sim step) *board_power_state = Power::OFF; @@ -138,7 +138,7 @@ impl Machine { continue; } let front_pos = dir.step(pos); - let Some(front_tile) = self.board.get_mut(front_pos) else { + let Some(front_tile) = self.grid.get_mut(front_pos) else { continue; }; // `machine` is being powered, in direction `dir` @@ -147,8 +147,8 @@ impl Machine { if front_tile.is_blank() { let pos_a = dir.left().step(pos); let pos_b = dir.right().step(pos); - let val_a = self.board.get_or_blank(pos_a).read_value(); - let val_b = self.board.get_or_blank(pos_b).read_value(); + let val_a = self.grid.get_or_blank(pos_a).read_value(); + let val_b = self.grid.get_or_blank(pos_b).read_value(); let value = match op { MathOp::Add => val_a.wrapping_add(val_b), @@ -215,11 +215,11 @@ impl Machine { let mut influenced_direction = vec![DirInfluence::None; self.marbles.len()]; for (i, &pos) in self.marbles.iter().enumerate() { - let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else { + let Some(Tile::Marble { value: _, dir }) = self.grid.get(pos) else { unreachable!() }; let front_pos = dir.step(pos); - let Some(front_tile) = self.board.get(front_pos) else { + let Some(front_tile) = self.grid.get(front_pos) else { continue; }; match front_tile { @@ -252,7 +252,7 @@ impl Machine { } // #### apply all direct bounces #### for (i, &pos) in self.marbles.iter().enumerate() { - let Some(Tile::Marble { value: _, dir }) = self.board.get_mut(pos) else { + let Some(Tile::Marble { value: _, dir }) = self.grid.get_mut(pos) else { unreachable!() }; if will_reverse_direction[i] { @@ -266,7 +266,7 @@ impl Machine { let mut claim_positions = Vec::new(); // prepare creating the new marbles for &(pos, _val, _dir) in &new_marbles { - let Some(Tile::Open(OpenTile::Blank, claim)) = self.board.get_mut(pos) else { + let Some(Tile::Open(OpenTile::Blank, claim)) = self.grid.get_mut(pos) else { unreachable!() }; if claim.claim_indirect() { @@ -276,22 +276,22 @@ impl Machine { // create new marbles // new marbles are past old_marbles index, so will not move this step for (pos, value, dir) in new_marbles { - let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.board.get_mut(pos) + let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.grid.get_mut(pos) else { continue; }; - self.board.set(pos, Tile::Marble { value, dir }); + self.grid.set(pos, Tile::Marble { value, dir }); self.marbles.push(pos); } // #### movement #### // mark claims to figure out what spaces can be moved to for &pos in &self.marbles[..old_marbles] { - let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else { + let Some(Tile::Marble { value: _, dir }) = self.grid.get(pos) else { unreachable!() }; let front_pos = dir.step(pos); - let Some(front_tile) = self.board.get_mut(front_pos) else { + let Some(front_tile) = self.grid.get_mut(front_pos) else { continue; }; if let Tile::Open(_type, claim) = front_tile { @@ -305,7 +305,7 @@ impl Machine { Tile::Button(_) => dir.step(front_pos), _ => continue, }; - let Some(target_tile) = self.board.get_mut(target_pos) else { + let Some(target_tile) = self.grid.get_mut(target_pos) else { continue; }; if let Tile::Open(_type, claim) = target_tile { @@ -319,15 +319,15 @@ impl Machine { let mut removed_marbles = Vec::new(); // move marbles for (i, pos) in self.marbles[..old_marbles].iter_mut().enumerate() { - let Some(Tile::Marble { value, dir }) = self.board.get(*pos) else { + let Some(Tile::Marble { value, dir }) = self.grid.get(*pos) else { unreachable!() }; let front_pos = dir.step(*pos); - let Some(front_tile) = self.board.get_mut(front_pos) else { + let Some(front_tile) = self.grid.get_mut(front_pos) else { continue; }; - let mut move_to = |tile, target_pos, dir, board: &mut Board| { + let mut move_to = |tile, target_pos, dir, board: &mut Grid| { let value = match tile { OpenTile::Blank => value, OpenTile::Digit(n) => value.wrapping_mul(10).wrapping_add(n as MarbleValue), @@ -339,11 +339,11 @@ impl Machine { if let Tile::Open(space_type, claim_state) = front_tile { if *claim_state == Claim::Claimed { - move_to(*space_type, front_pos, dir, &mut self.board); + move_to(*space_type, front_pos, dir, &mut self.grid); } else if *claim_state != Claim::Free { // (Free means a marble was just here but moved earlier this tick) // bounce on failed direct movement - self.board.set( + self.grid.set( *pos, Tile::Marble { value, @@ -379,11 +379,11 @@ impl Machine { } _ => continue, } - let Some(target_tile) = self.board.get_mut(target_pos) else { + let Some(target_tile) = self.grid.get_mut(target_pos) else { continue; }; if let Tile::Open(space_type, Claim::ClaimedIndirect) = target_tile { - move_to(*space_type, target_pos, new_dir, &mut self.board); + move_to(*space_type, target_pos, new_dir, &mut self.grid); if is_button { self.powered.push(front_pos); } @@ -392,14 +392,14 @@ impl Machine { } for pos in claim_positions { - if let Some(Tile::Open(_, claim_state)) = self.board.get_mut(pos) { + if let Some(Tile::Open(_, claim_state)) = self.grid.get_mut(pos) { *claim_state = Claim::Free; } } // remove marbles for &i in removed_marbles.iter().rev() { - self.board.set(self.marbles[i], Tile::BLANK); + self.grid.set(self.marbles[i], Tile::BLANK); self.marbles.swap_remove(i); } @@ -407,7 +407,7 @@ impl Machine { let mut i = 0; while i < self.powered.len() { let pos = self.powered[i]; - let Some(tile) = self.board.get_mut(pos) else { + let Some(tile) = self.grid.get_mut(pos) else { unreachable!() }; match tile { @@ -415,7 +415,7 @@ impl Machine { *state = true; for dir in Direction::ALL { let target_pos = dir.step(pos); - match self.board.get_mut(target_pos) { + match self.grid.get_mut(target_pos) { Some(Tile::Powerable(_, state)) => { if !state.get_dir(dir) { state.add_dir(dir); @@ -434,7 +434,7 @@ impl Machine { } #[cfg(debug_assertions)] self.debug_subticks.push(DebugSubTick { - board: self.board.clone(), + grid: self.grid.clone(), pos: Some(pos), }); } @@ -442,7 +442,7 @@ impl Machine { *state = true; for dir in wiretype.directions() { let target_pos = dir.step(pos); - match self.board.get_mut(target_pos) { + match self.grid.get_mut(target_pos) { Some(Tile::Powerable(_, state)) => { if !state.get_dir(*dir) { state.add_dir(*dir); @@ -461,7 +461,7 @@ impl Machine { } #[cfg(debug_assertions)] self.debug_subticks.push(DebugSubTick { - board: self.board.clone(), + grid: self.grid.clone(), pos: Some(pos), }); } @@ -473,14 +473,14 @@ impl Machine { continue; } let front_pos = dir.step(pos); - let Some(front_tile) = self.board.get_mut(front_pos) else { + let Some(front_tile) = self.grid.get_mut(front_pos) else { continue; }; if matches!(front_tile, Tile::Wire(_, _) | Tile::Powerable(_, _)) { let pos_a = dir.left().step(pos); let pos_b = dir.right().step(pos); - let val_a = self.board.get_or_blank(pos_a).read_value(); - let val_b = self.board.get_or_blank(pos_b).read_value(); + let val_a = self.grid.get_or_blank(pos_a).read_value(); + let val_b = self.grid.get_or_blank(pos_b).read_value(); let result = match comp { Comparison::LessThan => val_a < val_b, @@ -489,7 +489,7 @@ impl Machine { Comparison::NotEqual => val_a != val_b, }; if result { - match self.board.get_mut(front_pos) { + match self.grid.get_mut(front_pos) { Some(Tile::Powerable(_, state)) => { if !state.get_dir(dir) { state.add_dir(dir); @@ -509,14 +509,14 @@ impl Machine { } #[cfg(debug_assertions)] self.debug_subticks.push(DebugSubTick { - board: self.board.clone(), + grid: self.grid.clone(), pos: Some(pos), }); } Tile::Powerable(_, _state) => { #[cfg(debug_assertions)] self.debug_subticks.push(DebugSubTick { - board: self.board.clone(), + grid: self.grid.clone(), pos: Some(pos), }); } diff --git a/src/marble_engine/board.rs b/src/marble_engine/grid.rs similarity index 86% rename from src/marble_engine/board.rs rename to src/marble_engine/grid.rs index 86f153e..bcca531 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/grid.rs @@ -1,4 +1,5 @@ use raylib::prelude::*; +use serde::{Deserialize, Serialize}; use super::{tile::*, Pos, PosInt}; use crate::{ @@ -6,8 +7,9 @@ use crate::{ util::{draw_scaled_texture, Textures}, }; -#[derive(Debug, Clone, PartialEq)] -pub struct Board { +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(into = "String", from = "String")] +pub struct Grid { tiles: Vec, width: usize, height: usize, @@ -21,7 +23,7 @@ pub struct ResizeDeltas { pub y_neg: usize, } -impl Board { +impl Grid { pub fn parse(source: &str) -> Self { let mut rows = Vec::new(); @@ -48,7 +50,7 @@ impl Board { } } - pub fn serialize(&self) -> String { + pub fn to_str(&self) -> String { let mut out = String::new(); for y in 0..self.height { for x in 0..self.width { @@ -125,7 +127,7 @@ impl Board { } } - pub fn paste_board(&mut self, pos: Pos, source: &Board) { + pub fn paste_grid(&mut self, pos: Pos, source: &Grid) { for x in 0..source.width() { for y in 0..source.height() { let offset = (x, y).into(); @@ -136,8 +138,8 @@ impl Board { } } - pub fn get_rect(&self, pos: Pos, width: usize, height: usize) -> Board { - let mut out = Board::new_empty(width, height); + pub fn get_rect(&self, pos: Pos, width: usize, height: usize) -> Grid { + let mut out = Grid::new_empty(width, height); for x in 0..width { for y in 0..height { let offset = (x, y).into(); @@ -152,27 +154,27 @@ impl Board { pub fn grow(&mut self, deltas: &ResizeDeltas) { let new_width = self.width + deltas.x_neg + deltas.x_pos; let new_height = self.height + deltas.y_neg + deltas.y_pos; - let mut new_board = Board::new_empty(new_width, new_height); + let mut new_grid = Grid::new_empty(new_width, new_height); for x in 0..self.width { for y in 0..self.height { let tile = self.get_unchecked((x, y).into()); - new_board.set((x + deltas.x_neg, y + deltas.y_neg).into(), tile); + new_grid.set((x + deltas.x_neg, y + deltas.y_neg).into(), tile); } } - *self = new_board; + *self = new_grid; } pub fn shrink(&mut self, deltas: &ResizeDeltas) { let new_width = self.width - deltas.x_neg - deltas.x_pos; let new_height = self.height - deltas.y_neg - deltas.y_pos; - let mut new_board = Board::new_empty(new_width, new_height); + let mut new_grid = Grid::new_empty(new_width, new_height); for x in 0..new_width { for y in 0..new_height { let tile = self.get_unchecked((x + deltas.x_neg, y + deltas.y_neg).into()); - new_board.set((x, y).into(), tile); + new_grid.set((x, y).into(), tile); } } - *self = new_board; + *self = new_grid; } pub fn width(&self) -> usize { @@ -231,3 +233,15 @@ impl Board { } } } + +impl From for Grid { + fn from(value: String) -> Self { + Self::parse(&value) + } +} + +impl From for String { + fn from(val: Grid) -> String { + val.to_str() + } +} diff --git a/src/marble_engine/pos.rs b/src/marble_engine/pos.rs index 9eea35c..04cd8da 100644 --- a/src/marble_engine/pos.rs +++ b/src/marble_engine/pos.rs @@ -1,10 +1,11 @@ use std::ops::Add; use raylib::prelude::*; +use serde::{Deserialize, Serialize}; pub type PosInt = i16; -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct Pos { pub x: PosInt, pub y: PosInt, diff --git a/src/solution.rs b/src/solution.rs index 55f30fe..b353d13 100644 --- a/src/solution.rs +++ b/src/solution.rs @@ -6,14 +6,14 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{level::Level, util::userdata_dir}; +use crate::{board::Board, level::Level, util::userdata_dir}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Solution { solution_id: usize, level_id: String, pub name: String, - pub board: String, + pub board: Board, #[serde(default)] pub score: Option, } @@ -30,7 +30,7 @@ impl Solution { solution_id: id, level_id: level.id().to_owned(), name: format!("Unnamed {id}"), - board: level.init_board().unwrap_or(String::from(" ")), + board: level.init_board().unwrap_or_default(), score: None, } } diff --git a/tests/main.rs b/tests/main.rs index 3b0ab6f..336f92e 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,9 +1,9 @@ -use marble_machinations::marble_engine::{board::Board, Machine}; +use marble_machinations::marble_engine::{grid::Grid, Machine}; #[test] fn creating_marbles_cause_indirect_claim() { let mut eng = Machine::new_empty(); - eng.set_board(Board::parse( + eng.set_grid(Grid::parse( " I o 2