rewrite power propagation

This commit is contained in:
Crispy 2024-12-09 20:43:15 +01:00
parent e6f99d3776
commit e223a75c0e
5 changed files with 263 additions and 184 deletions

View file

@ -14,8 +14,6 @@ pub struct Machine {
board: Board,
marbles: Vec<Pos>,
powered: Vec<Pos>,
flipper_events: Vec<Pos>,
input: Vec<u8>,
input_index: usize,
output: Vec<u8>,
@ -28,7 +26,6 @@ impl Machine {
board: Board::new_empty(width, width),
marbles: Vec::new(),
powered: Vec::new(),
flipper_events: Vec::new(),
input,
input_index: 0,
output: Vec::new(),
@ -102,12 +99,115 @@ impl Machine {
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 {
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::Trigger(true)) => (),
_ => continue,
}
let Some(front_tile) = self.board.get_mut(front_pos) else {
continue;
};
// `machine`` is being powered, in direction `dir``
match machine {
PTile::Gate(_) => (), // 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::default()
&& 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::Bag => {
if front_tile == &Tile::default() {
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::Trigger(_state)) => (),
Some(Tile::Wire(_, _state)) => (),
_ => unreachable!(),
};
}
for &(pos, _val, _dir) in &new_marbles {
let Some(Tile::Open(OpenTile::Blank, claim)) = self.board.get_mut(pos) else {
unreachable!()
};
*state = false;
*claim = match claim {
Claim::Free => Claim::Claimed,
Claim::Claimed | Claim::Blocked => Claim::Blocked,
_ => unreachable!(),
}
}
// 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::Claimed)) = self.board.get_mut(pos) else {
continue;
};
self.board.set(pos, Tile::Marble { value, dir });
self.marbles.push(pos);
}
// 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::Trigger(state)) => *state = false,
Some(Tile::Wire(_, state)) => *state = false,
_ => (),
}
}
self.powered.clear();
@ -170,16 +270,14 @@ impl Machine {
};
if will_reverse_direction[i] {
*dir = dir.opposite();
} else {
if let DirInfluence::One(new_dir) = influenced_direction[i] {
*dir = new_dir;
}
} else if let DirInfluence::One(new_dir) = influenced_direction[i] {
*dir = new_dir;
}
}
let mut claim_positions = Vec::new();
// mark claims to figure out what spaces can be moved to
for &pos in &self.marbles {
for &pos in &self.marbles[..old_marbles] {
let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else {
unreachable!()
};
@ -189,51 +287,44 @@ impl Machine {
};
if let Tile::Open(_type, claim) = front_tile {
*claim = match claim {
MarbleTarget::Free => {
Claim::Free => {
claim_positions.push(front_pos);
MarbleTarget::Claimed
Claim::Claimed
}
MarbleTarget::ClaimedIndirect => MarbleTarget::Claimed,
MarbleTarget::BlockedIndirect => MarbleTarget::Claimed,
MarbleTarget::Claimed => MarbleTarget::Blocked,
MarbleTarget::Blocked => MarbleTarget::Blocked,
Claim::ClaimedIndirect => Claim::Claimed,
Claim::BlockedIndirect => Claim::Claimed,
Claim::Claimed => Claim::Blocked,
Claim::Blocked => Claim::Blocked,
};
} else {
let target_pos;
match front_tile {
Tile::Arrow(d) => {
target_pos = d.step(front_pos);
}
Tile::Mirror(m) => {
target_pos = m.new_dir(dir).step(front_pos);
}
Tile::Powerable(PTile::Trigger, _) => {
target_pos = dir.step(front_pos);
}
let target_pos = match front_tile {
Tile::Arrow(d) => d.step(front_pos),
Tile::Mirror(m) => m.new_dir(dir).step(front_pos),
Tile::Trigger(_) => 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 {
*claim = match claim {
MarbleTarget::Free => {
Claim::Free => {
claim_positions.push(front_pos);
MarbleTarget::ClaimedIndirect
Claim::ClaimedIndirect
}
MarbleTarget::ClaimedIndirect => MarbleTarget::BlockedIndirect,
MarbleTarget::BlockedIndirect => MarbleTarget::BlockedIndirect,
MarbleTarget::Claimed => MarbleTarget::Claimed,
MarbleTarget::Blocked => MarbleTarget::Blocked,
Claim::ClaimedIndirect => Claim::BlockedIndirect,
Claim::BlockedIndirect => Claim::BlockedIndirect,
Claim::Claimed => Claim::Claimed,
Claim::Blocked => Claim::Blocked,
};
}
}
}
let mut triggers_activated = Vec::new();
// let mut triggers_activated = Vec::new();
let mut removed_marbles = Vec::new();
// move marbles
for (i, pos) in self.marbles.iter_mut().enumerate() {
for (i, pos) in self.marbles[..old_marbles].iter_mut().enumerate() {
let Some(Tile::Marble { value, dir }) = self.board.get(*pos) else {
unreachable!()
};
@ -253,9 +344,9 @@ impl Machine {
};
if let Tile::Open(space_type, claim_state) = front_tile {
if *claim_state == MarbleTarget::Claimed {
if *claim_state == Claim::Claimed {
move_to(*space_type, front_pos, dir, &mut self.board);
} else if *claim_state != MarbleTarget::Free {
} 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(
@ -279,7 +370,7 @@ impl Machine {
new_dir = m.new_dir(dir);
target_pos = new_dir.step(front_pos);
}
Tile::Powerable(PTile::Trigger, _) => {
Tile::Trigger(_) => {
is_trigger = true;
target_pos = dir.step(front_pos);
}
@ -297,10 +388,10 @@ impl Machine {
let Some(target_tile) = self.board.get_mut(target_pos) else {
continue;
};
if let Tile::Open(space_type, MarbleTarget::ClaimedIndirect) = target_tile {
if let Tile::Open(space_type, Claim::ClaimedIndirect) = target_tile {
move_to(*space_type, target_pos, new_dir, &mut self.board);
if is_trigger {
triggers_activated.push(front_pos);
self.powered.push(front_pos);
}
}
}
@ -308,7 +399,7 @@ impl Machine {
for pos in claim_positions {
if let Some(Tile::Open(_, claim_state)) = self.board.get_mut(pos) {
*claim_state = MarbleTarget::Free;
*claim_state = Claim::Free;
}
}
@ -318,122 +409,102 @@ impl Machine {
self.marbles.swap_remove(i);
}
// process triggers
self.flipper_events.clear();
for pos in triggers_activated {
let Some(Tile::Powerable(PTile::Trigger, state)) = self.board.get_mut(pos) else {
// 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!()
};
self.powered.push(pos);
*state = true;
for dir in Direction::ALL {
self.propagate_power(dir, dir.step(pos));
}
}
// process flipped tiles
for &p in &self.flipper_events {
let Some(target) = self.board.get_mut(p) else {
continue;
};
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(),
_ => (),
};
}
}
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);
Tile::Trigger(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);
}
}
_ => (),
}
}
}
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);
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);
}
}
_ => (),
}
}
}
PTile::Flipper => {
self.flipper_events.push(front_pos);
}
PTile::Gate(gate) => {
Tile::Powerable(PTile::Gate(gate), state) => {
*state = true;
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();
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::Trigger(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 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));
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.powered.push(front_pos);
}
}
}
}
PTile::Trigger => (),
// state may be false if it was powered by a machine in earlier step
Tile::Powerable(_, state) => *state = true,
_ => {
dbg!(tile);
unreachable!()
}
}
i += 1;
}
}
}