import { getColor, isEmpty, setColorEmpty } from "@snk/types/grid"; import { getHeadX, getHeadY, getSnakeLength, nextSnake, } from "@snk/types/snake"; import { getPathTo } from "./getPathTo"; import { getBestTunnel, trimTunnelEnd, trimTunnelStart } from "./getBestTunnel"; import type { Snake } from "@snk/types/snake"; import type { Color, Grid } from "@snk/types/grid"; import type { Point } from "@snk/types/point"; /** * - list the cells lesser than that are reachable going through * - for each cell of the list * compute the best tunnel to get to the cell and back to the outside ( best = less usage of ) * - for each tunnel* * make the snake go to the start of the tunnel from where it was, traverse the tunnel * repeat * * *sort the tunnel: * - first one to go is the tunnel with the longest line on less than * - then the ones with the best ratio ( N of less than ) / ( N of ) */ export const cleanIntermediateLayer = ( grid: Grid, color: Color, snake0: Snake ) => { const tunnels: Point[][] = []; const chain: Snake[] = [snake0]; for (let x = grid.width; x--; ) for (let y = grid.height; y--; ) { const c = getColor(grid, x, y); if (!isEmpty(c) && c < color) { const tunnel = getBestTunnel(grid, x, y, color, getSnakeLength(snake0)); if (tunnel) tunnels.push(tunnel); } } // find the best first tunnel let i = -1; for (let j = tunnels.length; j--; ) if ( i === -1 || scoreFirst(grid, color, tunnels[i]) < scoreFirst(grid, color, tunnels[j]) ) i = j; while (i >= 0) { const [tunnel] = tunnels.splice(i, 1); // push to chain // 1\ the path to the start on the tunnel const path = getPathTo(grid, chain[0], tunnel[0].x, tunnel[0].y)!; chain.unshift(...path); // 2\ the path into the tunnel for (let i = 1; i < tunnel.length; i++) { const dx = tunnel[i].x - getHeadX(chain[0]); const dy = tunnel[i].y - getHeadY(chain[0]); const snake = nextSnake(chain[0], dx, dy); chain.unshift(snake); } // mutate grid for (const { x, y } of tunnel) setColorEmpty(grid, x, y); // remove the cell that we eat for (let j = tunnels.length; j--; ) { updateTunnel(grid, tunnels[j], tunnel); if (!tunnels[j].length) tunnels.splice(j, 1); } // select the next one i = -1; for (let j = tunnels.length; j--; ) if ( i === -1 || score(grid, color, tunnels[i]) < score(grid, color, tunnels[j]) ) i = j; } return chain; }; const scoreFirst = (grid: Grid, color: Color, tunnel: Point[]) => tunnel.findIndex(({ x, y }) => getColor(grid, x, y) === color); const score = (grid: Grid, color: Color, tunnel: Point[]) => { let nColor = 0; let nLessColor = 0; for (let i = 0; i < tunnel.length; i++) { const { x, y } = tunnel[i]; const j = tunnel.findIndex((u) => x === u.x && y === u.y); if (i !== j) { const c = getColor(grid, x, y); if (c === color) nColor++; else if (!isEmpty(c)) nLessColor++; } } return nLessColor / nColor; }; /** * assuming the grid change and the colors got deleted, update the tunnel */ const updateTunnel = (grid: Grid, tunnel: Point[], toDelete: Point[]) => { while (tunnel.length) { const { x, y } = tunnel[0]; if (toDelete.some((p) => p.x === x && p.y === y)) { tunnel.shift(); trimTunnelStart(grid, tunnel); } else break; } while (tunnel.length) { const { x, y } = tunnel[tunnel.length - 1]; if (toDelete.some((p) => p.x === x && p.y === y)) { tunnel.pop(); trimTunnelEnd(grid, tunnel); } else break; } };