🚀 refactor getBestRoute
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { getGithubUserContribution, Cell } from "@snk/github-user-contribution";
|
import { getGithubUserContribution, Cell } from "@snk/github-user-contribution";
|
||||||
import { setColor, createEmptyGrid } from "@snk/compute/grid";
|
import { setColor, createEmptyGrid } from "@snk/compute/grid";
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { createGif } from "@snk/gif-creator";
|
||||||
import { createGif } from "../gif-creator";
|
import { getBestRoute } from "@snk/compute/getBestRoute";
|
||||||
|
|
||||||
export const userContributionToGrid = (cells: Cell[]) => {
|
export const userContributionToGrid = (cells: Cell[]) => {
|
||||||
const width = Math.max(0, ...cells.map((c) => c.x)) + 1;
|
const width = Math.max(0, ...cells.map((c) => c.x)) + 1;
|
||||||
@@ -43,7 +43,7 @@ export const generateContributionSnake = async (userName: string) => {
|
|||||||
|
|
||||||
const gifOptions = { delay: 3 };
|
const gifOptions = { delay: 3 };
|
||||||
|
|
||||||
const commands = computeBestRun(grid0, snake0, gameOptions);
|
const commands = getBestRoute(grid0, snake0, gameOptions, 600);
|
||||||
|
|
||||||
const buffer = await createGif(
|
const buffer = await createGif(
|
||||||
grid0,
|
grid0,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as ParkMiller from "park-miller";
|
|||||||
import { generateRandomGrid } from "../generateGrid";
|
import { generateRandomGrid } from "../generateGrid";
|
||||||
import { Snake } from "../snake";
|
import { Snake } from "../snake";
|
||||||
import { Grid } from "../grid";
|
import { Grid } from "../grid";
|
||||||
import { computeBestRun } from "..";
|
import { getBestRoute } from "../getBestRoute";
|
||||||
import { performance } from "perf_hooks";
|
import { performance } from "perf_hooks";
|
||||||
|
|
||||||
const snake0 = [
|
const snake0 = [
|
||||||
@@ -19,16 +19,18 @@ const gameOptions = {
|
|||||||
colors: [1, 2, 3, 4],
|
colors: [1, 2, 3, 4],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MAX_DURATION = 60 * 1000;
|
||||||
|
const MAX_ITERATION = 10;
|
||||||
|
|
||||||
const run = (grid: Grid, snake0: Snake, k: number) => {
|
const run = (grid: Grid, snake0: Snake, k: number) => {
|
||||||
const stats: number[] = [];
|
const stats: number[] = [];
|
||||||
const s0 = performance.now();
|
const s0 = performance.now();
|
||||||
|
|
||||||
const M = 60 * 1000;
|
let n = 0;
|
||||||
let n = 40;
|
|
||||||
|
|
||||||
while (performance.now() - s0 < M && n-- > 0) {
|
while (performance.now() - s0 < MAX_DURATION && n++ < MAX_ITERATION) {
|
||||||
const s = performance.now();
|
const s = performance.now();
|
||||||
computeBestRun(grid, snake0, gameOptions, k);
|
getBestRoute(grid, snake0, gameOptions, k);
|
||||||
|
|
||||||
stats.push(performance.now() - s);
|
stats.push(performance.now() - s);
|
||||||
}
|
}
|
||||||
@@ -59,17 +61,19 @@ const report = (arr: number[]) => {
|
|||||||
|
|
||||||
[
|
[
|
||||||
//
|
//
|
||||||
[10, 10, 1000],
|
[10, 10, 100],
|
||||||
[21, 7, 1000],
|
[30, 7, 100],
|
||||||
[42, 7, 1000],
|
[52, 7, 100],
|
||||||
[42, 7, 5000],
|
|
||||||
[42, 7, 14000],
|
[10, 10, 800],
|
||||||
[42, 7, 30000],
|
[30, 7, 800],
|
||||||
|
[52, 7, 800],
|
||||||
].forEach(([w, h, k]) => {
|
].forEach(([w, h, k]) => {
|
||||||
const random = new ParkMiller(1);
|
const random = new ParkMiller(10);
|
||||||
const grid = generateRandomGrid(w, h, { ...gameOptions, emptyP: 3 }, (a, b) =>
|
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);
|
const stats = run(grid, snake0, k);
|
||||||
|
|
||||||
console.log(`${w}x${h} : ${k}\n ${report(stats)}\n`);
|
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([]);
|
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", () => {
|
it("should find one route in single cell grid", () => {
|
||||||
const grid = createEmptyGrid(10, 10);
|
const grid = createEmptyGrid(10, 10);
|
||||||
setColor(grid, 3, 2, 3);
|
setColor(grid, 3, 2, 3);
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
import { Grid, isInsideLarge, getColor, isInside, Color } from "./grid";
|
import { Grid, isInsideLarge, getColor, isInside, Color } from "./grid";
|
||||||
import { around4, Point, pointEquals } from "./point";
|
import { around4, Point, pointEquals } from "./point";
|
||||||
import {
|
import { snakeWillSelfCollide, Snake } from "./snake";
|
||||||
// copySnake,
|
|
||||||
// snakeSelfCollide,
|
|
||||||
snakeWillSelfCollide,
|
|
||||||
Snake,
|
|
||||||
copySnake,
|
|
||||||
} from "./snake";
|
|
||||||
|
|
||||||
const computeSnakeKey = (snake: Snake) =>
|
const computeSnakeKey = (snake: Snake) =>
|
||||||
snake.map((p) => p.x + "." + p.y).join(",");
|
snake.map((p) => p.x + "." + p.y).join(",");
|
||||||
@@ -40,9 +34,11 @@ const snakeEquals = (a: Snake, b: Snake, n = 99999) => {
|
|||||||
export const getAvailableRoutes = (
|
export const getAvailableRoutes = (
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
options: any,
|
options: { maxSnakeLength: number },
|
||||||
maxSolutions: number = 10,
|
maxSolutions = 10,
|
||||||
maxLengthEquality: number = 1
|
maxLengthEquality = 1,
|
||||||
|
maxWeight = 30,
|
||||||
|
maxIterations = 500
|
||||||
) => {
|
) => {
|
||||||
const openList: I[] = [
|
const openList: I[] = [
|
||||||
{
|
{
|
||||||
@@ -55,7 +51,7 @@ export const getAvailableRoutes = (
|
|||||||
// heuristic
|
// heuristic
|
||||||
h: 0,
|
h: 0,
|
||||||
|
|
||||||
// fitness, more is better
|
// fitness, smaller is better
|
||||||
f: 0,
|
f: 0,
|
||||||
parent: null,
|
parent: null,
|
||||||
},
|
},
|
||||||
@@ -64,17 +60,18 @@ export const getAvailableRoutes = (
|
|||||||
|
|
||||||
const solutions: { color: Color; snakeN: Snake; directions: Point[] }[] = [];
|
const solutions: { color: Color; snakeN: Snake; directions: Point[] }[] = [];
|
||||||
|
|
||||||
let i = 0;
|
while (
|
||||||
|
openList.length &&
|
||||||
debugger;
|
maxIterations-- > 0 &&
|
||||||
|
openList[0].w <= maxWeight &&
|
||||||
while (openList.length && i++ < 5000 && solutions.length < maxSolutions) {
|
solutions.length < maxSolutions
|
||||||
openList.sort((a, b) => b.f - a.f);
|
) {
|
||||||
|
openList.sort((a, b) => a.f - b.f);
|
||||||
const c = openList.shift()!;
|
const c = openList.shift()!;
|
||||||
|
|
||||||
closeList[c.key] = c;
|
closeList[c.key] = c;
|
||||||
|
|
||||||
snakeSteps.push(copySnake(c.snake));
|
// snakeSteps.push(copySnake(c.snake));
|
||||||
|
|
||||||
const [head] = c.snake;
|
const [head] = c.snake;
|
||||||
const color =
|
const color =
|
||||||
@@ -114,7 +111,7 @@ export const getAvailableRoutes = (
|
|||||||
|
|
||||||
const w = 1 + c.w;
|
const w = 1 + c.w;
|
||||||
|
|
||||||
const f = -w;
|
const f = w;
|
||||||
// const f = h * 0.6 - w;
|
// const f = h * 0.6 - w;
|
||||||
|
|
||||||
openList.push({ key, snake, f, w, h, parent: c });
|
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);
|
|
||||||
};
|
|
||||||
48
packages/demo/canvas.ts
Normal file
48
packages/demo/canvas.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Color, Grid } from "@snk/compute/grid";
|
||||||
|
import { drawWorld } from "@snk/draw/drawWorld";
|
||||||
|
import { Snake } from "@snk/compute/snake";
|
||||||
|
|
||||||
|
export const drawOptions = {
|
||||||
|
sizeBorderRadius: 2,
|
||||||
|
sizeCell: 16,
|
||||||
|
sizeDot: 12,
|
||||||
|
colorBorder: "#1b1f230a",
|
||||||
|
colorDots: {
|
||||||
|
1: "#9be9a8",
|
||||||
|
2: "#40c463",
|
||||||
|
3: "#30a14e",
|
||||||
|
4: "#216e39",
|
||||||
|
5: "orange",
|
||||||
|
},
|
||||||
|
colorEmpty: "#ebedf0",
|
||||||
|
colorSnake: "purple",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createCanvas = ({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const upscale = 2;
|
||||||
|
const w = drawOptions.sizeCell * (width + 4);
|
||||||
|
const h = drawOptions.sizeCell * (height + 4) + 200;
|
||||||
|
canvas.width = w * upscale;
|
||||||
|
canvas.height = h * upscale;
|
||||||
|
canvas.style.width = w + "px";
|
||||||
|
canvas.style.height = h + "px";
|
||||||
|
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.scale(upscale, upscale);
|
||||||
|
|
||||||
|
const draw = (grid: Grid, snake: Snake, stack: Color[]) => {
|
||||||
|
ctx.clearRect(0, 0, 9999, 9999);
|
||||||
|
drawWorld(ctx, grid, snake, stack, drawOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { draw, canvas };
|
||||||
|
};
|
||||||
105
packages/demo/demo.getAvailableRoutes.ts
Normal file
105
packages/demo/demo.getAvailableRoutes.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { copyGrid } from "@snk/compute/grid";
|
||||||
|
import { copySnake } from "@snk/compute/snake";
|
||||||
|
import { createCanvas } from "./canvas";
|
||||||
|
import { stepSnake } from "@snk/compute/step";
|
||||||
|
import { samples } from "./samples";
|
||||||
|
import { getAvailableRoutes } from "@snk/compute/getAvailableRoutes";
|
||||||
|
|
||||||
|
//
|
||||||
|
// init
|
||||||
|
|
||||||
|
const label = new URLSearchParams(window.location.search).get("sample");
|
||||||
|
const { grid: grid0, snake: snake0, gameOptions } =
|
||||||
|
samples.find((s) => s.label === label) || samples[0];
|
||||||
|
|
||||||
|
//
|
||||||
|
// compute
|
||||||
|
|
||||||
|
const routes = getAvailableRoutes(grid0, snake0, gameOptions, 20);
|
||||||
|
|
||||||
|
//
|
||||||
|
// draw
|
||||||
|
|
||||||
|
const { canvas, draw } = createCanvas(grid0);
|
||||||
|
|
||||||
|
const update = (n: number, k: number) => {
|
||||||
|
const snake = copySnake(snake0);
|
||||||
|
const grid = copyGrid(grid0);
|
||||||
|
const route = routes[n];
|
||||||
|
|
||||||
|
const trace = [{ ...snake[0] }];
|
||||||
|
|
||||||
|
for (let i = 0; i < k; i++) {
|
||||||
|
stepSnake(snake, route.directions[i], gameOptions);
|
||||||
|
trace.push({ ...snake[0] });
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(grid, snake, []);
|
||||||
|
|
||||||
|
const [cell] = route.snakeN;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
|
||||||
|
for (let i = 0; i < routes.length; i++) {
|
||||||
|
ctx.fillStyle = "orange";
|
||||||
|
ctx.fillRect(
|
||||||
|
16 * (routes[i].snakeN[0].x + 1) + 6,
|
||||||
|
16 * (routes[i].snakeN[0].y + 2) + 6,
|
||||||
|
4,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = "orange";
|
||||||
|
ctx.fillRect(16 * (cell.x + 1) + 4, 16 * (cell.y + 2) + 4, 8, 8);
|
||||||
|
|
||||||
|
for (let i = 0; i < trace.length; i++) {
|
||||||
|
ctx.fillStyle = "purple";
|
||||||
|
ctx.fillRect(16 * (trace[i].x + 1) + 6, 16 * (trace[i].y + 2) + 6, 4, 4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// controls
|
||||||
|
|
||||||
|
// const input0: any = document.createElement("input");
|
||||||
|
// input0.type = "range";
|
||||||
|
// input0.style.width = "100%";
|
||||||
|
// input0.min = 0;
|
||||||
|
// input0.max = snakeSteps.length - 1;
|
||||||
|
// input0.step = 1;
|
||||||
|
// input0.value = 0;
|
||||||
|
// input0.addEventListener("input", () => {
|
||||||
|
// const grid = copyGrid(grid0);
|
||||||
|
// draw(grid, snakeSteps[+input0.value], []);
|
||||||
|
// });
|
||||||
|
// document.body.appendChild(input0);
|
||||||
|
|
||||||
|
const inputA: any = document.createElement("input");
|
||||||
|
inputA.type = "range";
|
||||||
|
inputA.style.width = "100%";
|
||||||
|
inputA.min = 0;
|
||||||
|
inputA.max = routes.length - 1;
|
||||||
|
inputA.step = 1;
|
||||||
|
inputA.value = 0;
|
||||||
|
inputA.addEventListener("input", () => {
|
||||||
|
inputB.value = inputB.max = routes[+inputA.value].directions.length;
|
||||||
|
update(+inputA.value, +inputB.value);
|
||||||
|
});
|
||||||
|
document.body.appendChild(inputA);
|
||||||
|
|
||||||
|
const inputB: any = document.createElement("input");
|
||||||
|
inputB.type = "range";
|
||||||
|
inputB.style.width = "100%";
|
||||||
|
inputB.min = 0;
|
||||||
|
inputB.step = 1;
|
||||||
|
inputB.value = 0;
|
||||||
|
inputB.addEventListener("input", () => {
|
||||||
|
update(+inputA.value, +inputB.value);
|
||||||
|
});
|
||||||
|
document.body.appendChild(inputB);
|
||||||
|
|
||||||
|
if (routes[+inputA.value]) {
|
||||||
|
inputB.value = inputB.max = routes[+inputA.value].directions.length;
|
||||||
|
update(+inputA.value, +inputB.value);
|
||||||
|
}
|
||||||
48
packages/demo/demo.getBestRoute.ts
Normal file
48
packages/demo/demo.getBestRoute.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { copyGrid } from "@snk/compute/grid";
|
||||||
|
import { copySnake } from "@snk/compute/snake";
|
||||||
|
import { createCanvas } from "./canvas";
|
||||||
|
import { step } from "@snk/compute/step";
|
||||||
|
import { getBestRoute } from "@snk/compute/getBestRoute";
|
||||||
|
import { samples } from "./samples";
|
||||||
|
|
||||||
|
//
|
||||||
|
// init
|
||||||
|
|
||||||
|
const label = new URLSearchParams(window.location.search).get("sample");
|
||||||
|
const { grid: grid0, snake: snake0, gameOptions } =
|
||||||
|
samples.find((s) => s.label === label) || samples[0];
|
||||||
|
|
||||||
|
//
|
||||||
|
// compute
|
||||||
|
|
||||||
|
const s0 = Date.now();
|
||||||
|
const bestRoute = getBestRoute(grid0, snake0, gameOptions);
|
||||||
|
console.log(`computed in ${Date.now() - s0}ms`);
|
||||||
|
|
||||||
|
//
|
||||||
|
// draw
|
||||||
|
|
||||||
|
const { draw } = createCanvas(grid0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// controls
|
||||||
|
|
||||||
|
const inputK: any = document.createElement("input");
|
||||||
|
inputK.type = "range";
|
||||||
|
inputK.style.width = "100%";
|
||||||
|
inputK.min = 0;
|
||||||
|
inputK.max = bestRoute.length;
|
||||||
|
inputK.step = 1;
|
||||||
|
inputK.value = 0;
|
||||||
|
inputK.addEventListener("input", () => {
|
||||||
|
const snake = copySnake(snake0);
|
||||||
|
const grid = copyGrid(grid0);
|
||||||
|
const stack: any[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < +inputK.value; i++)
|
||||||
|
step(grid, snake, stack, bestRoute[i], gameOptions);
|
||||||
|
|
||||||
|
draw(grid, snake, stack);
|
||||||
|
});
|
||||||
|
document.body.appendChild(inputK);
|
||||||
|
draw(grid0, snake0, []);
|
||||||
48
packages/demo/demo.index.ts
Normal file
48
packages/demo/demo.index.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { copyGrid } from "@snk/compute/grid";
|
||||||
|
import { copySnake } from "@snk/compute/snake";
|
||||||
|
import { createCanvas } from "./canvas";
|
||||||
|
import { step } from "@snk/compute/step";
|
||||||
|
import { getBestRoute } from "@snk/compute/getBestRoute";
|
||||||
|
import { samples } from "./samples";
|
||||||
|
|
||||||
|
//
|
||||||
|
// init
|
||||||
|
|
||||||
|
const label = "realistic";
|
||||||
|
const { grid: grid0, snake: snake0, gameOptions } = samples.find(
|
||||||
|
(s) => s.label === label
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
// compute
|
||||||
|
|
||||||
|
const s0 = performance.now();
|
||||||
|
const bestRoute = getBestRoute(grid0, snake0, gameOptions, 120);
|
||||||
|
console.log(performance.now() - s0);
|
||||||
|
//
|
||||||
|
// draw
|
||||||
|
|
||||||
|
const { draw } = createCanvas(grid0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// controls
|
||||||
|
|
||||||
|
const inputK: any = document.createElement("input");
|
||||||
|
inputK.type = "range";
|
||||||
|
inputK.style.width = "100%";
|
||||||
|
inputK.min = 0;
|
||||||
|
inputK.max = bestRoute.length;
|
||||||
|
inputK.step = 1;
|
||||||
|
inputK.value = 0;
|
||||||
|
inputK.addEventListener("input", () => {
|
||||||
|
const snake = copySnake(snake0);
|
||||||
|
const grid = copyGrid(grid0);
|
||||||
|
const stack: any[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < +inputK.value; i++)
|
||||||
|
step(grid, snake, stack, bestRoute[i], gameOptions);
|
||||||
|
|
||||||
|
draw(grid, snake, stack);
|
||||||
|
});
|
||||||
|
document.body.appendChild(inputK);
|
||||||
|
draw(grid0, snake0, []);
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
|
||||||
import { Color, copyGrid } from "@snk/compute/grid";
|
|
||||||
import { computeBestRun } from "@snk/compute";
|
|
||||||
import { step } from "@snk/compute/step";
|
|
||||||
import { drawWorld } from "@snk/draw/drawWorld";
|
|
||||||
import { copySnake } from "@snk/compute/snake";
|
|
||||||
|
|
||||||
const drawOptions = {
|
|
||||||
sizeBorderRadius: 2,
|
|
||||||
sizeCell: 16,
|
|
||||||
sizeDot: 12,
|
|
||||||
colorBorder: "#1b1f230a",
|
|
||||||
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
|
|
||||||
colorEmpty: "#ebedf0",
|
|
||||||
colorSnake: "purple",
|
|
||||||
};
|
|
||||||
|
|
||||||
const gameOptions = { colors: [1, 2, 3], maxSnakeLength: 5 };
|
|
||||||
|
|
||||||
const grid0 = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 2 });
|
|
||||||
|
|
||||||
const snake0 = [
|
|
||||||
{ x: 4, y: -1 },
|
|
||||||
{ x: 3, y: -1 },
|
|
||||||
{ x: 2, y: -1 },
|
|
||||||
{ x: 1, y: -1 },
|
|
||||||
{ x: 0, y: -1 },
|
|
||||||
];
|
|
||||||
const stack0: Color[] = [];
|
|
||||||
|
|
||||||
const chain = computeBestRun(grid0, snake0, gameOptions);
|
|
||||||
|
|
||||||
//
|
|
||||||
// draw
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const upscale = 2;
|
|
||||||
const width = drawOptions.sizeCell * (grid0.width + 4);
|
|
||||||
const height = drawOptions.sizeCell * (grid0.height + 4) + 100;
|
|
||||||
canvas.width = width * upscale;
|
|
||||||
canvas.height = height * upscale;
|
|
||||||
canvas.style.width = width + "px";
|
|
||||||
canvas.style.height = height + "px";
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
const ctx = canvas.getContext("2d")!;
|
|
||||||
ctx.scale(upscale, upscale);
|
|
||||||
|
|
||||||
const update = (n: number) => {
|
|
||||||
const snake = copySnake(snake0);
|
|
||||||
const stack = stack0.slice();
|
|
||||||
const grid = copyGrid(grid0);
|
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) step(grid, snake, stack, chain[i], gameOptions);
|
|
||||||
|
|
||||||
ctx.clearRect(0, 0, 9999, 9999);
|
|
||||||
drawWorld(ctx, grid, snake, stack, drawOptions);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// controls
|
|
||||||
|
|
||||||
const input: any = document.createElement("input");
|
|
||||||
input.type = "range";
|
|
||||||
input.style.width = "100%";
|
|
||||||
input.min = 0;
|
|
||||||
input.max = chain.length;
|
|
||||||
input.step = 1;
|
|
||||||
input.value = 0;
|
|
||||||
input.addEventListener("input", () => {
|
|
||||||
setAutoPlay(false);
|
|
||||||
update(+input.value);
|
|
||||||
});
|
|
||||||
document.addEventListener("click", () => input.focus());
|
|
||||||
|
|
||||||
document.body.appendChild(input);
|
|
||||||
|
|
||||||
const autoplayButton = document.createElement("button");
|
|
||||||
let cancel: any;
|
|
||||||
const loop = () => {
|
|
||||||
input.value = (+input.value + 1) % +input.max;
|
|
||||||
update(+input.value);
|
|
||||||
cancelAnimationFrame(cancel);
|
|
||||||
cancel = requestAnimationFrame(loop);
|
|
||||||
};
|
|
||||||
const setAutoPlay = (a: boolean) => {
|
|
||||||
autoplayButton.innerHTML = a ? "pause" : "play";
|
|
||||||
if (a) loop();
|
|
||||||
else cancelAnimationFrame(cancel);
|
|
||||||
};
|
|
||||||
autoplayButton.addEventListener("click", () => {
|
|
||||||
debugger;
|
|
||||||
setAutoPlay(autoplayButton.innerHTML === "play");
|
|
||||||
});
|
|
||||||
document.body.appendChild(autoplayButton);
|
|
||||||
|
|
||||||
setAutoPlay(true);
|
|
||||||
update(+input.value);
|
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
"@snk/draw": "1.0.0"
|
"@snk/draw": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"html-webpack-plugin": "4.3.0",
|
||||||
|
"ts-loader": "8.0.1",
|
||||||
"webpack": "4.43.0",
|
"webpack": "4.43.0",
|
||||||
"webpack-cli": "3.3.12",
|
"webpack-cli": "3.3.12",
|
||||||
"webpack-dev-server": "3.11.0",
|
"webpack-dev-server": "3.11.0"
|
||||||
"ts-loader": "8.0.1",
|
|
||||||
"html-webpack-plugin": "4.3.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "tsc webpack.config.ts",
|
"prepare": "tsc webpack.config.ts",
|
||||||
|
|||||||
85
packages/demo/samples.ts
Normal file
85
packages/demo/samples.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import * as ParkMiller from "park-miller";
|
||||||
|
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
||||||
|
import { createEmptyGrid, setColor } from "@snk/compute/grid";
|
||||||
|
|
||||||
|
export const samples: any[] = [];
|
||||||
|
|
||||||
|
{
|
||||||
|
const gameOptions = {
|
||||||
|
colors: [1, 2, 3],
|
||||||
|
maxSnakeLength: 1,
|
||||||
|
};
|
||||||
|
const snake = [{ x: 0, y: -1 }];
|
||||||
|
const grid = createEmptyGrid(6, 6);
|
||||||
|
samples.push({
|
||||||
|
label: "empty",
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
gameOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const gameOptions = {
|
||||||
|
colors: [1, 2, 3],
|
||||||
|
maxSnakeLength: 1,
|
||||||
|
};
|
||||||
|
const snake = [{ x: 0, y: -1 }];
|
||||||
|
const grid = createEmptyGrid(6, 6);
|
||||||
|
setColor(grid, 2, 2, 2);
|
||||||
|
samples.push({
|
||||||
|
label: "small",
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
gameOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const gameOptions = {
|
||||||
|
colors: [1, 2, 3],
|
||||||
|
maxSnakeLength: 5,
|
||||||
|
};
|
||||||
|
const random = new ParkMiller(10);
|
||||||
|
const rand = (a: number, b: number) => random.integerInRange(a, b - 1);
|
||||||
|
const grid = generateRandomGrid(52, 7, { ...gameOptions, emptyP: 2 }, rand);
|
||||||
|
const snake = [
|
||||||
|
{ x: 4, y: -1 },
|
||||||
|
{ x: 3, y: -1 },
|
||||||
|
{ x: 2, y: -1 },
|
||||||
|
{ x: 1, y: -1 },
|
||||||
|
{ x: 0, y: -1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
samples.push({
|
||||||
|
label: "realistic",
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
gameOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const gameOptions = {
|
||||||
|
colors: [1, 2, 3],
|
||||||
|
maxSnakeLength: 5,
|
||||||
|
};
|
||||||
|
const random = new ParkMiller(10);
|
||||||
|
const rand = (a: number, b: number) => random.integerInRange(a, b - 1);
|
||||||
|
const grid = generateRandomGrid(20, 7, { ...gameOptions, emptyP: 2 }, rand);
|
||||||
|
const snake = [
|
||||||
|
{ x: 4, y: -1 },
|
||||||
|
{ x: 3, y: -1 },
|
||||||
|
{ x: 2, y: -1 },
|
||||||
|
{ x: 1, y: -1 },
|
||||||
|
{ x: 0, y: -1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
samples.push({
|
||||||
|
label: "realistic-small",
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
gameOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as HtmlWebpackPlugin from "html-webpack-plugin";
|
import * as HtmlWebpackPlugin from "html-webpack-plugin";
|
||||||
|
|
||||||
import type { Configuration } from "webpack";
|
import type { Configuration } from "webpack";
|
||||||
|
|
||||||
const basePathname = (process.env.BASE_PATHNAME || "")
|
const basePathname = (process.env.BASE_PATHNAME || "")
|
||||||
@@ -10,7 +10,11 @@ const basePathname = (process.env.BASE_PATHNAME || "")
|
|||||||
|
|
||||||
const config: Configuration = {
|
const config: Configuration = {
|
||||||
mode: "development",
|
mode: "development",
|
||||||
entry: "./index",
|
entry: {
|
||||||
|
"demo.getAvailableRoutes": "./demo.getAvailableRoutes",
|
||||||
|
"demo.getBestRoute": "./demo.getBestRoute",
|
||||||
|
"demo.index": "./demo.index",
|
||||||
|
},
|
||||||
resolve: { extensions: [".ts", ".js"] },
|
resolve: { extensions: [".ts", ".js"] },
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, "dist"),
|
path: path.join(__dirname, "dist"),
|
||||||
@@ -28,11 +32,16 @@ const config: Configuration = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
title: "demo",
|
|
||||||
filename: "index.html",
|
filename: "index.html",
|
||||||
meta: {
|
chunks: ["demo.index"],
|
||||||
viewport: "width=device-width, initial-scale=1, shrink-to-fit=no",
|
}),
|
||||||
},
|
new HtmlWebpackPlugin({
|
||||||
|
filename: "demo-getAvailableRoutes.html",
|
||||||
|
chunks: ["demo.getAvailableRoutes"],
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
filename: "demo-getBestRoute.html",
|
||||||
|
chunks: ["demo.getBestRoute"],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
84
packages/draw/drawCircleStack.ts
Normal file
84
packages/draw/drawCircleStack.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { Color } from "@snk/compute/grid";
|
||||||
|
import { pathRoundedRect } from "./pathRoundedRect";
|
||||||
|
import { Point } from "@snk/compute/point";
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
colorDots: Record<Color, string>;
|
||||||
|
colorBorder: string;
|
||||||
|
sizeCell: number;
|
||||||
|
sizeDot: number;
|
||||||
|
sizeBorderRadius: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isInsideCircle = (x: number, y: number, r: number) => {
|
||||||
|
const l = 6;
|
||||||
|
let k = 0;
|
||||||
|
for (let dx = 0; dx < l; dx++)
|
||||||
|
for (let dy = 0; dy < l; dy++) {
|
||||||
|
const ux = x + (dx + 0.5) / l;
|
||||||
|
const uy = y + (dy + 0.5) / l;
|
||||||
|
|
||||||
|
if (ux * ux + uy * uy < r * r) k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return k > l * l * 0.6;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCellPath = (n: number): Point[] => {
|
||||||
|
const l = Math.ceil(Math.sqrt(n));
|
||||||
|
|
||||||
|
const cells = [];
|
||||||
|
|
||||||
|
for (let x = -l; x <= l; x++)
|
||||||
|
for (let y = -l; y <= l; y++) {
|
||||||
|
const a = (Math.atan2(y, x) + (5 * Math.PI) / 2) % (Math.PI * 2);
|
||||||
|
|
||||||
|
let r = 0;
|
||||||
|
|
||||||
|
while (!isInsideCircle(x, y, r + 0.5)) r++;
|
||||||
|
|
||||||
|
cells.push({ x, y, f: r * 100 + a });
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells.sort((a, b) => a.f - b.f).slice(0, n);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cellPath = getCellPath(52 * 7 + 5);
|
||||||
|
|
||||||
|
export const getCircleSize = (n: number) => {
|
||||||
|
const c = cellPath.slice(0, n);
|
||||||
|
const xs = c.map((p) => p.x);
|
||||||
|
const ys = c.map((p) => p.y);
|
||||||
|
|
||||||
|
return {
|
||||||
|
max: { x: Math.max(0, ...xs), y: Math.max(0, ...ys) },
|
||||||
|
min: { x: Math.min(0, ...xs), y: Math.min(0, ...ys) },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawCircleStack = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
stack: Color[],
|
||||||
|
o: Options
|
||||||
|
) => {
|
||||||
|
for (let i = stack.length; i--; ) {
|
||||||
|
const { x, y } = cellPath[i];
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(
|
||||||
|
x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2,
|
||||||
|
y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2
|
||||||
|
);
|
||||||
|
ctx.fillStyle = o.colorDots[stack[i]];
|
||||||
|
ctx.strokeStyle = o.colorBorder;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
pathRoundedRect(ctx, o.sizeDot, o.sizeDot, o.sizeBorderRadius);
|
||||||
|
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createGif } from "..";
|
import { createGif } from "..";
|
||||||
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { getBestRoute } from "@snk/compute/getBestRoute";
|
||||||
|
|
||||||
const drawOptions = {
|
const drawOptions = {
|
||||||
sizeBorderRadius: 2,
|
sizeBorderRadius: 2,
|
||||||
@@ -17,7 +17,7 @@ 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, { ...gameOptions, emptyP: 3 });
|
const grid = generateRandomGrid(7, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
@@ -27,7 +27,7 @@ it("should generate gif", async () => {
|
|||||||
{ x: 0, y: -1 },
|
{ x: 0, y: -1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const commands = computeBestRun(grid, snake, gameOptions).slice(0, 9);
|
const commands = getBestRoute(grid, snake, gameOptions, 50).slice(0, 9);
|
||||||
|
|
||||||
const gif = await createGif(
|
const gif = await createGif(
|
||||||
grid,
|
grid,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createGif } from "..";
|
import { createGif } from "..";
|
||||||
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
import { generateRandomGrid } from "@snk/compute/generateGrid";
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { getBestRoute } from "@snk/compute/getBestRoute";
|
||||||
|
|
||||||
const drawOptions = {
|
const drawOptions = {
|
||||||
sizeBorderRadius: 2,
|
sizeBorderRadius: 2,
|
||||||
@@ -16,7 +16,7 @@ const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] };
|
|||||||
|
|
||||||
const gifOptions = { delay: 20 };
|
const gifOptions = { delay: 20 };
|
||||||
|
|
||||||
const grid = generateRandomGrid(42, 7, { ...gameOptions, emptyP: 3 });
|
const grid = generateRandomGrid(14, 7, { ...gameOptions, emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
@@ -26,7 +26,7 @@ const snake = [
|
|||||||
{ x: 0, y: -1 },
|
{ x: 0, y: -1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const commands = computeBestRun(grid, snake, gameOptions);
|
const commands = getBestRoute(grid, snake, gameOptions, 50);
|
||||||
|
|
||||||
createGif(grid, snake, commands, drawOptions, gameOptions, gifOptions).then(
|
createGif(grid, snake, commands, drawOptions, gameOptions, gifOptions).then(
|
||||||
(buffer) => {
|
(buffer) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user