Compare commits
4 commits
e0d0a297b9
...
42a355d387
Author | SHA1 | Date | |
---|---|---|---|
42a355d387 | |||
28213da9f3 | |||
1d001be403 | |||
e6d8c1246f |
4 changed files with 125 additions and 109 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue