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!
This commit is contained in:
Eevee (Evelyn Woods) 2024-05-06 14:07:35 -06:00
parent 3c7b8948ae
commit 626d146375
2 changed files with 66 additions and 52 deletions

View File

@ -267,8 +267,8 @@ export class Cell extends Array {
class UndoEntry { class UndoEntry {
constructor() { constructor() {
this.misc_closures = [];
this.tile_changes = new Map; this.tile_changes = new Map;
this.sokoban_changes = null;
this.level_props = {}; this.level_props = {};
this.actor_splices = []; this.actor_splices = [];
this.toggle_green_tiles = false; this.toggle_green_tiles = false;
@ -283,6 +283,15 @@ class UndoEntry {
} }
return changes; 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; let yellow_teleporter_count = 0;
this.allow_taking_yellow_teleporters = false; this.allow_taking_yellow_teleporters = false;
// Sokoban buttons function as a group // 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++) { for (let y = 0; y < this.height; y++) {
let row = []; let row = [];
for (let x = 0; x < this.width; x++) { for (let x = 0; x < this.width; x++) {
@ -426,7 +436,7 @@ export class Level extends LevelInterface {
this.linear_cells.push(cell); this.linear_cells.push(cell);
let stored_cell = this.stored_level.linear_cells[n]; let stored_cell = this.stored_level.linear_cells[n];
n++; n += 1;
for (let template_tile of stored_cell) { for (let template_tile of stored_cell) {
if (! template_tile) if (! template_tile)
continue; continue;
@ -444,7 +454,7 @@ export class Level extends LevelInterface {
} }
} }
if (tile.type.is_required_chip && this.stored_level.chips_required === null) { if (tile.type.is_required_chip && this.stored_level.chips_required === null) {
this.chips_remaining++; this.chips_remaining += 1;
} }
if (tile.type.is_actor) { if (tile.type.is_actor) {
this.actors.push(tile); this.actors.push(tile);
@ -466,8 +476,8 @@ export class Level extends LevelInterface {
} }
} }
else if (tile.type.name === 'sokoban_button') { else if (tile.type.name === 'sokoban_button') {
this.sokoban_buttons_unpressed[tile.color] = this.sokoban_unpressed[tile.color] += 1;
(this.sokoban_buttons_unpressed[tile.color] ?? 0) + 1; this.sokoban_satisfied[tile.color] = false;
} }
} }
} }
@ -1302,6 +1312,8 @@ export class Level extends LevelInterface {
this.pending_undo.toggle_green_tiles = true; this.pending_undo.toggle_green_tiles = true;
} }
} }
this.__check_sokoban_buttons();
} }
__toggle_green_tiles() { __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() { _do_cleanup_phase() {
// Lynx compat: Any blue tank that still has the reversal flag set here, but is in motion, // 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!) // 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); console.log(entry);
// Undo in reverse order! There's no redo, so it's okay to use the destructive reverse(). // 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) { if (entry.pending_green_toggle) {
this.__toggle_green_tiles(); this.__toggle_green_tiles();
} }
if (entry.sokoban_changes) {
entry.misc_closures.reverse(); Object.assign(this.sokoban_unpressed, entry.sokoban_changes);
for (let closure of entry.misc_closures) { this.__check_sokoban_buttons();
closure();
} }
entry.actor_splices.reverse(); 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 ------------------------------------------------------------------------------- // Level alteration -------------------------------------------------------------------------------
// EVERYTHING that changes the state of a level, including the state of a single tile, should do // 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 // 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) { kill_actor(actor, killer, animation_name = null, sfx = null, fail_reason = null) {
if (actor.type.is_real_player) { if (actor.type.is_real_player) {
// Resurrect using the ankh tile, if possible // Resurrect using the ankh tile, if possible

View File

@ -1570,54 +1570,26 @@ const TILE_TYPES = {
if (actor && ! (actor.type.name === 'sokoban_block' && actor.color !== me.color)) { if (actor && ! (actor.type.name === 'sokoban_block' && actor.color !== me.color)) {
// Already held down, make sure the level knows // Already held down, make sure the level knows
me.pressed = true; me.pressed = true;
level.sokoban_buttons_unpressed[me.color] -= 1; level.press_sokoban(me.color);
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'];
}
}
}
} }
}, },
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (me.pressed)
return;
if (other.type.name === 'sokoban_block' && me.color !== other.color) if (other.type.name === 'sokoban_block' && me.color !== other.color)
return; return;
level._set_tile_prop(me, 'pressed', true); level._set_tile_prop(me, 'pressed', true);
level.sfx.play_once('button-press', me.cell); level.sfx.play_once('button-press', me.cell);
level.sokoban_buttons_unpressed[me.color] -= 1; level.press_sokoban(me.color);
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');
}
}
}
}, },
on_depart(me, level, other) { on_depart(me, level, other) {
level._set_tile_prop(me, 'pressed', false); if (! me.pressed)
if (other.type.name === 'sokoban_block' && me.color !== other.color)
return; return;
level._set_tile_prop(me, 'pressed', false);
level.sfx.play_once('button-release', me.cell); level.sfx.play_once('button-release', me.cell);
level.sokoban_buttons_unpressed[me.color] += 1; level.unpress_sokoban(me.color);
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');
}
}
}
}, },
visual_state(me) { visual_state(me) {
return (me.color ?? 'red') + '_' + (me.pressed ? 'pressed' : 'released'); return (me.color ?? 'red') + '_' + (me.pressed ? 'pressed' : 'released');