🚿 refactor + clean up
This commit is contained in:
57
packages/types/__fixtures__/grid.ts
Normal file
57
packages/types/__fixtures__/grid.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
// @ts-ignore
|
||||
import * as ParkMiller from "park-miller";
|
||||
import { Color, createEmptyGrid, setColor } from "../grid";
|
||||
import { randomlyFillGrid } from "../randomlyFillGrid";
|
||||
|
||||
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);
|
||||
randomlyFillGrid(
|
||||
grid,
|
||||
{ colors, emptyP },
|
||||
random.integerInRange.bind(random)
|
||||
);
|
||||
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);
|
||||
9
packages/types/__fixtures__/snake.ts
Normal file
9
packages/types/__fixtures__/snake.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createSnakeFromCells } from "../snake";
|
||||
|
||||
const create = (length: number) =>
|
||||
createSnakeFromCells(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);
|
||||
25
packages/types/__tests__/grid.spec.ts
Normal file
25
packages/types/__tests__/grid.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
});
|
||||
45
packages/types/__tests__/snake.spec.ts
Normal file
45
packages/types/__tests__/snake.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
createSnakeFromCells,
|
||||
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(createSnakeFromCells(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(createSnakeFromCells(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(createSnakeFromCells(snk0), 1, 0)).toBe(false);
|
||||
expect(snakeWillSelfCollide(createSnakeFromCells(snk0), 0, -1)).toBe(true);
|
||||
});
|
||||
54
packages/types/grid.ts
Normal file
54
packages/types/grid.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
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 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),
|
||||
});
|
||||
|
||||
const getIndex = (grid: Grid, x: number, y: number) => x * grid.height + y;
|
||||
|
||||
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);
|
||||
|
||||
export const gridEquals = (a: Grid, b: Grid) =>
|
||||
a.data.every((_, i) => a.data[i] === b.data[i]);
|
||||
|
||||
export const createEmptyGrid = (width: number, height: number) => ({
|
||||
width,
|
||||
height,
|
||||
data: new Uint8Array(width * height),
|
||||
});
|
||||
7
packages/types/package.json
Normal file
7
packages/types/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@snk/types",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"park-miller": "1.1.0"
|
||||
}
|
||||
}
|
||||
10
packages/types/point.ts
Normal file
10
packages/types/point.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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;
|
||||
21
packages/types/randomlyFillGrid.ts
Normal file
21
packages/types/randomlyFillGrid.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Grid, Color, setColor, setColorEmpty } from "./grid";
|
||||
|
||||
const defaultRand = (a: number, b: number) =>
|
||||
Math.floor(Math.random() * (b - a + 1)) + a;
|
||||
|
||||
export const randomlyFillGrid = (
|
||||
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 - 1);
|
||||
|
||||
if (k >= 0) setColor(grid, x, y, colors[k]);
|
||||
else setColorEmpty(grid, x, y);
|
||||
}
|
||||
};
|
||||
52
packages/types/snake.ts
Normal file
52
packages/types/snake.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { 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 copySnake = (snake: Snake) => snake.slice() as Snake;
|
||||
|
||||
export const snakeEquals = (a: Snake, b: Snake) => {
|
||||
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* return a copy of the next snake, considering that dx, dy is the direction
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* return true if the next snake will collide with itself
|
||||
*/
|
||||
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): Point[] =>
|
||||
Array.from({ length: snake.length / 2 }, (_, i) => ({
|
||||
x: snake[i * 2 + 0] - 2,
|
||||
y: snake[i * 2 + 1] - 2,
|
||||
}));
|
||||
|
||||
export const createSnakeFromCells = (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;
|
||||
};
|
||||
Reference in New Issue
Block a user