add comment storage to boards
This commit is contained in:
parent
0b9f41cbf6
commit
5c48b531f6
13 changed files with 219 additions and 113 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
|||
/target
|
||||
/user
|
||||
/user*
|
||||
*.zip
|
||||
version.txt
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
104
src/board.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod blueprint;
|
||||
pub mod board;
|
||||
pub mod editor;
|
||||
pub mod level;
|
||||
pub mod marble_engine;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue