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 {
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

View File

@ -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');