🚀 benchmark
This commit is contained in:
13
.github/workflows/main.yml
vendored
13
.github/workflows/main.yml
vendored
@@ -20,6 +20,19 @@ jobs:
|
|||||||
- run: yarn test --ci
|
- run: yarn test --ci
|
||||||
- run: yarn build:action
|
- run: yarn build:action
|
||||||
|
|
||||||
|
test-benchmark:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- uses: actions/setup-node@v1.4.2
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
|
||||||
|
- uses: bahmutov/npm-install@v1.4.1
|
||||||
|
|
||||||
|
- run: ( cd packages/compute ; yarn benchmark )
|
||||||
|
|
||||||
test-action:
|
test-action:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getGithubUserContribution, Cell } from "@snk/github-user-contribution";
|
import { getGithubUserContribution, Cell } from "@snk/github-user-contribution";
|
||||||
import { generateEmptyGrid } from "@snk/compute/generateGrid";
|
import { setColor, createEmptyGrid } from "@snk/compute/grid";
|
||||||
import { setColor } from "@snk/compute/grid";
|
|
||||||
import { computeBestRun } from "@snk/compute";
|
import { computeBestRun } from "@snk/compute";
|
||||||
import { createGif } from "../gif-creator";
|
import { createGif } from "../gif-creator";
|
||||||
|
|
||||||
@@ -8,7 +7,7 @@ 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;
|
||||||
const height = Math.max(0, ...cells.map((c) => c.y)) + 1;
|
const height = Math.max(0, ...cells.map((c) => c.y)) + 1;
|
||||||
|
|
||||||
const grid = generateEmptyGrid(width, height);
|
const grid = createEmptyGrid(width, height);
|
||||||
for (const c of cells) setColor(grid, c.x, c.y, c.k === 0 ? null : c.k);
|
for (const c of cells) setColor(grid, c.x, c.y, c.k === 0 ? null : c.k);
|
||||||
|
|
||||||
return grid;
|
return grid;
|
||||||
|
|||||||
75
packages/compute/__tests__/benchmark.ts
Normal file
75
packages/compute/__tests__/benchmark.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import * as ParkMiller from "park-miller";
|
||||||
|
import { generateRandomGrid } from "../generateGrid";
|
||||||
|
import { Snake } from "../snake";
|
||||||
|
import { Grid } from "../grid";
|
||||||
|
import { computeBestRun } from "..";
|
||||||
|
import { performance } from "perf_hooks";
|
||||||
|
|
||||||
|
const snake0 = [
|
||||||
|
{ x: 4, y: -1 },
|
||||||
|
{ x: 3, y: -1 },
|
||||||
|
{ x: 2, y: -1 },
|
||||||
|
{ x: 1, y: -1 },
|
||||||
|
{ x: 0, y: -1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const gameOptions = {
|
||||||
|
maxSnakeLength: 5,
|
||||||
|
colors: [1, 2, 3, 4],
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = (grid: Grid, snake0: Snake, k: number) => {
|
||||||
|
const stats: number[] = [];
|
||||||
|
const s0 = performance.now();
|
||||||
|
|
||||||
|
const M = 60 * 1000;
|
||||||
|
let n = 40;
|
||||||
|
|
||||||
|
while (performance.now() - s0 < M && n-- > 0) {
|
||||||
|
const s = performance.now();
|
||||||
|
computeBestRun(grid, snake0, gameOptions, k);
|
||||||
|
|
||||||
|
stats.push(performance.now() - s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
};
|
||||||
|
|
||||||
|
const report = (arr: number[]) => {
|
||||||
|
const average = (arr: number[]) =>
|
||||||
|
arr.reduce((s, x) => s + x, 0) / arr.length;
|
||||||
|
|
||||||
|
const spread = (arr: number[]) => {
|
||||||
|
const m = average(arr);
|
||||||
|
const v = average(arr.map((x) => Math.pow(x - m, 2)));
|
||||||
|
return Math.sqrt(v) / m;
|
||||||
|
};
|
||||||
|
|
||||||
|
const format = (x: number): string => {
|
||||||
|
const u = Math.floor(x / 1000);
|
||||||
|
const d = Math.floor(x % 1000).toString();
|
||||||
|
return u === 0 ? d : format(u) + " " + d.padEnd(3, "0");
|
||||||
|
};
|
||||||
|
|
||||||
|
return `${format(average(arr)).padStart(12)} ms ±${(spread(arr) * 100)
|
||||||
|
.toFixed(2)
|
||||||
|
.padStart(5)}% (x${arr.length.toString().padStart(3)})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
//
|
||||||
|
[10, 10, 1000],
|
||||||
|
[21, 7, 1000],
|
||||||
|
[42, 7, 1000],
|
||||||
|
[42, 7, 5000],
|
||||||
|
[42, 7, 14000],
|
||||||
|
[42, 7, 30000],
|
||||||
|
].forEach(([w, h, k]) => {
|
||||||
|
const random = new ParkMiller(1);
|
||||||
|
const grid = generateRandomGrid(w, h, { ...gameOptions, emptyP: 3 }, (a, b) =>
|
||||||
|
random.integerInRange(a, b)
|
||||||
|
);
|
||||||
|
const stats = run(grid, snake0, k);
|
||||||
|
console.log(`${w}x${h} : ${k}\n ${report(stats)}\n`);
|
||||||
|
});
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { generateEmptyGrid } from "../generateGrid";
|
import { createEmptyGrid, setColor, getColor, isInside } from "../grid";
|
||||||
import { setColor, getColor, isInside } from "../grid";
|
|
||||||
|
|
||||||
it("should set / get cell", () => {
|
it("should set / get cell", () => {
|
||||||
const grid = generateEmptyGrid(2, 3);
|
const grid = createEmptyGrid(2, 3);
|
||||||
|
|
||||||
expect(getColor(grid, 0, 1)).toBe(null);
|
expect(getColor(grid, 0, 1)).toBe(null);
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ test.each([
|
|||||||
[2, 1, false],
|
[2, 1, false],
|
||||||
[0, 3, false],
|
[0, 3, false],
|
||||||
])("isInside", (x, y, output) => {
|
])("isInside", (x, y, output) => {
|
||||||
const grid = generateEmptyGrid(2, 3);
|
const grid = createEmptyGrid(2, 3);
|
||||||
|
|
||||||
expect(isInside(grid, x, y)).toBe(output);
|
expect(isInside(grid, x, y)).toBe(output);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { snakeSelfCollide } from "../snake";
|
import { snakeSelfCollide, snakeWillSelfCollide } from "../snake";
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
[[{ x: 0, y: 0 }], false],
|
[[{ x: 0, y: 0 }], false],
|
||||||
@@ -22,3 +22,41 @@ test.each([
|
|||||||
])("should report snake collision", (snake, collide) => {
|
])("should report snake collision", (snake, collide) => {
|
||||||
expect(snakeSelfCollide(snake)).toBe(collide);
|
expect(snakeSelfCollide(snake)).toBe(collide);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{ x: 1, y: 7 },
|
||||||
|
{ x: 0, y: 7 },
|
||||||
|
{ x: 0, y: 8 },
|
||||||
|
{ x: 0, y: 9 },
|
||||||
|
{ x: 1, y: 9 },
|
||||||
|
],
|
||||||
|
{ x: 1, y: 7 },
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{ x: 1, y: 7 },
|
||||||
|
{ x: 0, y: 7 },
|
||||||
|
{ x: 0, y: 8 },
|
||||||
|
{ x: 0, y: 9 },
|
||||||
|
{ x: 1, y: 9 },
|
||||||
|
],
|
||||||
|
{ x: 1, y: 8 },
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{ x: 1, y: 7 },
|
||||||
|
{ x: 0, y: 7 },
|
||||||
|
{ x: 0, y: 8 },
|
||||||
|
{ x: 0, y: 9 },
|
||||||
|
{ x: 1, y: 9 },
|
||||||
|
],
|
||||||
|
{ x: 1, y: 8 },
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
])("should report snake collision next", (snake, { x, y }, collide) => {
|
||||||
|
expect(snakeWillSelfCollide(snake, x, y)).toBe(collide);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { step } from "../step";
|
import { step } from "../step";
|
||||||
import { generateEmptyGrid } from "../generateGrid";
|
|
||||||
import { around4 } from "../point";
|
import { around4 } from "../point";
|
||||||
import { setColor, getColor } from "../grid";
|
import { createEmptyGrid, setColor, getColor } from "../grid";
|
||||||
|
|
||||||
it("should move snake", () => {
|
it("should move snake", () => {
|
||||||
const grid = generateEmptyGrid(4, 3);
|
const grid = createEmptyGrid(4, 3);
|
||||||
const snake = [{ x: 1, y: 1 }];
|
const snake = [{ x: 1, y: 1 }];
|
||||||
const direction = around4[0];
|
const direction = around4[0];
|
||||||
const stack: number[] = [];
|
const stack: number[] = [];
|
||||||
@@ -36,7 +35,7 @@ it("should move snake", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should move short snake", () => {
|
it("should move short snake", () => {
|
||||||
const grid = generateEmptyGrid(8, 3);
|
const grid = createEmptyGrid(8, 3);
|
||||||
const snake = [{ x: 1, y: 1 }];
|
const snake = [{ x: 1, y: 1 }];
|
||||||
const direction = around4[0];
|
const direction = around4[0];
|
||||||
const stack: number[] = [];
|
const stack: number[] = [];
|
||||||
@@ -75,7 +74,7 @@ it("should move short snake", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should pick up fruit", () => {
|
it("should pick up fruit", () => {
|
||||||
const grid = generateEmptyGrid(4, 3);
|
const grid = createEmptyGrid(4, 3);
|
||||||
const snake = [{ x: 1, y: 1 }];
|
const snake = [{ x: 1, y: 1 }];
|
||||||
const direction = around4[0];
|
const direction = around4[0];
|
||||||
const stack: number[] = [];
|
const stack: number[] = [];
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Grid, Color } from "./grid";
|
import { Grid, Color, setColor, createEmptyGrid } from "./grid";
|
||||||
|
|
||||||
const rand = (a: number, b: number) => Math.floor(Math.random() * (b - a)) + a;
|
const defaultRand = (a: number, b: number) =>
|
||||||
|
Math.floor(Math.random() * (b - a)) + a;
|
||||||
export const generateEmptyGrid = (width: number, height: number) =>
|
|
||||||
generateRandomGrid(width, height, { colors: [], emptyP: 1 });
|
|
||||||
|
|
||||||
export const generateRandomGrid = (
|
export const generateRandomGrid = (
|
||||||
width: number,
|
width: number,
|
||||||
@@ -11,17 +9,17 @@ export const generateRandomGrid = (
|
|||||||
options: { colors: Color[]; emptyP: number } = {
|
options: { colors: Color[]; emptyP: number } = {
|
||||||
colors: [1, 2, 3],
|
colors: [1, 2, 3],
|
||||||
emptyP: 2,
|
emptyP: 2,
|
||||||
}
|
},
|
||||||
|
rand = defaultRand
|
||||||
): Grid => {
|
): Grid => {
|
||||||
const g = {
|
const grid = createEmptyGrid(width, height);
|
||||||
width,
|
|
||||||
height,
|
|
||||||
data: Array.from({ length: width * height }, () => {
|
|
||||||
const x = rand(-options.emptyP, options.colors.length);
|
|
||||||
|
|
||||||
return x < 0 ? null : options.colors[x];
|
for (let x = width; x--; )
|
||||||
}),
|
for (let y = height; y--; ) {
|
||||||
};
|
const k = rand(-options.emptyP, options.colors.length);
|
||||||
|
|
||||||
return g;
|
if (k >= 0) setColor(grid, x, y, options.colors[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,3 +28,9 @@ export const setColor = (
|
|||||||
) => {
|
) => {
|
||||||
grid.data[getIndex(grid, x, y)] = color;
|
grid.data[getIndex(grid, x, y)] = color;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createEmptyGrid = (width: number, height: number) => ({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: Array.from({ length: width * height }, () => null),
|
||||||
|
});
|
||||||
|
|||||||
@@ -80,12 +80,9 @@ const unwrap = (c: ReturnType<typeof createCell> | null): Point[] =>
|
|||||||
export const computeBestRun = (
|
export const computeBestRun = (
|
||||||
grid0: Grid,
|
grid0: Grid,
|
||||||
snake0: Snake,
|
snake0: Snake,
|
||||||
options: { maxSnakeLength: number; colors: Color[] }
|
options: { maxSnakeLength: number; colors: Color[] },
|
||||||
|
u = 8000
|
||||||
) => {
|
) => {
|
||||||
// const grid = copyGrid(grid0);
|
|
||||||
// const snake = copySnake(snake0);
|
|
||||||
// const stack: Color[] = [];
|
|
||||||
|
|
||||||
const computeHeuristic = createComputeHeuristic(
|
const computeHeuristic = createComputeHeuristic(
|
||||||
grid0,
|
grid0,
|
||||||
snake0,
|
snake0,
|
||||||
@@ -104,8 +101,6 @@ export const computeBestRun = (
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
let u = 8000;
|
|
||||||
|
|
||||||
let best = openList[0];
|
let best = openList[0];
|
||||||
|
|
||||||
while (openList.length && u-- > 0) {
|
while (openList.length && u-- > 0) {
|
||||||
@@ -147,28 +142,4 @@ export const computeBestRun = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return unwrap(best);
|
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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@snk/compute",
|
"name": "@snk/compute",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@zeit/ncc": "0.22.3",
|
||||||
|
"park-miller": "1.1.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"benchmark": "ncc run __tests__/benchmark.ts --quiet"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ export const snakeSelfCollideNext = (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const snakeWillSelfCollide = (
|
||||||
|
snake: Snake,
|
||||||
|
headx: number,
|
||||||
|
heady: number
|
||||||
|
) => {
|
||||||
|
for (let i = 0; i < snake.length - 1; i++)
|
||||||
|
if (snake[i].x === headx && snake[i].y === heady) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const snakeSelfCollide = (snake: Snake) => {
|
export const snakeSelfCollide = (snake: Snake) => {
|
||||||
for (let i = 1; i < snake.length; i++)
|
for (let i = 1; i < snake.length; i++)
|
||||||
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true;
|
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Grid, Color, getColor, isInside, setColor } from "./grid";
|
import { Grid, Color, getColor, isInside, setColor } from "./grid";
|
||||||
import { Point } from "./point";
|
import { Point } from "./point";
|
||||||
|
|
||||||
const moveSnake = (snake: Point[], headx: number, heady: number) => {
|
export const moveSnake = (snake: Point[], headx: number, heady: number) => {
|
||||||
for (let k = snake.length - 1; k > 0; k--) {
|
for (let k = snake.length - 1; k > 0; k--) {
|
||||||
snake[k].x = snake[k - 1].x;
|
snake[k].x = snake[k - 1].x;
|
||||||
snake[k].y = snake[k - 1].y;
|
snake[k].y = snake[k - 1].y;
|
||||||
|
|||||||
@@ -4896,6 +4896,11 @@ param-case@^3.0.3:
|
|||||||
dot-case "^3.0.3"
|
dot-case "^3.0.3"
|
||||||
tslib "^1.10.0"
|
tslib "^1.10.0"
|
||||||
|
|
||||||
|
park-miller@1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/park-miller/-/park-miller-1.1.0.tgz#5831ca3b353166735e52a9c5fbb9ff8b1211b7c5"
|
||||||
|
integrity sha512-6mLXc2jkM9dcavPoxDHfof2QM/baCsrgK51iHbJDHl94AwymJv2Z/iGKQVwJMWUAXFiIV6FzY9UskjU3+KvLuA==
|
||||||
|
|
||||||
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
|
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
|
||||||
version "5.1.5"
|
version "5.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
|
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
|
||||||
|
|||||||
Reference in New Issue
Block a user