diff --git a/README.md b/README.md index 439e700..d60eeba 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble ## todo +- undo/redo - more levels - make direct power (comparator -> machine) work, (needs storing power direction in machine tiles) - cut selections, copy to system clipboard diff --git a/src/editor.rs b/src/editor.rs index 5c17ce4..80f4512 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -12,7 +12,7 @@ use crate::{ draw_scaled_texture, draw_usize, get_free_id, get_scroll, level::Level, marble_engine::{ - board::{Board, ResizeDeltas}, + board::Board, pos::{Pos, PosInt}, tile::{Claim, Comparison, Direction, MathOp, MirrorType, OpenTile, PTile, Tile, WireType}, Machine, @@ -70,13 +70,6 @@ pub struct Editor { start_time: Instant, pasting_board: Option, draw_overlay: bool, - undo_history: Vec, - undo_index: usize, -} - -#[derive(Debug, Clone)] -enum Action { - SetArea(ResizeDeltas, Pos, Board, Board), } #[derive(Debug, PartialEq)] @@ -157,74 +150,9 @@ impl Editor { start_time: Instant::now(), pasting_board: None, draw_overlay: true, - undo_history: Vec::new(), - undo_index: 0, } } - fn redo(&mut self) { - if self.undo_index >= self.undo_history.len() { - return; - } - let action = self.undo_history[self.undo_index].clone(); - self.do_action(action); - self.undo_index += 1; - } - - fn push_action(&mut self, action: Action) { - self.do_action(action.clone()); - self.undo_history.truncate(self.undo_index); - self.undo_history.push(action); - self.undo_index += 1; - } - - fn do_action(&mut self, action: Action) { - match action { - Action::SetArea(deltas, pos, _old, new) => { - self.shift_world(deltas.x_neg as f32, deltas.y_neg as f32); - self.source_board.grow(&deltas); - self.source_board.paste_board(pos, &new); - } - } - } - - fn undo(&mut self) { - if self.undo_index == 0 { - return; - } - self.undo_index -= 1; - let action = &self.undo_history[self.undo_index]; - match action { - Action::SetArea(deltas, pos, old, _new) => { - self.source_board.paste_board(*pos, old); - self.source_board.shrink(deltas); - self.shift_world(-(deltas.x_neg as f32), -(deltas.y_neg as f32)); - } - } - } - - fn shift_world(&mut self, x: f32, y: f32) { - match &mut self.active_tool { - Tool::SelectArea(Selection { - area: Some((a, b)), - is_selecting: _, - }) => { - a.x += x as PosInt; - a.y += y as PosInt; - b.x += x as PosInt; - b.y += y as PosInt; - } - Tool::Digits(Some(pos)) => { - pos.x += x as PosInt; - pos.y += y as PosInt; - } - _ => (), - } - let tile_size = TILE_TEXTURE_SIZE * self.zoom; - self.view_offset.x -= x * tile_size; - self.view_offset.y -= y * tile_size; - } - pub fn get_exit_state(&self) -> ExitState { self.exit_state } @@ -356,50 +284,56 @@ impl Editor { let id = get_free_id(&self.blueprints, Blueprint::id); let mut blueprint = Blueprint::new(&board, id); if !self.new_blueprint_name.is_empty() { - blueprint.name.clone_from(&self.new_blueprint_name); + blueprint.name = self.new_blueprint_name.clone(); } blueprint.save(); self.blueprints.push(blueprint); self.active_tool = Tool::Blueprint; } - fn set_area(&mut self, pos: Pos, area: Board) { - let old_area = self.source_board.get_rect(pos, area.width(), area.height()); - if area == old_area { - return; + fn grow_board_and_update_view(&mut self, pos: &mut Pos) { + let tile_size = TILE_TEXTURE_SIZE * self.zoom; + let (x, y) = self.source_board.grow_to_include(*pos); + if x != 0 || y != 0 { + self.view_offset.x -= x as f32 * tile_size; + self.view_offset.y -= y as f32 * tile_size; + pos.x += x; + pos.y += y; + match &mut self.active_tool { + Tool::Digits(Some(pos)) => { + pos.x += x; + pos.y += y; + } + Tool::SelectArea(Selection { + area: Some((start, end)), + is_selecting: _, + }) => { + start.x += x; + start.y += y; + end.x += x; + end.y += y; + } + _ => (), + } } - let width = self.source_board.width() as PosInt; - let height = self.source_board.height() as PosInt; - let resize = ResizeDeltas { - x_pos: if (pos.x + BOARD_MARGIN + area.width() as PosInt) > width { - pos.x + BOARD_MARGIN + area.width() as PosInt - width - } else { - 0 - } as usize, - x_neg: if pos.x < BOARD_MARGIN { - BOARD_MARGIN - pos.x - } else { - 0 - } as usize, - y_pos: if (pos.y + BOARD_MARGIN + area.height() as PosInt) > height { - pos.y + BOARD_MARGIN + area.height() as PosInt - height - } else { - 0 - } as usize, - y_neg: if pos.y < BOARD_MARGIN { - BOARD_MARGIN - pos.y - } else { - 0 - } as usize, - }; - let mut pos = pos; - pos.x += resize.x_neg as PosInt; - pos.y += resize.y_neg as PosInt; - self.push_action(Action::SetArea(resize, pos, old_area, area)); } - fn set_tile(&mut self, pos: Pos, tile: Tile) { - self.set_area(pos, Board::new_single(tile)); + fn set_tile(&mut self, mut pos: Pos, tile: Tile) { + let tile_size = TILE_TEXTURE_SIZE * self.zoom; + pos.x -= BOARD_MARGIN; + pos.y -= BOARD_MARGIN; + self.grow_board_and_update_view(&mut pos); + pos.x += BOARD_MARGIN * 2; + pos.y += BOARD_MARGIN * 2; + self.grow_board_and_update_view(&mut pos); + pos.x -= BOARD_MARGIN; + pos.y -= BOARD_MARGIN; + self.source_board.set(pos, tile); + if tile.is_blank() { + let (x, y) = self.source_board.trim_size(BOARD_MARGIN as usize); + self.view_offset.x += x as f32 * tile_size; + self.view_offset.y += y as f32 * tile_size; + } } pub fn update(&mut self, rl: &RaylibHandle) { @@ -483,16 +417,12 @@ impl Editor { } if self.sim_state == SimState::Editing { - 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); - self.pasting_board = Some(b); - } - } else if rl.is_key_pressed(KeyboardKey::KEY_Z) { - self.undo() - } else if rl.is_key_pressed(KeyboardKey::KEY_Y) { - self.redo(); + if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) + && rl.is_key_pressed(KeyboardKey::KEY_V) + { + if let Ok(text) = rl.get_clipboard_text() { + let b = Board::parse(&text); + self.pasting_board = Some(b); } } } @@ -809,9 +739,11 @@ impl Editor { if simple_button(d, 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); - self.set_area(min, board); + for x in min.x..=max.x { + for y in min.y..=max.y { + self.source_board.set(Pos { x, y }, Tile::BLANK); + } + } } draw_scaled_texture(d, textures.get("eraser"), 236, y + 4, 2.); } @@ -944,7 +876,7 @@ impl Editor { { format!("{:?}", expected_byte as char) } else { - format!("{expected_byte}") + format!("{}", expected_byte) }; d.draw_text(&top_text, x + 2, y + 5, 20, Color::WHITE); } @@ -954,7 +886,7 @@ impl Editor { { format!("{:?}", real_byte as char) } else { - format!("{real_byte}") + format!("{}", real_byte) }; d.draw_text(&bottom_text, x + 2, y + 40, 20, Color::WHITE); } @@ -985,8 +917,14 @@ impl Editor { if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { let tile_pos = (mouse_pos - self.view_offset) / tile_size; let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor()); + let mut pos = tile_pos.into(); + let board = self.pasting_board.take().unwrap(); - self.set_area(tile_pos.into(), board); + self.grow_board_and_update_view(&mut pos); + self.grow_board_and_update_view( + &mut (pos + (board.width() - 1, board.height() - 1).into()), + ); + self.source_board.paste_board(pos, &board); } } return; @@ -1057,10 +995,11 @@ impl Editor { if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) { let pos = tile_pos.into(); match self.active_tool { - Tool::None | Tool::Erase | Tool::SelectArea(_) => (), + Tool::None => (), + Tool::Erase => (), Tool::SetTile(tile) => self.set_tile(pos, tile), Tool::Math => { - self.set_tile(pos, Tile::Powerable(PTile::Math(self.tool_math), false)); + self.set_tile(pos, Tile::Powerable(PTile::Math(self.tool_math), false)) } Tool::Comparator => self.set_tile( pos, @@ -1081,16 +1020,22 @@ impl Editor { if 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); + let mut pos = pos; + self.grow_board_and_update_view(&mut pos); + self.grow_board_and_update_view( + &mut (pos + (board.width() - 1, board.height() - 1).into()), + ); + self.source_board.paste_board(pos, &board); } } } + Tool::SelectArea(_) => (), } } if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) && self.active_tool == Tool::Erase { - self.set_tile(tile_pos.into(), Tile::BLANK); + self.set_tile(tile_pos.into(), Tile::BLANK) } if let Tool::SelectArea(selection) = &mut self.active_tool { if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) { diff --git a/src/main.rs b/src/main.rs index 2d30e18..4fb7834 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,23 +53,14 @@ impl Game { textures.load_dir("assets/tiles", rl, thread); textures.load_dir("assets/digits", rl, thread); - let levels = get_levels(); - let solutions = get_solutions(); - let mut selected_solution = 0; - - // select the last solution of the first level, if there is one - if let Some(s) = levels.first().and_then(|l| solutions.get(l.id())) { - selected_solution = s.len().saturating_sub(1); - } - Self { - levels, + levels: get_levels(), level_scroll: 0, - solutions, + solutions: get_solutions(), open_editor: None, textures, selected_level: 0, - selected_solution, + selected_solution: 0, editing_solution_name: false, } } @@ -146,13 +137,9 @@ impl Game { && bounds.check_collision_point_rec(mouse_pos) && self.selected_level != index { + self.selected_solution = 0; self.editing_solution_name = false; self.selected_level = index; - self.selected_solution = 0; - // select the last solution of the level, if there is one - if let Some(solutions) = self.solutions.get(level.id()) { - self.selected_solution = solutions.len().saturating_sub(1); - } } d.draw_rectangle_rec(bounds, widget_bg(self.selected_level == index)); @@ -315,7 +302,7 @@ fn get_solutions() -> HashMap> { .as_deref() .and_then(|s| serde_json::from_str(s).ok()); if let Some(solution) = s { - solutions.push(solution); + solutions.push(solution) } } solutions.sort_unstable_by_key(Solution::id); diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs index 0dd1954..1b21540 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/board.rs @@ -6,21 +6,13 @@ use super::Pos; use super::PosInt; use raylib::prelude::*; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Board { rows: Vec>, width: usize, height: usize, } -#[derive(Debug, Clone)] -pub struct ResizeDeltas { - pub x_pos: usize, - pub x_neg: usize, - pub y_pos: usize, - pub y_neg: usize, -} - impl Board { pub fn parse(source: &str) -> Self { let mut rows = Vec::new(); @@ -45,7 +37,7 @@ impl Board { let mut out = String::new(); for row in &self.rows { for tile in row { - out.push(tile.to_char()); + out.push(tile.to_char()) } out.push('\n'); } @@ -61,14 +53,6 @@ impl Board { } } - pub fn new_single(tile: Tile) -> Self { - Self { - rows: vec![vec![tile]], - width: 1, - height: 1, - } - } - pub fn new(rows: Vec>) -> Self { Self { width: rows[0].len(), @@ -148,12 +132,47 @@ impl Board { out } + pub fn grow_to_include(&mut self, p: Pos) -> (PosInt, PosInt) { + let mut offset_x = 0; + let mut offset_y = 0; + if p.x < 0 { + let len = p.x.unsigned_abs() as usize; + for row in &mut self.rows { + let mut new_row = vec![Tile::BLANK; len]; + new_row.append(row); + *row = new_row; + } + offset_x = len; + self.width += len; + } else if p.x as usize >= self.width { + let new_width = p.x as usize + 1; + for row in &mut self.rows { + row.resize(new_width, Tile::BLANK); + } + self.width = new_width; + } + + if p.y < 0 { + let len = p.y.unsigned_abs() as usize; + let mut new_rows = vec![vec![Tile::BLANK; self.width]; len]; + new_rows.append(&mut self.rows); + self.rows = new_rows; + offset_y = len; + self.height += len; + } else if p.y as usize >= self.height { + let new_height = p.y as usize + 1; + self.rows.resize(new_height, vec![Tile::BLANK; self.width]); + self.height = new_height; + } + (offset_x as PosInt, offset_y as PosInt) + } + pub fn trim_size(&mut self, margin: usize) -> (usize, usize) { let (offset_x, offset_y); // top { let mut n = 0; - while n < self.height && self.rows[n].iter().all(|t| t.is_blank()) { + while n < self.height && self.rows[n].iter().all(Tile::is_blank) { n += 1; } let trim_top = n.saturating_sub(margin); @@ -166,7 +185,7 @@ impl Board { // bottom { let mut n = 0; - while n < self.height && self.rows[self.height - n - 1].iter().all(|t| t.is_blank()) { + while n < self.height && self.rows[self.height - n - 1].iter().all(Tile::is_blank) { n += 1; } let trim_bottom = n.saturating_sub(margin); @@ -207,32 +226,6 @@ impl Board { (offset_x, offset_y) } - 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); - for x in 0..self.width { - for y in 0..self.height { - let tile = self.rows[y][x]; - new_board.rows[y + deltas.y_neg][x + deltas.x_neg] = tile; - } - } - *self = new_board; - } - - 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); - for x in 0..new_width { - for y in 0..new_height { - let tile = self.rows[y + deltas.y_neg][x + deltas.x_neg]; - new_board.rows[y][x] = tile; - } - } - *self = new_board; - } - pub fn width(&self) -> usize { self.width } diff --git a/src/marble_engine/tile.rs b/src/marble_engine/tile.rs index 3619c19..808fe94 100644 --- a/src/marble_engine/tile.rs +++ b/src/marble_engine/tile.rs @@ -155,19 +155,19 @@ impl Tile { } } - pub fn is_blank(self) -> bool { + pub fn is_blank(&self) -> bool { matches!(self, Tile::Open(OpenTile::Blank, _)) } - pub fn read_value(self) -> MarbleValue { + pub fn read_value(&self) -> MarbleValue { match self { - Tile::Marble { value, dir: _ } => value, - Tile::Open(OpenTile::Digit(d), _) => d as MarbleValue, + Tile::Marble { value, dir: _ } => *value, + Tile::Open(OpenTile::Digit(d), _) => *d as MarbleValue, _ => 0, } } - pub fn texture(self) -> &'static str { + pub fn texture(&self) -> &str { match self { Tile::Open(OpenTile::Blank, _) => "", Tile::Block => "block", @@ -188,19 +188,19 @@ impl Tile { Tile::Mirror(mirror) => mirror.texture_name(), Tile::Arrow(dir) => dir.arrow_tile_texture_name(), Tile::Button(state) => { - if state { + if *state { return "button_on"; } "button_off" } Tile::Wire(wire, state) => { - if state { + if *state { return wire.texture_name_on(); } wire.texture_name_off() } Tile::Powerable(tile, state) => { - if state { + if *state { return match tile { PTile::Comparator(comp) => comp.texture_name_on(), PTile::Math(math_op) => math_op.texture_name_on(), @@ -209,13 +209,13 @@ impl Tile { PTile::IO => "io_tile_on", }; } - match tile { + return match tile { PTile::Comparator(comp) => comp.texture_name_off(), PTile::Math(math_op) => math_op.texture_name_off(), PTile::Silo => "silo_off", PTile::Flipper => "flipper_off", PTile::IO => "io_tile_off", - } + }; } } } @@ -229,7 +229,7 @@ impl Direction { Direction::Right, ]; - pub fn opposite(self) -> Direction { + pub fn opposite(&self) -> Direction { match self { Direction::Up => Direction::Down, Direction::Down => Direction::Up, @@ -238,7 +238,7 @@ impl Direction { } } - pub fn right(self) -> Direction { + pub fn right(&self) -> Direction { match self { Direction::Up => Direction::Right, Direction::Down => Direction::Left, @@ -247,11 +247,11 @@ impl Direction { } } - pub fn left(self) -> Direction { + pub fn left(&self) -> Direction { self.right().opposite() } - pub fn step(self, mut pos: Pos) -> Pos { + pub fn step(&self, mut pos: Pos) -> Pos { match self { Direction::Up => pos.y -= 1, Direction::Down => pos.y += 1, @@ -261,7 +261,7 @@ impl Direction { pos } - pub const fn arrow_tile_texture_name(self) -> &'static str { + pub const fn arrow_tile_texture_name(&self) -> &'static str { match self { Direction::Up => "arrow_up", Direction::Down => "arrow_down", @@ -270,7 +270,7 @@ impl Direction { } } - pub const fn arrow_texture_name(self) -> &'static str { + pub const fn arrow_texture_name(&self) -> &'static str { match self { Direction::Up => "direction_up", Direction::Down => "direction_down", @@ -313,14 +313,14 @@ impl WireType { } } - pub const fn texture_name_on(self) -> &'static str { + pub const fn texture_name_on(&self) -> &'static str { match self { WireType::Vertical => "wire_vertical_on", WireType::Horizontal => "wire_horizontal_on", WireType::Cross => "wire_cross_on", } } - pub const fn texture_name_off(self) -> &'static str { + pub const fn texture_name_off(&self) -> &'static str { match self { WireType::Vertical => "wire_vertical_off", WireType::Horizontal => "wire_horizontal_off", @@ -354,7 +354,7 @@ impl MirrorType { } } - pub const fn texture_name(self) -> &'static str { + pub const fn texture_name(&self) -> &'static str { match self { MirrorType::Forward => "mirror_forward", MirrorType::Back => "mirror_back", @@ -363,7 +363,7 @@ impl MirrorType { } impl MathOp { - pub const fn texture_name_on(self) -> &'static str { + pub const fn texture_name_on(&self) -> &'static str { match self { MathOp::Add => "add_on", MathOp::Sub => "sub_on", @@ -372,7 +372,7 @@ impl MathOp { MathOp::Rem => "rem_on", } } - pub const fn texture_name_off(self) -> &'static str { + pub const fn texture_name_off(&self) -> &'static str { match self { MathOp::Add => "add_off", MathOp::Sub => "sub_off", @@ -404,7 +404,7 @@ impl MathOp { } impl Comparison { - pub const fn texture_name_on(self) -> &'static str { + pub const fn texture_name_on(&self) -> &'static str { match self { Comparison::LessThan => "lt_on", Comparison::GreaterThan => "gt_on", @@ -412,7 +412,7 @@ impl Comparison { Comparison::NotEqual => "neq_on", } } - pub const fn texture_name_off(self) -> &'static str { + pub const fn texture_name_off(&self) -> &'static str { match self { Comparison::LessThan => "lt_off", Comparison::GreaterThan => "gt_off", diff --git a/src/util.rs b/src/util.rs index 4a9ac2d..20a28d0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -278,7 +278,7 @@ pub fn slider( let percent = (mouse_pos.x - bounds.x) / bounds.width; let new_value = min + (percent * (max - min + 1) as f32) as u8; if *value != new_value { - *value = new_value; + *value = new_value } } else if d.get_mouse_wheel_move() > 0.5 && *value < max { *value += 1;