🚀 refactor getBestRoute
This commit is contained in:
@@ -3,7 +3,7 @@ import * as ParkMiller from "park-miller";
|
||||
import { generateRandomGrid } from "../generateGrid";
|
||||
import { Snake } from "../snake";
|
||||
import { Grid } from "../grid";
|
||||
import { computeBestRun } from "..";
|
||||
import { getBestRoute } from "../getBestRoute";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
const snake0 = [
|
||||
@@ -19,16 +19,18 @@ const gameOptions = {
|
||||
colors: [1, 2, 3, 4],
|
||||
};
|
||||
|
||||
const MAX_DURATION = 60 * 1000;
|
||||
const MAX_ITERATION = 10;
|
||||
|
||||
const run = (grid: Grid, snake0: Snake, k: number) => {
|
||||
const stats: number[] = [];
|
||||
const s0 = performance.now();
|
||||
|
||||
const M = 60 * 1000;
|
||||
let n = 40;
|
||||
let n = 0;
|
||||
|
||||
while (performance.now() - s0 < M && n-- > 0) {
|
||||
while (performance.now() - s0 < MAX_DURATION && n++ < MAX_ITERATION) {
|
||||
const s = performance.now();
|
||||
computeBestRun(grid, snake0, gameOptions, k);
|
||||
getBestRoute(grid, snake0, gameOptions, k);
|
||||
|
||||
stats.push(performance.now() - s);
|
||||
}
|
||||
@@ -59,17 +61,19 @@ const report = (arr: number[]) => {
|
||||
|
||||
[
|
||||
//
|
||||
[10, 10, 1000],
|
||||
[21, 7, 1000],
|
||||
[42, 7, 1000],
|
||||
[42, 7, 5000],
|
||||
[42, 7, 14000],
|
||||
[42, 7, 30000],
|
||||
[10, 10, 100],
|
||||
[30, 7, 100],
|
||||
[52, 7, 100],
|
||||
|
||||
[10, 10, 800],
|
||||
[30, 7, 800],
|
||||
[52, 7, 800],
|
||||
].forEach(([w, h, k]) => {
|
||||
const random = new ParkMiller(1);
|
||||
const random = new ParkMiller(10);
|
||||
const grid = generateRandomGrid(w, h, { ...gameOptions, emptyP: 3 }, (a, b) =>
|
||||
random.integerInRange(a, b)
|
||||
random.integerInRange(a, b - 1)
|
||||
);
|
||||
const stats = run(grid, snake0, k);
|
||||
|
||||
console.log(`${w}x${h} : ${k}\n ${report(stats)}\n`);
|
||||
});
|
||||
|
||||
@@ -9,18 +9,6 @@ it("should find no routes in empty grid", () => {
|
||||
expect(getAvailableRoutes(grid, snake, options)).toEqual([]);
|
||||
});
|
||||
|
||||
xit("should find no routes in empty grid", () => {
|
||||
const grid = createEmptyGrid(100, 100);
|
||||
const snake = [{ x: 2, y: 2 }];
|
||||
const options = { maxSnakeLength: 1 };
|
||||
|
||||
const s = Date.now();
|
||||
|
||||
getAvailableRoutes(grid, snake, options);
|
||||
|
||||
console.log(Date.now() - s);
|
||||
});
|
||||
|
||||
it("should find one route in single cell grid", () => {
|
||||
const grid = createEmptyGrid(10, 10);
|
||||
setColor(grid, 3, 2, 3);
|
||||
@@ -1,12 +1,6 @@
|
||||
import { Grid, isInsideLarge, getColor, isInside, Color } from "./grid";
|
||||
import { around4, Point, pointEquals } from "./point";
|
||||
import {
|
||||
// copySnake,
|
||||
// snakeSelfCollide,
|
||||
snakeWillSelfCollide,
|
||||
Snake,
|
||||
copySnake,
|
||||
} from "./snake";
|
||||
import { snakeWillSelfCollide, Snake } from "./snake";
|
||||
|
||||
const computeSnakeKey = (snake: Snake) =>
|
||||
snake.map((p) => p.x + "." + p.y).join(",");
|
||||
@@ -40,9 +34,11 @@ const snakeEquals = (a: Snake, b: Snake, n = 99999) => {
|
||||
export const getAvailableRoutes = (
|
||||
grid: Grid,
|
||||
snake0: Snake,
|
||||
options: any,
|
||||
maxSolutions: number = 10,
|
||||
maxLengthEquality: number = 1
|
||||
options: { maxSnakeLength: number },
|
||||
maxSolutions = 10,
|
||||
maxLengthEquality = 1,
|
||||
maxWeight = 30,
|
||||
maxIterations = 500
|
||||
) => {
|
||||
const openList: I[] = [
|
||||
{
|
||||
@@ -55,7 +51,7 @@ export const getAvailableRoutes = (
|
||||
// heuristic
|
||||
h: 0,
|
||||
|
||||
// fitness, more is better
|
||||
// fitness, smaller is better
|
||||
f: 0,
|
||||
parent: null,
|
||||
},
|
||||
@@ -64,17 +60,18 @@ export const getAvailableRoutes = (
|
||||
|
||||
const solutions: { color: Color; snakeN: Snake; directions: Point[] }[] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
debugger;
|
||||
|
||||
while (openList.length && i++ < 5000 && solutions.length < maxSolutions) {
|
||||
openList.sort((a, b) => b.f - a.f);
|
||||
while (
|
||||
openList.length &&
|
||||
maxIterations-- > 0 &&
|
||||
openList[0].w <= maxWeight &&
|
||||
solutions.length < maxSolutions
|
||||
) {
|
||||
openList.sort((a, b) => a.f - b.f);
|
||||
const c = openList.shift()!;
|
||||
|
||||
closeList[c.key] = c;
|
||||
|
||||
snakeSteps.push(copySnake(c.snake));
|
||||
// snakeSteps.push(copySnake(c.snake));
|
||||
|
||||
const [head] = c.snake;
|
||||
const color =
|
||||
@@ -114,7 +111,7 @@ export const getAvailableRoutes = (
|
||||
|
||||
const w = 1 + c.w;
|
||||
|
||||
const f = -w;
|
||||
const f = w;
|
||||
// const f = h * 0.6 - w;
|
||||
|
||||
openList.push({ key, snake, f, w, h, parent: c });
|
||||
|
||||
152
packages/compute/getBestRoute.ts
Normal file
152
packages/compute/getBestRoute.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Grid, Color, copyGrid, getColor, setColor } from "./grid";
|
||||
import { Point } from "./point";
|
||||
import { Snake } from "./snake";
|
||||
import { getAvailableRoutes } from "./getAvailableRoutes";
|
||||
|
||||
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
|
||||
|
||||
const createComputeHeuristic = (
|
||||
grid0: Grid,
|
||||
_snake0: Snake,
|
||||
colors: Color[]
|
||||
) => {
|
||||
const colorCount: Record<Color, number> = {};
|
||||
for (let x = grid0.width; x--; )
|
||||
for (let y = grid0.height; y--; ) {
|
||||
const c = getColor(grid0, x, y);
|
||||
if (c !== null) colorCount[c] = 1 + (colorCount[c] || 0);
|
||||
}
|
||||
|
||||
const values = colors
|
||||
.map((k) => Array.from({ length: colorCount[k] }, () => k))
|
||||
.flat();
|
||||
|
||||
return (_grid: Grid, _snake: Snake, stack: Color[]) => {
|
||||
let score = 0;
|
||||
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
if (stack[i] === values[i]) {
|
||||
score += 52;
|
||||
} else {
|
||||
const u = Math.abs(stack[i] - values[i]);
|
||||
|
||||
score += 5 - u;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
};
|
||||
|
||||
const computeKey = (grid: Grid, snake: Snake, stack: Color[]) =>
|
||||
grid.data.map((x) => x || 0).join("") +
|
||||
"|" +
|
||||
snake.map((p) => p.x + "." + p.y).join(",") +
|
||||
"|" +
|
||||
stack.join("");
|
||||
|
||||
type I = {
|
||||
h: number;
|
||||
f: number;
|
||||
w: number;
|
||||
key: string;
|
||||
snake: Snake;
|
||||
grid: Grid;
|
||||
stack: Color[];
|
||||
parent: I | null;
|
||||
directions: Point[];
|
||||
};
|
||||
|
||||
export const getBestRoute = (
|
||||
grid0: Grid,
|
||||
snake0: Snake,
|
||||
options: { maxSnakeLength: number; colors: Color[] },
|
||||
maxIterations = 500
|
||||
) => {
|
||||
const computeHeuristic = createComputeHeuristic(
|
||||
grid0,
|
||||
snake0,
|
||||
options.colors
|
||||
);
|
||||
|
||||
const closeList: Record<string, I> = {};
|
||||
const openList: I[] = [];
|
||||
|
||||
{
|
||||
const h = computeHeuristic(grid0, snake0, []);
|
||||
const w = 0;
|
||||
const f = h + w;
|
||||
openList.push({
|
||||
key: computeKey(grid0, snake0, []),
|
||||
grid: grid0,
|
||||
snake: snake0,
|
||||
stack: [],
|
||||
parent: null,
|
||||
f,
|
||||
h,
|
||||
w,
|
||||
directions: [],
|
||||
});
|
||||
}
|
||||
|
||||
let best = openList[0];
|
||||
|
||||
while (openList.length && maxIterations-- > 0) {
|
||||
openList.sort((a, b) => a.f - b.f);
|
||||
const c = openList.shift()!;
|
||||
|
||||
closeList[c.key] = c;
|
||||
|
||||
if (c.f < best.f) best = c;
|
||||
|
||||
if (!isGridEmpty(c.grid)) {
|
||||
const availableRoutes = getAvailableRoutes(
|
||||
c.grid,
|
||||
c.snake,
|
||||
options,
|
||||
30,
|
||||
1,
|
||||
20,
|
||||
500
|
||||
);
|
||||
|
||||
for (const route of availableRoutes) {
|
||||
const stack = c.stack.slice();
|
||||
const grid = copyGrid(c.grid);
|
||||
const snake = route.snakeN;
|
||||
|
||||
const { x, y } = route.snakeN[0];
|
||||
|
||||
stack.push(getColor(grid, x, y)!);
|
||||
setColor(grid, x, y, null);
|
||||
|
||||
const key = computeKey(grid, snake, stack);
|
||||
|
||||
if (!closeList[key] && !openList.some((s) => s.key === key)) {
|
||||
const h = computeHeuristic(grid, snake, stack);
|
||||
const w = c.w + route.directions.length;
|
||||
const f = w - h;
|
||||
|
||||
openList.push({
|
||||
key,
|
||||
grid,
|
||||
snake,
|
||||
stack,
|
||||
parent: c,
|
||||
h,
|
||||
w,
|
||||
f,
|
||||
directions: route.directions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unwrap(best);
|
||||
};
|
||||
|
||||
const unwrap = (o: I | null): Point[] => {
|
||||
if (!o) return [];
|
||||
return [...unwrap(o.parent), ...o.directions];
|
||||
};
|
||||
@@ -1,145 +0,0 @@
|
||||
import { Grid, Color, copyGrid, isInsideLarge, getColor } from "./grid";
|
||||
import { Point, around4 } from "./point";
|
||||
import { step } from "./step";
|
||||
import { copySnake, snakeSelfCollide, Snake } from "./snake";
|
||||
|
||||
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
|
||||
|
||||
const createComputeHeuristic = (
|
||||
grid0: Grid,
|
||||
_snake0: Snake,
|
||||
colors: Color[]
|
||||
) => {
|
||||
const colorCount: Record<Color, number> = {};
|
||||
for (let x = grid0.width; x--; )
|
||||
for (let y = grid0.height; y--; ) {
|
||||
const c = getColor(grid0, x, y);
|
||||
if (c !== null) colorCount[c] = 1 + (colorCount[c] || 0);
|
||||
}
|
||||
|
||||
const values = colors
|
||||
.map((k) => Array.from({ length: colorCount[k] }, () => k))
|
||||
.flat();
|
||||
const weights = colors
|
||||
.map((k) =>
|
||||
Array.from({ length: colorCount[k] }).map(
|
||||
(_, i, arr) => i / (arr.length - 1)
|
||||
)
|
||||
)
|
||||
.flat();
|
||||
|
||||
return (_grid: Grid, _snake: Snake, stack: Color[]) => {
|
||||
let score = 0;
|
||||
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
const u = stack[i] - values[i];
|
||||
|
||||
if (u !== 0) debugger;
|
||||
|
||||
if (u > 0) score -= 100 * u * (1 + 1 - weights[i]);
|
||||
else if (u < 0) score -= 100 * -u * (1 + weights[i]);
|
||||
else score += 100;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
};
|
||||
|
||||
const computeKey = (grid: Grid, snake: Snake, stack: Color[]) =>
|
||||
grid.data.map((x) => x || 0).join("") +
|
||||
"|" +
|
||||
snake.map((p) => p.x + "." + p.y).join(",") +
|
||||
"|" +
|
||||
stack.join("");
|
||||
|
||||
const createCell = (
|
||||
key: string,
|
||||
grid: Grid,
|
||||
snake: Snake,
|
||||
stack: Color[],
|
||||
parent: any | null,
|
||||
heuristic: number
|
||||
) => ({
|
||||
key,
|
||||
parent,
|
||||
grid,
|
||||
snake,
|
||||
stack,
|
||||
weight: 1 + (parent?.weight || 0),
|
||||
f: heuristic - 0 * (1 + (parent?.weight || 0)),
|
||||
});
|
||||
|
||||
const unwrap = (c: ReturnType<typeof createCell> | null): Point[] =>
|
||||
c && c.parent
|
||||
? [
|
||||
...unwrap(c.parent),
|
||||
{ x: c.snake[0].x - c.snake[1].x, y: c.snake[0].y - c.snake[1].y },
|
||||
]
|
||||
: [];
|
||||
|
||||
export const computeBestRun = (
|
||||
grid0: Grid,
|
||||
snake0: Snake,
|
||||
options: { maxSnakeLength: number; colors: Color[] },
|
||||
u = 8000
|
||||
) => {
|
||||
const computeHeuristic = createComputeHeuristic(
|
||||
grid0,
|
||||
snake0,
|
||||
options.colors
|
||||
);
|
||||
|
||||
const closeList: any = {};
|
||||
const openList = [
|
||||
createCell(
|
||||
computeKey(grid0, snake0, []),
|
||||
grid0,
|
||||
snake0,
|
||||
[],
|
||||
null,
|
||||
computeHeuristic(grid0, snake0, [])
|
||||
),
|
||||
];
|
||||
|
||||
let best = openList[0];
|
||||
|
||||
while (openList.length && u-- > 0) {
|
||||
openList.sort((a, b) => b.f - a.f);
|
||||
const c = openList.shift()!;
|
||||
|
||||
closeList[c.key] = true;
|
||||
|
||||
if (isGridEmpty(c.grid)) return unwrap(c);
|
||||
|
||||
if (c.f > best.f) best = c;
|
||||
|
||||
for (const direction of around4) {
|
||||
const snake = copySnake(c.snake);
|
||||
const stack = c.stack.slice();
|
||||
const grid = copyGrid(c.grid);
|
||||
|
||||
step(grid, snake, stack, direction, options);
|
||||
|
||||
const key = computeKey(grid, snake, stack);
|
||||
|
||||
if (
|
||||
!closeList[key] &&
|
||||
isInsideLarge(grid, 1, snake[0].x, snake[0].y) &&
|
||||
!snakeSelfCollide(snake)
|
||||
) {
|
||||
openList.push(
|
||||
createCell(
|
||||
key,
|
||||
grid,
|
||||
snake,
|
||||
stack,
|
||||
c,
|
||||
computeHeuristic(grid, snake, stack)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unwrap(best);
|
||||
};
|
||||
Reference in New Issue
Block a user