2024-10-04 01:21:52 +02:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
fs::{read_dir, read_to_string},
|
2024-10-04 21:20:53 +02:00
|
|
|
ops::Rem,
|
2024-10-04 01:21:52 +02:00
|
|
|
};
|
2024-10-03 22:59:49 +02:00
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
use marble_engine::{board::Board, parse, tile::Tile, Machine};
|
2024-10-03 22:59:49 +02:00
|
|
|
use raylib::prelude::*;
|
|
|
|
|
|
|
|
mod marble_engine;
|
2024-10-04 21:20:53 +02:00
|
|
|
mod util;
|
|
|
|
use util::*;
|
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
#[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,
|
|
|
|
input_text_selected: bool,
|
|
|
|
sim_speed: f32,
|
|
|
|
time_since_step: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
enum Tool {
|
|
|
|
None,
|
|
|
|
SetTile(Tile),
|
|
|
|
// Erase,
|
|
|
|
// Select,
|
|
|
|
}
|
|
|
|
|
2024-10-04 21:20:53 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-10-04 22:10:00 +02:00
|
|
|
enum SimState {
|
2024-10-04 21:20:53 +02:00
|
|
|
Editing,
|
2024-10-04 22:10:00 +02:00
|
|
|
Running,
|
2024-10-04 21:20:53 +02:00
|
|
|
Stepping,
|
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
|
|
|
|
fn main() {
|
2024-10-04 01:21:52 +02:00
|
|
|
let (mut rl, thread) = raylib::init()
|
|
|
|
.resizable()
|
|
|
|
.title("good window title")
|
|
|
|
.build();
|
2024-10-03 22:59:49 +02:00
|
|
|
rl.set_target_fps(60);
|
2024-10-04 22:10:00 +02:00
|
|
|
rl.set_exit_key(None);
|
2024-10-03 22:59:49 +02:00
|
|
|
|
2024-10-04 01:21:52 +02:00
|
|
|
let mut textures: HashMap<String, Texture2D> = HashMap::new();
|
|
|
|
for d in read_dir("assets/tiles").unwrap().flatten() {
|
|
|
|
let name = d.file_name();
|
|
|
|
if d.path().is_file() {
|
|
|
|
let name = name.to_string_lossy();
|
|
|
|
let texture = rl
|
|
|
|
.load_texture(&thread, &format!("assets/tiles/{name}"))
|
|
|
|
.unwrap();
|
|
|
|
textures.insert(name.to_string(), texture);
|
|
|
|
}
|
|
|
|
}
|
2024-10-04 22:10:00 +02:00
|
|
|
let mut game = Game::new_sandbox();
|
|
|
|
let board = parse(&read_to_string("boards/adder.mbl").unwrap());
|
|
|
|
game.load_board(board);
|
2024-10-04 01:21:52 +02:00
|
|
|
|
2024-10-03 22:59:49 +02:00
|
|
|
while !rl.window_should_close() {
|
2024-10-04 22:10:00 +02:00
|
|
|
game.input(&rl);
|
|
|
|
let mut d = rl.begin_drawing(&thread);
|
|
|
|
d.clear_background(Color::new(64, 64, 64, 255));
|
|
|
|
game.gui(&mut d, &textures);
|
|
|
|
d.draw_fps(2, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Game {
|
|
|
|
fn new_sandbox() -> Self {
|
|
|
|
Self {
|
|
|
|
source_board: Board::new_empty(1, 1),
|
|
|
|
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.,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 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();
|
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
2024-10-04 22:10:00 +02:00
|
|
|
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;
|
2024-10-04 21:20:53 +02:00
|
|
|
}
|
2024-10-04 22:10:00 +02:00
|
|
|
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;
|
2024-10-04 21:20:53 +02:00
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
if rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_MIDDLE) {
|
2024-10-04 22:10:00 +02:00
|
|
|
self.view_offset += rl.get_mouse_delta()
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
|
|
|
if rl.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_RIGHT) {
|
2024-10-04 22:10:00 +02:00
|
|
|
self.view_offset = Vector2::zero();
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
2024-10-04 22:10:00 +02:00
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
fn draw_board(&self, d: &mut RaylibDrawHandle, textures: &HashMap<String, Texture2D>) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
fn gui(&mut self, d: &mut RaylibDrawHandle, textures: &HashMap<String, Texture2D>) {
|
|
|
|
self.draw_board(d, textures);
|
2024-10-04 21:20:53 +02:00
|
|
|
|
|
|
|
let height = d.get_screen_height();
|
|
|
|
let footer_height = 100;
|
|
|
|
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),
|
|
|
|
);
|
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
let tile_size = (16 << 1) 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;
|
2024-10-04 21:20:53 +02:00
|
|
|
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")),
|
2024-10-04 22:10:00 +02:00
|
|
|
&mut self.output_as_text,
|
2024-10-04 21:20:53 +02:00
|
|
|
);
|
2024-10-04 22:10:00 +02:00
|
|
|
let out_text = if self.output_as_text {
|
|
|
|
String::from_utf8_lossy(self.machine.output()).to_string()
|
2024-10-04 21:20:53 +02:00
|
|
|
} else {
|
2024-10-04 22:10:00 +02:00
|
|
|
format!("{:?}", self.machine.output())
|
2024-10-04 21:20:53 +02:00
|
|
|
};
|
|
|
|
d.draw_text(&out_text, 5, footer_top as i32 + 35, 20, Color::WHITE);
|
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
let mut input_text = String::from_utf8_lossy(self.machine.input()).to_string();
|
2024-10-04 21:20:53 +02:00
|
|
|
if text_input(
|
2024-10-04 22:10:00 +02:00
|
|
|
d,
|
2024-10-04 21:20:53 +02:00
|
|
|
Rectangle::new(350., footer_top + 60., 200., 25.),
|
|
|
|
&mut input_text,
|
2024-10-04 22:10:00 +02:00
|
|
|
&mut self.input_text_selected,
|
2024-10-04 21:20:53 +02:00
|
|
|
) {
|
2024-10-04 22:10:00 +02:00
|
|
|
self.machine.set_input(input_text.into_bytes());
|
2024-10-04 21:20:53 +02:00
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
|
|
|
}
|