🚀 smarter snake
This commit is contained in:
@@ -37,7 +37,10 @@ export const generateContributionSnake = async (userName: string) => {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = {
|
||||||
|
maxSnakeLength: 5,
|
||||||
|
colors: Array.from({ length: colorScheme.length - 1 }, (_, i) => i + 1),
|
||||||
|
};
|
||||||
|
|
||||||
const gifOptions = { delay: 10 };
|
const gifOptions = { delay: 10 };
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,167 @@
|
|||||||
import { Grid, Color, copyGrid, isInsideLarge } from "./grid";
|
import { Grid, Color, copyGrid, isInsideLarge, getColor } from "./grid";
|
||||||
import { Point, around4 } from "./point";
|
import { Point, around4 } from "./point";
|
||||||
import { stepSnake, step } from "./step";
|
import { step } from "./step";
|
||||||
import { copySnake, snakeSelfCollide } from "./snake";
|
import { copySnake, snakeSelfCollide, Snake } from "./snake";
|
||||||
|
|
||||||
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
|
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
|
||||||
|
|
||||||
export const computeBestRun = (
|
const createComputeHeuristic = (
|
||||||
grid: Grid,
|
grid0: Grid,
|
||||||
snake: Point[],
|
_snake0: Snake,
|
||||||
options: { maxSnakeLength: number }
|
colors: Color[]
|
||||||
) => {
|
) => {
|
||||||
const g = copyGrid(grid);
|
const colorCount: Record<Color, number> = {};
|
||||||
const s = copySnake(snake);
|
for (let x = grid0.width; x--; )
|
||||||
const q: Color[] = [];
|
for (let y = grid0.height; y--; ) {
|
||||||
|
const c = getColor(grid0, x, y);
|
||||||
const commands: Point[] = [];
|
if (c !== null) colorCount[c] = 1 + (colorCount[c] || 0);
|
||||||
|
|
||||||
let u = 500;
|
|
||||||
|
|
||||||
while (!isGridEmpty(g) && u-- > 0) {
|
|
||||||
let direction;
|
|
||||||
|
|
||||||
for (let k = 10; k--; ) {
|
|
||||||
direction = around4[Math.floor(Math.random() * around4.length)];
|
|
||||||
|
|
||||||
const sn = copySnake(s);
|
|
||||||
stepSnake(sn, direction, options);
|
|
||||||
|
|
||||||
if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
direction = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (direction !== undefined) {
|
const values = colors
|
||||||
step(g, s, q, direction, options);
|
.map((k) => Array.from({ length: colorCount[k] }, () => k))
|
||||||
commands.push(direction);
|
.flat();
|
||||||
|
|
||||||
|
return (_grid: Grid, _snake: Snake, stack: Color[]) => {
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < stack.length; i++) {
|
||||||
|
const k = Math.abs(stack[i] - values[i]);
|
||||||
|
score += k === 0 ? 100 : -100 * k;
|
||||||
|
}
|
||||||
|
|
||||||
|
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[],
|
||||||
|
direction: Point | null,
|
||||||
|
parent: any | null,
|
||||||
|
heuristic: number
|
||||||
|
) => ({
|
||||||
|
key,
|
||||||
|
parent,
|
||||||
|
direction,
|
||||||
|
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.direction ? [...unwrap(c.parent), c.direction] : [];
|
||||||
|
// c && c.parent
|
||||||
|
// ? [
|
||||||
|
// ...unwrap(c.parent),
|
||||||
|
// { x: c.snake[1].x - c.snake[0].x, y: c.snake[1].y - c.snake[0].y },
|
||||||
|
// ]
|
||||||
|
// : [];
|
||||||
|
|
||||||
|
export const computeBestRun = (
|
||||||
|
grid0: Grid,
|
||||||
|
snake0: Snake,
|
||||||
|
options: { maxSnakeLength: number; colors: Color[] }
|
||||||
|
) => {
|
||||||
|
// const grid = copyGrid(grid0);
|
||||||
|
// const snake = copySnake(snake0);
|
||||||
|
// const stack: Color[] = [];
|
||||||
|
|
||||||
|
const computeHeuristic = createComputeHeuristic(
|
||||||
|
grid0,
|
||||||
|
snake0,
|
||||||
|
options.colors
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeList: any = {};
|
||||||
|
const openList = [
|
||||||
|
createCell(
|
||||||
|
computeKey(grid0, snake0, []),
|
||||||
|
grid0,
|
||||||
|
snake0,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
computeHeuristic(grid0, snake0, [])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let u = 7000;
|
||||||
|
|
||||||
|
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,
|
||||||
|
direction,
|
||||||
|
c,
|
||||||
|
computeHeuristic(grid, snake, stack)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands;
|
return unwrap(best);
|
||||||
|
|
||||||
|
// while (!isGridEmpty(g) && u-- > 0) {
|
||||||
|
// let direction;
|
||||||
|
|
||||||
|
// for (let k = 10; k--; ) {
|
||||||
|
// direction = around4[Math.floor(Math.random() * around4.length)];
|
||||||
|
|
||||||
|
// const sn = copySnake(s);
|
||||||
|
// stepSnake(sn, direction, options);
|
||||||
|
|
||||||
|
// if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
|
||||||
|
// break;
|
||||||
|
// } else {
|
||||||
|
// direction = undefined;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (direction !== undefined) {
|
||||||
|
// step(g, s, q, direction, options);
|
||||||
|
// commands.push(direction);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return commands;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ const drawOptions = {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = { colors: [1, 2, 3, 4], maxSnakeLength: 5 };
|
||||||
|
|
||||||
const grid0 = generateRandomGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
const grid0 = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake0 = [
|
const snake0 = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
@@ -71,8 +71,6 @@ document.body.appendChild(input);
|
|||||||
const autoplayButton = document.createElement("button");
|
const autoplayButton = document.createElement("button");
|
||||||
let cancel: any;
|
let cancel: any;
|
||||||
const loop = () => {
|
const loop = () => {
|
||||||
debugger;
|
|
||||||
|
|
||||||
input.value = (+input.value + 1) % +input.max;
|
input.value = (+input.value + 1) % +input.max;
|
||||||
update(+input.value);
|
update(+input.value);
|
||||||
cancelAnimationFrame(cancel);
|
cancelAnimationFrame(cancel);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const drawWorld = (
|
|||||||
) => {
|
) => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
ctx.translate(2 * o.sizeCell, 2 * o.sizeCell);
|
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
|
||||||
drawGrid(ctx, grid, o);
|
drawGrid(ctx, grid, o);
|
||||||
drawSnake(ctx, snake, o);
|
drawSnake(ctx, snake, o);
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ const drawOptions = {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
|
||||||
|
|
||||||
const gifOptions = { delay: 200 };
|
const gifOptions = { delay: 200 };
|
||||||
|
|
||||||
it("should generate gif", async () => {
|
it("should generate gif", async () => {
|
||||||
const grid = generateRandomGrid(14, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
const grid = generateRandomGrid(14, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ const drawOptions = {
|
|||||||
colorSnake: "purple",
|
colorSnake: "purple",
|
||||||
};
|
};
|
||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
|
||||||
|
|
||||||
const gifOptions = { delay: 20 };
|
const gifOptions = { delay: 20 };
|
||||||
|
|
||||||
const grid = generateRandomGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
const grid = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
|
|||||||
Reference in New Issue
Block a user