enter and exit solution editor

This commit is contained in:
Crispy 2024-10-06 16:00:12 +02:00
parent 44b7d63cde
commit c4381ac1a1
8 changed files with 195 additions and 41 deletions

View file

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

View file

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

View file

@ -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<Board> {
self.init_board.as_deref().map(Board::parse)
// pub fn init_board(&self) -> Option<Board> {
// self.init_board.as_deref().map(Board::parse)
// }
pub fn init_board(&self)->Option<String>{
self.init_board.clone()
}
pub fn inputs(&self) -> &[u8] {

View file

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

View file

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

View file

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

View file

@ -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<Score>,
}
#[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!(

View file

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