lexys-labyrinth/js/algorithms.js
Eevee (Evelyn Woods) 3cf81b53ad Improve the connection tool somewhat; show implicit connections
For example, you can now make connections with the connection tool.
Remarkable.

Unfortunately, implicit connections aren't updated as you edit the level
yet.

Also came with some refactors for searching a level and whatnot.
2024-04-18 00:56:20 -06:00

184 lines
6.4 KiB
JavaScript

import { DIRECTIONS, LAYERS } from './defs.js';
// Iterates over every terrain tile in the grid that has one of the given types (a Set of type
// names), in linear order, optionally in reverse. The starting cell is checked last.
// Yields [tile, cell].
export function* find_terrain_linear(levelish, start_cell, type_names, reverse = false) {
let i = levelish.coords_to_scalar(start_cell.x, start_cell.y);
while (true) {
if (reverse) {
i -= 1;
if (i < 0) {
i += levelish.size_x * levelish.size_y;
}
}
else {
i += 1;
i %= levelish.size_x * levelish.size_y;
}
let cell = levelish.linear_cells[i];
let tile = cell[LAYERS.terrain];
if (tile && type_names.has(tile.type.name)) {
yield [tile, cell];
}
if (cell === start_cell)
return;
}
}
// Iterates over every terrain tile in the grid that has one of the given types (a Set of type
// names), spreading outward in a diamond pattern. The starting cell is not included.
// Only used by orange buttons.
// Yields [tile, cell].
export function* find_terrain_diamond(levelish, start_cell, type_names) {
let max_search_radius = (
Math.max(start_cell.x, levelish.size_x - start_cell.x) +
Math.max(start_cell.y, levelish.size_y - start_cell.y));
for (let dist = 1; dist <= max_search_radius; dist++) {
// Start east and move counterclockwise
let sx = start_cell.x + dist;
let sy = start_cell.y;
for (let direction of [[-1, -1], [-1, 1], [1, 1], [1, -1]]) {
for (let i = 0; i < dist; i++) {
let cell = levelish.cell(sx, sy);
sx += direction[0];
sy += direction[1];
if (! cell)
continue;
let terrain = cell[LAYERS.terrain];
if (type_names.has(terrain.type.name)) {
yield [terrain, cell];
}
}
}
}
}
// TODO make this guy work generically for orange, red, brown buttons? others...?
export function find_implicit_connection() {
}
export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_dead_end) {
let is_first = true;
let pending = [[start_cell, start_edge]];
let seen_cells = new Map;
while (pending.length > 0) {
let next = [];
for (let [cell, edge] of pending) {
let terrain = cell.get_terrain();
if (! terrain)
continue;
let edgeinfo = DIRECTIONS[edge];
let seen_edges = seen_cells.get(cell) ?? 0;
if (seen_edges & edgeinfo.bit)
continue;
let tile = terrain;
let actor = cell.get_actor();
if (actor && actor.type.contains_wire && (
actor.movement_cooldown === 0 || level.compat.tiles_react_instantly))
{
tile = actor;
}
// The wire comes in from this edge towards the center; see how it connects within this
// cell, then check for any neighbors
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);
}
continue;
}
else if (mode === 'none') {
// The wires in this tile never connect to each other
}
else if (mode === 'cross' || (mode === 'autocross' && tile.wire_directions === 0x0f)) {
// This is a cross pattern, so only opposite edges connect
if (tile.wire_directions & edgeinfo.opposite_bit) {
connections |= edgeinfo.opposite_bit;
}
}
else {
// Everything connects
connections |= tile.wire_directions;
}
seen_cells.set(cell, seen_edges | connections);
if (on_wire) {
on_wire(tile, connections);
}
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
// Obviously don't go backwards, but that doesn't apply if this is our first pass
if (direction === edge && ! is_first)
continue;
if ((connections & dirinfo.bit) === 0)
continue;
let neighbor;
if ((terrain.wire_tunnel_directions ?? 0) & dirinfo.bit) {
// 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);
}
else {
neighbor = level.get_neighboring_cell(cell, direction);
}
/*
if (! neighbor || (((neighbor.get_terrain().wire_directions ?? 0) & dirinfo.opposite_bit) === 0)) {
console.log("bailing here", neighbor, direction);
continue;
}
*/
if (! neighbor)
continue;
next.push([neighbor, dirinfo.opposite]);
}
}
pending = next;
is_first = false;
}
}
export function find_matching_wire_tunnel(level, 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);
if (! candidate)
return null;
let neighbor = candidate.get_terrain();
if (! neighbor)
continue;
if ((neighbor.wire_tunnel_directions ?? 0) & dirinfo.opposite_bit) {
if (nesting === 0) {
return candidate;
}
else {
nesting -= 1;
}
}
if ((neighbor.wire_tunnel_directions ?? 0) & dirinfo.bit) {
nesting += 1;
}
}
}