2024-10-04 21:20:53 +02:00
|
|
|
use raylib::prelude::*;
|
2024-10-03 22:59:49 +02:00
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
pub mod board;
|
2024-10-10 17:09:41 +02:00
|
|
|
pub mod pos;
|
2024-10-04 22:10:00 +02:00
|
|
|
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::*;
|
|
|
|
|
2024-10-12 20:53:47 +02:00
|
|
|
use crate::{draw_usize_small, Textures};
|
|
|
|
|
2024-10-03 22:59:49 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Machine {
|
|
|
|
board: Board,
|
|
|
|
marbles: Vec<Pos>,
|
|
|
|
|
|
|
|
input: Vec<u8>,
|
|
|
|
input_index: usize,
|
|
|
|
output: Vec<u8>,
|
|
|
|
steps: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Machine {
|
2024-10-06 22:24:37 +02:00
|
|
|
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(),
|
2024-10-06 20:02:43 +02:00
|
|
|
input,
|
2024-10-03 22:59:49 +02:00
|
|
|
input_index: 0,
|
|
|
|
output: Vec::new(),
|
|
|
|
steps: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-04 22:10:00 +02:00
|
|
|
pub fn reset(&mut self) {
|
|
|
|
self.steps = 0;
|
|
|
|
self.input_index = 0;
|
|
|
|
self.output.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_board(&mut self, board: Board) {
|
2024-10-07 17:57:07 +02:00
|
|
|
self.marbles = board.get_marbles();
|
2024-10-04 22:10:00 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-10-06 22:24:37 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-10-12 20:53:47 +02:00
|
|
|
pub fn draw_marble_values(
|
|
|
|
&self,
|
|
|
|
d: &mut RaylibDrawHandle,
|
|
|
|
textures: &Textures,
|
|
|
|
offset: Vector2,
|
|
|
|
zoom: i32,
|
|
|
|
) {
|
2024-10-04 21:20:53 +02:00
|
|
|
let tile_size = 16 << zoom;
|
2024-10-12 23:27:28 +02:00
|
|
|
let scale = (1 << zoom) as f32;
|
2024-10-07 17:57:07 +02:00
|
|
|
for marble in &self.marbles {
|
|
|
|
let x = marble.x;
|
|
|
|
let y = marble.y;
|
|
|
|
if let Some(tile) = self.board.get(*marble) {
|
2024-10-12 20:53:47 +02:00
|
|
|
let px = x as i32 * tile_size + offset.x as i32;
|
|
|
|
let py = y as i32 * tile_size + offset.y as i32;
|
2024-10-07 17:57:07 +02:00
|
|
|
if let Tile::Marble { value, dir } = tile {
|
2024-10-12 20:53:47 +02:00
|
|
|
let texture = textures.get(dir.arrow_texture_name());
|
|
|
|
let pos = Vector2::new(px as f32, py as f32);
|
2024-10-12 23:27:28 +02:00
|
|
|
let faded_white = Color::new(255, 255, 255, 100);
|
|
|
|
d.draw_texture_ex(texture, pos, 0., scale, faded_white);
|
2024-10-12 20:53:47 +02:00
|
|
|
draw_usize_small(d, textures, value as usize, px, py, scale);
|
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() {
|
2024-10-05 23:16:24 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-12 20:53:47 +02:00
|
|
|
if self.marbles.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-08 21:51:31 +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();
|
2024-10-08 22:04:12 +02:00
|
|
|
let Tile::Marble { value, dir } = marble else {
|
2024-10-08 21:51:31 +02:00
|
|
|
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
|
|
|
}
|
2024-10-10 16:58:50 +02:00
|
|
|
if let Tile::Powerable(PTile::IO, _) = front_tile {
|
2024-10-08 22:04:12 +02:00
|
|
|
self.output.push(value as u8);
|
|
|
|
return Event::Remove;
|
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
|
|
|
|
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: _,
|
2024-10-05 23:16:24 +02:00
|
|
|
dir: other_dir,
|
2024-10-08 21:51:31 +02:00
|
|
|
} = front_tile
|
|
|
|
{
|
|
|
|
if other_dir != dir {
|
|
|
|
Event::Bounce(
|
|
|
|
self.marbles.iter().position(|m| m == &front_pos).unwrap(),
|
|
|
|
dir.opposite(),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
Event::Stay
|
2024-10-05 23:16:24 +02:00
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
} else {
|
|
|
|
Event::Stay
|
2024-10-05 23:16:24 +02:00
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
})
|
|
|
|
.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
|
|
|
}
|
2024-10-08 21:51:31 +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()),
|
2024-10-05 23:16:24 +02:00
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
}
|
2024-10-05 23:16:24 +02:00
|
|
|
}
|
2024-10-08 21:51:31 +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
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
*new_tile = Tile::Marble {
|
|
|
|
value,
|
|
|
|
dir: new_dir,
|
|
|
|
};
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
Event::Bounce(_other, new_dir) => *dir = new_dir,
|
2024-10-05 23:16:24 +02:00
|
|
|
_ => (),
|
|
|
|
}
|
2024-10-08 21:51:31 +02:00
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
|
2024-10-08 21:51:31 +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
|
|
|
}
|
|
|
|
}
|
2024-10-08 21:51:31 +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) {
|
2024-10-05 23:16:24 +02:00
|
|
|
let Some(tile) = self.board.get_mut(pos) else {
|
2024-10-03 22:59:49 +02:00
|
|
|
return;
|
2024-10-05 23:16:24 +02:00
|
|
|
};
|
2024-10-03 22:59:49 +02:00
|
|
|
let front_pos = dir.step(pos);
|
2024-10-03 23:34:16 +02:00
|
|
|
if let Tile::Powerable(tile, state) = tile {
|
2024-10-04 01:21:52 +02:00
|
|
|
if let PTile::Trigger = tile {
|
|
|
|
return;
|
|
|
|
}
|
2024-10-12 19:00:02 +02:00
|
|
|
let was_powered = *state;
|
2024-10-03 23:34:16 +02:00
|
|
|
*state = true;
|
|
|
|
match tile {
|
|
|
|
PTile::Wire(wiretype) => {
|
2024-10-12 19:00:02 +02:00
|
|
|
if was_powered {
|
|
|
|
return;
|
|
|
|
}
|
2024-10-03 23:34:16 +02:00
|
|
|
let dirs = wiretype.directions();
|
|
|
|
for d in dirs {
|
|
|
|
self.propagate_power(*d, d.step(pos));
|
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
2024-10-03 23:34:16 +02:00
|
|
|
PTile::Bag => {
|
2024-10-05 23:16:24 +02:00
|
|
|
if let Some(front) = self.board.get_blank_mut(front_pos) {
|
|
|
|
*front = Tile::Marble { value: 0, dir };
|
2024-10-03 23:34:16 +02:00
|
|
|
self.marbles.push(front_pos);
|
|
|
|
}
|
2024-10-03 22:59:49 +02:00
|
|
|
}
|
2024-10-08 22:04:12 +02:00
|
|
|
PTile::IO => {
|
2024-10-05 23:16:24 +02:00
|
|
|
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
|
|
|
}
|
2024-10-03 23:34:16 +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();
|
2024-10-05 23:16:24 +02:00
|
|
|
if !self.board.get_or_blank(pos_a).is_blank()
|
|
|
|
|| !self.board.get_or_blank(pos_b).is_blank()
|
2024-10-03 23:34:16 +02:00
|
|
|
{
|
2024-10-05 20:48:05 +02:00
|
|
|
let value = match op {
|
2024-10-03 23:34:16 +02:00
|
|
|
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
|
|
|
};
|
2024-10-03 23:34:16 +02:00
|
|
|
// println!("{op:?} a:{val_a} b:{val_b}");
|
2024-10-05 23:16:24 +02:00
|
|
|
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
|
|
|
}
|
2024-10-03 23:34:16 +02:00
|
|
|
}
|
|
|
|
PTile::Flipper => {
|
2024-10-05 23:16:24 +02:00
|
|
|
let Some(target) = self.board.get_mut(front_pos) else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
match target {
|
2024-10-03 23:34:16 +02:00
|
|
|
Tile::Powerable(PTile::Wire(wire_type), _) => {
|
|
|
|
*wire_type = match *wire_type {
|
|
|
|
WireType::Vertical => WireType::Horizontal,
|
|
|
|
WireType::Horizontal => WireType::Vertical,
|
|
|
|
WireType::Cross => WireType::Cross,
|
2024-10-13 00:55:28 +02:00
|
|
|
}
|
2024-10-03 23:34:16 +02:00
|
|
|
}
|
2024-10-13 00:55:28 +02:00
|
|
|
Tile::Mirror(mirror) => mirror.flip(),
|
|
|
|
Tile::Arrow(dir) => *dir = dir.opposite(),
|
2024-10-03 23:34:16 +02:00
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
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
|
|
|
|
2024-10-03 23:34:16 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|