🚿 refactor + clean up

This commit is contained in:
platane
2020-10-20 16:53:42 +02:00
parent 2e818ce425
commit a5c9eed6cc
34 changed files with 141 additions and 250 deletions

View File

@@ -0,0 +1,27 @@
# implementation
## target
The goal is have the stack of eaten color as sorted as possible.
The number of step is not very optimized as for now.
## algorithm
- for each type of color in the grid
- determine all the "free" cell of that color.
> a free cell can be reached by going through only empty cell ( or cell of the same color )
>
> basically, grabbing those cells have no penalty since we don't touch other color to get to the cell and to leave the cell
- eat all the free cells (without optimizing the path for the sake of performance)
- repeat for the next color, consider the current color as the same color
## future
- have an intermediate phase where we eat the remaining cell that are not free, to get rid of them before the next "eat free cells" phase
- use a better heuristic to allows to optimize the number of steps in the "eat free cells" phase

View File

@@ -1,54 +0,0 @@
// @ts-ignore
import * as ParkMiller from "park-miller";
import { Color, createEmptyGrid, setColor } from "@snk/compute/grid";
import { fillRandomGrid } from "../generateGrid";
const colors = [1, 2, 3] as Color[];
// empty small grid
export const empty = createEmptyGrid(5, 5);
// empty small grid with a unique color at the middle
export const simple = createEmptyGrid(5, 5);
setColor(simple, 2, 2, 1 as Color);
// empty small grid with color at each corner
export const corner = createEmptyGrid(5, 5);
setColor(corner, 0, 4, 1 as Color);
setColor(corner, 4, 0, 1 as Color);
setColor(corner, 4, 4, 1 as Color);
setColor(corner, 0, 0, 1 as Color);
// enclaved color
export const enclave = createEmptyGrid(7, 7);
setColor(enclave, 3, 4, 2 as Color);
setColor(enclave, 2, 3, 2 as Color);
setColor(enclave, 2, 4, 2 as Color);
setColor(enclave, 4, 4, 2 as Color);
setColor(enclave, 4, 3, 2 as Color);
setColor(enclave, 3, 3, 1 as Color);
setColor(enclave, 5, 5, 1 as Color);
// enclaved color
export const enclaveBorder = createEmptyGrid(7, 7);
setColor(enclaveBorder, 1, 0, 3 as Color);
setColor(enclaveBorder, 2, 1, 3 as Color);
setColor(enclaveBorder, 3, 0, 3 as Color);
setColor(enclaveBorder, 2, 0, 1 as Color);
const create = (width: number, height: number, emptyP: number) => {
const grid = createEmptyGrid(width, height);
const random = new ParkMiller(10);
const rand = (a: number, b: number) => random.integerInRange(a, b - 1);
fillRandomGrid(grid, { colors, emptyP }, rand);
return grid;
};
// small realistic
export const small = create(10, 7, 3);
export const smallPacked = create(10, 7, 1);
export const smallFull = create(10, 7, 0);
// small realistic
export const realistic = create(52, 7, 3);
export const realisticFull = create(52, 7, 0);

View File

@@ -1,10 +0,0 @@
// @ts-ignore
import { createSnake } from "../snake";
const create = (length: number) =>
createSnake(Array.from({ length }, (_, i) => ({ x: i, y: -1 })));
export const snake1 = create(1);
export const snake3 = create(3);
export const snake5 = create(5);
export const snake9 = create(9);

View File

@@ -1,5 +1,5 @@
import { realistic as grid } from "../__fixtures__/grid";
import { snake3 } from "../__fixtures__/snake";
import { realistic as grid } from "@snk/types/__fixtures__/grid";
import { snake3 } from "@snk/types/__fixtures__/snake";
import { performance } from "perf_hooks";
import { getAvailableRoutes } from "../getAvailableRoutes";
import { getBestRoute } from "../getBestRoute";

View File

