add comment storage to boards

This commit is contained in:
Crispy 2025-03-28 23:06:27 +01:00
parent 0b9f41cbf6
commit 5c48b531f6
13 changed files with 219 additions and 113 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
/target
/user
/user*
*.zip
version.txt

View file

@ -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());

View file

@ -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<Board>,
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();

104
src/board.rs Normal file
View file

@ -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<Comment>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
enum CompatBoard {
V1(String),
V2 { grid: Grid, comments: Vec<Comment> },
}
impl From<CompatBoard> 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()
}
}

View file

@ -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)

View file

@ -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<String>,
init_board: Option<Board>,
/// no stages means sandbox
#[serde(default)]
stages: Vec<Stage>,
@ -61,7 +63,7 @@ impl Level {
self.stages.is_empty()
}
pub fn init_board(&self) -> Option<String> {
pub fn init_board(&self) -> Option<Board> {
self.init_board.clone()
}

View file

@ -1,4 +1,5 @@
pub mod blueprint;
pub mod board;
pub mod editor;
pub mod level;
pub mod marble_engine;

View file

@ -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();
}

View file

@ -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<Pos>,
powered: Vec<Pos>,
input: Vec<u8>,
@ -23,14 +23,14 @@ pub struct Machine {
#[derive(Debug)]
pub struct DebugSubTick {
pub board: Board,
pub grid: Grid,
pub pos: Option<Pos>,
}
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),
});
}

View file

@ -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<Tile>,
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<String> for Grid {
fn from(value: String) -> Self {
Self::parse(&value)
}
}
impl From<Grid> for String {
fn from(val: Grid) -> String {
val.to_str()
}
}

View file

@ -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,

View file

@ -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<Score>,
}
@ -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,
}
}

View file

@ -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