From 626d146375eb188c67618bea31f75bb9b51fe827 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Mon, 6 May 2024 14:07:35 -0600 Subject: [PATCH] Add explicit support in the level for sokoban buttons Gets a lot of junk out of the sokoban buttons' implementations. Also, undo closures are gone now! --- js/game.js | 76 ++++++++++++++++++++++++++++++++++++++----------- js/tiletypes.js | 42 +++++---------------------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/js/game.js b/js/game.js index dd5adcd..1d56a80 100644 --- a/js/game.js +++ b/js/game.js @@ -267,8 +267,8 @@ export class Cell extends Array { class UndoEntry { constructor() { - this.misc_closures = []; this.tile_changes = new Map; + this.sokoban_changes = null; this.level_props = {}; this.actor_splices = []; this.toggle_green_tiles = false; @@ -283,6 +283,15 @@ class UndoEntry { } return changes; } + + preserve_sokoban(color, count) { + if (! this.sokoban_changes) { + this.sokoban_changes = {}; + } + if (! (color in this.sokoban_changes)) { + this.sokoban_changes[color] = count; + } + } } @@ -417,7 +426,8 @@ export class Level extends LevelInterface { let yellow_teleporter_count = 0; this.allow_taking_yellow_teleporters = false; // Sokoban buttons function as a group - this.sokoban_buttons_unpressed = {}; + this.sokoban_unpressed = { red: 0, blue: 0, yellow: 0, green: 0 }; + this.sokoban_satisfied = { red: true, blue: true, yellow: true, green: true }; for (let y = 0; y < this.height; y++) { let row = []; for (let x = 0; x < this.width; x++) { @@ -426,7 +436,7 @@ export class Level extends LevelInterface { this.linear_cells.push(cell); let stored_cell = this.stored_level.linear_cells[n]; - n++; + n += 1; for (let template_tile of stored_cell) { if (! template_tile) continue; @@ -444,7 +454,7 @@ export class Level extends LevelInterface { } } if (tile.type.is_required_chip && this.stored_level.chips_required === null) { - this.chips_remaining++; + this.chips_remaining += 1; } if (tile.type.is_actor) { this.actors.push(tile); @@ -466,8 +476,8 @@ export class Level extends LevelInterface { } } else if (tile.type.name === 'sokoban_button') { - this.sokoban_buttons_unpressed[tile.color] = - (this.sokoban_buttons_unpressed[tile.color] ?? 0) + 1; + this.sokoban_unpressed[tile.color] += 1; + this.sokoban_satisfied[tile.color] = false; } } } @@ -1302,6 +1312,8 @@ export class Level extends LevelInterface { this.pending_undo.toggle_green_tiles = true; } } + + this.__check_sokoban_buttons(); } __toggle_green_tiles() { @@ -1322,6 +1334,27 @@ export class Level extends LevelInterface { } } + // Check for changes to sokoban buttons, and swap the appropriate floors/walls if necessary. + // NOT undo-safe; this is undone by calling it again after an undo. + __check_sokoban_buttons() { + for (let [color, was_satisfied] of Object.entries(this.sokoban_satisfied)) { + let is_satisfied = this.sokoban_unpressed[color] === 0; + if (was_satisfied !== is_satisfied) { + this.sokoban_satisfied[color] = is_satisfied; + let new_type = TILE_TYPES[is_satisfied ? 'sokoban_floor' : 'sokoban_wall']; + console.log(color, this.sokoban_unpressed[color], was_satisfied, is_satisfied, new_type); + for (let cell of this.linear_cells) { + let terrain = cell.get_terrain(); + if ((terrain.type.name === 'sokoban_wall' || terrain.type.name === 'sokoban_floor') && + terrain.color === color) + { + terrain.type = new_type; + } + } + } + } + } + _do_cleanup_phase() { // Lynx compat: Any blue tank that still has the reversal flag set here, but is in motion, // should ignore it. Unfortunately this has to be done as its own pass (as it is in Lynx!) @@ -2542,14 +2575,13 @@ export class Level extends LevelInterface { console.log(entry); // Undo in reverse order! There's no redo, so it's okay to use the destructive reverse(). - // Green toggle goes first, since it's the last thing to happen in a tic + // These toggles go first, since they're the last things to happen in a tic if (entry.pending_green_toggle) { this.__toggle_green_tiles(); } - - entry.misc_closures.reverse(); - for (let closure of entry.misc_closures) { - closure(); + if (entry.sokoban_changes) { + Object.assign(this.sokoban_unpressed, entry.sokoban_changes); + this.__check_sokoban_buttons(); } entry.actor_splices.reverse(); @@ -2588,12 +2620,6 @@ export class Level extends LevelInterface { } } - _push_pending_undo(thunk) { - if (this.undo_enabled) { - this.pending_undo.misc_closures.push(thunk); - } - } - // Level alteration ------------------------------------------------------------------------------- // EVERYTHING that changes the state of a level, including the state of a single tile, should do // it through one of these for undo/rewind purposes @@ -2683,6 +2709,22 @@ export class Level extends LevelInterface { } } + press_sokoban(color) { + if (this.undo_enabled) { + this.pending_undo.preserve_sokoban(color, this.sokoban_unpressed[color]); + } + + this.sokoban_unpressed[color] -= 1; + } + + unpress_sokoban(color) { + if (this.undo_enabled) { + this.pending_undo.preserve_sokoban(color, this.sokoban_unpressed[color]); + } + + this.sokoban_unpressed[color] += 1; + } + kill_actor(actor, killer, animation_name = null, sfx = null, fail_reason = null) { if (actor.type.is_real_player) { // Resurrect using the ankh tile, if possible diff --git a/js/tiletypes.js b/js/tiletypes.js index a376a37..e18c6fe 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -1570,54 +1570,26 @@ const TILE_TYPES = { if (actor && ! (actor.type.name === 'sokoban_block' && actor.color !== me.color)) { // Already held down, make sure the level knows me.pressed = true; - level.sokoban_buttons_unpressed[me.color] -= 1; - if (level.sokoban_buttons_unpressed[me.color] === 0) { - for (let cell of level.linear_cells) { - let terrain = cell.get_terrain(); - if (terrain.type.name === 'sokoban_wall' && terrain.color === me.color) { - terrain.type = TILE_TYPES['sokoban_floor']; - } - } - } + level.press_sokoban(me.color); } }, on_arrive(me, level, other) { + if (me.pressed) + return; if (other.type.name === 'sokoban_block' && me.color !== other.color) return; level._set_tile_prop(me, 'pressed', true); level.sfx.play_once('button-press', me.cell); - level.sokoban_buttons_unpressed[me.color] -= 1; - level._push_pending_undo(() => { - level.sokoban_buttons_unpressed[me.color] += 1; - }); - if (level.sokoban_buttons_unpressed[me.color] === 0) { - for (let cell of level.linear_cells) { - let terrain = cell.get_terrain(); - if (terrain.type.name === 'sokoban_wall' && terrain.color === me.color) { - level.transmute_tile(terrain, 'sokoban_floor'); - } - } - } + level.press_sokoban(me.color); }, on_depart(me, level, other) { - level._set_tile_prop(me, 'pressed', false); - if (other.type.name === 'sokoban_block' && me.color !== other.color) + if (! me.pressed) return; + level._set_tile_prop(me, 'pressed', false); level.sfx.play_once('button-release', me.cell); - level.sokoban_buttons_unpressed[me.color] += 1; - level._push_pending_undo(() => { - level.sokoban_buttons_unpressed[me.color] -= 1; - }); - if (level.sokoban_buttons_unpressed[me.color] === 1) { - for (let cell of level.linear_cells) { - let terrain = cell.get_terrain(); - if (terrain.type.name === 'sokoban_floor' && terrain.color === me.color) { - level.transmute_tile(terrain, 'sokoban_wall'); - } - } - } + level.unpress_sokoban(me.color); }, visual_state(me) { return (me.color ?? 'red') + '_' + (me.pressed ? 'pressed' : 'released');