use std::hint::unreachable_unchecked; use raylib::prelude::*; pub mod board; pub mod pos; pub mod tile; use board::Board; use pos::*; use tile::*; use crate::{draw_usize_small, Textures}; #[derive(Debug)] pub struct Machine { board: Board, marbles: Vec, powered: Vec, input: Vec, input_index: usize, output: Vec, steps: usize, } impl Machine { pub fn new_empty(input: Vec, width: usize) -> Self { Self { board: Board::new_empty(width, width), marbles: Vec::new(), powered: Vec::new(), input, 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 } pub fn output(&self) -> &[u8] { &self.output } pub fn input(&self) -> &[u8] { &self.input } pub fn input_index(&self) -> usize { self.input_index } pub fn step_count(&self) -> usize { self.steps } pub fn set_input(&mut self, bytes: Vec) { self.input_index = self.input_index.min(bytes.len()); self.input = bytes; } pub fn draw_marble_values( &self, d: &mut RaylibDrawHandle, textures: &Textures, offset: Vector2, zoom: i32, ) { let tile_size = 16 << zoom; let scale = (1 << zoom) as f32; 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; let py = y as i32 * tile_size + offset.y as i32; if let Tile::Marble { value, dir } = tile { let texture = textures.get(dir.arrow_texture_name()); let pos = Vector2::new(px as f32, py as f32); let faded_white = Color::new(255, 255, 255, 100); d.draw_texture_ex(texture, pos, 0., scale, faded_white); draw_usize_small(d, textures, value as usize, px, py, scale); } } } } pub fn step(&mut self) { self.steps += 1; // reset wires for &p in &self.powered { let Some(Tile::Powerable(_, state)) = self.board.get_mut(p) else { unsafe { unreachable_unchecked() } }; *state = false; } self.powered.clear(); if self.marbles.is_empty() { 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, } let mut marble_events: Vec = 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; } 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()) } // 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; } } } } } // 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); } *new_tile = Tile::Marble { value, dir: new_dir, }; } Event::Bounce(_other, new_dir) => *dir = new_dir, _ => (), } } // resolve deletions of marbles for (i, event) in marble_events.iter().enumerate().rev() { if let Event::Remove = event { self.marbles.remove(i); } } // process triggers for pos in triggers_activated { 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 { return; }; let front_pos = dir.step(pos); if let Tile::Powerable(tile, state) = tile { if let PTile::Trigger = tile { return; } let was_powered = *state; *state = true; self.powered.push(pos); match tile { PTile::Wire(wiretype) => { if was_powered { return; } let dirs = wiretype.directions(); for d in dirs { self.propagate_power(*d, d.step(pos)); } } PTile::Bag => { if let Some(front) = self.board.get_blank_mut(front_pos) { *front = Tile::Marble { value: 0, dir }; self.marbles.push(front_pos); } } 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); } } } 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() { 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(), }; // 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); } } } 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.flip(), 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(); 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)); } } PTile::Trigger => (), } } } }