Refactor circuit-tracing to be more in algorithms
This should make it more usable in the editor.
This commit is contained in:
parent
c45ebe60e1
commit
04d6b3dddb
@ -61,10 +61,33 @@ export function* find_terrain_diamond(levelish, start_cell, type_names) {
|
||||
export function find_implicit_connection() {
|
||||
}
|
||||
|
||||
export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_dead_end) {
|
||||
export class Circuit {
|
||||
constructor() {
|
||||
this.is_powered = null;
|
||||
this.tiles = new Map;
|
||||
this.inputs = new Map;
|
||||
}
|
||||
|
||||
add_tile_edge(tile, edgebits) {
|
||||
this.tiles.set(tile, (this.tiles.get(tile) ?? 0) | edgebits);
|
||||
}
|
||||
|
||||
add_input_edge(tile, edgebits) {
|
||||
this.inputs.set(tile, (this.inputs.get(tile) ?? 0) | edgebits);
|
||||
}
|
||||
}
|
||||
|
||||
// Traces a wire circuit and calls the given callbacks when finding either a new wire or an ending.
|
||||
// actor_mode describes how to handle circuit blocks:
|
||||
// - still: Actor wires are examined only for actors with a zero cooldown. (Normal behavior.)
|
||||
// - always: Actor wires are always examined. (compat.tiles_react_instantly behavior.)
|
||||
// - ignore: Skip actors entirely. (Editor behavior.)
|
||||
// Returns a Circuit.
|
||||
export function trace_floor_circuit(levelish, actor_mode, start_cell, start_edge, on_wire, on_dead_end) {
|
||||
let is_first = true;
|
||||
let pending = [[start_cell, start_edge]];
|
||||
let seen_cells = new Map;
|
||||
let circuit = new Circuit;
|
||||
while (pending.length > 0) {
|
||||
let next = [];
|
||||
for (let [cell, edge] of pending) {
|
||||
@ -80,7 +103,7 @@ export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_d
|
||||
let tile = terrain;
|
||||
let actor = cell.get_actor();
|
||||
if (actor && actor.type.contains_wire && (
|
||||
actor.movement_cooldown === 0 || level.compat.tiles_react_instantly))
|
||||
(actor_mode === 'still' && actor.movement_cooldown === 0) || actor_mode === 'always'))
|
||||
{
|
||||
tile = actor;
|
||||
}
|
||||
@ -90,10 +113,27 @@ export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_d
|
||||
let connections = edgeinfo.bit;
|
||||
let mode = tile.wire_propagation_mode ?? tile.type.wire_propagation_mode;
|
||||
if (! is_first && ((tile.wire_directions ?? 0) & edgeinfo.bit) === 0) {
|
||||
// There's not actually a wire here (but not if this is our starting cell, in which
|
||||
// case we trust the caller)
|
||||
if (on_dead_end) {
|
||||
on_dead_end(cell, edge);
|
||||
// There's not actually a wire here, so check for things that respond to receiving
|
||||
// power... but if this is the starting cell, we trust the caller and skip it (XXX why)
|
||||
for (let tile2 of cell) {
|
||||
if (! tile2)
|
||||
continue;
|
||||
|
||||
if (tile2.type.name === 'logic_gate') {
|
||||
// Logic gates are technically not wired, but still attached to
|
||||
// circuits, mostly so blue teleporters can follow them
|
||||
let wire = tile2.type._gate_types[tile2.gate_type][
|
||||
(DIRECTIONS[edge].index - DIRECTIONS[tile2.direction].index + 4) % 4];
|
||||
if (! wire)
|
||||
continue;
|
||||
circuit.add_tile_edge(tile2, DIRECTIONS[edge].bit);
|
||||
if (wire.match(/^out/)) {
|
||||
circuit.add_input_edge(tile2, DIRECTIONS[edge].bit);
|
||||
}
|
||||
}
|
||||
else if (tile2.type.on_power) {
|
||||
circuit.add_tile_edge(tile2, DIRECTIONS[edge].bit);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -113,8 +153,11 @@ export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_d
|
||||
|
||||
seen_cells.set(cell, seen_edges | connections);
|
||||
|
||||
if (on_wire) {
|
||||
on_wire(tile, connections);
|
||||
circuit.add_tile_edge(tile, connections);
|
||||
|
||||
if (tile.type.is_power_source) {
|
||||
// TODO could just do this in a pass afterwards?
|
||||
circuit.add_input_edge(tile, connections);
|
||||
}
|
||||
|
||||
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
||||
@ -130,10 +173,10 @@ export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_d
|
||||
// Search in this direction for a matching tunnel
|
||||
// Note that while actors (the fuckin circuit block) can be wired, tunnels ONLY
|
||||
// appear on terrain, and are NOT affected by actors on top
|
||||
neighbor = find_matching_wire_tunnel(level, cell.x, cell.y, direction);
|
||||
neighbor = find_matching_wire_tunnel(levelish, cell.x, cell.y, direction);
|
||||
}
|
||||
else {
|
||||
neighbor = level.get_neighboring_cell(cell, direction);
|
||||
neighbor = levelish.get_neighboring_cell(cell, direction);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -151,16 +194,18 @@ export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_d
|
||||
pending = next;
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
return circuit;
|
||||
}
|
||||
|
||||
export function find_matching_wire_tunnel(level, x, y, direction) {
|
||||
export function find_matching_wire_tunnel(levelish, x, y, direction) {
|
||||
let dirinfo = DIRECTIONS[direction];
|
||||
let [dx, dy] = dirinfo.movement;
|
||||
let nesting = 0;
|
||||
while (true) {
|
||||
x += dx;
|
||||
y += dy;
|
||||
let candidate = level.cell(x, y);
|
||||
let candidate = levelish.cell(x, y);
|
||||
if (! candidate)
|
||||
return null;
|
||||
|
||||
|
||||
@ -77,6 +77,10 @@ export class LevelInterface {
|
||||
return x + y * this.size_x;
|
||||
}
|
||||
|
||||
cell_to_scalar(cell) {
|
||||
return this.coords_to_scalar(cell.x, cell.y);
|
||||
}
|
||||
|
||||
is_point_within_bounds(x, y) {
|
||||
return (x >= 0 && x < this.size_x && y >= 0 && y < this.size_y);
|
||||
}
|
||||
|
||||
117
js/game.js
117
js/game.js
@ -520,21 +520,10 @@ export class Level extends LevelInterface {
|
||||
// Build circuits out of connected wires
|
||||
// TODO document this idea
|
||||
|
||||
if (!first_time) {
|
||||
for (let circuit of this.circuits) {
|
||||
for (let tile of circuit.tiles) {
|
||||
tile[0].circuits = [null, null, null, null];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.circuits = [];
|
||||
this.power_sources = [];
|
||||
let wired_outputs = new Set;
|
||||
this.wired_outputs = [];
|
||||
let add_to_edge_map = (map, item, edges) => {
|
||||
map.set(item, (map.get(item) ?? 0) | edges);
|
||||
};
|
||||
let seen_edges = new Map;
|
||||
for (let cell of this.linear_cells) {
|
||||
// We're interested in static circuitry, which means terrain
|
||||
// OR circuit blocks on top
|
||||
@ -585,73 +574,49 @@ export class Level extends LevelInterface {
|
||||
if (! ((wire_directions | terrain.wire_tunnel_directions) & dirinfo.bit))
|
||||
continue;
|
||||
|
||||
if (terrain.circuits && terrain.circuits[dirinfo.index])
|
||||
if ((seen_edges.get(terrain) ?? 0) & dirinfo.bit)
|
||||
continue;
|
||||
|
||||
let circuit = {
|
||||
is_powered: first_time ? false : null,
|
||||
tiles: new Map,
|
||||
inputs: new Map,
|
||||
};
|
||||
this.circuits.push(circuit);
|
||||
// At last, a wired cell edge we have not yet handled. Floodfill from here
|
||||
algorithms.trace_floor_circuit(
|
||||
this, terrain.cell, direction,
|
||||
// Wire handling
|
||||
(tile, edges) => {
|
||||
if (! tile.circuits) {
|
||||
tile.circuits = [null, null, null, null];
|
||||
}
|
||||
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
||||
if (edges & dirinfo.bit) {
|
||||
tile.circuits[dirinfo.index] = circuit;
|
||||
}
|
||||
}
|
||||
add_to_edge_map(circuit.tiles, tile, edges);
|
||||
if (tile.type.on_power) {
|
||||
// Red teleporters contain wires and /also/ have an on_power
|
||||
// FIXME this isn't quite right since there's seemingly a 1-frame delay
|
||||
wired_outputs.add(tile);
|
||||
}
|
||||
|
||||
if (tile.type.is_power_source) {
|
||||
// TODO could just do this in a pass afterwards
|
||||
add_to_edge_map(circuit.inputs, tile, edges);
|
||||
}
|
||||
},
|
||||
// Dead end handling (potentially logic gates, etc.)
|
||||
(cell, edge) => {
|
||||
for (let tile of cell) {
|
||||
if (! tile) {
|
||||
continue;
|
||||
}
|
||||
else if (tile.type.name === 'logic_gate') {
|
||||
// Logic gates are the one non-wired tile that get attached to circuits,
|
||||
// mostly so blue teleporters can follow them
|
||||
if (! tile.circuits) {
|
||||
tile.circuits = [null, null, null, null];
|
||||
}
|
||||
tile.circuits[DIRECTIONS[edge].index] = circuit;
|
||||
|
||||
let wire = tile.type._gate_types[tile.gate_type][
|
||||
(DIRECTIONS[edge].index - DIRECTIONS[tile.direction].index + 4) % 4];
|
||||
if (! wire)
|
||||
return;
|
||||
add_to_edge_map(circuit.tiles, tile, DIRECTIONS[edge].bit);
|
||||
if (wire.match(/^out/)) {
|
||||
add_to_edge_map(circuit.inputs, tile, DIRECTIONS[edge].bit);
|
||||
}
|
||||
}
|
||||
else if (tile.type.on_power) {
|
||||
// FIXME this isn't quite right since there's seemingly a 1-frame delay
|
||||
add_to_edge_map(circuit.tiles, tile, DIRECTIONS[edge].bit);
|
||||
wired_outputs.add(tile);
|
||||
}
|
||||
}
|
||||
},
|
||||
let circuit = algorithms.trace_floor_circuit(
|
||||
this, this.compat.tiles_react_instantly ? 'always' : 'still',
|
||||
terrain.cell, direction,
|
||||
);
|
||||
this.circuits.push(circuit);
|
||||
|
||||
// All wires are explicitly off when the level starts
|
||||
// TODO what does this mean for recomputing due to circuit blocks? might we send a
|
||||
// pulse despite the wires never visibly turning off??
|
||||
// TODO wait what happens if you push a circuit block on a pink button...
|
||||
if (first_time) {
|
||||
circuit.is_powered = false;
|
||||
}
|
||||
|
||||
// Search the circuit for tiles that act as outputs, so we can check whether to
|
||||
// update them during each wire phase
|
||||
for (let [tile, edges] of circuit.tiles) {
|
||||
seen_edges.set((seen_edges.get(tile) ?? 0) | edges);
|
||||
if (tile.type.on_power) {
|
||||
wired_outputs.add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make an index of cell indices to the circuits they belong to
|
||||
this.cells_to_circuits = new Map;
|
||||
for (let circuit of this.circuits) {
|
||||
for (let tile of circuit.tiles.keys()) {
|
||||
let n = this.cell_to_scalar(tile.cell);
|
||||
let set = this.cells_to_circuits.get(n);
|
||||
if (! set) {
|
||||
set = new Set;
|
||||
this.cells_to_circuits.set(n, set);
|
||||
}
|
||||
set.add(circuit);
|
||||
}
|
||||
}
|
||||
|
||||
this.wired_outputs = Array.from(wired_outputs);
|
||||
this.wired_outputs.sort((a, b) => this.coords_to_scalar(b.cell.x, b.cell.y) - this.coords_to_scalar(a.cell.x, a.cell.y));
|
||||
|
||||
@ -2349,10 +2314,8 @@ export class Level extends LevelInterface {
|
||||
let wired_tile = actor.cell.get_wired_tile();
|
||||
if (wired_tile && (wired_tile === actor || wired_tile.type.name === 'floor' || wired_tile.type.name === 'electrified_floor')) {
|
||||
emitting = wired_tile.wire_directions;
|
||||
for (let circuit of wired_tile.circuits) {
|
||||
if (circuit) {
|
||||
externally_powered_circuits.add(circuit);
|
||||
}
|
||||
for (let circuit of this.cells_to_circuits.get(this.cell_to_scalar(wired_tile.cell))) {
|
||||
externally_powered_circuits.add(circuit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1808,14 +1808,14 @@ const TILE_TYPES = {
|
||||
// Anyway, let's do a breadth-first search for teleporters.
|
||||
let walked_circuits = new Set;
|
||||
let candidate_teleporters = new Set;
|
||||
let circuits = me.circuits;
|
||||
let circuits = [...level.cells_to_circuits.get(level.cell_to_scalar(me.cell))];
|
||||
for (let i = 0; i < circuits.length; i++) {
|
||||
let circuit = circuits[i];
|
||||
if (! circuit || walked_circuits.has(circuit))
|
||||
continue;
|
||||
walked_circuits.add(circuit);
|
||||
|
||||
for (let [tile, edges] of circuit.tiles.entries()) {
|
||||
for (let tile of circuit.tiles.keys()) {
|
||||
if (tile.type === me.type || tile.type.name === 'teleport_blue_exit') {
|
||||
candidate_teleporters.add(tile);
|
||||
}
|
||||
@ -1823,7 +1823,7 @@ const TILE_TYPES = {
|
||||
// This logic gate is functioning as an output, so walk through it and also
|
||||
// trace any circuits that treat it as an input (as long as those circuits
|
||||
// are currently powered)
|
||||
for (let subcircuit of tile.circuits) {
|
||||
for (let subcircuit of level.cells_to_circuits.get(level.cell_to_scalar(tile.cell))) {
|
||||
if (subcircuit && subcircuit.is_powered && subcircuit.inputs.get(tile)) {
|
||||
circuits.push(subcircuit);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user