marble-machinations/src/marble_engine.rs

402 lines
10 KiB
Rust
Raw Normal View History

2024-10-04 21:20:53 +02:00
use raylib::prelude::*;
2024-10-03 22:59:49 +02:00
pub mod board;
2024-10-10 17:09:41 +02:00
pub mod pos;
pub mod tile;
2024-10-10 17:09:41 +02:00
use board::Board;
use pos::*;
2024-10-03 22:59:49 +02:00
use tile::*;
#[derive(Debug)]
pub struct Machine {
board: Board,
marbles: Vec<Pos>,
input: Vec<u8>,
input_index: usize,
output: Vec<u8>,
steps: usize,
}
impl Machine {
pub fn new_empty(input: Vec<u8>, width: usize) -> Self {
2024-10-03 22:59:49 +02:00
Self {
board: Board::new_empty(width, width),
marbles: Vec::new(),
input,
2024-10-03 22:59:49 +02:00
input_index: 0,
output: Vec::new(),
steps: 0,
}
}
pub fn reset(&mut self) {
self.steps = 0;
self.input_index = 0;
self.output.clear();
}
pub fn set_board(&mut self, board: Board) {
self.marbles = board.get_marbles();
self.board = board;
}
pub fn board(&self) -> &Board {
&self.board
}
2024-10-04 21:20:53 +02:00
pub fn output(&self) -> &[u8] {
&self.output
}
pub fn input(&self) -> &[u8] {
&self.input
}
2024-10-07 17:29:56 +02:00
pub fn input_index(&self) -> usize {
self.input_index
}
pub fn step_count(&self) -> usize {
self.steps
}
2024-10-04 21:20:53 +02:00
pub fn set_input(&mut self, bytes: Vec<u8>) {
2024-10-05 15:57:20 +02:00
self.input_index = self.input_index.min(bytes.len());
2024-10-04 21:20:53 +02:00
self.input = bytes;
}
pub fn draw_marble_values(&self, d: &mut RaylibDrawHandle, offset: Vector2, zoom: i32) {
2024-10-04 21:20:53 +02:00
let tile_size = 16 << zoom;
for marble in &self.marbles {
let x = marble.x;
let y = marble.y;
if let Some(tile) = self.board.get(*marble) {
let px = x as i32 * tile_size + offset.x as i32 + tile_size / 2;
let py = y as i32 * tile_size + offset.y as i32 + tile_size / 2;
if let Tile::Marble { value, dir } = tile {
let fontsize = (zoom + 1) * 10;
d.draw_text(
&format!("{value}"),
px - tile_size / 2 + 2,
py - tile_size / 2 + 2,
fontsize,
Color::MAGENTA,
);
d.draw_text(
match dir {
Direction::Up => "^",
Direction::Down => "v",
Direction::Left => "<",
Direction::Right => ">",
},
px - tile_size / 2 + 2,
py - tile_size / 2 + fontsize,
fontsize,
Color::MAGENTA,
);
2024-10-03 22:59:49 +02:00
}
}
}
}
pub fn step(&mut self) {
self.steps += 1;
// reset wires
for y in 0..self.board.height() {
for x in 0..self.board.width() {
if let Some(Tile::Powerable(_, state)) = self.board.get_mut((x, y).into()) {
2024-10-04 01:21:52 +02:00
*state = false;
2024-10-03 22:59:49 +02:00
}
}
}
#[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,
}
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 {
return Event::Remove;
2024-10-03 22:59:49 +02:00
}
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_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 target = self.board.get(target_pos);
if can_move_to(target) {
Event::Trigger(target_pos, dir, front_pos)
} else {
Event::Stay
}
} 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
}
})
.collect();
// resolve bounces
for i in 0..marble_events.len() {
let event = marble_events[i];
if let Event::Bounce(other_index, dir) = event {
match marble_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())
2024-10-03 22:59:49 +02:00
}
// let already bouncing marbles continue
Event::Bounce(_, _) => (),
// interrupt any other movement/staying to bounce
_ => marble_events[other_index] = Event::Bounce(i, dir.opposite()),
}
}
}
// resolve deletions of tiles
for (i, event) in marble_events.iter().enumerate() {
if let Event::Remove = event {
self.board.set(self.marbles[i], Tile::Blank);
}
}
// resolve triggers
let mut triggers_activated = Vec::new();
for event in &mut marble_events {
if let Event::Trigger(new_pos, dir, trigger_pos) = event {
triggers_activated.push(*trigger_pos);
*event = Event::MoveTo(*new_pos, *dir);
}
}
// resolve collisions (multiple marbles entering the same space)
for i in 0..(marble_events.len() - 1) {
let event = marble_events[i];
if let Event::MoveTo(new_pos, _dir) = event {
for other_event in &mut marble_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 {
*other_event = Event::Stay;
}
2024-10-03 22:59:49 +02:00
}
}
}
}
// resolve movement
for (i, &event) in marble_events.iter().enumerate() {
if let Event::Remove = event {
continue;
}
let marble = self.board.get_mut(self.marbles[i]).unwrap();
let Tile::Marble { value, dir } = marble else {
panic!("invalid marble");
};
match event {
Event::MoveTo(new_pos, new_dir) => {
let mut value = *value;
self.board.set(self.marbles[i], Tile::Blank);
self.marbles[i] = new_pos;
let new_tile = self.board.get_mut(new_pos).unwrap();
if let Tile::Digit(n) = new_tile {
value = value.wrapping_mul(10).wrapping_add(*n as MarbleValue);
2024-10-03 22:59:49 +02:00
}
*new_tile = Tile::Marble {
value,
dir: new_dir,
};
2024-10-03 22:59:49 +02:00
}
Event::Bounce(_other, new_dir) => *dir = new_dir,
_ => (),
}
}
2024-10-03 22:59:49 +02:00
// resolve deletions of marbles
for (i, event) in marble_events.iter().enumerate().rev() {
if let Event::Remove = event {
self.marbles.remove(i);
2024-10-03 22:59:49 +02:00
}
}
// process triggers
for pos in triggers_activated {
2024-10-03 22:59:49 +02:00
for dir in Direction::ALL {
self.propagate_power(dir, dir.step(pos));
}
}
}
fn propagate_power(&mut self, dir: Direction, pos: Pos) {
let Some(tile) = self.board.get_mut(pos) else {
2024-10-03 22:59:49 +02:00
return;
};
2024-10-03 22:59:49 +02:00
let front_pos = dir.step(pos);
if let Tile::Powerable(tile, state) = tile {
2024-10-04 01:21:52 +02:00
if let PTile::Trigger = tile {
return;
}
let was_powered = *state;
*state = true;
match tile {
PTile::Wire(wiretype) => {
if was_powered {
return;
}
let dirs = wiretype.directions();
for d in dirs {
self.propagate_power(*d, d.step(pos));
}
2024-10-03 22:59:49 +02:00
}
PTile::Bag => {
if let Some(front) = self.board.get_blank_mut(front_pos) {
*front = Tile::Marble { value: 0, dir };
self.marbles.push(front_pos);
}
2024-10-03 22:59:49 +02:00
}
PTile::IO => {
if let Some(front) = self.board.get_blank_mut(front_pos) {
if self.input_index < self.input.len() {
let value = self.input[self.input_index] as MarbleValue;
self.input_index += 1;
*front = Tile::Marble { value, dir };
self.marbles.push(front_pos);
}
2024-10-03 22:59:49 +02:00
}
}
PTile::Math(op) => {
let op = *op;
let pos_a = dir.left().step(pos);
let pos_b = dir.right().step(pos);
let val_a = self.board.get_or_blank(pos_a).read_value();
let val_b = self.board.get_or_blank(pos_b).read_value();
if !self.board.get_or_blank(pos_a).is_blank()
|| !self.board.get_or_blank(pos_b).is_blank()
{
2024-10-05 20:48:05 +02:00
let value = match op {
MathOp::Add => val_a.wrapping_add(val_b),
MathOp::Sub => val_a.wrapping_sub(val_b),
MathOp::Mul => val_a.wrapping_mul(val_b),
MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(),
MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(),
2024-10-03 22:59:49 +02:00
};
// println!("{op:?} a:{val_a} b:{val_b}");
if let Some(front) = self.board.get_blank_mut(front_pos) {
*front = Tile::Marble { value, dir };
self.marbles.push(front_pos);
}
2024-10-03 22:59:49 +02:00
}
}
PTile::Flipper => {
let Some(target) = self.board.get_mut(front_pos) else {
return;
};
match target {
Tile::Powerable(PTile::Wire(wire_type), _) => {
*wire_type = match *wire_type {
WireType::Vertical => WireType::Horizontal,
WireType::Horizontal => WireType::Vertical,
WireType::Cross => WireType::Cross,
};
}
Tile::Mirror(mirror) => {
*mirror = match *mirror {
MirrorType::Forward => MirrorType::Back,
MirrorType::Back => MirrorType::Forward,
};
}
Tile::Arrow(dir) => {
*dir = dir.opposite();
}
_ => (),
};
}
PTile::Gate(gate) => {
let gate = *gate;
let pos_a = dir.left().step(pos);
let pos_b = dir.right().step(pos);
let val_a = self.board.get_or_blank(pos_a).read_value();
let val_b = self.board.get_or_blank(pos_b).read_value();
2024-10-03 22:59:49 +02:00
let result = match gate {
GateType::LessThan => val_a < val_b,
GateType::GreaterThan => val_a > val_b,
GateType::Equal => val_a == val_b,
GateType::NotEqual => val_a != val_b,
};
if result {
self.propagate_power(dir, dir.step(pos));
}
2024-10-03 22:59:49 +02:00
}
2024-10-04 01:21:52 +02:00
PTile::Trigger => (),
2024-10-03 22:59:49 +02:00
}
}
}
}