From 48f085d0dfc88f390b67689d2b0b5aa96de0f4d3 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Thu, 17 Dec 2020 15:51:57 -0700 Subject: [PATCH] Remove Level.cells in favor of linear_cells --- js/format-base.js | 39 +++++++++++++---- js/game.js | 99 +++++++++++++++---------------------------- js/main-editor.js | 17 -------- js/main.js | 2 +- js/renderer-canvas.js | 6 +-- js/tiletypes.js | 30 ++++++------- 6 files changed, 83 insertions(+), 110 deletions(-) diff --git a/js/format-base.js b/js/format-base.js index c4c08f2..a01f1a7 100644 --- a/js/format-base.js +++ b/js/format-base.js @@ -48,8 +48,37 @@ export class Replay { } } -export class StoredLevel { +// Small shared helper methods for navigating a StoredLevel or Level +export class LevelInterface { + // Expected attributes: + // .size_x + // .size_y + // .linear_cells + scalar_to_coords(n) { + return [n % this.size_x, Math.floor(n / this.size_x)]; + } + + coords_to_scalar(x, y) { + return x + y * this.size_x; + } + + is_point_within_bounds(x, y) { + return (x >= 0 && x < this.size_x && y >= 0 && y < this.size_y); + } + + cell(x, y) { + if (this.is_point_within_bounds(x, y)) { + return this.linear_cells[this.coords_to_scalar(x, y)]; + } + else { + return null; + } + } +} + +export class StoredLevel extends LevelInterface { constructor(number) { + super(); // TODO still not sure this belongs here this.number = number; // one-based this.title = ''; @@ -88,14 +117,6 @@ export class StoredLevel { this.camera_regions = []; } - scalar_to_coords(n) { - return [n % this.size_x, Math.floor(n / this.size_x)]; - } - - coords_to_scalar(x, y) { - return x + y * this.size_x; - } - check() { } diff --git a/js/game.js b/js/game.js index f17fa9d..6bb9ea8 100644 --- a/js/game.js +++ b/js/game.js @@ -1,4 +1,5 @@ import { DIRECTIONS, DIRECTION_ORDER, INPUT_BITS, TICS_PER_SECOND } from './defs.js'; +import { LevelInterface } from './format-base.js'; import TILE_TYPES from './tiletypes.js'; export class Tile { @@ -301,8 +302,9 @@ Cell.prototype.powered_edges = 0; // sitting completely idle, undo consumes about 2 MB every five seconds, so this shouldn't go beyond // 12 MB for any remotely reasonable level. const UNDO_BUFFER_SIZE = TICS_PER_SECOND * 30; -export class Level { +export class Level extends LevelInterface { constructor(stored_level, compat = {}) { + super(); this.stored_level = stored_level; this.restart(compat); } @@ -322,7 +324,7 @@ export class Level { this.size_x = this.stored_level.size_x; this.size_y = this.stored_level.size_y; - this.cells = []; + this.linear_cells = []; this.player = null; this.p1_input = 0; this.p1_released = 0xff; @@ -380,10 +382,10 @@ export class Level { // - if an actor is in the cell, set the trap to open and unstick everything in it for (let y = 0; y < this.height; y++) { let row = []; - this.cells.push(row); for (let x = 0; x < this.width; x++) { let cell = new Cell(x, y); row.push(cell); + this.linear_cells.push(cell); let stored_cell = this.stored_level.linear_cells[n]; n++; @@ -444,7 +446,7 @@ export class Level { } if (target_cell_n && target_cell_n < this.width * this.height) { let [tx, ty] = this.stored_level.scalar_to_coords(target_cell_n); - for (let tile of this.cells[ty][tx]) { + for (let tile of this.cell(tx, ty)) { if (goals === tile.type.name) { connectable.connection = tile; break; @@ -483,15 +485,13 @@ export class Level { } // Finally, let all tiles do any custom init behavior - for (let row of this.cells) { - for (let cell of row) { - for (let tile of cell) { - if (tile.type.on_ready) { - tile.type.on_ready(tile, this); - } - if (cell === this.player.cell && tile.type.is_hint) { - this.hint_shown = tile.hint_text ?? this.stored_level.hint; - } + for (let cell of this.linear_cells) { + for (let tile of cell) { + if (tile.type.on_ready) { + tile.type.on_ready(tile, this); + } + if (cell === this.player.cell && tile.type.is_hint) { + this.hint_shown = tile.hint_text ?? this.stored_level.hint; } } } @@ -1453,13 +1453,9 @@ export class Level { return; // Turn off power to every cell - // TODO wonder if i need a linear cell list, or even a flat list of all tiles (that sounds - // like hell to keep updated though) - for (let row of this.cells) { - for (let cell of row) { - cell.prev_powered_edges = cell.powered_edges; - cell.powered_edges = 0; - } + for (let cell of this.linear_cells) { + cell.prev_powered_edges = cell.powered_edges; + cell.powered_edges = 0; } // Iterate over emitters and flood-fill outwards one edge at a time @@ -1519,10 +1515,9 @@ export class Level { while (true) { x += dirinfo.movement[0]; y += dirinfo.movement[1]; - if (! this.is_point_within_bounds(x, y)) + let candidate = this.cell(x, y); + if (! candidate) break; - - let candidate = this.cells[y][x]; neighbor_wire = candidate.get_wired_tile(); if (neighbor_wire) { if ((neighbor_wire.wire_tunnel_directions ?? 0) & opposite_bit) { @@ -1559,14 +1554,12 @@ export class Level { } // Inform any affected cells of power changes - for (let row of this.cells) { - for (let cell of row) { - if ((cell.prev_powered_edges === 0) !== (cell.powered_edges === 0)) { - let method = cell.powered_edges ? 'on_power' : 'on_depower'; - for (let tile of cell) { - if (tile.type[method]) { - tile.type[method](tile, this); - } + for (let cell of this.linear_cells) { + if ((cell.prev_powered_edges === 0) !== (cell.powered_edges === 0)) { + let method = cell.powered_edges ? 'on_power' : 'on_depower'; + for (let tile of cell) { + if (tile.type[method]) { + tile.type[method](tile, this); } } } @@ -1581,53 +1574,30 @@ export class Level { // ------------------------------------------------------------------------- // Board inspection - is_point_within_bounds(x, y) { - return (x >= 0 && x < this.width && y >= 0 && y < this.height); - } - - cell(x, y) { - if (this.is_point_within_bounds(x, y)) { - return this.cells[y][x]; - } - else { - return null; - } - } - get_neighboring_cell(cell, direction) { let move = DIRECTIONS[direction].movement; let goal_x = cell.x + move[0]; let goal_y = cell.y + move[1]; - if (this.is_point_within_bounds(goal_x, goal_y)) { - return this.cells[goal_y][goal_x]; - } - else { - return null; - } + return this.cell(cell.x + move[0], cell.y + move[1]); } // Iterates over the grid in (reverse?) reading order and yields all tiles with the given name. // The starting cell is iterated last. *iter_tiles_in_reading_order(start_cell, name, reverse = false) { - let x = start_cell.x; - let y = start_cell.y; + let i = this.coords_to_scalar(start_cell.x, start_cell.y); while (true) { if (reverse) { - x -= 1; - if (x < 0) { - x = this.width - 1; - y = (y - 1 + this.height) % this.height; + i -= 1; + if (i < 0) { + i += this.size_x * this.size_y; } } else { - x += 1; - if (x >= this.width) { - x = 0; - y = (y + 1) % this.height; - } + i += 1; + i %= this.size_x * this.size_y; } - let cell = this.cells[y][x]; + let cell = this.linear_cells[i]; for (let tile of cell) { if (tile.type.name === name) { yield tile; @@ -1649,8 +1619,9 @@ export class Level { let sy = start_cell.y; for (let direction of [[-1, -1], [-1, 1], [1, 1], [1, -1]]) { for (let i = 0; i < dist; i++) { - if (this.is_point_within_bounds(sx, sy)) { - yield this.cells[sy][sx]; + let cell = this.cell(sx, sy); + if (cell) { + yield cell; } sx += direction[0]; sy += direction[1]; diff --git a/js/main-editor.js b/js/main-editor.js index 12f44b7..3575897 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -243,7 +243,6 @@ class EditorLevelBrowserOverlay extends DialogOverlay { let index = this.awaiting_renders.shift(); let element = this.list.childNodes[index]; let stored_level = this.conductor.stored_game.load_level(index); - this.conductor.editor._xxx_update_stored_level_cells(stored_level); this.renderer.set_level(stored_level); this.renderer.set_viewport_size(stored_level.size_x, stored_level.size_y); this.renderer.draw(); @@ -1851,26 +1850,11 @@ export class Editor extends PrimaryView { load_game(stored_game) { } - _xxx_update_stored_level_cells(stored_level) { - // XXX need this for renderer compat, not used otherwise, PLEASE delete - stored_level.cells = []; - let row; - for (let [i, cell] of stored_level.linear_cells.entries()) { - if (i % stored_level.size_x === 0) { - row = []; - stored_level.cells.push(row); - } - row.push(cell); - } - } - load_level(stored_level) { // TODO support a game too i guess this.stored_level = stored_level; this.update_viewport_size(); - this._xxx_update_stored_level_cells(this.stored_level); - // Load connections this.connections_g.textContent = ''; for (let [src, dest] of Object.entries(this.stored_level.custom_trap_wiring)) { @@ -2100,7 +2084,6 @@ export class Editor extends PrimaryView { this.stored_level.linear_cells = new_cells; this.stored_level.size_x = size_x; this.stored_level.size_y = size_y; - this._xxx_update_stored_level_cells(this.stored_level); this.update_viewport_size(); this.renderer.draw(); } diff --git a/js/main.js b/js/main.js index e252519..fdb876c 100644 --- a/js/main.js +++ b/js/main.js @@ -411,7 +411,7 @@ class Player extends PrimaryView { return; let [x, y] = this.renderer.cell_coords_from_event(ev); - this.level.move_to(this.level.player, this.level.cells[y][x], 1); + this.level.move_to(this.level.player, this.level.cell(x, y), 1); // TODO this behaves a bit weirdly when paused (doesn't redraw even with a force), i // think because we're still claiming a speed of 1 so time has to pass before the move // actually "happens" diff --git a/js/renderer-canvas.js b/js/renderer-canvas.js index 0200bce..01d736f 100644 --- a/js/renderer-canvas.js +++ b/js/renderer-canvas.js @@ -147,7 +147,7 @@ export class CanvasRenderer { for (let layer = 0; layer < DRAW_LAYERS.MAX; layer++) { for (let x = xf0; x <= x1; x++) { for (let y = yf0; y <= y1; y++) { - for (let tile of this.level.cells[y][x]) { + for (let tile of this.level.cell(x, y)) { if (tile.type.draw_layer !== layer) continue; @@ -185,11 +185,11 @@ export class CanvasRenderer { } } - if (this.show_actor_bboxes && ! this.level.linear_cells) { // FIXME dumb hack so this doesn't happen in editor + if (this.show_actor_bboxes && this.level.constructor.name === 'Level') { // FIXME dumb hack so this doesn't happen in editor this.ctx.fillStyle = '#f004'; for (let x = xf0; x <= x1; x++) { for (let y = yf0; y <= y1; y++) { - let actor = this.level.cells[y][x].get_actor(); + let actor = this.level.cell(x, y).get_actor(); if (! actor) continue; let [vx, vy] = actor.visual_position(tic_offset); diff --git a/js/tiletypes.js b/js/tiletypes.js index 6b50db8..752aae8 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -1412,21 +1412,19 @@ const TILE_TYPES = { do_button(level) { // Swap green floors and walls // TODO could probably make this more compact for undo purposes - for (let row of level.cells) { - for (let cell of row) { - for (let tile of cell) { - if (tile.type.name === 'green_floor') { - level.transmute_tile(tile, 'green_wall'); - } - else if (tile.type.name === 'green_wall') { - level.transmute_tile(tile, 'green_floor'); - } - else if (tile.type.name === 'green_chip') { - level.transmute_tile(tile, 'green_bomb'); - } - else if (tile.type.name === 'green_bomb') { - level.transmute_tile(tile, 'green_chip'); - } + for (let cell of level.linear_cells) { + for (let tile of cell) { + if (tile.type.name === 'green_floor') { + level.transmute_tile(tile, 'green_wall'); + } + else if (tile.type.name === 'green_wall') { + level.transmute_tile(tile, 'green_floor'); + } + else if (tile.type.name === 'green_chip') { + level.transmute_tile(tile, 'green_bomb'); + } + else if (tile.type.name === 'green_bomb') { + level.transmute_tile(tile, 'green_chip'); } } } @@ -1558,7 +1556,7 @@ const TILE_TYPES = { for (let x = Math.max(0, me.cell.x - 2); x <= Math.min(level.width - 1, me.cell.x + 2); x++) { for (let y = Math.max(0, me.cell.y - 2); y <= Math.min(level.height - 1, me.cell.y + 2); y++) { - let cell = level.cells[y][x]; + let cell = level.cell(x, y); // TODO wait is this right if (cell === me.cell) continue;