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 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 is_first = true;
|
||||||
let pending = [[start_cell, start_edge]];
|
let pending = [[start_cell, start_edge]];
|
||||||
let seen_cells = new Map;
|
let seen_cells = new Map;
|
||||||
|
let circuit = new Circuit;
|
||||||
while (pending.length > 0) {
|
while (pending.length > 0) {
|
||||||
let next = [];
|
let next = [];
|
||||||
for (let [cell, edge] of pending) {
|
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 tile = terrain;
|
||||||
let actor = cell.get_actor();
|
let actor = cell.get_actor();
|
||||||
if (actor && actor.type.contains_wire && (
|
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;
|
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 connections = edgeinfo.bit;
|
||||||
let mode = tile.wire_propagation_mode ?? tile.type.wire_propagation_mode;
|
let mode = tile.wire_propagation_mode ?? tile.type.wire_propagation_mode;
|
||||||
if (! is_first && ((tile.wire_directions ?? 0) & edgeinfo.bit) === 0) {
|
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
|
// There's not actually a wire here, so check for things that respond to receiving
|
||||||
// case we trust the caller)
|
// power... but if this is the starting cell, we trust the caller and skip it (XXX why)
|
||||||
if (on_dead_end) {
|
for (let tile2 of cell) {
|
||||||
on_dead_end(cell, edge);
|
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;
|
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);
|
seen_cells.set(cell, seen_edges | connections);
|
||||||
|
|
||||||
if (on_wire) {
|
circuit.add_tile_edge(tile, connections);
|
||||||
on_wire(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)) {
|
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
|
// Search in this direction for a matching tunnel
|
||||||
// Note that while actors (the fuckin circuit block) can be wired, tunnels ONLY
|
// Note that while actors (the fuckin circuit block) can be wired, tunnels ONLY
|
||||||
// appear on terrain, and are NOT affected by actors on top
|
// 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 {
|
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;
|
pending = next;
|
||||||
is_first = false;
|
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 dirinfo = DIRECTIONS[direction];
|
||||||
let [dx, dy] = dirinfo.movement;
|
let [dx, dy] = dirinfo.movement;
|
||||||
let nesting = 0;
|
let nesting = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
x += dx;
|
x += dx;
|
||||||
y += dy;
|
y += dy;
|
||||||
let candidate = level.cell(x, y);
|
let candidate = levelish.cell(x, y);
|
||||||
if (! candidate)
|
if (! candidate)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,10 @@ export class LevelInterface {
|
|||||||
return x + y * this.size_x;
|
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) {
|
is_point_within_bounds(x, y) {
|
||||||
return (x >= 0 && x < this.size_x && y >= 0 && y < this.size_y);
|
return (x >= 0 && x < this.size_x && y >= 0 && y < this.size_y);
|
||||||
}
|
}
|
||||||
|
|||||||
115
js/game.js
115
js/game.js
@ -520,21 +520,10 @@ export class Level extends LevelInterface {
|
|||||||
// Build circuits out of connected wires
|
// Build circuits out of connected wires
|
||||||
// TODO document this idea
|
// 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.circuits = [];
|
||||||
this.power_sources = [];
|
this.power_sources = [];
|
||||||
let wired_outputs = new Set;
|
let wired_outputs = new Set;
|
||||||
this.wired_outputs = [];
|
let seen_edges = new Map;
|
||||||
let add_to_edge_map = (map, item, edges) => {
|
|
||||||
map.set(item, (map.get(item) ?? 0) | edges);
|
|
||||||
};
|
|
||||||
for (let cell of this.linear_cells) {
|
for (let cell of this.linear_cells) {
|
||||||
// We're interested in static circuitry, which means terrain
|
// We're interested in static circuitry, which means terrain
|
||||||
// OR circuit blocks on top
|
// OR circuit blocks on top
|
||||||
@ -585,73 +574,49 @@ export class Level extends LevelInterface {
|
|||||||
if (! ((wire_directions | terrain.wire_tunnel_directions) & dirinfo.bit))
|
if (! ((wire_directions | terrain.wire_tunnel_directions) & dirinfo.bit))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (terrain.circuits && terrain.circuits[dirinfo.index])
|
if ((seen_edges.get(terrain) ?? 0) & dirinfo.bit)
|
||||||
continue;
|
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
|
// At last, a wired cell edge we have not yet handled. Floodfill from here
|
||||||
algorithms.trace_floor_circuit(
|
let circuit = algorithms.trace_floor_circuit(
|
||||||
this, terrain.cell, direction,
|
this, this.compat.tiles_react_instantly ? 'always' : 'still',
|
||||||
// Wire handling
|
terrain.cell, direction,
|
||||||
(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
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 = 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));
|
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,13 +2314,11 @@ export class Level extends LevelInterface {
|
|||||||
let wired_tile = actor.cell.get_wired_tile();
|
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')) {
|
if (wired_tile && (wired_tile === actor || wired_tile.type.name === 'floor' || wired_tile.type.name === 'electrified_floor')) {
|
||||||
emitting = wired_tile.wire_directions;
|
emitting = wired_tile.wire_directions;
|
||||||
for (let circuit of wired_tile.circuits) {
|
for (let circuit of this.cells_to_circuits.get(this.cell_to_scalar(wired_tile.cell))) {
|
||||||
if (circuit) {
|
|
||||||
externally_powered_circuits.add(circuit);
|
externally_powered_circuits.add(circuit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (emitting !== actor.emitting_edges) {
|
if (emitting !== actor.emitting_edges) {
|
||||||
any_changed = true;
|
any_changed = true;
|
||||||
this._set_tile_prop(actor, 'emitting_edges', emitting);
|
this._set_tile_prop(actor, 'emitting_edges', emitting);
|
||||||
|
|||||||
@ -1808,14 +1808,14 @@ const TILE_TYPES = {
|
|||||||
// Anyway, let's do a breadth-first search for teleporters.
|
// Anyway, let's do a breadth-first search for teleporters.
|
||||||
let walked_circuits = new Set;
|
let walked_circuits = new Set;
|
||||||
let candidate_teleporters = 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++) {
|
for (let i = 0; i < circuits.length; i++) {
|
||||||
let circuit = circuits[i];
|
let circuit = circuits[i];
|
||||||
if (! circuit || walked_circuits.has(circuit))
|
if (! circuit || walked_circuits.has(circuit))
|
||||||
continue;
|
continue;
|
||||||
walked_circuits.add(circuit);
|
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') {
|
if (tile.type === me.type || tile.type.name === 'teleport_blue_exit') {
|
||||||
candidate_teleporters.add(tile);
|
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
|
// 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
|
// trace any circuits that treat it as an input (as long as those circuits
|
||||||
// are currently powered)
|
// 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)) {
|
if (subcircuit && subcircuit.is_powered && subcircuit.inputs.get(tile)) {
|
||||||
circuits.push(subcircuit);
|
circuits.push(subcircuit);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user