From fe12c599bc29a1451eb5abd89f0c1e0f762ead1e Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Wed, 16 Sep 2020 18:14:06 -0600 Subject: [PATCH] Abort the tic and draw a final frame after the game ends; fix dead player appearance --- js/game.js | 30 +++++++++++++++++++++--------- js/main.js | 18 ++++++++++-------- js/renderer-canvas.js | 5 ++--- js/tiletypes.js | 19 ++++++++----------- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/js/game.js b/js/game.js index 16a4f06..853fd08 100644 --- a/js/game.js +++ b/js/game.js @@ -163,6 +163,8 @@ export class Cell extends Array { } } +class GameEnded extends Error {} + export class Level { constructor(stored_level, compat = {}) { this.stored_level = stored_level; @@ -345,6 +347,23 @@ export class Level { return; } + try { + this._advance_tic(p1_primary_direction, p1_secondary_direction); + } + catch (e) { + if (e instanceof GameEnded) { + // Do nothing, the game ended and we just wanted to skip the rest + } + else { + throw e; + } + } + + // Commit the undo state at the end of each tic + this.commit(); + } + + _advance_tic(p1_primary_direction, p1_secondary_direction) { // Player's secondary direction is set immediately; it applies on arrival to cells even if // it wasn't held the last time the player started moving this._set_prop(this.player, 'secondary_direction', p1_secondary_direction); @@ -352,8 +371,6 @@ export class Level { // Used to check for a monster chomping the player's tail this.player_leaving_cell = this.player.cell; - // XXX this entire turn order is rather different in ms rules - // FIXME OK, do a pass to make everyone decide their movement, and then actually do it. the question iiis, where does that fit in with animation // First pass: tick cooldowns and animations; have actors arrive in their cells for (let actor of this.actors) { // Actors with no cell were destroyed @@ -554,10 +571,6 @@ export class Level { let old_cell = actor.cell; this.attempt_step(actor, actor.decision); - // TODO do i need to do this more aggressively? - if (this.state === 'success' || this.state === 'failure') - break; - // Players can also bump the tiles in the cell next to the one they're leaving let dir2 = actor.secondary_direction; if (actor.type.is_player && dir2 && @@ -617,9 +630,6 @@ export class Level { this.tic_counter = tic_counter; }); } - - // Commit the undo state at the end of each tic - this.commit(); } // Try to move the given actor one tile in the given direction and update @@ -933,11 +943,13 @@ export class Level { }); this.state = 'failure'; this.fail_message = message; + throw new GameEnded; } win() { this.pending_undo.push(() => this.state = 'playing'); this.state = 'success'; + throw new GameEnded; } // Get the next direction a random force floor will use. They share global diff --git a/js/main.js b/js/main.js index e7db469..58c82d9 100644 --- a/js/main.js +++ b/js/main.js @@ -501,13 +501,6 @@ class Player extends PrimaryView { // Redraws every frame, unless the game isn't running redraw() { - // FIXME draw one more frame after losing, so we can see the player explode or whatever - // TODO for bonus points, also finish the player animation (but don't advance the game any further) - if (this.state !== 'playing' && this.state !== 'rewinding') { - this._redraw_handle = null; - return; - } - // Calculate this here, not in _redraw, because that's called at weird // times when the game might not have actually advanced at all yet // TODO this is not gonna be right while pausing lol @@ -516,7 +509,16 @@ class Player extends PrimaryView { this.tic_offset = Math.min(0.9999, (performance.now() - this.last_advance) / 1000 / (1 / TICS_PER_SECOND)); this._redraw(); - this._redraw_handle = requestAnimationFrame(this._redraw_bound); + + // Check for a stopped game *after* drawing, so that if the game ends, we still draw its + // final result before stopping the draw loop + // TODO for bonus points, also finish the player animation (but don't advance the game any further) + if (this.state === 'playing' || this.state === 'rewinding') { + this._redraw_handle = requestAnimationFrame(this._redraw_bound); + } + else { + this._redraw_handle = null; + } } // Actually redraw. Used to force drawing outside of normal play diff --git a/js/renderer-canvas.js b/js/renderer-canvas.js index 58ef87f..577d840 100644 --- a/js/renderer-canvas.js +++ b/js/renderer-canvas.js @@ -100,11 +100,10 @@ export class CanvasRenderer { let y1 = Math.min(this.level.size_y - 1, Math.ceil(y0 + this.viewport_size_y)); // Draw one layer at a time, so animated objects aren't overdrawn by // neighboring terrain - let x, y; // XXX layer count hardcoded here for (let layer = 0; layer < 4; layer++) { - for (x = xf0; x <= x1; x++) { - for (y = yf0; y <= y1; y++) { + for (let x = xf0; x <= x1; x++) { + for (let y = yf0; y <= y1; y++) { for (let tile of this.level.cells[y][x]) { if (tile.type.draw_layer !== layer) continue; diff --git a/js/tiletypes.js b/js/tiletypes.js index 7b3751d..4fc04cf 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -290,8 +290,8 @@ const TILE_TYPES = { level.transmute_tile(me, 'water'); } else if (other.type.is_player) { - level.fail("Oops! You can't walk on fire without fire boots!"); level.transmute_tile(other, 'player_burned'); + level.fail("Oops! You can't walk on fire without fire boots!"); } else { level.remove_tile(other); @@ -311,8 +311,8 @@ const TILE_TYPES = { level.transmute_tile(me, 'ice'); } else if (other.type.is_player) { + level.transmute_tile(other, 'splash'); level.fail("swimming with the fishes"); - level.transmute_tile(other, 'player_drowned'); } else { level.transmute_tile(other, 'splash'); @@ -431,15 +431,13 @@ const TILE_TYPES = { }, bomb: { draw_layer: LAYER_ITEM, - // TODO explode on_arrive(me, level, other) { - let cell = me.cell; level.remove_tile(me); - if (other.type.is_player) { - // Check this /before/ we change it... + let was_player = other.type.is_player; + level.transmute_tile(other, 'explosion'); + if (was_player) { level.fail("watch where you step"); } - level.transmute_tile(other, 'explosion'); }, }, thief_tools: { @@ -553,13 +551,12 @@ const TILE_TYPES = { draw_layer: LAYER_ITEM, is_required_chip: true, on_arrive(me, level, other) { - // TODO explode level.remove_tile(me); - if (other.type.is_player) { - // Check this /before/ we change it... + let was_player = other.type.is_player; + level.transmute_tile(other, 'explosion'); + if (was_player) { level.fail("watch where you step"); } - level.transmute_tile(other, 'explosion'); }, }, purple_floor: {