Compare commits
3 commits
7574ec20f5
...
1d3841fb6d
Author | SHA1 | Date | |
---|---|---|---|
1d3841fb6d | |||
d3a3471fcb | |||
181f76a341 |
9 changed files with 189 additions and 65 deletions
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Marble Machinations Change Log
|
||||
|
||||
## [Unreleased]
|
||||
- added (dev): sub-tick visualisation in debug mode
|
||||
- fixed: equal comparator did not output one of two incoming signals in some cases, depending on wire length and update order
|
||||
- changed: comparators can now power other tiles without a wire between, including other comparators
|
||||
- changed: directly moving marbles (to adjactent tile without anything between) now have priority over new marbles being created, instead of the two events cancelling each other
|
||||
|
||||
## v0.2.1 - 2025-03-14
|
||||
- added: "Simple comparison" level
|
||||
- fixed: phantom marble (empty tile causing other marbles to bounce away) appearing after multiple machines tried to output to the same location at once
|
||||
|
||||
## v0.2.0 - 2024-12-24
|
BIN
assets/debug_arrow_down.png
Normal file
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
BIN
assets/debug_arrow_left.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 B |
BIN
assets/debug_arrow_right.png
Normal file
BIN
assets/debug_arrow_right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 105 B |
BIN
assets/debug_arrow_up.png
Normal file
BIN
assets/debug_arrow_up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 104 B |
|
@ -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)),
|
||||
|
|
|
@ -17,6 +17,14 @@ pub struct Machine {
|
|||
input_index: usize,
|
||||
output: Vec<u8>,
|
||||
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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue