use std::collections::HashMap; use raylib::{drawing::RaylibDrawHandle, math::Vector2, texture::Texture2D}; mod board; 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 new(grid: Board, input: Vec) -> Self { // let (grid, marbles) = parse(source); let mut marbles = Vec::new(); for y in 0..grid.height() { for x in 0..grid.width() { if let Some(Tile::Marble { value: _, dir: _ }) = grid.get((x, y).into()) { marbles.push((x, y).into()); } } } Self { board: grid, marbles, input, input_index: 0, output: Vec::new(), steps: 0, } } pub fn draw( &self, d: &mut RaylibDrawHandle, textures: &HashMap, offset: Vector2, ) { let tile_size = 32; 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; tile.draw(d, textures, px, py, tile_size); } } } } 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(); let mut in_comment = false; for char in line.chars() { if in_comment { if char == ')' { in_comment = false; } if char == ' ' { // allow marbles to pass through gaps in comments tiles.push(Tile::Blank); } else { tiles.push(Tile::Comment(char as u8)); } continue; } tiles.push(match char { '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, '(' => { in_comment = true; Tile::Comment(b'(') } _ => Tile::Blank, }); } rows.push(tiles); } for line in &mut rows { line.resize(width, Tile::Blank); } Board::new(rows) }