use raylib::prelude::*; pub mod board; pub mod pos; pub mod tile; use board::Board; use pos::*; use tile::*; use crate::{ui::draw_usize_small, Textures, TILE_TEXTURE_SIZE}; #[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() -> Self { Self { board: Board::new_empty(5, 5), marbles: Vec::new(), powered: 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(); self.powered.clear(); } pub fn set_board(&mut self, board: Board) { self.marbles = board.get_marbles(); self.powered.clear(); 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, scale: f32, ) { let tile_size = (TILE_TEXTURE_SIZE * scale) as i32; 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; let old_marbles = self.marbles.len(); let mut new_marbles = Vec::new(); // activate all powered machines for &pos in &self.powered { match self.board.get_mut(pos) { Some(Tile::Powerable(machine, state)) => { *state = false; let machine = *machine; for dir in Direction::ALL { let front_pos = dir.step(pos); let source_pos = dir.opposite().step(pos); match self.board.get(source_pos) { Some(Tile::Wire(wiretype, true)) => { if !wiretype.has_output(dir) { continue; } } Some(Tile::Button(true)) => (), _ => continue, } let Some(front_tile) = self.board.get_mut(front_pos) else { continue; }; // `machine`` is being powered, in direction `dir`` match machine { PTile::Comparator(_) => (), // handled at the power propagation stage (end of step) PTile::Math(op) => { if front_tile.is_blank() { 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 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(), }; new_marbles.push((front_pos, value, dir)); } } PTile::IO => { if front_tile == &Tile::BLANK && self.input_index < self.input.len() { let value = self.input[self.input_index] as MarbleValue; self.input_index += 1; new_marbles.push((front_pos, value, dir)); } } PTile::Silo => { if front_tile == &Tile::BLANK { new_marbles.push((front_pos, 0, dir)); } } PTile::Flipper => { match front_tile { Tile::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(), _ => (), }; } } } } Some(Tile::Button(_state)) => (), Some(Tile::Wire(_, _state)) => (), _ => unreachable!(), }; } // old wires have to be reset after machine processing, // so they can figure out which directions they are powered from for &p in &self.powered { match self.board.get_mut(p) { Some(Tile::Button(state)) => *state = false, Some(Tile::Wire(_, state)) => *state = false, _ => (), } } self.powered.clear(); if self.marbles.is_empty() { return; } #[derive(Clone, Copy, Debug)] enum DirInfluence { None, One(Direction), Multiple, } // #### find all direct bounces #### let mut will_reverse_direction = vec![false; self.marbles.len()]; // todo store in tile to remove search through self.marbles let mut influenced_direction = vec![DirInfluence::None; self.marbles.len()]; for (i, &pos) in self.marbles.iter().enumerate() { let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else { unreachable!() }; let front_pos = dir.step(pos); let Some(front_tile) = self.board.get(front_pos) else { continue; }; match front_tile { Tile::Marble { value: _, dir: other_dir, } => { if other_dir != dir { // this marble is facing another marble, and will therefore definitely bounce will_reverse_direction[i] = true; // the other marble will bounce too, either let other_index = self.marbles.iter().position(|m| *m == front_pos).unwrap(); let influence = &mut influenced_direction[other_index]; *influence = match *influence { DirInfluence::None => DirInfluence::One(dir), DirInfluence::One(_) => DirInfluence::Multiple, DirInfluence::Multiple => DirInfluence::Multiple, } } } Tile::Arrow(arrow_dir) => { if arrow_dir == dir.opposite() { // bounce on a reverse facing arrow will_reverse_direction[i] = true; } } _ => (), } } // #### apply all direct bounces #### for (i, &pos) in self.marbles.iter().enumerate() { let Some(Tile::Marble { value: _, dir }) = self.board.get_mut(pos) else { unreachable!() }; if will_reverse_direction[i] { *dir = dir.opposite(); } else if let DirInfluence::One(new_dir) = influenced_direction[i] { *dir = new_dir; } } // #### new marbles #### let mut claim_positions = Vec::new(); // prepare creating the new marbles for &(pos, _val, _dir) in &new_marbles { let Some(Tile::Open(OpenTile::Blank, claim)) = self.board.get_mut(pos) else { unreachable!() }; if claim.claim_indirect() { claim_positions.push(pos); } } // create new marbles // new marbles are past old_marbles index, so will not move this step for (pos, value, dir) in new_marbles { let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.board.get_mut(pos) else { continue; }; self.board.set(pos, Tile::Marble { value, dir }); self.marbles.push(pos); } // #### movement #### // mark claims to figure out what spaces can be moved to for &pos in &self.marbles[..old_marbles] { let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else { unreachable!() }; let front_pos = dir.step(pos); let Some(front_tile) = self.board.get_mut(front_pos) else { continue; }; if let Tile::Open(_type, claim) = front_tile { if claim.claim() { claim_positions.push(front_pos); } } else { let target_pos = match front_tile { Tile::Arrow(d) => d.step(front_pos), Tile::Mirror(m) => m.new_dir(dir).step(front_pos), Tile::Button(_) => dir.step(front_pos), _ => continue, }; let Some(target_tile) = self.board.get_mut(target_pos) else { continue; }; if let Tile::Open(_type, claim) = target_tile { if claim.claim_indirect() { claim_positions.push(front_pos); } } } } let mut removed_marbles = Vec::new(); // move marbles for (i, pos) in self.marbles[..old_marbles].iter_mut().enumerate() { let Some(Tile::Marble { value, dir }) = self.board.get(*pos) else { unreachable!() }; let front_pos = dir.step(*pos); let Some(front_tile) = self.board.get_mut(front_pos) else { continue; }; let mut move_to = |tile, target_pos, dir, board: &mut Board| { let value = match tile { OpenTile::Blank => value, OpenTile::Digit(n) => value.wrapping_mul(10).wrapping_add(n as MarbleValue), }; board.set(*pos, Tile::BLANK); board.set(target_pos, Tile::Marble { value, dir }); *pos = target_pos; }; if let Tile::Open(space_type, claim_state) = front_tile { if *claim_state == Claim::Claimed { move_to(*space_type, front_pos, dir, &mut self.board); } else if *claim_state != Claim::Free { // (Free means a marble was just here but moved earlier this tick) // bounce on failed direct movement self.board.set( *pos, Tile::Marble { value, dir: dir.opposite(), }, ); } } else { let target_pos; let mut is_button = false; let mut new_dir = dir; match front_tile { Tile::Arrow(d) => { target_pos = d.step(front_pos); new_dir = *d; } Tile::Mirror(m) => { new_dir = m.new_dir(dir); target_pos = new_dir.step(front_pos); } Tile::Button(_) => { is_button = true; target_pos = dir.step(front_pos); } Tile::Powerable(PTile::Silo, _) => { removed_marbles.push(i); continue; } Tile::Powerable(PTile::IO, _) => { removed_marbles.push(i); self.output.push(value as u8); continue; } _ => continue, } let Some(target_tile) = self.board.get_mut(target_pos) else { continue; }; if let Tile::Open(space_type, Claim::ClaimedIndirect) = target_tile { move_to(*space_type, target_pos, new_dir, &mut self.board); if is_button { self.powered.push(front_pos); } } } } for pos in claim_positions { if let Some(Tile::Open(_, claim_state)) = self.board.get_mut(pos) { *claim_state = Claim::Free; } } // remove marbles for &i in removed_marbles.iter().rev() { self.board.set(self.marbles[i], Tile::BLANK); self.marbles.swap_remove(i); } // propagate power let mut i = 0; while i < self.powered.len() { let pos = self.powered[i]; let Some(tile) = self.board.get_mut(pos) else { unreachable!() }; match tile { Tile::Button(state) => { *state = true; for dir in Direction::ALL { let target_pos = dir.step(pos); match self.board.get_mut(target_pos) { Some(Tile::Powerable(_, state)) => { if !*state { *state = true; self.powered.push(target_pos); } } Some(Tile::Wire(_, state)) => { // only push it if it hasnt already been pushed if !*state { *state = true; self.powered.push(target_pos); } } _ => (), } } } Tile::Wire(wiretype, state) => { *state = true; for dir in wiretype.directions() { let target_pos = dir.step(pos); match self.board.get_mut(target_pos) { Some(Tile::Powerable(_, state)) => { if !*state { *state = true; self.powered.push(target_pos); } } Some(Tile::Wire(_, state)) => { // only push it if it hasnt already been pushed if !*state { *state = true; self.powered.push(target_pos); } } _ => (), } } } Tile::Powerable(PTile::Comparator(comp), state) => { *state = true; let comp = *comp; for dir in Direction::ALL { let front_pos = dir.step(pos); let source_pos = dir.opposite().step(pos); match self.board.get(source_pos) { Some(Tile::Wire(wiretype, true)) => { if !wiretype.has_output(dir) { continue; } } Some(Tile::Button(true)) => (), _ => continue, } let Some(front_tile) = self.board.get_mut(front_pos) else { continue; }; if matches!(front_tile, Tile::Wire(_, _) | Tile::Powerable(_, _)) { 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 comp { Comparison::LessThan => val_a < val_b, Comparison::GreaterThan => val_a > val_b, Comparison::Equal => val_a == val_b, Comparison::NotEqual => val_a != val_b, }; if result { self.powered.push(front_pos); } } } } // state may be false if it was powered by a machine in earlier step Tile::Powerable(_, state) => *state = true, _ => { dbg!(tile); unreachable!() } } i += 1; } } }