feat: add Ctrl+Shift+0 crazy mode with ten extra balls
Pressing Ctrl+Shift+0 during a game drops ten balls at fixed points spread over the open playfield (CRAZY_BALL_POSITIONS): two near the top bumpers, a row in the middle, and four above the slingshots. Repeated presses add another ten each time. The original engine is a faithful two-slot reconstruction (primary and secondary ball) whose claw, lock-hole, special-hole, and effect-seven rules all assume at most two balls. Rather than generalising that engine, crazy balls live in a separate Game::crazy_balls list with a reduced step: - gravity, speed clamp, and the same static record ranges as the main scan (walls, passive circles, bumpers, flippers); - wall and bumper rules, so they score, light banks, and kick normally; - flipper kicks and nudge impulses; - no sensors, holes, magnets, claw, or ball-to-ball collisions. A crazy ball that leaves the board, hits the drain, or comes to rest on the shooter stop is removed. When the primary ball drains (and no secondary or special respawn takes priority), a crazy ball is promoted into the primary slot, so the turn only ends once every ball is gone. The promoted ball then runs the full original rules. Because the mode is effectively a cheat, Game records that it was used and the browser build skips the online high-score submission for that game; the local table still records the score. The App suppresses the nudge that releasing Shift would otherwise fire after the combo. Ctrl also raises the left flipper while held, which is left as is. Browsers may reserve Ctrl+Shift+0 for themselves. A new `crazy` simulation scenario starts a game with crazy balls. Test Plan: - cargo test: new tests check every drop point is clear of all static records, that the balls move, score, and eventually drain without costing a ball, that a primary drain promotes a crazy ball, and that a finished game rejects the mode. - Manual: start a game, press Ctrl+Shift+0, release Shift, and confirm ten balls appear and no nudge/tilt sound plays.
This commit is contained in:
1 parent
3fc1096500
commit
139e641005
6 files changed
+265
-6
No files matched your search
@@ -8,6 +8,12 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Crazy mode: Ctrl+Shift+0 drops ten extra balls onto the playfield. They bounce
|
||||
and score off walls, bumpers, and flippers, replace the main ball when it
|
||||
drains, and keep the game's scores off the online high-score table.
|
||||
|
||||
## [1.2.1] - 2026-09-01
|
||||
|
||||
### Fixed
|
||||
|
||||
+8
-1
@@ -60,7 +60,7 @@ cargo run -- --simulate claw-6 --at 0.15 \
|
||||
Use `--step N` instead of `--at SECONDS` to reproduce one exact update. The
|
||||
available scenarios are `loading`, `attract`, `highscores`, `name-entry`,
|
||||
`effect-7`, `autoplay`, `launcher`, `flippers`, `panel`, `targets`, `claw-1`, `claw-6`,
|
||||
`claw-7`, and `claw-18`; `--seed N` fixes random choices.
|
||||
`claw-7`, `claw-18`, and `crazy`; `--seed N` fixes random choices.
|
||||
`autoplay` charges each ball and operates the flippers from live ball position
|
||||
for long end-to-end validation runs.
|
||||
|
||||
@@ -77,6 +77,13 @@ for long end-to-end validation runs.
|
||||
| Settings | `TDKPIN.INI` before launch | F10 opens the portable settings screen |
|
||||
| High scores | Automatic when a player finishes | F9 opens the portable table viewer |
|
||||
| Sound | F12 | - |
|
||||
| Crazy mode: drop 10 extra balls | - | Ctrl+Shift+0 |
|
||||
|
||||
Crazy mode is a modern addition. Its balls bounce off walls, bumpers, and
|
||||
flippers and score through them, but skip sensors, holes, and the claw. When
|
||||
the main ball drains, a crazy ball takes its place before the turn ends. Scores
|
||||
from games that used crazy mode are not submitted to the online high-score
|
||||
table.
|
||||
|
||||
The five original speed choices remain available in settings. Physics now uses
|
||||
the original invariant 100 Hz millipixel substep, batched as 5/4/3/2/1 steps on
|
||||
|
||||
@@ -88,6 +88,8 @@ decoded, build-ready subset; it does not replace that evidence archive.
|
||||
- `app.rs`: screen flow, controls, rendering, audio dispatch, and overlays;
|
||||
- `assets.rs`: compile-time original asset embedding;
|
||||
- `game.rs`: fixed-step game state, recovered walls, rules, and scoring;
|
||||
- crazy mode (a modern addition): extra balls outside the original two ball
|
||||
slots, with static collisions, wall/bumper rules, and flipper kicks only;
|
||||
- `geometry.rs`: segment/circle collision primitives;
|
||||
- `original_physics.rs`: millipixel integration and recovered type-2 responses;
|
||||
- `real48.rs`: bit-exact Borland six-byte arithmetic, conversion, and square root;
|
||||
|
||||
+31
-1
@@ -223,6 +223,8 @@ pub struct App {
|
||||
name: String,
|
||||
pending_score: u32,
|
||||
attract: AttractAnimation,
|
||||
/// Ctrl+Shift+0 holds Shift, whose release would otherwise nudge.
|
||||
crazy_shift_nudge_suppressed: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -251,6 +253,7 @@ impl App {
|
||||
name: String::new(),
|
||||
pending_score: 0,
|
||||
attract: AttractAnimation::default(),
|
||||
crazy_shift_nudge_suppressed: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +401,21 @@ impl App {
|
||||
|| is_key_down(KeyCode::Enter)
|
||||
|| is_key_down(KeyCode::D)
|
||||
|| is_key_down(KeyCode::Right);
|
||||
let nudge = if is_key_released(KeyCode::LeftShift) {
|
||||
let shift_down = is_key_down(KeyCode::LeftShift) || is_key_down(KeyCode::RightShift);
|
||||
let ctrl_down = is_key_down(KeyCode::LeftControl) || is_key_down(KeyCode::RightControl);
|
||||
if ctrl_down
|
||||
&& shift_down
|
||||
&& is_key_pressed(KeyCode::Key0)
|
||||
&& self.game.as_mut().is_some_and(Game::add_crazy_balls)
|
||||
{
|
||||
self.crazy_shift_nudge_suppressed = true;
|
||||
self.assets.play(2007, self.sounds_enabled);
|
||||
}
|
||||
let nudge = if self.crazy_shift_nudge_suppressed
|
||||
&& (is_key_released(KeyCode::LeftShift) || is_key_released(KeyCode::RightShift))
|
||||
{
|
||||
Nudge::None
|
||||
} else if is_key_released(KeyCode::LeftShift) {
|
||||
Nudge::Left
|
||||
} else if is_key_released(KeyCode::RightShift) || is_key_released(KeyCode::Kp3) {
|
||||
Nudge::Right
|
||||
@@ -407,6 +424,9 @@ impl App {
|
||||
} else {
|
||||
Nudge::None
|
||||
};
|
||||
if !shift_down {
|
||||
self.crazy_shift_nudge_suppressed = false;
|
||||
}
|
||||
let controls = Controls {
|
||||
left_flipper,
|
||||
right_flipper,
|
||||
@@ -565,7 +585,9 @@ impl App {
|
||||
};
|
||||
insert_high_score(&mut self.saved.high_scores, entry.clone());
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if !self.game.as_ref().is_some_and(Game::crazy_mode_used) {
|
||||
self.persistence.submit_high_score(&entry);
|
||||
}
|
||||
self.save();
|
||||
if self.game.as_ref().is_some_and(|game| game.finished) {
|
||||
self.take_game_and_preserve_random_seed();
|
||||
@@ -919,6 +941,14 @@ impl App {
|
||||
WHITE,
|
||||
);
|
||||
}
|
||||
for ball in &game.crazy_balls {
|
||||
draw_texture(
|
||||
&self.assets.ball,
|
||||
ball.position.x - 8.0,
|
||||
ball.position.y - 8.0,
|
||||
WHITE,
|
||||
);
|
||||
}
|
||||
if let Some(source) = game.claw.sprite_source() {
|
||||
draw_texture_ex(
|
||||
&self.assets.robot,
|
||||
|
||||
@@ -264,6 +264,13 @@ enum BallDoubleState {
|
||||
Active,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
enum CrazyMode {
|
||||
#[default]
|
||||
Unused,
|
||||
Used,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
enum SpecialHoleGate {
|
||||
#[default]
|
||||
@@ -365,12 +372,31 @@ impl Default for Ball {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop points for the Ctrl+Shift+0 crazy mode, spread over open playfield
|
||||
/// between the bumpers, slingshots, and lanes.
|
||||
pub const CRAZY_BALL_POSITIONS: [Vec2; 10] = [
|
||||
vec2(170.0, 112.0),
|
||||
vec2(250.0, 140.0),
|
||||
vec2(120.0, 160.0),
|
||||
vec2(90.0, 215.0),
|
||||
vec2(250.0, 215.0),
|
||||
vec2(110.0, 260.0),
|
||||
vec2(215.0, 260.0),
|
||||
vec2(160.0, 300.0),
|
||||
vec2(120.0, 340.0),
|
||||
vec2(195.0, 335.0),
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Game {
|
||||
pub players: Vec<Player>,
|
||||
pub current_player: usize,
|
||||
pub ball: Ball,
|
||||
pub secondary_ball: Option<Ball>,
|
||||
/// Extra balls added by crazy mode. They are not part of the original
|
||||
/// two-slot engine: they collide with static records, score through the
|
||||
/// wall and bumper rules, and replace the primary ball when it drains.
|
||||
pub crazy_balls: Vec<Ball>,
|
||||
pub wheel_holes: [bool; 5],
|
||||
pub top_targets: [bool; 3],
|
||||
pub tilted: bool,
|
||||
@@ -402,6 +428,7 @@ pub struct Game {
|
||||
special_hole_gate: SpecialHoleGate,
|
||||
score_mode: ScoreMode,
|
||||
ball_double: BallDoubleState,
|
||||
crazy_mode: CrazyMode,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
@@ -416,6 +443,7 @@ impl Game {
|
||||
current_player: 0,
|
||||
ball: Ball::default(),
|
||||
secondary_ball: None,
|
||||
crazy_balls: Vec::new(),
|
||||
wheel_holes: [false; 5],
|
||||
top_targets: [false; 3],
|
||||
tilted: false,
|
||||
@@ -447,6 +475,7 @@ impl Game {
|
||||
special_hole_gate: SpecialHoleGate::Enabled,
|
||||
score_mode: ScoreMode::Normal,
|
||||
ball_double: BallDoubleState::Inactive,
|
||||
crazy_mode: CrazyMode::Unused,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +513,28 @@ impl Game {
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether crazy mode was used, so its scores stay off the online table.
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
pub fn crazy_mode_used(&self) -> bool {
|
||||
self.crazy_mode == CrazyMode::Used
|
||||
}
|
||||
|
||||
/// Crazy mode: drop ten extra balls onto the playfield.
|
||||
pub fn add_crazy_balls(&mut self) -> bool {
|
||||
if self.finished {
|
||||
return false;
|
||||
}
|
||||
self.crazy_mode = CrazyMode::Used;
|
||||
self.crazy_balls
|
||||
.extend(CRAZY_BALL_POSITIONS.into_iter().map(|position| Ball {
|
||||
position,
|
||||
velocity: Vec2::ZERO,
|
||||
in_launcher: false,
|
||||
..Ball::default()
|
||||
}));
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn begin_claw_scenario(&mut self, terminal_frame: u8) -> Vec<Event> {
|
||||
self.ball.in_launcher = false;
|
||||
@@ -676,6 +727,9 @@ impl Game {
|
||||
// The two slots are mutually exclusive while the claw is holding
|
||||
// a ball.
|
||||
debug_assert!(!(primary_held && secondary_held));
|
||||
if !self.finished {
|
||||
self.advance_crazy_balls(substeps, events);
|
||||
}
|
||||
}
|
||||
|
||||
self.pending_flipper_edges[0] =
|
||||
@@ -793,6 +847,16 @@ impl Game {
|
||||
}
|
||||
Nudge::None => return,
|
||||
}
|
||||
let primary_velocity = MilliVec::from_velocity_per_second(self.ball.velocity);
|
||||
let impulse = MilliVec {
|
||||
x: velocity.x.wrapping_sub(primary_velocity.x),
|
||||
y: velocity.y.wrapping_sub(primary_velocity.y),
|
||||
};
|
||||
for ball in &mut self.crazy_balls {
|
||||
ball.velocity = MilliVec::from_velocity_per_second(ball.velocity)
|
||||
.add(impulse)
|
||||
.to_velocity_per_second();
|
||||
}
|
||||
let primary_held = self.claw_holds_slot(ClawBallSlot::Primary);
|
||||
let secondary_held = self.claw_holds_slot(ClawBallSlot::Secondary);
|
||||
if let Some(secondary) = &mut self.secondary_ball {
|
||||
@@ -1395,6 +1459,74 @@ impl Game {
|
||||
collided || scan.action == BallAction::Suspend
|
||||
}
|
||||
|
||||
fn advance_crazy_balls(&mut self, substeps: u8, events: &mut Vec<Event>) {
|
||||
let mut balls = std::mem::take(&mut self.crazy_balls);
|
||||
balls.retain_mut(|ball| {
|
||||
for _ in 0..substeps {
|
||||
match self.advance_crazy_ball(ball, events) {
|
||||
None => return false,
|
||||
Some(true) => break,
|
||||
Some(false) => {}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
debug_assert!(self.crazy_balls.is_empty());
|
||||
self.crazy_balls = balls;
|
||||
}
|
||||
|
||||
/// Moves one crazy ball by one physics substep. Returns `None` once the
|
||||
/// ball has left the table, otherwise whether it collided.
|
||||
fn advance_crazy_ball(&mut self, ball: &mut Ball, events: &mut Vec<Event>) -> Option<bool> {
|
||||
let old_position = MilliVec::from_position(ball.position);
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
velocity.y += GRAVITY_MILLI_PER_STEP;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
let speed_before_collision = milli_distance(velocity);
|
||||
let predicted = old_position.add(velocity);
|
||||
if predicted_outside_board(predicted) {
|
||||
return None;
|
||||
}
|
||||
let mut best = None;
|
||||
for record_ids in [1..=5, 7..=88, 90..=128, 134..=139, 155..=173] {
|
||||
self.retain_static_range(old_position, predicted, velocity, record_ids, &mut best);
|
||||
}
|
||||
let mut hit = None;
|
||||
let mut auxiliary_fired = false;
|
||||
if let Some((object_id, is_wall, candidate)) = best {
|
||||
let response = candidate.resolve(velocity, ball.spin);
|
||||
velocity = response.velocity;
|
||||
ball.spin = response.spin;
|
||||
auxiliary_fired = response.auxiliary_fired;
|
||||
hit = Some((object_id, is_wall));
|
||||
}
|
||||
if hit == Some((25, true)) {
|
||||
velocity.x = 0;
|
||||
if speed_before_collision < 1_000 {
|
||||
// Resting on the shooter stop would park it in the launcher.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
ball.position = old_position.add(velocity).to_position();
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
match hit {
|
||||
Some((2, true)) => {
|
||||
events.push(Event::Drain);
|
||||
return None;
|
||||
}
|
||||
Some((object_id, true)) => {
|
||||
self.apply_wall_rule(object_id, events);
|
||||
if auxiliary_fired && matches!(object_id, 55 | 74 | 107) {
|
||||
events.push(Event::Sound(2019));
|
||||
}
|
||||
}
|
||||
Some((object_id, false)) => self.apply_bumper_rule(object_id, auxiliary_fired, events),
|
||||
None => {}
|
||||
}
|
||||
Some(hit.is_some())
|
||||
}
|
||||
|
||||
fn apply_bumper_rule(&mut self, object_id: u8, auxiliary_fired: bool, events: &mut Vec<Event>) {
|
||||
if self.tilted || !BUMPERS.iter().any(|bumper| bumper.id == object_id) {
|
||||
return;
|
||||
@@ -2199,6 +2331,9 @@ impl Game {
|
||||
{
|
||||
apply_flipper_response_to_ball(secondary, delta, side);
|
||||
}
|
||||
for ball in &mut self.crazy_balls {
|
||||
apply_flipper_response_to_ball(ball, delta, side);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2354,6 +2489,11 @@ impl Game {
|
||||
self.special_hole_gate = SpecialHoleGate::Suppressed;
|
||||
return;
|
||||
}
|
||||
if let Some(crazy_ball) = self.crazy_balls.pop() {
|
||||
self.ball = crazy_ball;
|
||||
events.push(Event::Drain);
|
||||
return;
|
||||
}
|
||||
self.ball_double = BallDoubleState::Inactive;
|
||||
self.multiball_state = MultiballState::Unavailable;
|
||||
self.special_hole_gate = SpecialHoleGate::Enabled;
|
||||
@@ -2590,6 +2730,72 @@ mod tests {
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crazy_balls_start_clear_of_every_static_record() {
|
||||
let game = Game::new_with_seed(1, 7);
|
||||
for position in CRAZY_BALL_POSITIONS {
|
||||
let origin = MilliVec::from_position(position);
|
||||
for (x, y) in [(3_000, 0), (-3_000, 0), (0, 3_000), (0, -3_000)] {
|
||||
assert!(
|
||||
game.find_static_collision_candidate(origin, MilliVec { x, y })
|
||||
.is_none(),
|
||||
"{position:?} touches a record moving ({x}, {y})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crazy_mode_adds_ten_balls_that_fall_and_score() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
assert!(game.add_crazy_balls());
|
||||
assert!(game.crazy_mode_used());
|
||||
assert_eq!(game.crazy_balls.len(), 10);
|
||||
let start: Vec<_> = game.crazy_balls.iter().map(|ball| ball.position).collect();
|
||||
for _ in 0..20 {
|
||||
game.update(0.03, 3, Controls::default());
|
||||
}
|
||||
assert!(game.ball.in_launcher);
|
||||
assert!(
|
||||
game.crazy_balls
|
||||
.iter()
|
||||
.zip(&start)
|
||||
.all(|(ball, start)| ball.position != *start)
|
||||
);
|
||||
for _ in 0..2_000 {
|
||||
game.update(0.03, 3, Controls::default());
|
||||
}
|
||||
assert!(game.crazy_balls.is_empty());
|
||||
assert!(game.player().score > 0);
|
||||
assert_eq!(game.player().balls, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draining_primary_ball_promotes_a_crazy_ball() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
game.ball.in_launcher = false;
|
||||
game.add_crazy_balls();
|
||||
let mut events = Vec::new();
|
||||
game.drain(&mut events);
|
||||
assert_eq!(game.crazy_balls.len(), 9);
|
||||
assert_eq!(game.ball.position, CRAZY_BALL_POSITIONS[9]);
|
||||
assert!(!game.ball.in_launcher);
|
||||
assert_eq!(game.player().balls, 3);
|
||||
game.crazy_balls.clear();
|
||||
game.drain(&mut events);
|
||||
assert_eq!(game.player().balls, 2);
|
||||
assert!(game.ball.in_launcher);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finished_game_rejects_crazy_mode() {
|
||||
let mut game = Game::new_with_seed(1, 7);
|
||||
game.finished = true;
|
||||
assert!(!game.add_crazy_balls());
|
||||
assert!(game.crazy_balls.is_empty());
|
||||
assert!(!game.crazy_mode_used());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_count_is_bounded() {
|
||||
assert_eq!(Game::new(0).players.len(), 1);
|
||||
|
||||
@@ -29,6 +29,7 @@ pub enum Scenario {
|
||||
Claw6,
|
||||
Claw7,
|
||||
Claw18,
|
||||
Crazy,
|
||||
}
|
||||
|
||||
impl Scenario {
|
||||
@@ -48,6 +49,7 @@ impl Scenario {
|
||||
Self::Claw6 => "claw-6",
|
||||
Self::Claw7 => "claw-7",
|
||||
Self::Claw18 => "claw-18",
|
||||
Self::Crazy => "crazy",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +68,8 @@ impl Scenario {
|
||||
| Self::Launcher
|
||||
| Self::Flippers
|
||||
| Self::Panel
|
||||
| Self::Targets => None,
|
||||
| Self::Targets
|
||||
| Self::Crazy => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,8 +93,9 @@ impl FromStr for Scenario {
|
||||
"claw-6" => Ok(Self::Claw6),
|
||||
"claw-7" => Ok(Self::Claw7),
|
||||
"claw-18" => Ok(Self::Claw18),
|
||||
"crazy" => Ok(Self::Crazy),
|
||||
_ => Err(format!(
|
||||
"unknown scenario {value:?}; expected loading, attract, highscores, name-entry, effect-7, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, or claw-18"
|
||||
"unknown scenario {value:?}; expected loading, attract, highscores, name-entry, effect-7, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18, or crazy"
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -175,7 +179,7 @@ fn seconds_to_step(value: &str) -> Result<u64, String> {
|
||||
}
|
||||
|
||||
pub const fn usage() -> &'static str {
|
||||
"Usage:\n tdkpin-rs\n tdkpin-rs --simulate SCENARIO [--at SECONDS | --step N] [--screenshot FILE.png] [--trace FILE.json] [--seed N]\n\nScenarios: loading, attract, highscores, name-entry, effect-7, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18"
|
||||
"Usage:\n tdkpin-rs\n tdkpin-rs --simulate SCENARIO [--at SECONDS | --step N] [--screenshot FILE.png] [--trace FILE.json] [--seed N]\n\nScenarios: loading, attract, highscores, name-entry, effect-7, autoplay, launcher, flippers, panel, targets, claw-1, claw-6, claw-7, claw-18, crazy"
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
@@ -237,6 +241,9 @@ impl Simulation {
|
||||
if scenario == Scenario::Effect7 {
|
||||
game.begin_effect_scenario(7);
|
||||
}
|
||||
if scenario == Scenario::Crazy {
|
||||
game.add_crazy_balls();
|
||||
}
|
||||
let initial_events = scenario
|
||||
.claw_terminal()
|
||||
.map_or_else(Vec::new, |terminal| game.begin_claw_scenario(terminal));
|
||||
@@ -378,7 +385,8 @@ fn controls_for(scenario: Scenario, step: u64) -> Controls {
|
||||
| Scenario::Claw1
|
||||
| Scenario::Claw6
|
||||
| Scenario::Claw7
|
||||
| Scenario::Claw18 => Controls::default(),
|
||||
| Scenario::Claw18
|
||||
| Scenario::Crazy => Controls::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user