fix input being consumed even when marble creation is blocked; made multiple simultaneous inputs use the same value

This commit is contained in:
Crispy 2025-04-20 20:35:22 +02:00
parent f0b878e93d
commit 80ab3b676e
4 changed files with 33 additions and 11 deletions

View file

@ -2,7 +2,11 @@
Game store page: https://crispypin.itch.io/marble-machinations Game store page: https://crispypin.itch.io/marble-machinations
## [unreleased] ## [unreleased]
### changed
- when multiple I/O silos (or multiple directions of one) are activated in the same tick, they will all output the same value instead of pulling input in an arbitrary order
### fixed ### fixed
- input bytes are consumed even if the marble can't be created because another one was taking its place
- crash when saving config if no user dir exists - crash when saving config if no user dir exists
- keybindings activated even when typing in a text field, making especially renaming blueprints difficult - keybindings activated even when typing in a text field, making especially renaming blueprints difficult
- after removing a binding that was a superset of another, the remaining one did not stop being blocked by the removed ones additional modifiers until another binding was added or edited - after removing a binding that was a superset of another, the remaining one did not stop being blocked by the removed ones additional modifiers until another binding was added or edited

View file

@ -9,7 +9,6 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
- engine tests - engine tests
- blag post about marble movement logic? - blag post about marble movement logic?
### bugs ### bugs
- input tiles still consume input when their marble creation is blocked
- Shift+A and A+Shift conflict - Shift+A and A+Shift conflict
- rigt side grid rendering broken - rigt side grid rendering broken
### features ### features

View file

@ -21,7 +21,7 @@ pub struct Machine {
// used across steps // used across steps
powered: Vec<Pos>, powered: Vec<Pos>,
// used within steps // used within steps
new_marbles: Vec<(Pos, MarbleValue, Direction)>, new_marbles: Vec<(Pos, MarbleValue, Direction, bool)>,
influenced_direction: Vec<(bool, DirInfluence)>, influenced_direction: Vec<(bool, DirInfluence)>,
claim_positions: Vec<Pos>, claim_positions: Vec<Pos>,
removed_marbles: Vec<usize>, removed_marbles: Vec<usize>,
@ -216,20 +216,19 @@ impl Machine {
MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(), MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(),
MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(), MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(),
}; };
self.new_marbles.push((front_pos, value, dir)); self.new_marbles.push((front_pos, value, dir, false));
} }
} }
PTile::IO => { PTile::IO => {
if front_tile == &Tile::BLANK && self.input_index < self.input.len() if front_tile == &Tile::BLANK && self.input_index < self.input.len()
{ {
let value = self.input[self.input_index] as MarbleValue; let value = self.input[self.input_index] as MarbleValue;
self.input_index += 1; self.new_marbles.push((front_pos, value, dir, true));
self.new_marbles.push((front_pos, value, dir));
} }
} }
PTile::Silo => { PTile::Silo => {
if front_tile == &Tile::BLANK { if front_tile == &Tile::BLANK {
self.new_marbles.push((front_pos, 0, dir)); self.new_marbles.push((front_pos, 0, dir, false));
} }
} }
PTile::Flipper => { PTile::Flipper => {
@ -321,9 +320,9 @@ impl Machine {
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)] #[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
fn step_prepare_creating_marbles(&mut self) { fn step_prepare_creating_marbles(&mut self) {
// #### new marbles #### // #### new marbles ####
// self.claim_positions.clear(); // already drained self.claim_positions.clear(); // already drained
// prepare creating the new marbles // prepare creating the new marbles
for &(pos, _val, _dir) in &self.new_marbles { for &(pos, _val, _dir, _is_input) in &self.new_marbles {
let Some(Tile::Open(OpenTile::Blank, claim)) = self.grid.get_mut(pos) else { let Some(Tile::Open(OpenTile::Blank, claim)) = self.grid.get_mut(pos) else {
unreachable!() unreachable!()
}; };
@ -336,15 +335,20 @@ impl Machine {
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)] #[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
fn step_create_marbles(&mut self) { fn step_create_marbles(&mut self) {
// create new marbles // create new marbles
let mut advance_input = false;
// new marbles are past old_marbles index, so will not move this step // new marbles are past old_marbles index, so will not move this step
for (pos, value, dir) in self.new_marbles.drain(..) { for (pos, value, dir, is_input) in self.new_marbles.drain(..) {
let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.grid.get_mut(pos) let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.grid.get_mut(pos)
else { else {
continue; continue;
}; };
advance_input |= is_input;
self.grid.set(pos, Tile::Marble { value, dir }); self.grid.set(pos, Tile::Marble { value, dir });
self.marbles.push(pos); self.marbles.push(pos);
} }
if advance_input {
self.input_index += 1;
}
} }
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)] #[cfg_attr(feature = "inline_less", inline(never), no_mangle)]

View file

@ -1,8 +1,9 @@
use marble_machinations::marble_engine::{grid::Grid, Machine}; use marble_machinations::marble_engine::{grid::Grid, Machine};
fn no_input_test(steps: usize, output: &[u8], grid: &str) { fn do_test(steps: usize, input: &[u8], output: &[u8], grid: &str) {
let mut engine = Machine::new_empty(); let mut engine = Machine::new_empty();
engine.set_grid(Grid::from_ascii(grid)); engine.set_grid(Grid::from_ascii(grid));
engine.set_input(input.to_owned());
for _ in 0..(steps - 1) { for _ in 0..(steps - 1) {
engine.step(); engine.step();
} }
@ -11,11 +12,25 @@ fn no_input_test(steps: usize, output: &[u8], grid: &str) {
assert_eq!(engine.output(), output, "expected output"); assert_eq!(engine.output(), output, "expected output");
} }
fn no_input_test(steps: usize, output: &[u8], grid: &str) {
do_test(steps, &[], output, grid)
}
#[test] #[test]
fn creating_marbles_cause_indirect_claim() { fn creating_marbles_cause_indirect_claim() {
no_input_test(3, &[1], " o \n|-*-|\n| 1 |\n-B B-\n I\n"); no_input_test(3, &[1], " o \n|-*-|\n| 1 |\n-B B-\n I\n");
} }
#[test]
fn bug_input_consumed_when_marble_creation_blocked() {
do_test(
4,
&[1, 2, 3],
&[1, 1],
"# +-+\nIIo| I\n *+B\nII\n++*\n# #\n",
);
}
#[test] #[test]
fn bug_overlapping_marble_creation_blocks_tile_forever() { fn bug_overlapping_marble_creation_blocks_tile_forever() {
no_input_test( no_input_test(