Remove Level.cells in favor of linear_cells

This commit is contained in:
Eevee (Evelyn Woods) 2020-12-17 15:51:57 -07:00
parent 2fa35336cb
commit 48f085d0df
6 changed files with 83 additions and 110 deletions

View File

@ -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() {
}

View File

@ -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,8 +485,7 @@ 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 cell of this.linear_cells) {
for (let tile of cell) {
if (tile.type.on_ready) {
tile.type.on_ready(tile, this);
@ -494,7 +495,6 @@ export class Level {
}
}
}
}
// Erase undo, in case any on_ready added to it (we don't want to undo initialization!)
this.pending_undo = this.create_undo_entry();
}
@ -1453,14 +1453,10 @@ 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) {
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
// propagated it via flood-fill through neighboring wires
@ -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,8 +1554,7 @@ export class Level {
}
// Inform any affected cells of power changes
for (let row of this.cells) {
for (let cell of row) {
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) {
@ -1571,7 +1565,6 @@ export class Level {
}
}
}
}
// Performs a depth-first search for connected wires and wire objects, extending out from the
// given starting cell
@ -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];

View File

@ -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();
}

View File

@ -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"

View File

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

View File

@ -1412,8 +1412,7 @@ 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 cell of level.linear_cells) {
for (let tile of cell) {
if (tile.type.name === 'green_floor') {
level.transmute_tile(tile, 'green_wall');
@ -1429,7 +1428,6 @@ const TILE_TYPES = {
}
}
}
}
},
on_arrive(me, level, other) {
level.sfx.play_once('button-press', me.cell);
@ -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;