Files
snk/packages/compute/pruneLayer.ts

131 lines
3.2 KiB
TypeScript

import { getColor, isEmpty, isInside, setColorEmpty } from "@snk/types/grid";
import { around4 } from "@snk/types/point";
import { sortPush } from "./utils/sortPush";
import type { Color, Grid } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
import {
createSnakeFromCells,
getHeadX,
getHeadY,
nextSnake,
Snake,
snakeEquals,
snakeWillSelfCollide,
} from "@snk/types/snake";
type M = Point & { parent: M | null; h: number };
const unwrap = (m: M | null): Point[] => (m ? [...unwrap(m.parent), m] : []);
const getEscapePath = (grid: Grid, x: number, y: number, color: Color) => {
const openList: M[] = [{ x, y, h: 0, parent: null }];
const closeList: Point[] = [];
while (openList.length) {
const c = openList.shift()!;
if (c.y === -1 || c.y === grid.height) return unwrap(c);
for (const a of around4) {
const x = c.x + a.x;
const y = c.y + a.y;
if (!isInside(grid, x, y)) return unwrap({ x, y, parent: c } as any);
const u = getColor(grid, x, y);
if (
(isEmpty(u) || u <= color) &&
!closeList.some((cl) => cl.x === x && cl.y === y)
) {
const h = Math.abs(grid.height / 2 - y);
const o = { x, y, parent: c, h };
sortPush(openList, o, (a, b) => a.h - b.h);
closeList.push(o);
}
}
}
return null;
};
/**
* returns true if the snake can reach outside from it's location
*/
const snakeCanEscape = (grid: Grid, snake: Snake, color: Color) => {
const openList: Snake[] = [snake];
const closeList: Snake[] = [];
while (openList.length) {
const s = openList.shift()!;
for (const a of around4) {
if (!snakeWillSelfCollide(s, a.x, a.y)) {
const x = getHeadX(s) + a.x;
const y = getHeadY(s) + a.y;
if (!isInside(grid, x, y)) return true;
const u = getColor(grid, x, y);
if (isEmpty(u) || u <= color) {
const sn = nextSnake(s, a.x, a.y);
if (!closeList.some((s0) => snakeEquals(s0, sn))) {
openList.push(sn);
closeList.push(sn);
}
}
}
}
}
return false;
};
/**
* returns true if the cell can be reached by the snake from outside, and the snake can go back outside
*/
const isFree = (
grid: Grid,
x: number,
y: number,
color: Color,
snakeN: number
) => {
// get the first path to escape
const firstPath = getEscapePath(grid, x, y, color);
if (!firstPath) return false;
// build a snake from the path
// /!\ it might be not a valid snake as we stack up the queue if the path is too short
const s = firstPath.slice(0, snakeN);
while (s.length < snakeN) s.push(s[s.length - 1]);
const snake1 = createSnakeFromCells(s);
// check for a second route, considering snake collision
return snakeCanEscape(grid, snake1, color);
};
/**
* returns free cells
* and removes them from the grid
*/
export const pruneLayer = (grid: Grid, color: Color, snakeN: number) => {
const chunk: Point[] = [];
for (let x = grid.width; x--; )
for (let y = grid.height; y--; ) {
const c = getColor(grid, x, y);
if (!isEmpty(c) && c <= color && isFree(grid, x, y, color, snakeN)) {
setColorEmpty(grid, x, y);
chunk.push({ x, y });
}
}
return chunk;
};