@@ -1,6 +1,6 @@
import { getBestRoute } from "../getBestRoute";
import { Color, createEmptyGrid, setColor } from "../grid";
import { createSnake, snakeToCells } from "../snake";
import { Color, createEmptyGrid, setColor } from "@snk/types/grid";
import { createSnakeFromCells, snakeToCells } from "@snk/types/snake";
it("should find best route", () => {
const snk0 = [
@@ -11,7 +11,7 @@ it("should find best route", () => {
const grid = createEmptyGrid(5, 5);
setColor(grid, 3, 3, 1 as Color);
const chain = getBestRoute(grid, createSnake(snk0))!;
const chain = getBestRoute(grid, createSnakeFromCells(snk0))!;
expect(snakeToCells(chain[0])[1]).toEqual({ x: 0, y: 0 });

View File

@@ -1,25 +0,0 @@
import { createEmptyGrid, setColor, getColor, isInside, Color } from "../grid";
it("should set / get cell", () => {
const grid = createEmptyGrid(2, 3);
expect(getColor(grid, 0, 1)).toBe(0);
setColor(grid, 0, 1, 1 as Color);
expect(getColor(grid, 0, 1)).toBe(1);
});
test.each([
[0, 1, true],
[1, 2, true],
[-1, 1, false],
[0, -1, false],
[2, 1, false],
[0, 3, false],
])("isInside", (x, y, output) => {
const grid = createEmptyGrid(2, 3);
expect(isInside(grid, x, y)).toBe(output);
});

View File

@@ -1,43 +0,0 @@
import {
createSnake,
nextSnake,
snakeToCells,
snakeWillSelfCollide,
} from "../snake";
it("should convert to point", () => {
const snk0 = [
{ x: 1, y: -1 },
{ x: 1, y: 0 },
{ x: 0, y: 0 },
];
expect(snakeToCells(createSnake(snk0))).toEqual(snk0);
});
it("should return next snake", () => {
const snk0 = [
{ x: 1, y: 1 },
{ x: 1, y: 0 },
{ x: 0, y: 0 },
];
const snk1 = [
{ x: 2, y: 1 },
{ x: 1, y: 1 },
{ x: 1, y: 0 },
];
expect(snakeToCells(nextSnake(createSnake(snk0), 1, 0))).toEqual(snk1);
});
it("should test snake collision", () => {
const snk0 = [
{ x: 1, y: 1 },
{ x: 1, y: 0 },
{ x: 0, y: 0 },
];
expect(snakeWillSelfCollide(createSnake(snk0), 1, 0)).toBe(false);
expect(snakeWillSelfCollide(createSnake(snk0), 0, -1)).toBe(true);
});

View File

@@ -1,11 +1,11 @@
import { copyGrid, isEmpty, setColorEmpty } from "./grid";
import { getHeadX, getHeadY, snakeEquals } from "./snake";
import { copyGrid, isEmpty, setColorEmpty } from "@snk/types/grid";
import { getHeadX, getHeadY, snakeEquals } from "@snk/types/snake";
import { sortPush } from "./utils/sortPush";
import { arrayEquals } from "./utils/array";
import { getAvailableRoutes } from "./getAvailableRoutes";
import type { Snake } from "./snake";
import type { Grid } from "./grid";
import type { Point } from "./point";
import type { Snake } from "@snk/types/snake";
import type { Grid } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
type M = {
snake: Snake;

View File

@@ -1,21 +0,0 @@
import { Grid, Color, setColor, setColorEmpty } from "./grid";
const defaultRand = (a: number, b: number) =>
Math.floor(Math.random() * (b - a)) + a;
export const fillRandomGrid = (
grid: Grid,
{
colors = [1, 2, 3] as Color[],
emptyP = 2,
}: { colors?: Color[]; emptyP?: number } = {},
rand = defaultRand
) => {
for (let x = grid.width; x--; )
for (let y = grid.height; y--; ) {
const k = rand(-emptyP, colors.length);
if (k >= 0) setColor(grid, x, y, colors[k]);
else setColorEmpty(grid, x, y);
}
};

View File

@@ -1,15 +1,15 @@
import { isInsideLarge, getColor, isInside, isEmpty } from "./grid";
import { around4 } from "./point";
import { isInsideLarge, getColor, isInside, isEmpty } from "@snk/types/grid";
import { around4 } from "@snk/types/point";
import {
getHeadX,
getHeadY,
nextSnake,
snakeEquals,
snakeWillSelfCollide,
} from "./snake";
} from "@snk/types/snake";
import { sortPush } from "./utils/sortPush";
import type { Snake } from "./snake";
import type { Grid, Color } from "./grid";
import type { Snake } from "@snk/types/snake";
import type { Grid, Color } from "@snk/types/grid";
/**
* get routes leading to non-empty cells until onSolution returns true

View File

@@ -1,8 +1,8 @@
import { copyGrid, extractColors } from "./grid";
import type { Snake } from "./snake";
import type { Grid } from "./grid";
import { copyGrid, isEmpty } from "@snk/types/grid";
import { pruneLayer } from "./pruneLayer";
import { cleanLayer } from "./cleanLayer";
import type { Snake } from "@snk/types/snake";
import type { Color, Grid } from "@snk/types/grid";
export const getBestRoute = (grid0: Grid, snake0: Snake) => {
const grid = copyGrid(grid0);
@@ -20,3 +20,11 @@ export const getBestRoute = (grid0: Grid, snake0: Snake) => {
return chain.reverse().slice(1);
};
const extractColors = (grid: Grid): Color[] => {
const colors = new Set<Color>();
grid.data.forEach((c: any) => {
if (!isEmpty(c)) colors.add(c);
});
return Array.from(colors.keys()).sort();
};

View File

@@ -1,101 +0,0 @@
export type Color = (1 | 2 | 3 | 4 | 5 | 6) & { _tag: "__Color__" };
export type Empty = 0 & { _tag: "__Empty__" };
export type Grid = {
width: number;
height: number;
data: Uint8Array;
};
export const getIndex = (grid: Grid, x: number, y: number) =>
x * grid.height + y;
export const isInside = (grid: Grid, x: number, y: number) =>
x >= 0 && y >= 0 && x < grid.width && y < grid.height;
export const isInsideLarge = (grid: Grid, m: number, x: number, y: number) =>
x >= -m && y >= -m && x < grid.width + m && y < grid.height + m;
export const copyGrid = ({ width, height, data }: Grid) => ({
width,
height,
data: Uint8Array.from(data),
});
export const getColor = (grid: Grid, x: number, y: number) =>
grid.data[getIndex(grid, x, y)] as Color | Empty;
export const isEmpty = (color: Color | Empty): color is Empty => color === 0;
export const setColor = (
grid: Grid,
x: number,
y: number,
color: Color | Empty
) => {
grid.data[getIndex(grid, x, y)] = color || 0;
};
export const setColorEmpty = (grid: Grid, x: number, y: number) => {
setColor(grid, x, y, 0 as Empty);
};
/**
* return true if the grid is empty
*/
export const isGridEmpty = (grid: Grid) => grid.data.every((x) => x === 0);
/**
* extract colors
* return a list of the colors found in the grid
*/
export const extractColors = (grid: Grid): Color[] => {
const colors = new Set<Color>();
grid.data.forEach((c: any) => {
if (!isEmpty(c)) colors.add(c);
});
return Array.from(colors.keys()).sort();
};
/**
* extract colors count
* return a list of the colors and their occurrences found in the grid
*/
export const extractColorCount = (grid: Grid) => {
const colors = new Map<Color, number>();
grid.data.forEach((c: any) => {
if (!isEmpty(c)) colors.set(c, 1 + (colors.get(c) || 0));
});
return Array.from(colors.entries()).map(([color, count]) => ({
color,
count,
}));
};
/**
* return true if the both are equals
*/
export const gridEquals = (a: Grid, b: Grid) =>
a.data.every((_, i) => a.data[i] === b.data[i]);
/**
* return a unique string for the grid
*/
export const getGridKey = ({ data }: Grid) => {
let key = "";
const n = 5;
const radius = 1 << n;
for (let k = 0; k < data.length; k += n) {
let u = 0;
for (let i = n; i--; ) u += (1 << i) * +!!data[k + i];
key += u.toString(radius);
}
return key;
};
export const createEmptyGrid = (width: number, height: number) => ({
width,
height,
data: new Uint8Array(width * height),
});

View File

@@ -1,10 +0,0 @@
export type Point = { x: number; y: number };
export const around4 = [
{ x: 1, y: 0 },
{ x: 0, y: -1 },
{ x: -1, y: 0 },
{ x: 0, y: 1 },
] as const;
export const pointEquals = (a: Point, b: Point) => a.x === b.x && a.y === b.y;

View File

@@ -1,8 +1,8 @@
import { getColor, isEmpty, isInside, setColorEmpty } from "./grid";
import { around4 } from "./point";
import { getColor, isEmpty, isInside, setColorEmpty } from "@snk/types/grid";
import { around4 } from "@snk/types/point";
import { sortPush } from "./utils/sortPush";
import type { Color, Grid } from "./grid";
import type { Point } from "./point";
import type { Color, Grid } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
type M = Point & { parent: M | null; h: number };

View File

@@ -1,46 +0,0 @@
import { Point } from "./point";
export type Snake = Uint8Array & { _tag: "__Snake__" };
export const getHeadX = (snake: Snake) => snake[0] - 2;
export const getHeadY = (snake: Snake) => snake[1] - 2;
export const snakeEquals = (a: Snake, b: Snake) => {
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
return true;
};
export const nextSnake = (snake: Snake, dx: number, dy: number) => {
const copy = new Uint8Array(snake.length);
for (let i = 2; i < snake.length; i++) copy[i] = snake[i - 2];
copy[0] = snake[0] + dx;
copy[1] = snake[1] + dy;
return copy as Snake;
};
export const snakeWillSelfCollide = (snake: Snake, dx: number, dy: number) => {
const nx = snake[0] + dx;
const ny = snake[1] + dy;
for (let i = 2; i < snake.length - 2; i += 2)
if (snake[i + 0] === nx && snake[i + 1] === ny) return true;
return false;
};
export const snakeToCells = (snake: Snake) =>
Array.from({ length: snake.length / 2 }, (_, i) => ({
x: snake[i * 2 + 0] - 2,
y: snake[i * 2 + 1] - 2,
}));
export const createSnake = (points: Point[]) => {
const snake = new Uint8Array(points.length * 2);
for (let i = points.length; i--; ) {
snake[i * 2 + 0] = points[i].x + 2;
snake[i * 2 + 1] = points[i].y + 2;
}
return snake as Snake;
};
export const copySnake = (snake: Snake) => snake.slice() as Snake;

View File

@@ -5,8 +5,8 @@ import {
isEmpty,
isInside,
setColorEmpty,
} from "./grid";
import { getHeadX, getHeadY, Snake } from "./snake";
} from "@snk/types/grid";
import { getHeadX, getHeadY, Snake } from "@snk/types/snake";
export const step = (grid: Grid, stack: Color[], snake: Snake) => {
const x = getHeadX(snake);