use std::{fs::read_to_string, ops::Rem}; use marble_engine::{ board::{Board, Pos}, tile::{Direction, GateType, MathOp, MirrorType, PTile, Tile, WireType}, Machine, }; use raylib::prelude::*; mod marble_engine; mod util; use util::*; #[derive(Debug)] struct Game { source_board: Board, 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, sim_speed: f32, time_since_step: f32, } #[derive(Debug, Clone)] enum Tool { None, SetTile(Tile), Digits(Option), Math, Gate, Wire, Arrow, Mirror, // SelectArea, } #[derive(Debug, Clone, PartialEq)] enum SimState { Editing, Running, Stepping, } fn main() { let (mut rl, thread) = raylib::init() .resizable() .title("good window title") .build(); rl.set_target_fps(60); rl.set_window_min_size(640, 480); rl.set_mouse_cursor(MouseCursor::MOUSE_CURSOR_CROSSHAIR); rl.set_exit_key(None); let mut textures = Textures::default(); textures.load_dir("assets", &mut rl, &thread); textures.load_dir("assets/tiles", &mut rl, &thread); let mut game = Game::new_sandbox(); let board = Board::parse(&read_to_string("boards/adder.mbl").unwrap()); game.load_board(board); while !rl.window_should_close() { game.input(&rl); let mut d = rl.begin_drawing(&thread); d.clear_background(Color::new(64, 64, 64, 255)); game.gui(&mut d, &textures); } } impl Game { fn new_sandbox() -> Self { Self { source_board: Board::new_empty(16, 16), machine: Machine::new_empty(1), 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, sim_speed: 8., 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, } } fn load_board(&mut self, board: Board) { self.source_board = board; } fn start_sim(&mut self) { self.machine.reset(); self.machine.set_board(self.source_board.clone()); } 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, } } Tool::None => (), Tool::SetTile(_) => (), Tool::Digits(_) => (), } } fn input(&mut self, rl: &RaylibHandle) { if self.sim_state == SimState::Running { self.time_since_step += rl.get_frame_time(); if self.time_since_step > 1. / self.sim_speed { self.time_since_step = 0.; self.machine.step(); } } if rl.is_key_pressed(KeyboardKey::KEY_SPACE) { match self.sim_state { SimState::Editing => { self.start_sim(); self.machine.step(); } SimState::Running => (), SimState::Stepping => self.machine.step(), } 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 => { self.start_sim(); self.sim_state = SimState::Running; } SimState::Running => self.sim_state = SimState::Editing, 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(); } } 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); } } fn gui(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) { 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; 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., height as f32 - grid_spill_y - footer_height as f32, ), None, tile_size, 1, ); d.gui_check_box( Rectangle::new(5., footer_top + 5., 25., 25.), Some(rstr!("output as text")), &mut self.output_as_text, ); let out_text = if self.output_as_text { String::from_utf8_lossy(self.machine.output()).to_string() } else { format!("{:?}", self.machine.output()) }; d.draw_text(&out_text, 5, footer_top as i32 + 35, 20, Color::WHITE); let mut input_text = String::from_utf8_lossy(self.machine.input()).to_string(); if text_input( d, Rectangle::new(5., footer_top + 60., 200., 25.), &mut input_text, &mut self.input_text_selected, ) { self.machine.set_input(input_text.into_bytes()); } 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.; texture_button( d, Vector2 { x: 320. + col as f32 * bound_offset - if col < 0 { 15. } else { 0. }, y: footer_top + 5. + row as f32 * bound_offset, }, textures.get(texture), tool_option, &mut self.active_tool, 32., border, ); }; tool_button((0, -2), "eraser", Tool::SetTile(Tile::from_char(' '))); 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, ); let is_shift = d.is_key_down(KeyboardKey::KEY_LEFT_SHIFT); if d.is_key_pressed(KeyboardKey::KEY_R) { self.rotate_tool(is_shift); } let mouse_pos = d.get_mouse_position(); if self.sim_state == SimState::Editing { if let Tool::Digits(Some(pos)) = &mut self.active_tool { let tile_screen_pos = pos.to_vec() * (16 << self.zoom) as f32 + self.view_offset; d.draw_texture_ex( textures.get("selection"), tile_screen_pos, 0., (1 << self.zoom) as f32, Color::new(255, 180, 20, 255), ); for n in 0..10 { if d.is_key_pressed(unsafe { std::mem::transmute(KeyboardKey::KEY_ZERO as u32 + n) }) { self.source_board.set(*pos, Tile::Digit(n as u8)); } } 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; } } if mouse_pos.y < footer_top { 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()); let tile_screen_pos = tile_pos * (16 << self.zoom) as f32 + self.view_offset; if self.active_tool != Tool::None { let tex = match self.active_tool { Tool::None => unreachable!(), Tool::SetTile(t) => { if t == Tile::Blank { "selection".into() } else { t.texture() } } 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(), }; d.draw_texture_ex( textures.get(&tex), tile_screen_pos, 0., (1 << self.zoom) as f32, Color::new(255, 255, 255, 100), ); } if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) { match self.active_tool { Tool::None => (), Tool::SetTile(tile) => self.source_board.set(tile_pos.into(), tile), Tool::Math => self.source_board.set( tile_pos.into(), Tile::Powerable(PTile::Math(self.tool_menu_math), false), ), Tool::Gate => self.source_board.set( tile_pos.into(), Tile::Powerable(PTile::Gate(self.tool_menu_gate), false), ), Tool::Wire => self.source_board.set( tile_pos.into(), Tile::Powerable(PTile::Wire(self.tool_menu_wire), false), ), Tool::Arrow => self .source_board .set(tile_pos.into(), Tile::Arrow(self.tool_menu_arrow)), Tool::Mirror => self .source_board .set(tile_pos.into(), Tile::Mirror(self.tool_menu_mirror)), 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); } } } } } } } } } 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, _ => ::core::mem::discriminant(self) == ::core::mem::discriminant(other), } } }