🚀 prune layer algorithm
This commit is contained in:
@@ -29,6 +29,13 @@ setColor(enclave, 4, 3, 2 as Color);
|
||||
setColor(enclave, 3, 3, 1 as Color);
|
||||
setColor(enclave, 5, 5, 1 as Color);
|
||||
|
||||
// enclaved color
|
||||
export const enclaveBorder = createEmptyGrid(7, 7);
|
||||
setColor(enclaveBorder, 1, 0, 3 as Color);
|
||||
setColor(enclaveBorder, 2, 1, 3 as Color);
|
||||
setColor(enclaveBorder, 3, 0, 3 as Color);
|
||||
setColor(enclaveBorder, 2, 0, 1 as Color);
|
||||
|
||||
const create = (width: number, height: number, emptyP: number) => {
|
||||
const grid = createEmptyGrid(width, height);
|
||||
const random = new ParkMiller(10);
|
||||
|
||||
86
packages/compute/__tests__/sortPush.spec.ts
Normal file
86
packages/compute/__tests__/sortPush.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { sortPush } from "../utils/sortPush";
|
||||
|
||||
const sortFn = (a: number, b: number) => a - b;
|
||||
|
||||
it("should sort push length=0", () => {
|
||||
const a: any[] = [];
|
||||
const x = -1;
|
||||
const res = [...a, x].sort(sortFn);
|
||||
|
||||
sortPush(a, x, sortFn);
|
||||
|
||||
expect(a).toEqual(res);
|
||||
});
|
||||
|
||||
it("should sort push under", () => {
|
||||
const a = [1, 2, 3, 4, 5];
|
||||
const x = -1;
|
||||
const res = [...a, x].sort(sortFn);
|
||||
|
||||
sortPush(a, x, sortFn);
|
||||
|
||||
expect(a).toEqual(res);
|
||||
});
|
||||
|
||||
it("should sort push 0", () => {
|
||||
const a = [1, 2, 3, 4, 5];
|
||||
const x = 1;
|
||||
const res = [...a, x].sort(sortFn);
|
||||
|
||||
sortPush(a, x, sortFn);
|
||||
|
||||
expect(a).toEqual(res);
|
||||
});
|
||||
|
||||
it("should sort push end", () => {
|
||||
const a = [1, 2, 3, 4, 5];
|
||||
const x = 5;
|
||||
const res = [...a, x].sort(sortFn);
|
||||
|
||||
sortPush(a, x, sortFn);
|
||||
|
||||
expect(a).toEqual(res);
|
||||
});
|
||||
|
||||
it("should sort push over", () => {
|
||||
const a = [1, 2, 3, 4, 5];
|
||||
const x = 10;
|
||||
const res = [...a, x].sort(sortFn);
|
||||
|
||||
sortPush(a, x, sortFn);
|
||||
|
||||
expect(a).toEqual(res);
|
||||
});
|
||||
|
||||
it("should sort push inside", () => {
|
||||
const a = [1, 2, 3, 4, 5];
|
||||
const x = 1.5;
|
||||
const res = [...a, x].sort(sortFn);
|
||||
|
||||
sortPush(a, x, sortFn);
|
||||
|
||||
expect(a).toEqual(res);
|
||||
});
|
||||
|
||||
describe("benchmark", () => {
|
||||
const n = 200;
|
||||
|
||||
const samples = Array.from({ length: 5000 }, () => [
|
||||
Math.random(),
|
||||
Array.from({ length: n }, () => Math.random()),
|
||||
]);
|
||||
const s0 = samples.map(([x, arr]: any) => [x, arr.slice()]);
|
||||
const s1 = samples.map(([x, arr]: any) => [x, arr.slice()]);
|
||||
|
||||
it("push + sort", () => {
|
||||
for (const [x, arr] of s0) {
|
||||
arr.push(x);
|
||||
arr.sort(sortFn);
|
||||
}
|
||||
});
|
||||
it("sortPush", () => {
|
||||
for (const [x, arr] of s1) {
|
||||
sortPush(arr, x, sortFn);
|
||||
}
|
||||
});
|
||||
});
|
||||
257
packages/compute/getBestRoute-layer.ts
Normal file
257
packages/compute/getBestRoute-layer.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import {
|
||||
Color,
|
||||
copyGrid,
|
||||
getColor,
|
||||
Grid,
|
||||
isEmpty,
|
||||
isInside,
|
||||
isInsideLarge,
|
||||
setColorEmpty,
|
||||
} from "./grid";
|
||||
import { around4 } from "./point";
|
||||
import {
|
||||
getHeadX,
|
||||
getHeadY,
|
||||
nextSnake,
|
||||
Snake,
|
||||
snakeEquals,
|
||||
snakeWillSelfCollide,
|
||||
} from "./snake";
|
||||
import { sortPush } from "./utils/sortPush";
|
||||
|
||||
type M = { x: number; y: number; parent: M | null; h: number };
|
||||
|
||||
const unwrap = (grid: Grid, m: M | null): { x: number; y: number }[] =>
|
||||
m ? [...unwrap(grid, m.parent), m] : [];
|
||||
|
||||
const getEscapePath = (
|
||||
grid: Grid,
|
||||
x: number,
|
||||
y: number,
|
||||
color: Color,
|
||||
forbidden: { x: number; y: number }[] = []
|
||||
) => {
|
||||
const openList: M[] = [{ x, y, h: 0, parent: null }];
|
||||
const closeList: { x: number; y: number }[] = [];
|
||||
|
||||
while (openList.length) {
|
||||
const c = openList.shift()!;
|
||||
|
||||
if (c.y === -1 || c.y === grid.height) return unwrap(grid, c);
|
||||
|
||||
for (const a of around4) {
|
||||
const x = c.x + a.x;
|
||||
const y = c.y + a.y;
|
||||
|
||||
if (!forbidden.some((cl) => cl.x === x && cl.y === y)) {
|
||||
if (!isInside(grid, x, y))
|
||||
return unwrap(grid, { 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 };
|
||||
|
||||
closeList.push(o);
|
||||
openList.push(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openList.sort((a, b) => a.h - b.h);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const isFree = (
|
||||
grid: Grid,
|
||||
x: number,
|
||||
y: number,
|
||||
color: Color,
|
||||
snakeN: number
|
||||
) => {
|
||||
const one = getEscapePath(grid, x, y, color);
|
||||
|
||||
if (!one) return false;
|
||||
|
||||
const two = getEscapePath(grid, x, y, color, one.slice(0, snakeN));
|
||||
|
||||
return !!two;
|
||||
};
|
||||
|
||||
export const pruneLayer = (grid: Grid, color: Color, snakeN: number) => {
|
||||
const chunk: { x: number; y: number }[] = [];
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const extractColors = (grid: Grid): Color[] => {
|
||||
const colors = new Set<Color>();
|
||||
grid.data.forEach((c: any) => {
|
||||
if (!isEmpty(c)) colors.add(c);
|
||||
});
|
||||
return Array.from(colors.keys()).sort();
|
||||
};
|
||||
|
||||
export const getAvailableRoutes = (
|
||||
grid: Grid,
|
||||
snake0: Snake,
|
||||
onSolution: (snakes: Snake[], color: Color) => boolean
|
||||
) => {
|
||||
const openList: Snake[][] = [[snake0]];
|
||||
const closeList: Snake[] = [];
|
||||
|
||||
while (openList.length) {
|
||||
const c = openList.shift()!;
|
||||
const [snake] = c;
|
||||
|
||||
const cx = getHeadX(snake);
|
||||
const cy = getHeadY(snake);
|
||||
|
||||
for (let i = 0; i < around4.length; i++) {
|
||||
const { x: dx, y: dy } = around4[i];
|
||||
|
||||
const nx = cx + dx;
|
||||
const ny = cy + dy;
|
||||
|
||||
if (
|
||||
isInsideLarge(grid, 1, nx, ny) &&
|
||||
!snakeWillSelfCollide(snake, dx, dy)
|
||||
) {
|
||||
const nsnake = nextSnake(snake, dx, dy);
|
||||
|
||||
if (!closeList.some((s) => snakeEquals(nsnake, s))) {
|
||||
const color = isInside(grid, nx, ny) && getColor(grid, nx, ny);
|
||||
|
||||
if (!color || isEmpty(color)) {
|
||||
sortPush(openList, [nsnake, ...c], (a, b) => a.length - b.length);
|
||||
closeList.push(nsnake);
|
||||
} else {
|
||||
if (onSolution([nsnake, ...c.slice(0, -1)], color)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getAvailableWhiteListedRoutes = (
|
||||
grid: Grid,
|
||||
snake: Snake,
|
||||
whiteList0: { x: number; y: number }[],
|
||||
n = 3
|
||||
) => {
|
||||
const whiteList = whiteList0.slice();
|
||||
const solutions: Snake[][] = [];
|
||||
|
||||
getAvailableRoutes(grid, snake, (chain) => {
|
||||
const hx = getHeadX(chain[0]);
|
||||
const hy = getHeadY(chain[0]);
|
||||
|
||||
const i = whiteList.findIndex(({ x, y }) => hx === x && hy === y);
|
||||
|
||||
if (i >= 0) {
|
||||
whiteList.splice(i, 1);
|
||||
solutions.push(chain);
|
||||
|
||||
if (solutions.length >= n || whiteList.length === 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return solutions;
|
||||
};
|
||||
|
||||
const arrayEquals = <T>(a: T[], b: T[]) =>
|
||||
a.length === b.length && a.every((_, i) => a[i] === b[i]);
|
||||
|
||||
type O = {
|
||||
snake: Snake;
|
||||
chain: Snake[];
|
||||
chunk: { x: number; y: number }[];
|
||||
grid: Grid;
|
||||
parent: O | null;
|
||||
};
|
||||
const uunwrap = (o: O | null): Snake[] =>
|
||||
!o ? [] : [...uunwrap(o.parent), ...o.chain.reverse()];
|
||||
|
||||
export const cleanLayer = (
|
||||
grid0: Grid,
|
||||
snake0: Snake,
|
||||
chunk0: { x: number; y: number }[]
|
||||
) => {
|
||||
const next = {
|
||||
grid: grid0,
|
||||
snake: snake0,
|
||||
chain: [snake0],
|
||||
chunk: chunk0,
|
||||
parent: null,
|
||||
};
|
||||
|
||||
const openList: O[] = [next];
|
||||
const closeList: O[] = [next];
|
||||
|
||||
while (openList.length) {
|
||||
const o = openList.shift()!;
|
||||
|
||||
if (o.chunk.length === 0) return uunwrap(o);
|
||||
|
||||
for (const chain of getAvailableWhiteListedRoutes(
|
||||
o.grid,
|
||||
o.snake,
|
||||
o.chunk,
|
||||
1
|
||||
)) {
|
||||
const snake = chain[0];
|
||||
const x = getHeadX(snake);
|
||||
const y = getHeadY(snake);
|
||||
|
||||
const chunk = o.chunk.filter((u) => u.x !== x || u.y !== y);
|
||||
|
||||
if (
|
||||
!closeList.some(
|
||||
(u) => snakeEquals(u.snake, snake) && arrayEquals(u.chunk, chunk)
|
||||
)
|
||||
) {
|
||||
const grid = copyGrid(o.grid);
|
||||
setColorEmpty(grid, x, y);
|
||||
|
||||
const next = { snake, chain, chunk, grid, parent: o };
|
||||
openList.push(next);
|
||||
closeList.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getBestRoute = (grid0: Grid, snake0: Snake) => {
|
||||
// for (const color of colors) {
|
||||
// const chunk = pruneLayer(grid0, color, snakeN);
|
||||
// layers.push({ chunk, grid: copyGrid(grid0) });
|
||||
// }
|
||||
|
||||
const grid = copyGrid(grid0);
|
||||
const colors = extractColors(grid0);
|
||||
|
||||
const chunk = pruneLayer(grid, colors[0], snake0.length / 2);
|
||||
|
||||
console.log(extractColors(grid0));
|
||||
|
||||
return cleanLayer(grid0, snake0, chunk);
|
||||
};
|
||||
22
packages/compute/utils/sortPush.ts
Normal file
22
packages/compute/utils/sortPush.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export const sortPush = <T>(arr: T[], x: T, sortFn: (a: T, b: T) => number) => {
|
||||
let a = 0;
|
||||
let b = arr.length;
|
||||
|
||||
if (arr.length === 0 || sortFn(x, arr[a]) <= 0) {
|
||||
arr.unshift(x);
|
||||
return;
|
||||
}
|
||||
|
||||
while (b - a > 1) {
|
||||
const e = Math.ceil((a + b) / 2);
|
||||
|
||||
const s = sortFn(x, arr[e]);
|
||||
|
||||
if (s === 0) a = b = e;
|
||||
else if (s > 0) a = e;
|
||||
else b = e;
|
||||
}
|
||||
|
||||
const e = Math.ceil((a + b) / 2);
|
||||
arr.splice(e, 0, x);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ const container = document.createElement("div");
|
||||
container.style.fontFamily = "helvetica";
|
||||
document.body.appendChild(container);
|
||||
|
||||
for (const demo of ["getAvailableRoutes", "getBestRoute"]) {
|
||||
for (const demo of require("./demo.json").filter((x: any) => x !== "index")) {
|
||||
const title = document.createElement("h1");
|
||||
title.innerText = demo;
|
||||
|
||||
@@ -14,7 +14,7 @@ for (const demo of ["getAvailableRoutes", "getBestRoute"]) {
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "block";
|
||||
a.innerText = `${demo} - ${g}`;
|
||||
a.href = `./demo-${demo}.html?grid=${g}`;
|
||||
a.href = `./${demo}.html?grid=${g}`;
|
||||
|
||||
container.appendChild(a);
|
||||
}
|
||||
|
||||
1
packages/demo/demo.json
Normal file
1
packages/demo/demo.json
Normal file
@@ -0,0 +1 @@
|
||||
["index", "getAvailableRoutes", "getBestRoute", "pruneLayer"]
|
||||
48
packages/demo/demo.pruneLayer.ts
Normal file
48
packages/demo/demo.pruneLayer.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { createCanvas } from "./canvas";
|
||||
import { Color, copyGrid } from "../compute/grid";
|
||||
import { grid, snake } from "./sample";
|
||||
import { pruneLayer } from "@snk/compute/getBestRoute-layer";
|
||||
|
||||
const colors = [1, 2, 3] as Color[];
|
||||
|
||||
const snakeN = snake.length / 2;
|
||||
|
||||
const layers = [{ grid, chunk: [] as { x: number; y: number }[] }];
|
||||
let grid0 = copyGrid(grid);
|
||||
for (const color of colors) {
|
||||
const chunk = pruneLayer(grid0, color, snakeN);
|
||||
layers.push({ chunk, grid: copyGrid(grid0) });
|
||||
}
|
||||
|
||||
const { canvas, ctx, draw } = createCanvas(grid);
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
let k = 0;
|
||||
|
||||
const loop = () => {
|
||||
const { grid, chunk } = layers[k];
|
||||
|
||||
draw(grid, snake, []);
|
||||
|
||||
ctx.fillStyle = "orange";
|
||||
chunk.forEach(({ x, y }) => {
|
||||
ctx.beginPath();
|
||||
ctx.fillRect((1 + x + 0.5) * 16 - 2, (2 + y + 0.5) * 16 - 2, 4, 4);
|
||||
});
|
||||
};
|
||||
|
||||
loop();
|
||||
|
||||
const input = document.createElement("input") as any;
|
||||
input.type = "range";
|
||||
input.value = 0;
|
||||
input.step = 1;
|
||||
input.min = 0;
|
||||
input.max = layers.length - 1;
|
||||
input.style.width = "90%";
|
||||
input.addEventListener("input", () => {
|
||||
k = +input.value;
|
||||
loop();
|
||||
});
|
||||
document.body.append(input);
|
||||
document.body.addEventListener("click", () => input.focus());
|
||||
@@ -8,13 +8,13 @@ const basePathname = (process.env.BASE_PATHNAME || "")
|
||||
.split("/")
|
||||
.filter(Boolean);
|
||||
|
||||
const demos: string[] = require("./demo.json");
|
||||
|
||||
const config: Configuration = {
|
||||
mode: "development",
|
||||
entry: {
|
||||
"demo.getAvailableRoutes": "./demo.getAvailableRoutes",
|
||||
"demo.getBestRoute": "./demo.getBestRoute",
|
||||
"demo.index": "./demo.index",
|
||||
},
|
||||
entry: Object.fromEntries(
|
||||
demos.map((demo: string) => [demo, `./demo.${demo}`])
|
||||
),
|
||||
resolve: { extensions: [".ts", ".js"] },
|
||||
output: {
|
||||
path: path.join(__dirname, "dist"),
|
||||
@@ -39,18 +39,13 @@ const config: Configuration = {
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "index.html",
|
||||
chunks: ["demo.index"],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "demo-getAvailableRoutes.html",
|
||||
chunks: ["demo.getAvailableRoutes"],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "demo-getBestRoute.html",
|
||||
chunks: ["demo.getBestRoute"],
|
||||
}),
|
||||
...demos.map(
|
||||
(demo) =>
|
||||
new HtmlWebpackPlugin({
|
||||
filename: `${demo}.html`,
|
||||
chunks: [demo],
|
||||
})
|
||||
),
|
||||
],
|
||||
|
||||
devtool: false,
|
||||
|
||||
Reference in New Issue
Block a user