From c4381ac1a1c3a7c5ae97c9844d973c45fedbd51c Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sun, 6 Oct 2024 16:00:12 +0200 Subject: [PATCH] enter and exit solution editor --- README.md | 3 +- src/editor.rs | 121 +++++++++++++++++++++++++++++-------- src/level.rs | 10 ++- src/main.rs | 22 ++++++- src/marble_engine/board.rs | 11 ++++ src/marble_engine/tile.rs | 44 ++++++++++++++ src/solution.rs | 23 ++++--- src/util.rs | 2 +- 8 files changed, 195 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 2e361f1..c19f078 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble ## todo -level selection - solution saving & loading +cleanup: unpowered texture names x_off -> x input/output display grow grid automatically while editing sim/speed control gui diff --git a/src/editor.rs b/src/editor.rs index 1fd4ec2..c2a4f45 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -9,9 +9,14 @@ use crate::{ tile::{Direction, GateType, MathOp, MirrorType, PTile, Tile, WireType}, Machine, }, + simple_button, + solution::Solution, text_input, texture_option_button, Textures, }; +const HEADER_HEIGHT: i32 = 40; +const FOOTER_HEIGHT: i32 = 95; + #[derive(Debug)] pub struct Editor { source_board: Board, @@ -31,6 +36,8 @@ pub struct Editor { input_text_selected: bool, sim_speed: f32, time_since_step: f32, + exit_state: ExitState, + exit_menu: bool, } #[derive(Debug, Clone)] @@ -53,10 +60,17 @@ enum SimState { Stepping, } +#[derive(Debug, Clone, Copy)] +pub enum ExitState { + Dont, + ExitAndSave, + ExitNoSave, +} + impl Editor { - pub fn new_sandbox() -> Self { + pub fn new(solution: Solution, level: Level) -> Self { Self { - source_board: Board::new_empty(16, 16), + source_board: Board::parse(&solution.board), machine: Machine::new_empty(1), sim_state: SimState::Editing, view_offset: Vector2::zero(), @@ -72,10 +86,28 @@ impl Editor { tool_menu_arrow: Direction::Right, tool_menu_mirror: MirrorType::Forward, tool_menu_wire: WireType::Vertical, - level: Level::new_sandbox(), + level, + exit_state: ExitState::Dont, + exit_menu: false, } } + pub fn get_exit_state(&self) -> ExitState { + self.exit_state + } + + pub fn level_id(&self) -> &str { + &self.level.id() + } + + pub fn save(&mut self) { + todo!() + } + + pub fn source_board(&self) -> &Board { + &self.source_board + } + pub fn load_board(&mut self, board: Board) { self.source_board = board; } @@ -131,6 +163,9 @@ impl Editor { } pub fn input(&mut self, rl: &RaylibHandle) { + if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) { + self.sim_state = SimState::Editing + } if self.sim_state == SimState::Running { self.time_since_step += rl.get_frame_time(); if self.time_since_step > 1. / self.sim_speed { @@ -149,9 +184,6 @@ impl Editor { } self.sim_state = SimState::Stepping; } - if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) { - self.sim_state = SimState::Editing; - } if rl.is_key_pressed(KeyboardKey::KEY_ENTER) { match self.sim_state { SimState::Editing => { @@ -175,6 +207,10 @@ impl Editor { if rl.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_RIGHT) { self.view_offset = Vector2::zero(); } + + if rl.is_key_pressed(KeyboardKey::KEY_R) { + self.rotate_tool(rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT)); + } } fn draw_board(&self, d: &mut RaylibDrawHandle, textures: &Textures) { @@ -192,18 +228,6 @@ impl Editor { pub fn draw(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { d.clear_background(Color::new(64, 64, 64, 255)); - self.draw_board(d, textures); - - let height = d.get_screen_height(); - let footer_height = 95; - let footer_top = (height - footer_height) as f32; - d.draw_rectangle( - 0, - height - footer_height, - d.get_screen_width(), - footer_height, - Color::new(32, 32, 32, 255), - ); let tile_size = (16 << self.zoom) as f32; let grid_spill_x = (self.view_offset.x).rem(tile_size) - tile_size; @@ -213,13 +237,62 @@ impl Editor { grid_spill_x, grid_spill_y, d.get_screen_width() as f32 * 2., - height as f32 - grid_spill_y - footer_height as f32, + d.get_screen_height() as f32 * 2., ), None, tile_size, 1, ); + self.draw_board(d, textures); + self.board_overlay(d, textures); + self.draw_bottom_bar(d, textures); + self.draw_top_bar(d, textures); + } + + fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, _textures: &Textures) { + // background + d.draw_rectangle( + 0, + 0, + d.get_screen_width(), + HEADER_HEIGHT, + Color::new(32, 32, 32, 255), + ); + + if self.exit_menu { + if simple_button(d, 5, 5, 80, 30) { + self.exit_menu = false; + } + d.draw_text("cancel", 10, 10, 20, Color::WHITE); + if simple_button(d, 90, 5, 80, 30) { + self.exit_state = ExitState::ExitAndSave; + } + d.draw_text("save", 95, 10, 20, Color::WHITE); + if simple_button(d, 175, 5, 80, 30) { + self.exit_state = ExitState::ExitNoSave; + } + d.draw_text("revert", 180, 10, 20, Color::WHITE); + } else { + if simple_button(d, 5, 5, 80, 30) { + self.exit_menu = true; + } + d.draw_text("exit", 10, 10, 20, Color::WHITE); + } + } + + fn draw_bottom_bar(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { + let height = d.get_screen_height(); + let footer_top = (height - FOOTER_HEIGHT) as f32; + // background + d.draw_rectangle( + 0, + height - FOOTER_HEIGHT, + d.get_screen_width(), + FOOTER_HEIGHT, + Color::new(32, 32, 32, 255), + ); + d.gui_check_box( Rectangle::new(5., footer_top + 5., 25., 25.), Some(rstr!("output as text")), @@ -297,12 +370,10 @@ impl Editor { &Tile::Powerable(PTile::Gate(self.tool_menu_gate), false).texture(), Tool::Gate, ); + } - let is_shift = d.is_key_down(KeyboardKey::KEY_LEFT_SHIFT); - if d.is_key_pressed(KeyboardKey::KEY_R) { - self.rotate_tool(is_shift); - } - + fn board_overlay(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { + let footer_top = (d.get_screen_height() - FOOTER_HEIGHT) as f32; let mouse_pos = d.get_mouse_position(); if self.sim_state == SimState::Editing { if let Tool::Digits(Some(pos)) = &mut self.active_tool { @@ -334,7 +405,7 @@ impl Editor { pos.y += 1; } } - if mouse_pos.y < footer_top { + if mouse_pos.y < footer_top && mouse_pos.y > HEADER_HEIGHT as f32 { let tile_pos = (mouse_pos - self.view_offset) / (16 << self.zoom) as f32; let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor()); diff --git a/src/level.rs b/src/level.rs index f6d19ac..749bd3d 100644 --- a/src/level.rs +++ b/src/level.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use crate::marble_engine::board::Board; -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct Level { id: String, name: String, @@ -36,8 +36,12 @@ impl Level { &self.description } - pub fn init_board(&self) -> Option { - self.init_board.as_deref().map(Board::parse) + // pub fn init_board(&self) -> Option { + // self.init_board.as_deref().map(Board::parse) + // } + + pub fn init_board(&self)->Option{ + self.init_board.clone() } pub fn inputs(&self) -> &[u8] { diff --git a/src/main.rs b/src/main.rs index b6e7515..9f3d421 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ mod marble_engine; mod solution; mod util; -use editor::Editor; +use editor::{Editor, ExitState}; use level::Level; use marble_engine::board::Board; use solution::Solution; @@ -67,6 +67,16 @@ impl Game { if let Some(editor) = &mut self.open_editor { editor.input(&d); editor.draw(&mut d, &self.textures); + match editor.get_exit_state() { + ExitState::Dont => (), + ExitState::ExitNoSave => self.open_editor = None, + ExitState::ExitAndSave => { + self.solutions.get_mut(editor.level_id()).unwrap() + [self.selected_solution] + .board = editor.source_board().to_string(); + self.open_editor = None; + } + } } else { self.draw(&mut d); } @@ -165,7 +175,7 @@ impl Game { if simple_button(d, level_list_width + 10, y, solution_entry_width, 30) { let n = solutions.len(); - solutions.push(Solution::new(level.id().to_owned(), n)); + solutions.push(Solution::new(&level, n)); } d.draw_text( "new solution", @@ -179,7 +189,7 @@ impl Game { let bounds = Rectangle { x: (level_list_width + 10 + solution_entry_width + 10) as f32, y: 60., - width: 240., + width: 220., height: 30., }; text_input( @@ -188,6 +198,12 @@ impl Game { &mut solution.name, &mut self.editing_solution_name, ); + + let button_x = level_list_width + solution_entry_width + 20; + if simple_button(d, button_x, 100, 220, 30) { + self.open_editor = Some(Editor::new(solution.clone(), level.clone())); + } + d.draw_text("edit", button_x + 5, 105, 20, Color::WHITE); } } else { self.solutions.insert(level.id().to_owned(), Vec::new()); diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs index f042e95..25ddb2c 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/board.rs @@ -63,6 +63,17 @@ impl Board { Board::new(rows) } + pub fn to_string(&self)->String{ + let mut out = String::new(); + for row in &self.rows{ + for tile in row{ + out.push(tile.to_char()) + } + out.push('\n'); + } + out + } + pub fn new_empty(width: usize, height: usize) -> Self { let rows = vec![vec![Tile::Blank; width]; height]; Self { diff --git a/src/marble_engine/tile.rs b/src/marble_engine/tile.rs index 5903191..7b5c7f3 100644 --- a/src/marble_engine/tile.rs +++ b/src/marble_engine/tile.rs @@ -104,6 +104,50 @@ impl Tile { } } + pub fn to_char(self) -> char { + match self { + Tile::Blank => ' ', + Tile::Block => '#', + Tile::Marble { value: _, dir: _ } => 'o', + Tile::Digit(n) => (b'0' + n) as char, + Tile::Mirror(dir) => match dir { + MirrorType::Forward => '/', + MirrorType::Back => '\\', + }, + Tile::Arrow(dir) => match dir { + Direction::Up => '^', + Direction::Down => 'v', + Direction::Left => '<', + Direction::Right => '>', + }, + Tile::Powerable(tile, _) => match tile { + PTile::Trigger => '*', + PTile::Wire(wire) => match wire { + WireType::Vertical => '|', + WireType::Horizontal => '-', + WireType::Cross => '+', + }, + PTile::Gate(gate) => match gate { + GateType::LessThan => 'L', + GateType::GreaterThan => 'G', + GateType::Equal => '=', + GateType::NotEqual => '!', + }, + PTile::Math(math) => match math { + MathOp::Add => 'A', + MathOp::Sub => 'S', + MathOp::Mul => 'M', + MathOp::Div => 'D', + MathOp::Rem => 'R', + }, + PTile::Bag => 'B', + PTile::Flipper => 'F', + PTile::Input => 'I', + PTile::Output => 'P', + }, + } + } + pub fn is_blank(&self) -> bool { matches!(self, Tile::Blank) } diff --git a/src/solution.rs b/src/solution.rs index 199e148..7b4de77 100644 --- a/src/solution.rs +++ b/src/solution.rs @@ -1,15 +1,17 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +use crate::level::Level; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Solution { - solution_id: String, + // solution_id: String, level_id: String, // redundant? pub name: String, pub board: String, pub score: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Score { pub cycles: u32, pub tiles: u32, @@ -17,16 +19,23 @@ pub struct Score { } impl Solution { - pub fn new(level_id: String, number: usize) -> Self { + pub fn new(level: &Level, number: usize) -> Self { Self { - solution_id: format!("solution_{number}"), - level_id, + // solution_id: format!("solution_{number}"), + level_id: level.id().to_owned(), name: format!("Unnamed {number}"), - board: " \n".repeat(20), // todo remove when auto resizing is implemented + board: level + .init_board() + .unwrap_or_else(|| " \n".repeat(20)), // todo remove when auto resizing is implemented // score: Some(Score { cycles: 5, tiles: 88, area: 987 }), score: None, } } + + pub fn level_id(&self) -> &str { + &self.level_id + } + pub fn score_text(&self) -> String { if let Some(score) = &self.score { format!( diff --git a/src/util.rs b/src/util.rs index 44a9961..e67cd3e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -122,7 +122,7 @@ pub fn text_input( if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) { *is_selected = false; } - if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) { + if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() { changed = true; text.pop(); }