marble-machinations/src/main.rs

283 lines
7 KiB
Rust
Raw Normal View History

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
use marble_engine::{board::Board, parse, tile::Tile, tile_to_char, 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::*;
#[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, Clone, PartialEq)]
enum Tool {
None,
SetTile(Tile),
// Erase,
// Select,
}
2024-10-04 21:20:53 +02:00
#[derive(Debug, Clone, PartialEq)]
enum SimState {
2024-10-04 21:20:53 +02:00
Editing,
Running,
2024-10-04 21:20:53 +02:00
Stepping,
}
2024-10-03 22:59:49 +02:00
2024-10-05 15:34:58 +02:00
fn load_textures_from(
folder: &str,
rl: &mut RaylibHandle,
thread: &RaylibThread,
textures: &mut HashMap<String, Texture2D>,
) {
2024-10-05 15:19:27 +02:00
for d in read_dir(folder).unwrap().flatten() {
let path = d.path();
if path.is_file() {
let name = path.file_stem().unwrap().to_string_lossy();
let texture = rl
2024-10-05 15:34:58 +02:00
.load_texture(thread, &format!("{folder}/{name}.png"))
2024-10-05 15:19:27 +02:00
.unwrap();
textures.insert(name.to_string(), texture);
}
}
}
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);
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();
2024-10-05 15:19:27 +02:00
load_textures_from("assets", &mut rl, &thread, &mut textures);
load_textures_from("assets/tiles", &mut rl, &thread, &mut textures);
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() {
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
}
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
}
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) {
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) {
self.view_offset = Vector2::zero();
2024-10-03 22:59:49 +02:00
}
}
2024-10-03 22:59:49 +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);
2024-10-04 22:35:15 +02:00
self.machine
.draw_marble_values(d, self.view_offset, self.zoom);
}
}
2024-10-03 22:59:49 +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:35:15 +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;
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")),
&mut self.output_as_text,
2024-10-04 21:20:53 +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 {
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);
let mut input_text = String::from_utf8_lossy(self.machine.input()).to_string();
2024-10-04 21:20:53 +02:00
if text_input(
d,
Rectangle::new(5., footer_top + 60., 200., 25.),
2024-10-04 21:20:53 +02:00
&mut input_text,
&mut self.input_text_selected,
2024-10-04 21:20:53 +02:00
) {
self.machine.set_input(input_text.into_bytes());
2024-10-04 21:20:53 +02:00
}
2024-10-05 15:19:27 +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.;
texture_button(
d,
Vector2 {
x: 300. + col as f32 * bound_offset,
y: footer_top + 5. + row as f32 * bound_offset,
},
textures.get(texture),
tool_option,
&mut self.active_tool,
32.,
border,
);
};
2024-10-05 15:34:58 +02:00
tool_button((0, -1), "eraser", Tool::SetTile(tile_to_char(' ')));
tool_button((1, -1), "", Tool::None);
2024-10-05 15:19:27 +02:00
tool_button((0, 0), "block", Tool::SetTile(tile_to_char('#')));
tool_button((0, 1), "bag_off", Tool::SetTile(tile_to_char('B')));
tool_button((0, 2), "trigger_off", Tool::SetTile(tile_to_char('*')));
tool_button(
(1, 0),
"wire_horizontal_off",
Tool::SetTile(tile_to_char('-')),
);
tool_button(
(1, 1),
"wire_vertical_off",
Tool::SetTile(tile_to_char('|')),
);
tool_button((1, 2), "wire_cross_off", Tool::SetTile(tile_to_char('+')));
2024-10-05 15:34:58 +02:00
let mouse_pos = d.get_mouse_position();
if self.sim_state == SimState::Editing && 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 let Tool::SetTile(tile) = self.active_tool {
d.draw_texture_ex(
textures.get("selection").unwrap(),
tile_screen_pos,
0.,
(1 << self.zoom) as f32,
Color::new(255, 255, 255, 150),
);
if d.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT) {
self.source_board.set(tile_pos.into(), tile)
}
}
}
2024-10-03 22:59:49 +02:00
}
}