diff --git a/assets/debug_arrow_down.png b/assets/debug_arrow_down.png new file mode 100644 index 0000000..8c8b802 Binary files /dev/null and b/assets/debug_arrow_down.png differ diff --git a/assets/debug_arrow_left.png b/assets/debug_arrow_left.png new file mode 100644 index 0000000..eec8a79 Binary files /dev/null and b/assets/debug_arrow_left.png differ diff --git a/assets/debug_arrow_right.png b/assets/debug_arrow_right.png new file mode 100644 index 0000000..e1ea79c Binary files /dev/null and b/assets/debug_arrow_right.png differ diff --git a/assets/debug_arrow_up.png b/assets/debug_arrow_up.png new file mode 100644 index 0000000..909b4b8 Binary files /dev/null and b/assets/debug_arrow_up.png differ diff --git a/src/editor.rs b/src/editor.rs index 548f201..644ffb4 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -536,6 +536,14 @@ impl Editor { self.rotate_tool(rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT)); } + if rl.is_key_pressed(KeyboardKey::KEY_N) { + self.machine.subtick_index += 1; + self.machine.subtick_index %= self.machine.debug_subticks.len(); + } + if rl.is_key_pressed(KeyboardKey::KEY_M) { + self.machine.subtick_index = self.machine.subtick_index.saturating_sub(1); + } + if self.sim_state == SimState::Editing { if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) { if rl.is_key_pressed(KeyboardKey::KEY_V) { @@ -557,9 +565,20 @@ impl Editor { self.source_board .draw(d, textures, self.view_offset, self.zoom); } else { - self.machine - .board() - .draw(d, textures, self.view_offset, self.zoom); + if self.machine.debug_subticks.is_empty() { + self.machine + .board() + .draw(d, textures, self.view_offset, self.zoom); + } else { + let subframe = &self.machine.debug_subticks[self.machine.subtick_index]; + subframe + .board + .draw(d, textures, self.view_offset, self.zoom); + if let Some(pos) = subframe.pos { + let p = self.pos_to_screen(pos.to_vec()); + d.draw_texture_ex(textures.get("selection"), p, 0., self.zoom, Color::ORANGE); + } + } if self.draw_overlay { self.machine .draw_marble_values(d, textures, self.view_offset, self.zoom); @@ -900,6 +919,12 @@ impl Editor { draw_usize(d, textures, self.step_time as usize, (260, 42), 9, 1); draw_usize(d, textures, self.max_step_time as usize, (260, 60), 9, 1); + #[cfg(debug_assertions)] + { + draw_usize(d, textures, self.machine.subtick_index, (260, 80), 9, 1); + let subtick_count = self.machine.debug_subticks.len(); + draw_usize(d, textures, subtick_count, (260, 100), 9, 1); + } d.draw_text("input:", 603, 8, 10, Color::WHITE); if simple_button((d, &self.mouse), 600, 20, 35, 15) { @@ -1316,11 +1341,14 @@ impl Editor { Tool::None | Tool::Erase | Tool::SelectArea(_) => (), Tool::SetTile(tile) => self.set_tile(pos, tile), Tool::Math => { - self.set_tile(pos, Tile::Powerable(PTile::Math(self.tool_math), false)); + self.set_tile( + pos, + Tile::Powerable(PTile::Math(self.tool_math), Power::OFF), + ); } Tool::Comparator => self.set_tile( pos, - Tile::Powerable(PTile::Comparator(self.tool_comparator), false), + Tile::Powerable(PTile::Comparator(self.tool_comparator), Power::OFF), ), Tool::Wire => self.set_tile(pos, Tile::Wire(self.tool_wire, false)), Tool::Arrow => self.set_tile(pos, Tile::Arrow(self.tool_arrow)), diff --git a/src/marble_engine.rs b/src/marble_engine.rs index bcaf731..36ebf83 100644 --- a/src/marble_engine.rs +++ b/src/marble_engine.rs @@ -17,6 +17,14 @@ pub struct Machine { input_index: usize, output: Vec, steps: usize, + pub subtick_index: usize, + pub debug_subticks: Vec, +} + +#[derive(Debug)] +pub struct DebugSubTick { + pub board: Board, + pub pos: Option, } impl Machine { @@ -29,6 +37,8 @@ impl Machine { input_index: 0, output: Vec::new(), steps: 0, + subtick_index: 0, + debug_subticks: Vec::new(), } } @@ -37,6 +47,8 @@ impl Machine { self.input_index = 0; self.output.clear(); self.powered.clear(); + self.debug_subticks.clear(); + self.subtick_index = 0; } pub fn set_board(&mut self, board: Board) { @@ -97,6 +109,15 @@ impl Machine { pub fn step(&mut self) { self.steps += 1; + #[cfg(debug_assertions)] + { + self.subtick_index = 0; + self.debug_subticks.clear(); + self.debug_subticks.push(DebugSubTick { + board: self.board.clone(), + pos: None, + }); + } let old_marbles = self.marbles.len(); @@ -104,27 +125,24 @@ impl Machine { // activate all powered machines for &pos in &self.powered { match self.board.get_mut(pos) { - Some(Tile::Powerable(machine, state)) => { - *state = false; + Some(Tile::Powerable(PTile::Comparator(_), board_power_state)) => { + // already handled at the power propagation stage (end of sim step) + *board_power_state = Power::OFF; + } + Some(Tile::Powerable(machine, board_power_state)) => { + let state = *board_power_state; + *board_power_state = Power::OFF; 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, + if !state.get_dir(dir) { + continue; } + let front_pos = dir.step(pos); let Some(front_tile) = self.board.get_mut(front_pos) else { continue; }; - // `machine`` is being powered, in direction `dir`` + // `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); @@ -169,24 +187,16 @@ impl Machine { _ => (), }; } + PTile::Comparator(_) => unreachable!(), } } } - Some(Tile::Button(_state)) => (), - Some(Tile::Wire(_, _state)) => (), - _ => unreachable!(), + Some(Tile::Button(state) | Tile::Wire(_, state)) => { + *state = false; + } + _ => unreachable!("non-powerable tile at {pos:?} in self.powered"), }; } - - // 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() { @@ -199,7 +209,6 @@ impl Machine { 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 @@ -408,8 +417,8 @@ impl Machine { let target_pos = dir.step(pos); match self.board.get_mut(target_pos) { Some(Tile::Powerable(_, state)) => { - if !*state { - *state = true; + if !state.get_dir(dir) { + state.add_dir(dir); self.powered.push(target_pos); } } @@ -423,6 +432,11 @@ impl Machine { _ => (), } } + #[cfg(debug_assertions)] + self.debug_subticks.push(DebugSubTick { + board: self.board.clone(), + pos: Some(pos), + }); } Tile::Wire(wiretype, state) => { *state = true; @@ -430,8 +444,8 @@ impl Machine { let target_pos = dir.step(pos); match self.board.get_mut(target_pos) { Some(Tile::Powerable(_, state)) => { - if !*state { - *state = true; + if !state.get_dir(*dir) { + state.add_dir(*dir); self.powered.push(target_pos); } } @@ -445,22 +459,20 @@ impl Machine { _ => (), } } + #[cfg(debug_assertions)] + self.debug_subticks.push(DebugSubTick { + board: self.board.clone(), + pos: Some(pos), + }); } Tile::Powerable(PTile::Comparator(comp), state) => { - *state = true; let comp = *comp; + let state = *state; 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, + if !state.get_dir(dir) { + continue; } + let front_pos = dir.step(pos); let Some(front_tile) = self.board.get_mut(front_pos) else { continue; }; @@ -477,13 +489,37 @@ impl Machine { Comparison::NotEqual => val_a != val_b, }; if result { - self.powered.push(front_pos); + match self.board.get_mut(front_pos) { + Some(Tile::Powerable(_, state)) => { + if !state.get_dir(dir) { + state.add_dir(dir); + self.powered.push(front_pos); + } + } + Some(Tile::Wire(_, state)) => { + if !*state { + *state = true; + self.powered.push(front_pos); + } + } + _ => (), + } } } } + #[cfg(debug_assertions)] + self.debug_subticks.push(DebugSubTick { + board: self.board.clone(), + pos: Some(pos), + }); + } + Tile::Powerable(_, _state) => { + #[cfg(debug_assertions)] + self.debug_subticks.push(DebugSubTick { + board: self.board.clone(), + pos: Some(pos), + }); } - // state may be false if it was powered by a machine in earlier step - Tile::Powerable(_, state) => *state = true, _ => { dbg!(tile); unreachable!() @@ -491,5 +527,9 @@ impl Machine { } i += 1; } + #[cfg(debug_assertions)] + { + self.subtick_index = self.debug_subticks.len() - 1; + } } } diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs index e6d008f..86f153e 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/board.rs @@ -214,6 +214,16 @@ impl Board { } let texture = textures.get(texname); draw_scaled_texture(d, texture, px, py, scale); + #[cfg(debug_assertions)] + // TODO: some in-game option to show power direction + if let Tile::Powerable(_, state) = &tile { + for dir in Direction::ALL { + if state.get_dir(dir) { + let texture = textures.get(dir.debug_arrow_texture_name()); + draw_scaled_texture(d, texture, px, py, scale); + } + } + } } else { d.draw_rectangle(px, py, tile_size, tile_size, Color::new(0, 0, 0, 80)); } diff --git a/src/marble_engine/tile.rs b/src/marble_engine/tile.rs index 568411d..798c51c 100644 --- a/src/marble_engine/tile.rs +++ b/src/marble_engine/tile.rs @@ -11,7 +11,7 @@ pub enum Tile { Arrow(Direction), Button(bool), Wire(WireType, bool), - Powerable(PTile, bool), + Powerable(PTile, Power), } #[derive(Debug, Clone, Copy, PartialEq)] @@ -38,6 +38,11 @@ pub enum PTile { IO, } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Power { + directions: u8, +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum MirrorType { Forward, @@ -94,18 +99,18 @@ impl Tile { 'v' => Tile::Arrow(Direction::Down), '<' => Tile::Arrow(Direction::Left), '>' => Tile::Arrow(Direction::Right), - '=' => Tile::Powerable(PTile::Comparator(Comparison::Equal), false), - '!' => Tile::Powerable(PTile::Comparator(Comparison::NotEqual), false), - 'L' => Tile::Powerable(PTile::Comparator(Comparison::LessThan), false), - 'G' => Tile::Powerable(PTile::Comparator(Comparison::GreaterThan), false), - 'I' | 'P' => Tile::Powerable(PTile::IO, 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::Silo, false), + '=' => Tile::Powerable(PTile::Comparator(Comparison::Equal), Power::OFF), + '!' => Tile::Powerable(PTile::Comparator(Comparison::NotEqual), Power::OFF), + 'L' => Tile::Powerable(PTile::Comparator(Comparison::LessThan), Power::OFF), + 'G' => Tile::Powerable(PTile::Comparator(Comparison::GreaterThan), Power::OFF), + 'I' | 'P' => Tile::Powerable(PTile::IO, Power::OFF), + 'F' => Tile::Powerable(PTile::Flipper, Power::OFF), + 'A' => Tile::Powerable(PTile::Math(MathOp::Add), Power::OFF), + 'S' => Tile::Powerable(PTile::Math(MathOp::Sub), Power::OFF), + 'M' => Tile::Powerable(PTile::Math(MathOp::Mul), Power::OFF), + 'D' => Tile::Powerable(PTile::Math(MathOp::Div), Power::OFF), + 'R' => Tile::Powerable(PTile::Math(MathOp::Rem), Power::OFF), + 'B' => Tile::Powerable(PTile::Silo, Power::OFF), d @ '0'..='9' => Tile::Open(OpenTile::Digit(d as u8 - b'0'), Claim::Free), '#' => Tile::Block, _ => Tile::Open(OpenTile::Blank, Claim::Free), @@ -200,7 +205,7 @@ impl Tile { wire.texture_name_off() } Tile::Powerable(tile, state) => { - if state { + if state.any() { return match tile { PTile::Comparator(comp) => comp.texture_name_on(), PTile::Math(math_op) => math_op.texture_name_on(), @@ -270,6 +275,15 @@ impl Direction { } } + pub const fn debug_arrow_texture_name(self) -> &'static str { + match self { + Direction::Up => "debug_arrow_up", + Direction::Down => "debug_arrow_down", + Direction::Left => "debug_arrow_left", + Direction::Right => "debug_arrow_right", + } + } + pub const fn arrow_tile_human_name(self) -> &'static str { match self { Direction::Up => "Up Arrow", @@ -517,3 +531,22 @@ impl Claim { was_free } } + +impl Power { + pub const OFF: Self = Self { directions: 0 }; + + #[inline] + pub fn any(self) -> bool { + self.directions != 0 + } + + #[inline] + pub fn get_dir(self, dir: Direction) -> bool { + self.directions & (1 << (dir as u8)) != 0 + } + + #[inline] + pub fn add_dir(&mut self, dir: Direction) { + self.directions |= 1 << (dir as u8) + } +}