add sub-tick debugging, add direction information to state of powerable tiles

This commit is contained in:
Crispy 2025-03-26 23:02:31 +01:00
parent 181f76a341
commit d3a3471fcb
8 changed files with 176 additions and 65 deletions

BIN
assets/debug_arrow_down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

BIN
assets/debug_arrow_left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
assets/debug_arrow_up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

View file

@ -536,6 +536,14 @@ impl Editor {
self.rotate_tool(rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT)); 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 self.sim_state == SimState::Editing {
if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) { if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) {
if rl.is_key_pressed(KeyboardKey::KEY_V) { if rl.is_key_pressed(KeyboardKey::KEY_V) {
@ -557,9 +565,20 @@ impl Editor {
self.source_board self.source_board
.draw(d, textures, self.view_offset, self.zoom); .draw(d, textures, self.view_offset, self.zoom);
} else { } else {
self.machine if self.machine.debug_subticks.is_empty() {
.board() self.machine
.draw(d, textures, self.view_offset, self.zoom); .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 { if self.draw_overlay {
self.machine self.machine
.draw_marble_values(d, textures, self.view_offset, self.zoom); .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.step_time as usize, (260, 42), 9, 1);
draw_usize(d, textures, self.max_step_time as usize, (260, 60), 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); d.draw_text("input:", 603, 8, 10, Color::WHITE);
if simple_button((d, &self.mouse), 600, 20, 35, 15) { if simple_button((d, &self.mouse), 600, 20, 35, 15) {
@ -1316,11 +1341,14 @@ impl Editor {
Tool::None | Tool::Erase | Tool::SelectArea(_) => (), Tool::None | Tool::Erase | Tool::SelectArea(_) => (),
Tool::SetTile(tile) => self.set_tile(pos, tile), Tool::SetTile(tile) => self.set_tile(pos, tile),
Tool::Math => { 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( Tool::Comparator => self.set_tile(
pos, 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::Wire => self.set_tile(pos, Tile::Wire(self.tool_wire, false)),
Tool::Arrow => self.set_tile(pos, Tile::Arrow(self.tool_arrow)), Tool::Arrow => self.set_tile(pos, Tile::Arrow(self.tool_arrow)),

View file

@ -17,6 +17,14 @@ pub struct Machine {
input_index: usize, input_index: usize,
output: Vec<u8>, output: Vec<u8>,
steps: usize, steps: usize,
pub subtick_index: usize,
pub debug_subticks: Vec<DebugSubTick>,
}
#[derive(Debug)]
pub struct DebugSubTick {
pub board: Board,
pub pos: Option<Pos>,
} }
impl Machine { impl Machine {
@ -29,6 +37,8 @@ impl Machine {
input_index: 0, input_index: 0,
output: Vec::new(), output: Vec::new(),
steps: 0, steps: 0,
subtick_index: 0,
debug_subticks: Vec::new(),
} }
} }
@ -37,6 +47,8 @@ impl Machine {
self.input_index = 0; self.input_index = 0;
self.output.clear(); self.output.clear();
self.powered.clear(); self.powered.clear();
self.debug_subticks.clear();
self.subtick_index = 0;
} }
pub fn set_board(&mut self, board: Board) { pub fn set_board(&mut self, board: Board) {
@ -97,6 +109,15 @@ impl Machine {
pub fn step(&mut self) { pub fn step(&mut self) {
self.steps += 1; 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(); let old_marbles = self.marbles.len();
@ -104,27 +125,24 @@ impl Machine {
// activate all powered machines // activate all powered machines
for &pos in &self.powered { for &pos in &self.powered {
match self.board.get_mut(pos) { match self.board.get_mut(pos) {
Some(Tile::Powerable(machine, state)) => { Some(Tile::Powerable(PTile::Comparator(_), board_power_state)) => {
*state = false; // 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; let machine = *machine;
for dir in Direction::ALL { for dir in Direction::ALL {
let front_pos = dir.step(pos); if !state.get_dir(dir) {
let source_pos = dir.opposite().step(pos); continue;
match self.board.get(source_pos) {
Some(Tile::Wire(wiretype, true)) => {
if !wiretype.has_output(dir) {
continue;
}
}
Some(Tile::Button(true)) => (),
_ => continue,
} }
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get_mut(front_pos) else { let Some(front_tile) = self.board.get_mut(front_pos) else {
continue; continue;
}; };
// `machine`` is being powered, in direction `dir`` // `machine` is being powered, in direction `dir`
match machine { match machine {
PTile::Comparator(_) => (), // handled at the power propagation stage (end of step)
PTile::Math(op) => { PTile::Math(op) => {
if front_tile.is_blank() { if front_tile.is_blank() {
let pos_a = dir.left().step(pos); let pos_a = dir.left().step(pos);
@ -169,24 +187,16 @@ impl Machine {
_ => (), _ => (),
}; };
} }
PTile::Comparator(_) => unreachable!(),
} }
} }
} }
Some(Tile::Button(_state)) => (), Some(Tile::Button(state) | Tile::Wire(_, state)) => {
Some(Tile::Wire(_, _state)) => (), *state = false;
_ => unreachable!(), }
_ => 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(); self.powered.clear();
if self.marbles.is_empty() { if self.marbles.is_empty() {
@ -199,7 +209,6 @@ impl Machine {
One(Direction), One(Direction),
Multiple, Multiple,
} }
// #### find all direct bounces #### // #### find all direct bounces ####
let mut will_reverse_direction = vec![false; self.marbles.len()]; let mut will_reverse_direction = vec![false; self.marbles.len()];
// todo store in tile to remove search through self.marbles // todo store in tile to remove search through self.marbles
@ -408,8 +417,8 @@ impl Machine {
let target_pos = dir.step(pos); let target_pos = dir.step(pos);
match self.board.get_mut(target_pos) { match self.board.get_mut(target_pos) {
Some(Tile::Powerable(_, state)) => { Some(Tile::Powerable(_, state)) => {
if !*state { if !state.get_dir(dir) {
*state = true; state.add_dir(dir);
self.powered.push(target_pos); 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) => { Tile::Wire(wiretype, state) => {
*state = true; *state = true;
@ -430,8 +444,8 @@ impl Machine {
let target_pos = dir.step(pos); let target_pos = dir.step(pos);
match self.board.get_mut(target_pos) { match self.board.get_mut(target_pos) {
Some(Tile::Powerable(_, state)) => { Some(Tile::Powerable(_, state)) => {
if !*state { if !state.get_dir(*dir) {
*state = true; state.add_dir(*dir);
self.powered.push(target_pos); 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) => { Tile::Powerable(PTile::Comparator(comp), state) => {
*state = true;
let comp = *comp; let comp = *comp;
let state = *state;
for dir in Direction::ALL { for dir in Direction::ALL {
let front_pos = dir.step(pos); if !state.get_dir(dir) {
let source_pos = dir.opposite().step(pos); continue;
match self.board.get(source_pos) {
Some(Tile::Wire(wiretype, true)) => {
if !wiretype.has_output(dir) {
continue;
}
}
Some(Tile::Button(true)) => (),
_ => continue,
} }
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get_mut(front_pos) else { let Some(front_tile) = self.board.get_mut(front_pos) else {
continue; continue;
}; };
@ -477,13 +489,37 @@ impl Machine {
Comparison::NotEqual => val_a != val_b, Comparison::NotEqual => val_a != val_b,
}; };
if result { 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); dbg!(tile);
unreachable!() unreachable!()
@ -491,5 +527,9 @@ impl Machine {
} }
i += 1; i += 1;
} }
#[cfg(debug_assertions)]
{
self.subtick_index = self.debug_subticks.len() - 1;
}
} }
} }

View file

@ -214,6 +214,16 @@ impl Board {
} }
let texture = textures.get(texname); let texture = textures.get(texname);
draw_scaled_texture(d, texture, px, py, scale); 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 { } else {
d.draw_rectangle(px, py, tile_size, tile_size, Color::new(0, 0, 0, 80)); d.draw_rectangle(px, py, tile_size, tile_size, Color::new(0, 0, 0, 80));
} }

View file

@ -11,7 +11,7 @@ pub enum Tile {
Arrow(Direction), Arrow(Direction),
Button(bool), Button(bool),
Wire(WireType, bool), Wire(WireType, bool),
Powerable(PTile, bool), Powerable(PTile, Power),
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -38,6 +38,11 @@ pub enum PTile {
IO, IO,
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Power {
directions: u8,
}
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum MirrorType { pub enum MirrorType {
Forward, Forward,
@ -94,18 +99,18 @@ impl Tile {
'v' => Tile::Arrow(Direction::Down), 'v' => Tile::Arrow(Direction::Down),
'<' => Tile::Arrow(Direction::Left), '<' => Tile::Arrow(Direction::Left),
'>' => Tile::Arrow(Direction::Right), '>' => Tile::Arrow(Direction::Right),
'=' => Tile::Powerable(PTile::Comparator(Comparison::Equal), false), '=' => Tile::Powerable(PTile::Comparator(Comparison::Equal), Power::OFF),
'!' => Tile::Powerable(PTile::Comparator(Comparison::NotEqual), false), '!' => Tile::Powerable(PTile::Comparator(Comparison::NotEqual), Power::OFF),
'L' => Tile::Powerable(PTile::Comparator(Comparison::LessThan), false), 'L' => Tile::Powerable(PTile::Comparator(Comparison::LessThan), Power::OFF),
'G' => Tile::Powerable(PTile::Comparator(Comparison::GreaterThan), false), 'G' => Tile::Powerable(PTile::Comparator(Comparison::GreaterThan), Power::OFF),
'I' | 'P' => Tile::Powerable(PTile::IO, false), 'I' | 'P' => Tile::Powerable(PTile::IO, Power::OFF),
'F' => Tile::Powerable(PTile::Flipper, false), 'F' => Tile::Powerable(PTile::Flipper, Power::OFF),
'A' => Tile::Powerable(PTile::Math(MathOp::Add), false), 'A' => Tile::Powerable(PTile::Math(MathOp::Add), Power::OFF),
'S' => Tile::Powerable(PTile::Math(MathOp::Sub), false), 'S' => Tile::Powerable(PTile::Math(MathOp::Sub), Power::OFF),
'M' => Tile::Powerable(PTile::Math(MathOp::Mul), false), 'M' => Tile::Powerable(PTile::Math(MathOp::Mul), Power::OFF),
'D' => Tile::Powerable(PTile::Math(MathOp::Div), false), 'D' => Tile::Powerable(PTile::Math(MathOp::Div), Power::OFF),
'R' => Tile::Powerable(PTile::Math(MathOp::Rem), false), 'R' => Tile::Powerable(PTile::Math(MathOp::Rem), Power::OFF),
'B' => Tile::Powerable(PTile::Silo, false), 'B' => Tile::Powerable(PTile::Silo, Power::OFF),
d @ '0'..='9' => Tile::Open(OpenTile::Digit(d as u8 - b'0'), Claim::Free), d @ '0'..='9' => Tile::Open(OpenTile::Digit(d as u8 - b'0'), Claim::Free),
'#' => Tile::Block, '#' => Tile::Block,
_ => Tile::Open(OpenTile::Blank, Claim::Free), _ => Tile::Open(OpenTile::Blank, Claim::Free),
@ -200,7 +205,7 @@ impl Tile {
wire.texture_name_off() wire.texture_name_off()
} }
Tile::Powerable(tile, state) => { Tile::Powerable(tile, state) => {
if state { if state.any() {
return match tile { return match tile {
PTile::Comparator(comp) => comp.texture_name_on(), PTile::Comparator(comp) => comp.texture_name_on(),
PTile::Math(math_op) => math_op.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 { pub const fn arrow_tile_human_name(self) -> &'static str {
match self { match self {
Direction::Up => "Up Arrow", Direction::Up => "Up Arrow",
@ -517,3 +531,22 @@ impl Claim {
was_free 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)
}
}