Skip to content

Commit fd9d7da

Browse files
committed
🚀 smarter snake
1 parent 73bfce9 commit fd9d7da

File tree

6 files changed

+161
-37
lines changed

6 files changed

+161
-37
lines changed

packages/action/generateContributionSnake.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ export const generateContributionSnake = async (userName: string) => {
3737
colorSnake: "purple",
3838
};
3939

40-
const gameOptions = { maxSnakeLength: 5 };
40+
const gameOptions = {
41+
maxSnakeLength: 5,
42+
colors: Array.from({ length: colorScheme.length - 1 }, (_, i) => i + 1),
43+
};
4144

4245
const gifOptions = { delay: 10 };
4346

packages/compute/index.ts

Lines changed: 150 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,167 @@
1-
import { Grid, Color, copyGrid, isInsideLarge } from "./grid";
1+
import { Grid, Color, copyGrid, isInsideLarge, getColor } from "./grid";
22
import { Point, around4 } from "./point";
3-
import { stepSnake, step } from "./step";
4-
import { copySnake, snakeSelfCollide } from "./snake";
3+
import { step } from "./step";
4+
import { copySnake, snakeSelfCollide, Snake } from "./snake";
55

66
const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null);
77

8-
export const computeBestRun = (
8+
const createComputeHeuristic = (
9+
grid0: Grid,
10+
_snake0: Snake,
11+
colors: Color[]
12+
) => {
13+
const colorCount: Record<Color, number> = {};
14+
for (let x = grid0.width; x--; )
15+
for (let y = grid0.height; y--; ) {
16+
const c = getColor(grid0, x, y);
17+
if (c !== null) colorCount[c] = 1 + (colorCount[c] || 0);
18+
}
19+
20+
const values = colors
21+
.map((k) => Array.from({ length: colorCount[k] }, () => k))
22+
.flat();
23+
24+
return (_grid: Grid, _snake: Snake, stack: Color[]) => {
25+
let score = 0;
26+
27+
for (let i = 0; i < stack.length; i++) {
28+
const k = Math.abs(stack[i] - values[i]);
29+
score += k === 0 ? 100 : -100 * k;
30+
}
31+
32+
return score;
33+
};
34+
};
35+
36+
const computeKey = (grid: Grid, snake: Snake, stack: Color[]) =>
37+
grid.data.map((x) => x || 0).join("") +
38+
"|" +
39+
snake.map((p) => p.x + "." + p.y).join(",") +
40+
"|" +
41+
stack.join("");
42+
43+
const createCell = (
44+
key: string,
945
grid: Grid,
10-
snake: Point[],
11-
options: { maxSnakeLength: number }
46+
snake: Snake,
47+
stack: Color[],
48+
direction: Point | null,
49+
parent: any | null,
50+
heuristic: number
51+
) => ({
52+
key,
53+
parent,
54+
direction,
55+
grid,
56+
snake,
57+
stack,
58+
weight: 1 + (parent?.weight || 0),
59+
f: heuristic - 0 * (1 + (parent?.weight || 0)),
60+
});
61+
62+
const unwrap = (c: ReturnType<typeof createCell> | null): Point[] =>
63+
c && c.direction ? [...unwrap(c.parent), c.direction] : [];
64+
// c && c.parent
65+
// ? [
66+
// ...unwrap(c.parent),
67+
// { x: c.snake[1].x - c.snake[0].x, y: c.snake[1].y - c.snake[0].y },
68+
// ]
69+
// : [];
70+
71+
export const computeBestRun = (
72+
grid0: Grid,
73+
snake0: Snake,
74+
options: { maxSnakeLength: number; colors: Color[] }
1275
) => {
13-
const g = copyGrid(grid);
14-
const s = copySnake(snake);
15-
const q: Color[] = [];
76+
// const grid = copyGrid(grid0);
77+
// const snake = copySnake(snake0);
78+
// const stack: Color[] = [];
1679

17-
const commands: Point[] = [];
80+
const computeHeuristic = createComputeHeuristic(
81+
grid0,
82+
snake0,
83+
options.colors
84+
);
1885

19-
let u = 500;
86+
const closeList: any = {};
87+
const openList = [
88+
createCell(
89+
computeKey(grid0, snake0, []),
90+
grid0,
91+
snake0,
92+
[],
93+
null,
94+
null,
95+
computeHeuristic(grid0, snake0, [])
96+
),
97+
];
2098

21-
while (!isGridEmpty(g) && u-- > 0) {
22-
let direction;
99+
let u = 7000;
23100

24-
for (let k = 10; k--; ) {
25-
direction = around4[Math.floor(Math.random() * around4.length)];
101+
let best = openList[0];
26102

27-
const sn = copySnake(s);
28-
stepSnake(sn, direction, options);
103+
while (openList.length && u-- > 0) {
104+
openList.sort((a, b) => b.f - a.f);
105+
const c = openList.shift()!;
29106

30-
if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
31-
break;
32-
} else {
33-
direction = undefined;
34-
}
35-
}
107+
closeList[c.key] = true;
108+
109+
if (isGridEmpty(c.grid)) return unwrap(c);
110+
111+
if (c.f > best.f) best = c;
112+
113+
for (const direction of around4) {
114+
const snake = copySnake(c.snake);
115+
const stack = c.stack.slice();
116+
const grid = copyGrid(c.grid);
117+
118+
step(grid, snake, stack, direction, options);
119+
120+
const key = computeKey(grid, snake, stack);
36121

37-
if (direction !== undefined) {
38-
step(g, s, q, direction, options);
39-
commands.push(direction);
122+
if (
123+
!closeList[key] &&
124+
isInsideLarge(grid, 1, snake[0].x, snake[0].y) &&
125+
!snakeSelfCollide(snake)
126+
) {
127+
openList.push(
128+
createCell(
129+
key,
130+
grid,
131+
snake,
132+
stack,
133+
direction,
134+
c,
135+
computeHeuristic(grid, snake, stack)
136+
)
137+
);
138+
}
40139
}
41140
}
42141

43-
return commands;
142+
return unwrap(best);
143+
144+
// while (!isGridEmpty(g) && u-- > 0) {
145+
// let direction;
146+
147+
// for (let k = 10; k--; ) {
148+
// direction = around4[Math.floor(Math.random() * around4.length)];
149+
150+
// const sn = copySnake(s);
151+
// stepSnake(sn, direction, options);
152+
153+
// if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
154+
// break;
155+
// } else {
156+
// direction = undefined;
157+
// }
158+
// }
159+
160+
// if (direction !== undefined) {
161+
// step(g, s, q, direction, options);
162+
// commands.push(direction);
163+
// }
164+
// }
165+
166+
// return commands;
44167
};

packages/demo/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ const drawOptions = {
1515
colorSnake: "purple",
1616
};
1717

18-
const gameOptions = { maxSnakeLength: 5 };
18+
const gameOptions = { colors: [1, 2, 3, 4], maxSnakeLength: 5 };
1919

20-
const grid0 = generateRandomGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
20+
const grid0 = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
2121

2222
const snake0 = [
2323
{ x: 4, y: -1 },
@@ -71,8 +71,6 @@ document.body.appendChild(input);
7171
const autoplayButton = document.createElement("button");
7272
let cancel: any;
7373
const loop = () => {
74-
debugger;
75-
7674
input.value = (+input.value + 1) % +input.max;
7775
update(+input.value);
7876
cancelAnimationFrame(cancel);

packages/draw/drawWorld.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const drawWorld = (
4545
) => {
4646
ctx.save();
4747

48-
ctx.translate(2 * o.sizeCell, 2 * o.sizeCell);
48+
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
4949
drawGrid(ctx, grid, o);
5050
drawSnake(ctx, snake, o);
5151

packages/gif-creator/__tests__/createGif.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ const drawOptions = {
1212
colorSnake: "purple",
1313
};
1414

15-
const gameOptions = { maxSnakeLength: 5 };
15+
const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
1616

1717
const gifOptions = { delay: 200 };
1818

1919
it("should generate gif", async () => {
20-
const grid = generateRandomGrid(14, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
20+
const grid = generateRandomGrid(14, 7, { ...gameOptions, emptyP: 3 });
2121

2222
const snake = [
2323
{ x: 4, y: -1 },

packages/gif-creator/__tests__/dev.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ const drawOptions = {
1212
colorSnake: "purple",
1313
};
1414

15-
const gameOptions = { maxSnakeLength: 5 };
15+
const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
1616

1717
const gifOptions = { delay: 20 };
1818

19-
const grid = generateRandomGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
19+
const grid = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
2020

2121
const snake = [
2222
{ x: 4, y: -1 },

0 commit comments

Comments
 (0)