diff --git a/packages/action/generateContributionSnake.ts b/packages/action/generateContributionSnake.ts index e7e413bbf..13f7795b7 100644 --- a/packages/action/generateContributionSnake.ts +++ b/packages/action/generateContributionSnake.ts @@ -37,7 +37,10 @@ export const generateContributionSnake = async (userName: string) => { colorSnake: "purple", }; - const gameOptions = { maxSnakeLength: 5 }; + const gameOptions = { + maxSnakeLength: 5, + colors: Array.from({ length: colorScheme.length - 1 }, (_, i) => i + 1), + }; const gifOptions = { delay: 10 }; diff --git a/packages/compute/index.ts b/packages/compute/index.ts index bc51350bc..4222ec99a 100644 --- a/packages/compute/index.ts +++ b/packages/compute/index.ts @@ -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 { stepSnake, step } from "./step"; -import { copySnake, snakeSelfCollide } from "./snake"; +import { step } from "./step"; +import { copySnake, snakeSelfCollide, Snake } from "./snake"; const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === null); -export const computeBestRun = ( +const createComputeHeuristic = ( + grid0: Grid, + _snake0: Snake, + colors: Color[] +) => { + const colorCount: Record = {}; + 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++) { + 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: Point[], - options: { maxSnakeLength: number } + 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 | 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 g = copyGrid(grid); - const s = copySnake(snake); - const q: Color[] = []; + // const grid = copyGrid(grid0); + // const snake = copySnake(snake0); + // const stack: Color[] = []; - const commands: Point[] = []; + const computeHeuristic = createComputeHeuristic( + grid0, + snake0, + options.colors + ); - let u = 500; + const closeList: any = {}; + const openList = [ + createCell( + computeKey(grid0, snake0, []), + grid0, + snake0, + [], + null, + null, + computeHeuristic(grid0, snake0, []) + ), + ]; - while (!isGridEmpty(g) && u-- > 0) { - let direction; + let u = 7000; - for (let k = 10; k--; ) { - direction = around4[Math.floor(Math.random() * around4.length)]; + let best = openList[0]; - const sn = copySnake(s); - stepSnake(sn, direction, options); + while (openList.length && u-- > 0) { + openList.sort((a, b) => b.f - a.f); + const c = openList.shift()!; - if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) { - break; - } else { - direction = undefined; - } - } + 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 (direction !== undefined) { - step(g, s, q, direction, options); - commands.push(direction); + 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; }; diff --git a/packages/demo/index.ts b/packages/demo/index.ts index 29d9e4f63..5bf8dc006 100644 --- a/packages/demo/index.ts +++ b/packages/demo/index.ts @@ -15,9 +15,9 @@ const drawOptions = { 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 = [ { x: 4, y: -1 }, @@ -71,8 +71,6 @@ document.body.appendChild(input); const autoplayButton = document.createElement("button"); let cancel: any; const loop = () => { - debugger; - input.value = (+input.value + 1) % +input.max; update(+input.value); cancelAnimationFrame(cancel); diff --git a/packages/draw/drawWorld.ts b/packages/draw/drawWorld.ts index 00f2b0440..6cb943e4f 100644 --- a/packages/draw/drawWorld.ts +++ b/packages/draw/drawWorld.ts @@ -45,7 +45,7 @@ export const drawWorld = ( ) => { ctx.save(); - ctx.translate(2 * o.sizeCell, 2 * o.sizeCell); + ctx.translate(1 * o.sizeCell, 2 * o.sizeCell); drawGrid(ctx, grid, o); drawSnake(ctx, snake, o); diff --git a/packages/gif-creator/__tests__/createGif.spec.ts b/packages/gif-creator/__tests__/createGif.spec.ts index 67a4fb7ee..8989d4ebe 100644 --- a/packages/gif-creator/__tests__/createGif.spec.ts +++ b/packages/gif-creator/__tests__/createGif.spec.ts @@ -12,12 +12,12 @@ const drawOptions = { colorSnake: "purple", }; -const gameOptions = { maxSnakeLength: 5 }; +const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] }; const gifOptions = { delay: 200 }; 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 = [ { x: 4, y: -1 }, diff --git a/packages/gif-creator/__tests__/dev.ts b/packages/gif-creator/__tests__/dev.ts index 78176713a..324fb9a4f 100644 --- a/packages/gif-creator/__tests__/dev.ts +++ b/packages/gif-creator/__tests__/dev.ts @@ -12,11 +12,11 @@ const drawOptions = { colorSnake: "purple", }; -const gameOptions = { maxSnakeLength: 5 }; +const gameOptions = { maxSnakeLength: 5, colors: [1, 2, 3, 4] }; 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 = [ { x: 4, y: -1 },