use raylib::prelude::*; pub mod board; pub mod tile; use board::{Board, Pos}; use tile::*; #[derive(Debug)] pub struct Machine { board: Board, marbles: Vec, input: Vec, input_index: usize, output: Vec, steps: usize, } impl Machine { pub fn new_empty(width: usize) -> Self { Self { board: Board::new_empty(width, width), marbles: Vec::new(), input: Vec::new(), 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.clear(); for y in 0..board.height() { for x in 0..board.width() { if let Some(Tile::Marble { value: _, dir: _ }) = board.get((x, y).into()) { self.marbles.push((x, y).into()); } } } 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_mut(&mut self) -> &mut Vec { &mut self.input } pub fn set_input(&mut self, bytes: Vec) { self.input = bytes; } pub fn draw_marble_values(&self, d: &mut RaylibDrawHandle, offset: Vector2, zoom: i32) { let tile_size = 16 << zoom; for x in 0..self.board.width() { for y in 0..self.board.height() { if let Some(tile) = self.board.get((x, y).into()) { 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 { d.draw_text( &format!("{value}"), px - tile_size / 2 + 2, py - tile_size / 2 + 2, 20, Color::MAGENTA, ) } } } } } 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 Tile::Powerable(_, state) = self.board.get_mut((x, y).into()) { *state = false; } } } let mut to_remove = Vec::new(); let mut triggers = Vec::new(); for i in 0..self.marbles.len() { let marble_pos = self.marbles[i]; let tile = self.board.get_or_blank(marble_pos); if let Tile::Marble { value, dir } = tile { let next_pos = dir.step(marble_pos); if !self.board.in_bounds(next_pos) { continue; } let mut new_tile = None; let target = self.board.get_mut(next_pos); match target { Tile::Blank => { *target = tile; self.marbles[i] = next_pos; new_tile = Some(Tile::Blank); } Tile::Digit(d) => { let new_val = value .wrapping_mul(10) .wrapping_add((*d - b'0') as MarbleValue); *target = Tile::Marble { value: new_val, dir, }; self.marbles[i] = next_pos; new_tile = Some(Tile::Blank); } Tile::Powerable(PTile::Bag, _) => { to_remove.push(i); new_tile = Some(Tile::Blank); } Tile::Marble { value: _other_value, dir: other_dir, } => { // bounce off other marbles if *other_dir != dir { new_tile = Some(Tile::Marble { value, dir: dir.opposite(), }); *other_dir = dir; } } Tile::Powerable(PTile::Trigger, state) => { *state = true; triggers.push(next_pos); let far_pos = dir.step(next_pos); if let Some(Tile::Blank) = self.board.get(far_pos) { self.board.set(far_pos, Tile::Marble { value, dir }); self.marbles[i] = far_pos; new_tile = Some(Tile::Blank); } } Tile::Arrow(arrow_dir) => { let far_pos = arrow_dir.step(next_pos); let arrow_dir = *arrow_dir; if let Some(Tile::Blank) = self.board.get(far_pos) { self.marbles[i] = far_pos; self.board.set( far_pos, Tile::Marble { value, dir: arrow_dir, }, ); new_tile = Some(Tile::Blank); } else if far_pos == marble_pos { // bounce on reverse arrow new_tile = Some(Tile::Marble { value, dir: dir.opposite(), }); } } Tile::Mirror(mirror) => { let new_dir = mirror.new_dir(dir); let far_pos = new_dir.step(next_pos); let far_target = self.board.get_mut(far_pos); if let Tile::Blank = far_target { *far_target = Tile::Marble { value, dir: new_dir, }; self.marbles[i] = far_pos; new_tile = Some(Tile::Blank); } } _ => (), } if let Some(t) = new_tile { *self.board.get_mut(marble_pos) = t; } } } let mut offset = 0; for i in to_remove { self.marbles.remove(i - offset); offset += 1; } for pos in triggers { for dir in Direction::ALL { self.propagate_power(dir, dir.step(pos)); } } } fn propagate_power(&mut self, dir: Direction, pos: Pos) { if !self.board.in_bounds(pos) { return; } let tile = self.board.get_mut(pos); let front_pos = dir.step(pos); if let Tile::Powerable(tile, state) = tile { if let PTile::Trigger = tile { return; } if *state { return; } *state = true; match tile { PTile::Wire(wiretype) => { let dirs = wiretype.directions(); for d in dirs { self.propagate_power(*d, d.step(pos)); } } PTile::Output => { let sample = self.board.get_or_blank(front_pos); if let Tile::Marble { value, dir: _ } = sample { self.output.push(value as u8); } } PTile::Bag => { if let Some(Tile::Blank) = self.board.get(front_pos) { *self.board.get_mut(front_pos) = Tile::Marble { value: 0, dir }; self.marbles.push(front_pos); } } PTile::Input => { if self.input_index < self.input.len() && self.board.get_or_blank(front_pos).is_blank() { let value = self.input[self.input_index] as MarbleValue; *self.board.get_mut(front_pos) = Tile::Marble { value, dir }; self.marbles.push(front_pos); self.input_index += 1; } } 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()) && self.board.get_or_blank(front_pos).is_blank() { let result = 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}"); *self.board.get_mut(front_pos) = Tile::Marble { value: result, dir }; self.marbles.push(front_pos); } } PTile::Flipper => { let m = self.board.get_mut(front_pos); match m { 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(); 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 => (), } } } } pub fn parse(source: &str) -> Board { let mut rows = Vec::new(); let mut width = 0; for line in source.lines() { width = width.max(line.len()); let mut tiles = Vec::new(); for char in line.chars() { tiles.push(tile_to_char(char)); } rows.push(tiles); } for line in &mut rows { line.resize(width, Tile::Blank); } Board::new(rows) } pub const fn tile_to_char(c: char) -> Tile { match c { 'o' => Tile::Marble { value: 0, dir: Direction::Down, }, '*' => Tile::Powerable(PTile::Trigger, false), '-' => Tile::Powerable(PTile::Wire(WireType::Horizontal), false), '|' => Tile::Powerable(PTile::Wire(WireType::Vertical), false), '+' => Tile::Powerable(PTile::Wire(WireType::Cross), false), '/' => Tile::Mirror(MirrorType::Forward), '\\' => Tile::Mirror(MirrorType::Back), '^' => Tile::Arrow(Direction::Up), 'v' => Tile::Arrow(Direction::Down), '<' => Tile::Arrow(Direction::Left), '>' => Tile::Arrow(Direction::Right), '=' => Tile::Powerable(PTile::Gate(GateType::Equal), false), '!' => Tile::Powerable(PTile::Gate(GateType::NotEqual), false), 'L' => Tile::Powerable(PTile::Gate(GateType::LessThan), false), 'G' => Tile::Powerable(PTile::Gate(GateType::GreaterThan), false), 'P' => Tile::Powerable(PTile::Output, false), 'I' => Tile::Powerable(PTile::Input, false), 'F' => Tile::Powerable(PTile::Flipper, false), 'A' => Tile::Powerable(PTile::Math(MathOp::Add), false), 'S' => Tile::Powerable(PTile::Math(MathOp::Sub), false), 'M' => Tile::Powerable(PTile::Math(MathOp::Mul), false), 'D' => Tile::Powerable(PTile::Math(MathOp::Div), false), 'R' => Tile::Powerable(PTile::Math(MathOp::Rem), false), 'B' => Tile::Powerable(PTile::Bag, false), d @ '0'..='9' => Tile::Digit(d as u8), '#' => Tile::Block, ' ' => Tile::Blank, _ => Tile::Blank, } }