2024-10-07 00:20:52 +02:00
|
|
|
use std::{mem::transmute, ops::Rem};
|
2024-10-06 00:24:11 +02:00
|
|
|
|
|
|
|
use raylib::prelude::*;
|
|
|
|
|
|
|
|
use crate::{
|
2024-10-07 13:16:26 +02:00
|
|
|
draw_scaled_texture, draw_usize,
|
2024-10-06 12:39:36 +02:00
|
|
|
level::Level,
|
2024-10-06 00:24:11 +02:00
|
|
|
marble_engine::{
|
|
|
|
board::{Board, Pos},
|
|
|
|
tile::{Direction, GateType, MathOp, MirrorType, PTile, Tile, WireType},
|
|
|
|
Machine,
|
|
|
|
},
|
2024-10-07 14:16:50 +02:00
|
|
|
simple_button, slider,
|
2024-10-06 22:24:37 +02:00
|
|
|
solution::{Score, Solution},
|
2024-10-06 12:39:36 +02:00
|
|
|
text_input, texture_option_button, Textures,
|
2024-10-06 00:24:11 +02:00
|
|
|
};
|
|
|
|
|
2024-10-06 16:00:12 +02:00
|
|
|
const HEADER_HEIGHT: i32 = 40;
|
|
|
|
const FOOTER_HEIGHT: i32 = 95;
|
|
|
|
|
2024-10-06 00:24:11 +02:00
|
|
|
#[derive(Debug)]
|
2024-10-06 00:57:24 +02:00
|
|
|
pub struct Editor {
|
2024-10-06 00:24:11 +02:00
|
|
|
source_board: Board,
|
2024-10-06 12:39:36 +02:00
|
|
|
level: Level,
|
2024-10-06 00:24:11 +02:00
|
|
|
machine: Machine,
|
|
|
|
sim_state: SimState,
|
|
|
|
view_offset: Vector2,
|
|
|
|
zoom: i32,
|
|
|
|
output_as_text: bool,
|
|
|
|
input_as_text: bool,
|
|
|
|
active_tool: Tool,
|
|
|
|
tool_menu_math: MathOp,
|
|
|
|
tool_menu_gate: GateType,
|
|
|
|
tool_menu_arrow: Direction,
|
|
|
|
tool_menu_mirror: MirrorType,
|
|
|
|
tool_menu_wire: WireType,
|
|
|
|
input_text_selected: bool,
|
2024-10-07 14:16:50 +02:00
|
|
|
sim_speed: u8,
|
2024-10-06 00:24:11 +02:00
|
|
|
time_since_step: f32,
|
2024-10-06 16:00:12 +02:00
|
|
|
exit_state: ExitState,
|
|
|
|
exit_menu: bool,
|
2024-10-06 22:24:37 +02:00
|
|
|
complete_popup: Popup,
|
|
|
|
fail_popup: Popup,
|
|
|
|
score: Option<Score>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
enum Popup {
|
|
|
|
Start,
|
|
|
|
Visible,
|
|
|
|
Dismissed,
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
enum Tool {
|
|
|
|
None,
|
2024-10-07 16:51:56 +02:00
|
|
|
Erase,
|
2024-10-06 00:24:11 +02:00
|
|
|
SetTile(Tile),
|
|
|
|
Digits(Option<Pos>),
|
|
|
|
Math,
|
|
|
|
Gate,
|
|
|
|
Wire,
|
|
|
|
Arrow,
|
|
|
|
Mirror,
|
2024-10-07 15:11:53 +02:00
|
|
|
SelectArea(Option<(Pos, Pos)>, bool),
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
enum SimState {
|
|
|
|
Editing,
|
|
|
|
Running,
|
|
|
|
Stepping,
|
|
|
|
}
|
|
|
|
|
2024-10-06 16:00:12 +02:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub enum ExitState {
|
|
|
|
Dont,
|
|
|
|
ExitAndSave,
|
2024-10-06 16:29:45 +02:00
|
|
|
Save,
|
2024-10-06 16:00:12 +02:00
|
|
|
ExitNoSave,
|
|
|
|
}
|
|
|
|
|
2024-10-06 00:57:24 +02:00
|
|
|
impl Editor {
|
2024-10-06 16:00:12 +02:00
|
|
|
pub fn new(solution: Solution, level: Level) -> Self {
|
2024-10-06 00:24:11 +02:00
|
|
|
Self {
|
2024-10-06 16:00:12 +02:00
|
|
|
source_board: Board::parse(&solution.board),
|
2024-10-06 20:02:43 +02:00
|
|
|
machine: Machine::new_empty(level.inputs().to_owned(), 1),
|
2024-10-06 00:24:11 +02:00
|
|
|
sim_state: SimState::Editing,
|
|
|
|
view_offset: Vector2::zero(),
|
|
|
|
zoom: 1,
|
|
|
|
active_tool: Tool::None,
|
|
|
|
output_as_text: false,
|
|
|
|
input_as_text: false,
|
|
|
|
input_text_selected: false,
|
2024-10-07 14:16:50 +02:00
|
|
|
sim_speed: 8,
|
2024-10-06 00:24:11 +02:00
|
|
|
time_since_step: 0.,
|
|
|
|
tool_menu_math: MathOp::Add,
|
|
|
|
tool_menu_gate: GateType::Equal,
|
|
|
|
tool_menu_arrow: Direction::Right,
|
|
|
|
tool_menu_mirror: MirrorType::Forward,
|
|
|
|
tool_menu_wire: WireType::Vertical,
|
2024-10-06 16:00:12 +02:00
|
|
|
level,
|
|
|
|
exit_state: ExitState::Dont,
|
|
|
|
exit_menu: false,
|
2024-10-06 22:24:37 +02:00
|
|
|
complete_popup: Popup::Start,
|
|
|
|
fail_popup: Popup::Start,
|
|
|
|
score: solution.score,
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-06 16:00:12 +02:00
|
|
|
pub fn get_exit_state(&self) -> ExitState {
|
|
|
|
self.exit_state
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn level_id(&self) -> &str {
|
2024-10-06 23:37:21 +02:00
|
|
|
self.level.id()
|
2024-10-06 16:00:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn source_board(&self) -> &Board {
|
|
|
|
&self.source_board
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:24:37 +02:00
|
|
|
pub fn score(&self) -> Option<Score> {
|
|
|
|
self.score.clone()
|
|
|
|
}
|
|
|
|
|
2024-10-07 15:11:53 +02:00
|
|
|
fn pos_to_screen(&self, pos: Vector2) -> Vector2 {
|
|
|
|
pos * (16 << self.zoom) as f32 + self.view_offset
|
|
|
|
}
|
|
|
|
|
2024-10-06 00:24:11 +02:00
|
|
|
fn start_sim(&mut self) {
|
|
|
|
self.machine.reset();
|
|
|
|
self.machine.set_board(self.source_board.clone());
|
|
|
|
}
|
|
|
|
|
2024-10-07 13:16:26 +02:00
|
|
|
fn step_pressed(&mut self) {
|
2024-10-07 12:57:32 +02:00
|
|
|
match self.sim_state {
|
|
|
|
SimState::Editing => {
|
|
|
|
self.start_sim();
|
|
|
|
self.step();
|
|
|
|
}
|
|
|
|
SimState::Running => (),
|
|
|
|
SimState::Stepping => self.step(),
|
|
|
|
}
|
|
|
|
self.sim_state = SimState::Stepping;
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:24:37 +02:00
|
|
|
fn step(&mut self) {
|
|
|
|
self.machine.step();
|
2024-10-07 12:44:15 +02:00
|
|
|
if !self.level.outputs().is_empty()
|
|
|
|
&& self.level.outputs() == self.machine.output()
|
|
|
|
&& self.complete_popup == Popup::Start
|
|
|
|
{
|
2024-10-06 22:24:37 +02:00
|
|
|
self.complete_popup = Popup::Visible;
|
|
|
|
self.exit_state = ExitState::Save;
|
|
|
|
self.sim_state = SimState::Stepping;
|
|
|
|
self.score = Some(Score {
|
|
|
|
cycles: self.machine.step_count(),
|
|
|
|
tiles: self.source_board.count_tiles(),
|
|
|
|
area: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-06 00:24:11 +02:00
|
|
|
fn rotate_tool(&mut self, shift: bool) {
|
|
|
|
match &self.active_tool {
|
|
|
|
Tool::Math => {
|
|
|
|
self.tool_menu_math = match self.tool_menu_math {
|
|
|
|
MathOp::Add => MathOp::Sub,
|
|
|
|
MathOp::Sub => MathOp::Mul,
|
|
|
|
MathOp::Mul => MathOp::Div,
|
|
|
|
MathOp::Div => MathOp::Rem,
|
|
|
|
MathOp::Rem => MathOp::Add,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Tool::Gate => {
|
|
|
|
self.tool_menu_gate = match self.tool_menu_gate {
|
|
|
|
GateType::LessThan => GateType::GreaterThan,
|
|
|
|
GateType::GreaterThan => GateType::Equal,
|
|
|
|
GateType::Equal => GateType::NotEqual,
|
|
|
|
GateType::NotEqual => GateType::LessThan,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Tool::Arrow => {
|
|
|
|
self.tool_menu_arrow = if shift {
|
|
|
|
self.tool_menu_arrow.left()
|
|
|
|
} else {
|
|
|
|
self.tool_menu_arrow.right()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Tool::Mirror => {
|
|
|
|
self.tool_menu_mirror = match self.tool_menu_mirror {
|
|
|
|
MirrorType::Forward => MirrorType::Back,
|
|
|
|
MirrorType::Back => MirrorType::Forward,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Tool::Wire => {
|
|
|
|
self.tool_menu_wire = match self.tool_menu_wire {
|
|
|
|
WireType::Vertical => WireType::Horizontal,
|
|
|
|
WireType::Horizontal => WireType::Cross,
|
|
|
|
WireType::Cross => WireType::Vertical,
|
|
|
|
}
|
|
|
|
}
|
2024-10-07 16:51:56 +02:00
|
|
|
Tool::None
|
|
|
|
| Tool::Erase
|
|
|
|
| Tool::SetTile(_)
|
|
|
|
| Tool::Digits(_)
|
|
|
|
| Tool::SelectArea(_, _) => (),
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-06 23:30:59 +02:00
|
|
|
fn set_tile(&mut self, pos: Pos, tile: Tile) {
|
|
|
|
self.source_board.grow_to_include(pos);
|
|
|
|
self.source_board.set(pos, tile);
|
2024-10-07 12:44:15 +02:00
|
|
|
if tile.is_blank() {
|
|
|
|
self.source_board.trim_size();
|
|
|
|
}
|
2024-10-06 23:30:59 +02:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:24:37 +02:00
|
|
|
pub fn update(&mut self, rl: &RaylibHandle) {
|
2024-10-06 16:00:12 +02:00
|
|
|
if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
2024-10-06 22:24:37 +02:00
|
|
|
self.sim_state = SimState::Editing;
|
|
|
|
self.complete_popup = Popup::Start;
|
2024-10-06 16:00:12 +02:00
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
if self.sim_state == SimState::Running {
|
|
|
|
self.time_since_step += rl.get_frame_time();
|
2024-10-07 14:16:50 +02:00
|
|
|
if self.time_since_step > 1. / self.sim_speed as f32 {
|
2024-10-06 00:24:11 +02:00
|
|
|
self.time_since_step = 0.;
|
2024-10-06 22:24:37 +02:00
|
|
|
self.step();
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if rl.is_key_pressed(KeyboardKey::KEY_SPACE) {
|
2024-10-07 12:57:32 +02:00
|
|
|
self.step_pressed()
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
|
|
|
|
match self.sim_state {
|
|
|
|
SimState::Editing => {
|
|
|
|
self.start_sim();
|
|
|
|
self.sim_state = SimState::Running;
|
|
|
|
}
|
2024-10-06 22:24:37 +02:00
|
|
|
SimState::Running => {
|
|
|
|
self.sim_state = SimState::Editing;
|
|
|
|
self.complete_popup = Popup::Start;
|
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
SimState::Stepping => self.sim_state = SimState::Running,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if rl.get_mouse_wheel_move() > 0. && self.zoom < 3 {
|
|
|
|
self.zoom += 1;
|
|
|
|
}
|
|
|
|
if rl.get_mouse_wheel_move() < 0. && self.zoom > 0 {
|
|
|
|
self.zoom -= 1;
|
|
|
|
}
|
|
|
|
if rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_MIDDLE) {
|
|
|
|
self.view_offset += rl.get_mouse_delta()
|
|
|
|
}
|
|
|
|
if rl.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_RIGHT) {
|
|
|
|
self.view_offset = Vector2::zero();
|
|
|
|
}
|
2024-10-06 16:00:12 +02:00
|
|
|
|
|
|
|
if rl.is_key_pressed(KeyboardKey::KEY_R) {
|
|
|
|
self.rotate_tool(rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT));
|
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn draw_board(&self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
|
|
|
if self.sim_state == SimState::Editing {
|
|
|
|
self.source_board
|
|
|
|
.draw(d, textures, self.view_offset, self.zoom);
|
|
|
|
} else {
|
|
|
|
self.machine
|
|
|
|
.board()
|
|
|
|
.draw(d, textures, self.view_offset, self.zoom);
|
|
|
|
self.machine
|
|
|
|
.draw_marble_values(d, self.view_offset, self.zoom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
2024-10-06 12:39:36 +02:00
|
|
|
d.clear_background(Color::new(64, 64, 64, 255));
|
2024-10-06 00:24:11 +02:00
|
|
|
|
|
|
|
let tile_size = (16 << self.zoom) as f32;
|
|
|
|
let grid_spill_x = (self.view_offset.x).rem(tile_size) - tile_size;
|
|
|
|
let grid_spill_y = (self.view_offset.y).rem(tile_size) - tile_size;
|
|
|
|
d.gui_grid(
|
|
|
|
Rectangle::new(
|
|
|
|
grid_spill_x,
|
|
|
|
grid_spill_y,
|
|
|
|
d.get_screen_width() as f32 * 2.,
|
2024-10-06 16:00:12 +02:00
|
|
|
d.get_screen_height() as f32 * 2.,
|
2024-10-06 00:24:11 +02:00
|
|
|
),
|
|
|
|
None,
|
|
|
|
tile_size,
|
|
|
|
1,
|
|
|
|
);
|
|
|
|
|
2024-10-06 16:00:12 +02:00
|
|
|
self.draw_board(d, textures);
|
|
|
|
self.board_overlay(d, textures);
|
|
|
|
self.draw_bottom_bar(d, textures);
|
|
|
|
self.draw_top_bar(d, textures);
|
2024-10-06 22:24:37 +02:00
|
|
|
|
|
|
|
if self.complete_popup == Popup::Visible {
|
|
|
|
let width = 310;
|
|
|
|
let height = 165;
|
|
|
|
let x = d.get_screen_width() / 2 - width / 2;
|
|
|
|
let y = d.get_screen_height() / 2 - height / 2;
|
|
|
|
d.draw_rectangle(x, y, width, height, Color::DIMGRAY);
|
|
|
|
d.draw_text("Level Complete!", x + 10, y + 10, 30, Color::LIME);
|
|
|
|
if let Some(score) = &self.score {
|
|
|
|
d.draw_text("cycles", x + 15, y + 45, 20, Color::WHITE);
|
|
|
|
d.draw_rectangle(x + 10, y + 70, 90, 30, Color::DARKGREEN);
|
|
|
|
d.draw_text(
|
|
|
|
&format!("{}", score.cycles),
|
|
|
|
x + 15,
|
|
|
|
y + 75,
|
|
|
|
20,
|
|
|
|
Color::WHITE,
|
|
|
|
);
|
|
|
|
|
|
|
|
d.draw_text("tiles", x + 115, y + 45, 20, Color::WHITE);
|
|
|
|
d.draw_rectangle(x + 110, y + 70, 90, 30, Color::DARKGREEN);
|
|
|
|
d.draw_text(
|
|
|
|
&format!("{}", score.tiles),
|
|
|
|
x + 115,
|
|
|
|
y + 75,
|
|
|
|
20,
|
|
|
|
Color::WHITE,
|
|
|
|
);
|
|
|
|
|
|
|
|
d.draw_text("area", x + 215, y + 45, 20, Color::WHITE);
|
|
|
|
d.draw_rectangle(x + 210, y + 70, 90, 30, Color::DARKGREEN);
|
|
|
|
d.draw_text(
|
|
|
|
&format!("{}", score.area),
|
|
|
|
x + 215,
|
|
|
|
y + 75,
|
|
|
|
20,
|
|
|
|
Color::WHITE,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if simple_button(d, x + 10, y + 110, 140, 45) {
|
|
|
|
self.complete_popup = Popup::Dismissed;
|
|
|
|
}
|
|
|
|
d.draw_text("continue\nediting", x + 15, y + 115, 20, Color::WHITE);
|
|
|
|
|
|
|
|
if simple_button(d, x + width / 2 + 5, y + 110, 140, 45) {
|
|
|
|
self.exit_state = ExitState::ExitAndSave;
|
|
|
|
}
|
|
|
|
d.draw_text(
|
|
|
|
"return to\nlevel list",
|
|
|
|
x + width / 2 + 10,
|
|
|
|
y + 115,
|
|
|
|
20,
|
|
|
|
Color::WHITE,
|
|
|
|
);
|
|
|
|
}
|
2024-10-06 16:00:12 +02:00
|
|
|
}
|
|
|
|
|
2024-10-07 01:16:52 +02:00
|
|
|
fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
2024-10-06 16:00:12 +02:00
|
|
|
// 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);
|
2024-10-06 16:29:45 +02:00
|
|
|
if simple_button(d, 90, 5, 80, 30) {
|
|
|
|
self.exit_state = ExitState::Save;
|
|
|
|
}
|
|
|
|
d.draw_text("save", 95, 10, 20, Color::WHITE);
|
2024-10-06 16:00:12 +02:00
|
|
|
}
|
2024-10-06 20:40:00 +02:00
|
|
|
|
2024-10-07 01:16:52 +02:00
|
|
|
match self.sim_state {
|
|
|
|
SimState::Editing => {
|
|
|
|
if simple_button(d, 260, 4, 32, 32) {
|
|
|
|
self.start_sim();
|
|
|
|
self.sim_state = SimState::Running;
|
|
|
|
}
|
|
|
|
draw_scaled_texture(d, textures.get("play"), 260, 4, 2.);
|
|
|
|
}
|
|
|
|
SimState::Running => {
|
|
|
|
if simple_button(d, 260, 4, 32, 32) {
|
|
|
|
self.sim_state = SimState::Stepping;
|
|
|
|
}
|
|
|
|
draw_scaled_texture(d, textures.get("pause"), 260, 4, 2.);
|
2024-10-07 12:57:32 +02:00
|
|
|
if simple_button(d, 296, 4, 32, 32) {
|
2024-10-07 01:16:52 +02:00
|
|
|
self.sim_state = SimState::Editing;
|
|
|
|
self.complete_popup = Popup::Start;
|
|
|
|
}
|
2024-10-07 12:57:32 +02:00
|
|
|
draw_scaled_texture(d, textures.get("stop"), 296, 4, 2.);
|
2024-10-07 01:16:52 +02:00
|
|
|
}
|
|
|
|
SimState::Stepping => {
|
|
|
|
if simple_button(d, 260, 4, 32, 32) {
|
|
|
|
self.sim_state = SimState::Running;
|
|
|
|
}
|
|
|
|
draw_scaled_texture(d, textures.get("play"), 260, 4, 2.);
|
2024-10-07 12:57:32 +02:00
|
|
|
if simple_button(d, 296, 4, 32, 32) {
|
2024-10-07 01:16:52 +02:00
|
|
|
self.sim_state = SimState::Editing;
|
|
|
|
self.complete_popup = Popup::Start;
|
|
|
|
}
|
2024-10-07 12:57:32 +02:00
|
|
|
draw_scaled_texture(d, textures.get("stop"), 296, 4, 2.);
|
2024-10-07 01:16:52 +02:00
|
|
|
}
|
|
|
|
}
|
2024-10-07 12:57:32 +02:00
|
|
|
if simple_button(d, 332, 4, 32, 32) {
|
|
|
|
self.step_pressed();
|
|
|
|
}
|
|
|
|
draw_scaled_texture(d, textures.get("step"), 332, 4, 2.);
|
2024-10-07 01:16:52 +02:00
|
|
|
|
2024-10-07 14:16:50 +02:00
|
|
|
d.draw_text("speed", 368, 6, 10, Color::WHITE);
|
|
|
|
draw_usize(d, textures, self.sim_speed as usize, 398, 4, 2, 1);
|
|
|
|
slider(d, &mut self.sim_speed, 1, 33, 368, 24, 48, 12);
|
|
|
|
|
|
|
|
draw_usize(d, textures, self.machine.step_count(), 420, 4, 5, 2);
|
2024-10-07 13:16:26 +02:00
|
|
|
|
2024-10-06 20:40:00 +02:00
|
|
|
let mut input_text = String::from_utf8_lossy(self.machine.input()).to_string();
|
|
|
|
let width = d.get_screen_width();
|
|
|
|
d.draw_text("input:", width - 260, 10, 20, Color::WHITE);
|
|
|
|
if text_input(
|
|
|
|
d,
|
|
|
|
Rectangle::new(width as f32 - 205., 5., 200., 30.),
|
|
|
|
&mut input_text,
|
|
|
|
&mut self.input_text_selected,
|
2024-10-07 12:21:57 +02:00
|
|
|
self.level.is_sandbox(),
|
2024-10-06 20:40:00 +02:00
|
|
|
) {
|
|
|
|
self.machine.set_input(input_text.into_bytes());
|
|
|
|
}
|
2024-10-06 16:00:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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),
|
|
|
|
);
|
|
|
|
|
2024-10-06 00:24:11 +02:00
|
|
|
let mut tool_button = |(row, col): (i32, i32), texture: &str, tool_option: Tool| {
|
|
|
|
let border = 4.;
|
|
|
|
let gap = 2.;
|
|
|
|
let bound_offset = 32. + gap * 2. + border * 2.;
|
2024-10-06 12:39:36 +02:00
|
|
|
texture_option_button(
|
2024-10-06 00:24:11 +02:00
|
|
|
d,
|
|
|
|
Vector2 {
|
2024-10-07 00:20:52 +02:00
|
|
|
x: 100. + col as f32 * bound_offset - if col < 0 { 10. } else { 0. },
|
2024-10-06 00:24:11 +02:00
|
|
|
y: footer_top + 5. + row as f32 * bound_offset,
|
|
|
|
},
|
|
|
|
textures.get(texture),
|
|
|
|
tool_option,
|
|
|
|
&mut self.active_tool,
|
|
|
|
32.,
|
|
|
|
border,
|
|
|
|
);
|
|
|
|
};
|
2024-10-07 16:51:56 +02:00
|
|
|
tool_button((0, -2), "eraser", Tool::Erase);
|
2024-10-07 15:11:53 +02:00
|
|
|
tool_button((1, -2), "selection", Tool::SelectArea(None, false));
|
2024-10-06 00:24:11 +02:00
|
|
|
tool_button((0, -1), "digit_tool", Tool::Digits(None));
|
|
|
|
tool_button((1, -1), "transparent", Tool::None);
|
|
|
|
|
|
|
|
tool_button((0, 0), "block", Tool::SetTile(Tile::from_char('#')));
|
|
|
|
tool_button((0, 1), "bag_off", Tool::SetTile(Tile::from_char('B')));
|
|
|
|
tool_button((0, 2), "trigger_off", Tool::SetTile(Tile::from_char('*')));
|
|
|
|
tool_button((0, 3), "input_off", Tool::SetTile(Tile::from_char('I')));
|
|
|
|
tool_button((0, 4), "output_off", Tool::SetTile(Tile::from_char('P')));
|
|
|
|
tool_button((0, 5), "flipper_off", Tool::SetTile(Tile::from_char('F')));
|
|
|
|
|
|
|
|
tool_button((1, 0), "marble", Tool::SetTile(Tile::from_char('o')));
|
|
|
|
tool_button(
|
|
|
|
(1, 1),
|
|
|
|
&Tile::Powerable(PTile::Wire(self.tool_menu_wire), false).texture(),
|
|
|
|
Tool::Wire,
|
|
|
|
);
|
|
|
|
|
|
|
|
tool_button(
|
|
|
|
(1, 2),
|
|
|
|
&Tile::Arrow(self.tool_menu_arrow).texture(),
|
|
|
|
Tool::Arrow,
|
|
|
|
);
|
|
|
|
tool_button(
|
|
|
|
(1, 3),
|
|
|
|
&Tile::Mirror(self.tool_menu_mirror).texture(),
|
|
|
|
Tool::Mirror,
|
|
|
|
);
|
|
|
|
tool_button(
|
|
|
|
(1, 4),
|
|
|
|
&Tile::Powerable(PTile::Math(self.tool_menu_math), false).texture(),
|
|
|
|
Tool::Math,
|
|
|
|
);
|
|
|
|
tool_button(
|
|
|
|
(1, 5),
|
|
|
|
&Tile::Powerable(PTile::Gate(self.tool_menu_gate), false).texture(),
|
|
|
|
Tool::Gate,
|
|
|
|
);
|
2024-10-06 19:51:09 +02:00
|
|
|
|
2024-10-07 00:20:52 +02:00
|
|
|
let output_x = 370;
|
|
|
|
let output_cell_width = 43;
|
|
|
|
let output_cells = (d.get_screen_width() - output_x) as usize / 43;
|
|
|
|
|
2024-10-06 19:51:09 +02:00
|
|
|
let y = footer_top as i32 + 5;
|
2024-10-07 00:20:52 +02:00
|
|
|
if simple_button(d, output_x, y + 70, 65, 15) {
|
2024-10-06 19:51:09 +02:00
|
|
|
self.output_as_text = !self.output_as_text
|
|
|
|
}
|
|
|
|
let output_mode_text = if self.output_as_text {
|
|
|
|
"show bytes"
|
|
|
|
} else {
|
|
|
|
"show text"
|
|
|
|
};
|
2024-10-07 00:20:52 +02:00
|
|
|
d.draw_text(output_mode_text, output_x + 5, y + 72, 10, Color::WHITE);
|
|
|
|
|
|
|
|
let output_start = self.machine.output().len().saturating_sub(output_cells);
|
|
|
|
let output_end = output_start + output_cells;
|
2024-10-06 19:51:09 +02:00
|
|
|
for (box_index, index) in (output_start..output_end).enumerate() {
|
2024-10-07 00:20:52 +02:00
|
|
|
let x = output_x + output_cell_width * box_index as i32;
|
2024-10-06 19:51:09 +02:00
|
|
|
|
|
|
|
let expected_byte = self.level.outputs().get(index);
|
|
|
|
let real_byte = self.machine.output().get(index);
|
|
|
|
|
|
|
|
let (top_color, bottom_color) =
|
|
|
|
if let (Some(&real_byte), Some(&expected_byte)) = (real_byte, expected_byte) {
|
|
|
|
if expected_byte == real_byte {
|
|
|
|
(Color::GREEN, Color::DARKGREEN)
|
|
|
|
} else {
|
|
|
|
(Color::RED, Color::DARKRED)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(Color::DARKGRAY, Color::DIMGRAY)
|
|
|
|
};
|
|
|
|
|
2024-10-06 20:02:43 +02:00
|
|
|
d.draw_rectangle(x, y, 38, 30, top_color);
|
|
|
|
d.draw_rectangle(x, y + 35, 38, 30, bottom_color);
|
2024-10-06 19:51:09 +02:00
|
|
|
if let Some(&expected_byte) = expected_byte {
|
|
|
|
let top_text = if self.output_as_text
|
|
|
|
&& (expected_byte.is_ascii_graphic() || expected_byte.is_ascii_whitespace())
|
|
|
|
{
|
|
|
|
format!("{:?}", expected_byte as char)
|
|
|
|
} else {
|
|
|
|
format!("{}", expected_byte)
|
|
|
|
};
|
|
|
|
d.draw_text(&top_text, x + 2, y + 5, 20, Color::WHITE);
|
|
|
|
}
|
|
|
|
if let Some(&real_byte) = real_byte {
|
|
|
|
let bottom_text = if self.output_as_text
|
|
|
|
&& (real_byte.is_ascii_graphic() || real_byte.is_ascii_whitespace())
|
|
|
|
{
|
|
|
|
format!("{:?}", real_byte as char)
|
|
|
|
} else {
|
|
|
|
format!("{}", real_byte)
|
|
|
|
};
|
|
|
|
d.draw_text(&bottom_text, x + 2, y + 40, 20, Color::WHITE);
|
|
|
|
}
|
|
|
|
}
|
2024-10-06 16:00:12 +02:00
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
|
2024-10-06 16:00:12 +02:00
|
|
|
fn board_overlay(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
|
|
|
let footer_top = (d.get_screen_height() - FOOTER_HEIGHT) as f32;
|
2024-10-06 00:24:11 +02:00
|
|
|
let mouse_pos = d.get_mouse_position();
|
2024-10-07 15:11:53 +02:00
|
|
|
let tile_scale = (1 << self.zoom) as f32;
|
|
|
|
let tile_size = 16 << self.zoom;
|
2024-10-06 00:24:11 +02:00
|
|
|
if self.sim_state == SimState::Editing {
|
|
|
|
if let Tool::Digits(Some(pos)) = &mut self.active_tool {
|
2024-10-07 15:11:53 +02:00
|
|
|
let tile_screen_pos = pos.to_vec() * tile_size as f32 + self.view_offset;
|
2024-10-06 00:24:11 +02:00
|
|
|
d.draw_texture_ex(
|
|
|
|
textures.get("selection"),
|
|
|
|
tile_screen_pos,
|
|
|
|
0.,
|
2024-10-07 15:11:53 +02:00
|
|
|
tile_scale,
|
2024-10-06 00:24:11 +02:00
|
|
|
Color::new(255, 180, 20, 255),
|
|
|
|
);
|
|
|
|
if d.is_key_pressed(KeyboardKey::KEY_LEFT) {
|
|
|
|
pos.x -= 1;
|
|
|
|
}
|
|
|
|
if d.is_key_pressed(KeyboardKey::KEY_RIGHT) {
|
|
|
|
pos.x += 1;
|
|
|
|
}
|
|
|
|
if d.is_key_pressed(KeyboardKey::KEY_UP) {
|
|
|
|
pos.y -= 1;
|
|
|
|
}
|
|
|
|
if d.is_key_pressed(KeyboardKey::KEY_DOWN) {
|
|
|
|
pos.y += 1;
|
|
|
|
}
|
2024-10-06 23:30:59 +02:00
|
|
|
let pos = *pos;
|
|
|
|
for n in 0..10 {
|
2024-10-07 00:20:52 +02:00
|
|
|
if d.is_key_pressed(unsafe { transmute::<u32, KeyboardKey>(b'0' as u32 + n) }) {
|
2024-10-06 23:30:59 +02:00
|
|
|
self.set_tile(pos, Tile::Digit(n as u8));
|
|
|
|
}
|
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
2024-10-06 16:00:12 +02:00
|
|
|
if mouse_pos.y < footer_top && mouse_pos.y > HEADER_HEIGHT as f32 {
|
2024-10-07 15:11:53 +02:00
|
|
|
let tile_pos = (mouse_pos - self.view_offset) / tile_size as f32;
|
2024-10-06 00:24:11 +02:00
|
|
|
let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor());
|
|
|
|
|
2024-10-07 15:11:53 +02:00
|
|
|
let tile_screen_pos = self.pos_to_screen(tile_pos);
|
2024-10-06 00:24:11 +02:00
|
|
|
|
|
|
|
if self.active_tool != Tool::None {
|
|
|
|
let tex = match self.active_tool {
|
|
|
|
Tool::None => unreachable!(),
|
2024-10-07 16:51:56 +02:00
|
|
|
Tool::Erase => "selection".into(),
|
|
|
|
Tool::SetTile(t) => t.texture(),
|
2024-10-06 00:24:11 +02:00
|
|
|
Tool::Math => format!("{}_off", self.tool_menu_math.texture_name()),
|
|
|
|
Tool::Gate => format!("{}_off", self.tool_menu_gate.texture_name()),
|
|
|
|
Tool::Wire => format!("{}_off", self.tool_menu_wire.texture_name()),
|
|
|
|
Tool::Arrow => self.tool_menu_arrow.arrow_texture_name().into(),
|
|
|
|
Tool::Mirror => self.tool_menu_mirror.texture_name().into(),
|
|
|
|
Tool::Digits(_) => "selection".into(),
|
2024-10-07 15:11:53 +02:00
|
|
|
Tool::SelectArea(_, _) => "area_full".into(),
|
2024-10-06 00:24:11 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
d.draw_texture_ex(
|
|
|
|
textures.get(&tex),
|
|
|
|
tile_screen_pos,
|
|
|
|
0.,
|
2024-10-07 15:11:53 +02:00
|
|
|
tile_scale,
|
2024-10-06 00:24:11 +02:00
|
|
|
Color::new(255, 255, 255, 100),
|
|
|
|
);
|
|
|
|
}
|
2024-10-07 15:11:53 +02:00
|
|
|
if d.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_LEFT) {
|
2024-10-06 00:24:11 +02:00
|
|
|
match self.active_tool {
|
|
|
|
Tool::None => (),
|
2024-10-07 16:51:56 +02:00
|
|
|
Tool::Erase => (),
|
2024-10-06 23:30:59 +02:00
|
|
|
Tool::SetTile(tile) => self.set_tile(tile_pos.into(), tile),
|
|
|
|
Tool::Math => self.set_tile(
|
2024-10-06 00:24:11 +02:00
|
|
|
tile_pos.into(),
|
|
|
|
Tile::Powerable(PTile::Math(self.tool_menu_math), false),
|
|
|
|
),
|
2024-10-06 23:30:59 +02:00
|
|
|
Tool::Gate => self.set_tile(
|
2024-10-06 00:24:11 +02:00
|
|
|
tile_pos.into(),
|
|
|
|
Tile::Powerable(PTile::Gate(self.tool_menu_gate), false),
|
|
|
|
),
|
2024-10-06 23:30:59 +02:00
|
|
|
Tool::Wire => self.set_tile(
|
2024-10-06 00:24:11 +02:00
|
|
|
tile_pos.into(),
|
|
|
|
Tile::Powerable(PTile::Wire(self.tool_menu_wire), false),
|
|
|
|
),
|
2024-10-06 23:30:59 +02:00
|
|
|
Tool::Arrow => {
|
|
|
|
self.set_tile(tile_pos.into(), Tile::Arrow(self.tool_menu_arrow))
|
|
|
|
}
|
|
|
|
Tool::Mirror => {
|
|
|
|
self.set_tile(tile_pos.into(), Tile::Mirror(self.tool_menu_mirror))
|
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
Tool::Digits(_pos) => {
|
|
|
|
self.active_tool = Tool::Digits(Some(tile_pos.into()));
|
|
|
|
if let Some(tile) = self.source_board.get_mut(tile_pos.into()) {
|
|
|
|
if let Tile::Digit(_) = tile {
|
|
|
|
} else {
|
|
|
|
*tile = Tile::Digit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-10-07 15:11:53 +02:00
|
|
|
Tool::SelectArea(_, _) => (),
|
|
|
|
}
|
|
|
|
}
|
2024-10-07 16:51:56 +02:00
|
|
|
if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) {
|
|
|
|
if self.active_tool == Tool::Erase {
|
|
|
|
self.set_tile(tile_pos.into(), Tile::Blank)
|
|
|
|
}
|
|
|
|
}
|
2024-10-07 15:11:53 +02:00
|
|
|
if let Tool::SelectArea(selection, is_selecting) = &mut self.active_tool {
|
|
|
|
if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) {
|
|
|
|
if *is_selecting {
|
|
|
|
if let Some((_start, end)) = selection {
|
|
|
|
*end = tile_pos.into();
|
|
|
|
} else {
|
|
|
|
*selection = Some((tile_pos.into(), tile_pos.into()));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*selection = Some((tile_pos.into(), tile_pos.into()));
|
|
|
|
*is_selecting = true;
|
|
|
|
}
|
|
|
|
} else if d.is_mouse_button_released(MouseButton::MOUSE_BUTTON_LEFT) {
|
|
|
|
*is_selecting = false;
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-10-07 15:11:53 +02:00
|
|
|
// draw selection
|
|
|
|
if let Tool::SelectArea(Some((start, end)), _) = self.active_tool {
|
|
|
|
let min = start.min(end);
|
|
|
|
let max = start.max(end);
|
|
|
|
let p_min = self.pos_to_screen(min.to_vec());
|
|
|
|
let p_max = self.pos_to_screen(max.to_vec());
|
|
|
|
let tex = textures.get("area_corner");
|
|
|
|
d.draw_texture_ex(tex, p_min, 0., tile_scale, Color::WHITE);
|
|
|
|
let one_xy = Vector2::new(tile_size as f32, tile_size as f32);
|
|
|
|
d.draw_texture_ex(tex, p_max + one_xy, 180., tile_scale, Color::WHITE);
|
|
|
|
let top_right = Vector2::new(p_max.x + tile_size as f32, p_min.y);
|
|
|
|
d.draw_texture_ex(tex, top_right, 90., tile_scale, Color::WHITE);
|
|
|
|
let bot_left = Vector2::new(p_min.x, p_max.y + tile_size as f32);
|
|
|
|
d.draw_texture_ex(tex, bot_left, -90., tile_scale, Color::WHITE);
|
|
|
|
}
|
2024-10-06 00:24:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Tool {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
match (self, other) {
|
|
|
|
(Self::SetTile(l0), Self::SetTile(r0)) => l0 == r0,
|
|
|
|
(Self::Digits(_), Self::Digits(_)) => true,
|
2024-10-07 15:11:53 +02:00
|
|
|
(Self::SelectArea(_, _), Self::SelectArea(_, _)) => true,
|
2024-10-06 00:24:11 +02:00
|
|
|
_ => ::core::mem::discriminant(self) == ::core::mem::discriminant(other),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|