Compare commits

...

4 commits

4 changed files with 125 additions and 109 deletions

View file

@ -13,7 +13,7 @@ use crate::{
level::Level,
marble_engine::{
board::Board,
pos::Pos,
pos::{Pos, PosInt},
tile::{Direction, GateType, MathOp, MirrorType, PTile, Tile, WireType},
Machine,
},
@ -28,10 +28,10 @@ const HEADER_HEIGHT: i32 = 40;
const FOOTER_HEIGHT: i32 = 95;
const SIDEBAR_WIDTH: i32 = 200 + 32 * 2 + 5 * 4;
const MAX_ZOOM_IN: i32 = 3;
const BOARD_MARGIN: isize = 3;
const BOARD_MARGIN: PosInt = 3;
const MAX_SPEED_POWER: u8 = 16;
const SPEED_DIGITS: u8 = 5;
const MAX_FRAME_TIME_MICROS: u128 = 1000_000 / 30;
const MAX_FRAME_TIME_MICROS: u128 = 1_000_000 / 30;
#[derive(Debug)]
pub struct Editor {
@ -63,6 +63,7 @@ pub struct Editor {
selected_blueprint: usize,
blueprint_scroll: usize,
step_time: u128,
max_step_time:u128,
}
#[derive(Debug, PartialEq)]
@ -139,6 +140,7 @@ impl Editor {
selected_blueprint: usize::MAX,
blueprint_scroll: 0,
step_time: 0,
max_step_time: 0,
}
}
@ -163,6 +165,7 @@ impl Editor {
}
fn start_sim(&mut self) {
self.max_step_time = 0;
self.machine.reset();
self.machine.set_board(self.source_board.clone());
}
@ -180,9 +183,7 @@ impl Editor {
}
fn step(&mut self) {
let start_time = Instant::now();
self.machine.step();
self.step_time = start_time.elapsed().as_micros();
if self.complete_popup == Popup::Visible {
self.complete_popup = Popup::Dismissed;
@ -334,9 +335,10 @@ impl Editor {
if self.sim_state == SimState::Running {
self.time_since_step += rl.get_frame_time();
let step_size = 1. / (1 << self.sim_speed) as f32;
let mut steps_taken = 0;
let start_time = Instant::now();
if self.time_since_step > step_size {
let step_count = (self.time_since_step / step_size) as u32;
let start_time = Instant::now();
for _ in 0..step_count {
if self.sim_state != SimState::Running {
// pause on level completion
@ -346,9 +348,17 @@ impl Editor {
break;
}
self.step();
steps_taken += 1;
}
self.time_since_step -= step_count as f32 * step_size;
}
let avg_step_time = start_time
.elapsed()
.as_micros()
.checked_div(steps_taken)
.unwrap_or_default();
self.step_time = avg_step_time;
self.max_step_time = avg_step_time.max(self.max_step_time);
}
if rl.is_key_pressed(KeyboardKey::KEY_SPACE) {
self.step_pressed()
@ -591,6 +601,7 @@ impl Editor {
draw_usize(d, textures, self.machine.step_count(), 420, 4, 9, 2);
draw_usize(d, textures, self.step_time as usize, 540, 42, 9, 1);
draw_usize(d, textures, self.max_step_time as usize, 540, 58, 9, 1);
d.draw_text("input:", 603, 8, 10, Color::WHITE);
if simple_button(d, 600, 20, 35, 15) {

View file

@ -16,6 +16,7 @@ pub struct Machine {
board: Board,
marbles: Vec<Pos>,
powered: Vec<Pos>,
events: Vec<Event>,
input: Vec<u8>,
input_index: usize,
@ -23,12 +24,27 @@ pub struct Machine {
steps: usize,
}
#[derive(Debug, Clone, Copy)]
enum Event {
Stay,
/// (new_pos, new_dir)
MoveTo(Pos, Direction),
/// (new_pos, new_dir, trigger_pos)
Trigger(Pos, Direction, Pos),
/// (other, new_dir)
/// other marble should be set to reverse of new_dir
/// and should be cancelled if it had a movement event planned
Bounce(usize, Direction),
Remove,
}
impl Machine {
pub fn new_empty(input: Vec<u8>, width: usize) -> Self {
Self {
board: Board::new_empty(width, width),
marbles: Vec::new(),
powered: Vec::new(),
events: Vec::new(),
input,
input_index: 0,
output: Vec::new(),
@ -113,116 +129,102 @@ impl Machine {
return;
}
#[derive(Debug, Clone, Copy)]
enum Event {
Stay,
/// (new_pos, new_dir)
MoveTo(Pos, Direction),
/// (new_pos, new_dir, trigger_pos)
Trigger(Pos, Direction, Pos),
/// (other, new_dir)
/// other marble should be set to reverse of new_dir
/// and should be cancelled if it had a movement event planned
Bounce(usize, Direction),
Remove,
}
self.events.clear();
for &pos in &self.marbles {
let marble = self.board.get(pos).unwrap();
let Tile::Marble { value, dir } = marble else {
panic!("broken marble");
};
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get(front_pos) else {
self.events.push(Event::Stay);
continue;
};
let mut marble_events: Vec<Event> = self
.marbles
.iter()
.map(|&pos| {
let marble = self.board.get(pos).unwrap();
let Tile::Marble { value, dir } = marble else {
panic!("broken marble");
};
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get(front_pos) else {
return Event::Stay;
};
if let Tile::Powerable(PTile::Bag, _) = front_tile {
self.events.push(Event::Remove);
continue;
}
if let Tile::Powerable(PTile::IO, _) = front_tile {
self.output.push(value as u8);
self.events.push(Event::Remove);
continue;
}
if let Tile::Powerable(PTile::Bag, _) = front_tile {
return Event::Remove;
}
if let Tile::Powerable(PTile::IO, _) = front_tile {
self.output.push(value as u8);
return Event::Remove;
}
let can_move_to = |tile| matches!(tile, Some(Tile::Blank | Tile::Digit(_)));
let can_move_to = |tile| matches!(tile, Some(Tile::Blank | Tile::Digit(_)));
let can_move_over = |tile| match tile {
Tile::Mirror(mirror) => {
let new_dir = mirror.new_dir(dir);
let target_pos = new_dir.step(front_pos);
let target = self.board.get(target_pos);
if can_move_to(target) {
Some((target_pos, new_dir))
} else {
None
}
}
Tile::Arrow(new_dir) => {
let target_pos = new_dir.step(front_pos);
let target = self.board.get(target_pos);
if target_pos == pos || can_move_to(target) {
Some((target_pos, new_dir))
} else {
None
}
}
_ => None,
};
if can_move_to(Some(front_tile)) {
Event::MoveTo(front_pos, dir)
} else if let Tile::Powerable(PTile::Trigger, _) = front_tile {
let target_pos = dir.step(front_pos);
let can_move_over = |tile| match tile {
Tile::Mirror(mirror) => {
let new_dir = mirror.new_dir(dir);
let target_pos = new_dir.step(front_pos);
let target = self.board.get(target_pos);
if can_move_to(target) {
Event::Trigger(target_pos, dir, front_pos)
Some((target_pos, new_dir))
} else {
Event::Stay
None
}
} else if let Some((new_pos, new_dir)) = can_move_over(front_tile) {
Event::MoveTo(new_pos, new_dir)
} else if let Tile::Marble {
value: _,
dir: other_dir,
} = front_tile
{
if other_dir != dir {
Event::Bounce(
self.marbles.iter().position(|m| m == &front_pos).unwrap(),
dir.opposite(),
)
}
Tile::Arrow(new_dir) => {
let target_pos = new_dir.step(front_pos);
let target = self.board.get(target_pos);
if target_pos == pos || can_move_to(target) {
Some((target_pos, new_dir))
} else {
Event::Stay
None
}
}
_ => None,
};
let e = if can_move_to(Some(front_tile)) {
Event::MoveTo(front_pos, dir)
} else if let Tile::Powerable(PTile::Trigger, _) = front_tile {
let target_pos = dir.step(front_pos);
let target = self.board.get(target_pos);
if can_move_to(target) {
Event::Trigger(target_pos, dir, front_pos)
} else {
Event::Stay
}
})
.collect();
} else if let Some((new_pos, new_dir)) = can_move_over(front_tile) {
Event::MoveTo(new_pos, new_dir)
} else if let Tile::Marble {
value: _,
dir: other_dir,
} = front_tile
{
if other_dir != dir {
Event::Bounce(
self.marbles.iter().position(|m| m == &front_pos).unwrap(),
dir.opposite(),
)
} else {
Event::Stay
}
} else {
Event::Stay
};
self.events.push(e);
}
// resolve bounces
for i in 0..marble_events.len() {
let event = marble_events[i];
for i in 0..self.events.len() {
let event = self.events[i];
if let Event::Bounce(other_index, dir) = event {
match marble_events[other_index] {
match self.events[other_index] {
// cancel bounces on marble that are about to disappear
Event::Remove => {
marble_events[i] = Event::MoveTo(self.marbles[other_index], dir.opposite())
self.events[i] = Event::MoveTo(self.marbles[other_index], dir.opposite())
}
// let already bouncing marbles continue
Event::Bounce(_, _) => (),
// interrupt any other movement/staying to bounce
_ => marble_events[other_index] = Event::Bounce(i, dir.opposite()),
_ => self.events[other_index] = Event::Bounce(i, dir.opposite()),
}
}
}
// resolve deletions of tiles
for (i, event) in marble_events.iter().enumerate() {
for (i, event) in self.events.iter().enumerate() {
if let Event::Remove = event {
self.board.set(self.marbles[i], Tile::Blank);
}
@ -230,7 +232,7 @@ impl Machine {
// resolve triggers
let mut triggers_activated = Vec::new();
for event in &mut marble_events {
for event in &mut self.events {
if let Event::Trigger(new_pos, dir, trigger_pos) = event {
triggers_activated.push(*trigger_pos);
*event = Event::MoveTo(*new_pos, *dir);
@ -238,10 +240,10 @@ impl Machine {
}
// resolve collisions (multiple marbles entering the same space)
for i in 0..(marble_events.len() - 1) {
let event = marble_events[i];
for i in 0..(self.events.len() - 1) {
let event = self.events[i];
if let Event::MoveTo(new_pos, _dir) = event {
for other_event in &mut marble_events[(i + 1)..] {
for other_event in &mut self.events[(i + 1)..] {
if let Event::MoveTo(other_pos, _other_dir) = other_event {
// todo: maybe sort by direction so the sucessful direction is consistent
if other_pos == &new_pos {
@ -253,7 +255,7 @@ impl Machine {
}
// resolve movement
for (i, &event) in marble_events.iter().enumerate() {
for (i, &event) in self.events.iter().enumerate() {
if let Event::Remove = event {
continue;
}
@ -281,7 +283,7 @@ impl Machine {
}
// resolve deletions of marbles
for (i, event) in marble_events.iter().enumerate().rev() {
for (i, event) in self.events.iter().enumerate().rev() {
if let Event::Remove = event {
self.marbles.remove(i);
}

View file

@ -2,6 +2,7 @@ use crate::{draw_scaled_texture, Textures};
use super::tile::*;
use super::Pos;
use super::PosInt;
use raylib::prelude::*;
#[derive(Debug, Clone)]
@ -73,7 +74,7 @@ impl Board {
}
fn in_bounds(&self, p: Pos) -> bool {
p.x >= 0 && p.y >= 0 && p.x < self.width as isize && p.y < self.height as isize
p.x >= 0 && p.y >= 0 && p.x < self.width as PosInt && p.y < self.height as PosInt
}
pub fn get(&self, p: Pos) -> Option<Tile> {
@ -116,11 +117,11 @@ impl Board {
}
}
pub fn grow_to_include(&mut self, p: Pos) -> (isize, isize) {
pub fn grow_to_include(&mut self, p: Pos) -> (PosInt,PosInt) {
let mut offset_x = 0;
let mut offset_y = 0;
if p.x < 0 {
let len = p.x.unsigned_abs();
let len = p.x.unsigned_abs() as usize;
for row in &mut self.rows {
let mut new_row = vec![Tile::Blank; len];
new_row.append(row);
@ -137,7 +138,7 @@ impl Board {
}
if p.y < 0 {
let len = p.y.unsigned_abs();
let len = p.y.unsigned_abs() as usize;
let mut new_rows = vec![vec![Tile::Blank; self.width]; len];
new_rows.append(&mut self.rows);
self.rows = new_rows;
@ -148,7 +149,7 @@ impl Board {
self.rows.resize(new_height, vec![Tile::Blank; self.width]);
self.height = new_height;
}
(offset_x as isize, offset_y as isize)
(offset_x as PosInt, offset_y as PosInt)
}
pub fn trim_size(&mut self, margin: usize) -> (usize, usize) {

View file

@ -2,10 +2,12 @@ use std::ops::Add;
use raylib::prelude::*;
pub type PosInt = i16;
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Pos {
pub x: isize,
pub y: isize,
pub x: PosInt,
pub y: PosInt,
}
impl Pos {
@ -34,8 +36,8 @@ impl Pos {
impl From<(usize, usize)> for Pos {
fn from(value: (usize, usize)) -> Self {
Self {
x: value.0 as isize,
y: value.1 as isize,
x: value.0 as PosInt,
y: value.1 as PosInt,
}
}
}
@ -43,8 +45,8 @@ impl From<(usize, usize)> for Pos {
impl From<(i32, i32)> for Pos {
fn from(value: (i32, i32)) -> Self {
Self {
x: value.0 as isize,
y: value.1 as isize,
x: value.0 as PosInt,
y: value.1 as PosInt,
}
}
}
@ -52,8 +54,8 @@ impl From<(i32, i32)> for Pos {
impl From<Vector2> for Pos {
fn from(vec: Vector2) -> Self {
Self {
x: vec.x as isize,
y: vec.y as isize,
x: vec.x as PosInt,
y: vec.y as PosInt,
}
}
}