domain/converters/learning.fragment.converter.ts
domain/converters/learning.fragment.converter.ts is a file in Bane's Lab Site. 175 lines of code and 45 definitions.
import { CHANNEL, GRID_STEP } from "#configuration/constants/learning.constants";
import type { LaidBlock, LearningGrid, LearningPoint } from "#types/learning.types";
import { cellAt, isSolid, searchGrid, walkBack } from "#domain/converters/learning.grid.converter";
interface Walker {
readonly block: LaidBlock;
readonly cx: number;
readonly cy: number;
}
interface Leg {
readonly dx: number;
readonly dy: number;
readonly flat: boolean;
readonly here: LaidBlock;
readonly stop: Walker;
readonly there: LaidBlock;
readonly next: Walker;
}
const isCollinear = function isCollinear(
before: LearningPoint | undefined,
here: LearningPoint,
after: LearningPoint | undefined,
): boolean {
if (before === undefined || after === undefined) {
return false;
}
const level = before.y === here.y && here.y === after.y;
return (before.x === here.x && here.x === after.x) || level;
};
const straighten = function straighten(walked: readonly LearningPoint[]): LearningPoint[] {
const [first] = walked;
const trimmed: LearningPoint[] = first === undefined ? [] : [first];
for (let index = 1; index < walked.length - 1; index += 1) {
const here = walked[index];
if (here !== undefined && !isCollinear(walked[index - 1], here, walked[index + 1])) {
trimmed.push(here);
}
}
const last = walked.at(-1);
if (last !== undefined) {
trimmed.push(last);
}
return trimmed;
};
const anchored = function anchored(
trimmed: readonly LearningPoint[],
from: LearningPoint,
to: LearningPoint,
): LearningPoint[] {
if (trimmed.length < 3) {
return [...trimmed];
}
const held = trimmed.map((point) => ({ ...point }));
const [first, head] = held;
if (head !== undefined && first !== undefined) {
if (first.y === head.y) {
head.y = from.y;
} else {
head.x = from.x;
}
}
const tail = held.at(-2);
const last = held.at(-1);
if (tail !== undefined && last !== undefined) {
if (last.y === tail.y) {
tail.y = to.y;
} else {
tail.x = to.x;
}
}
return held;
};
const routeBetween = function routeBetween(
grid: LearningGrid,
from: LearningPoint,
to: LearningPoint,
): readonly LearningPoint[] {
const start = cellAt(grid, from);
const goal = cellAt(grid, to);
const came = searchGrid(grid, start, goal);
if (came[goal] === -1) {
return [from, { x: to.x, y: from.y }, to];
}
const walked = walkBack(grid, came, start, goal).map((cell) => ({
x: (cell % grid.wide) * GRID_STEP,
y: Math.floor(cell / grid.wide) * GRID_STEP,
}));
return anchored(straighten(walked), from, to);
};
const doorFor = function doorFor(here: LaidBlock, cy: number, rightward: boolean): LearningPoint {
return { x: rightward ? here.x + here.width + CHANNEL : here.x - CHANNEL, y: cy };
};
const legFor = function legFor(grid: LearningGrid, stop: Walker, next: Walker): Leg {
const dx = next.cx - stop.cx;
const dy = next.cy - stop.cy;
const here = stop.block;
const blockedDoor = isSolid(grid, doorFor(here, stop.cy, dx >= 0));
return {
dx,
dy,
flat: Math.abs(dx) >= Math.abs(dy) || (dx !== 0 && !blockedDoor),
here,
next,
stop,
there: next.block,
};
};
const leaveAt = function leaveAt(
grid: LearningGrid,
leg: Leg,
entered: ReadonlyMap<LaidBlock, boolean | null>,
): LearningPoint {
const { dx, dy, here, stop } = leg;
const climbs = dy < 0 ? stop.cy > here.cy : stop.cy < here.cy;
if (!leg.flat && !climbs) {
return { x: stop.cx, y: dy < 0 ? here.y - CHANNEL : here.y + here.height + CHANNEL };
}
const blockedDoor = isSolid(grid, doorFor(here, stop.cy, dx >= 0));
const leaves = blockedDoor || entered.get(here) === dx >= 0 ? dx < 0 : dx >= 0;
return doorFor(here, stop.cy, leaves);
};
const enterAt = function enterAt(
grid: LearningGrid,
leg: Leg,
): { readonly flatIn: boolean; readonly point: LearningPoint } {
const { dx, dy, next, there } = leg;
const gate = { x: dx >= 0 ? there.x - CHANNEL : there.x + there.width + CHANNEL, y: next.cy };
const flatIn = leg.flat || (dx !== 0 && !isSolid(grid, gate));
const lands = dy > 0 ? next.cy > there.cy : next.cy < there.cy;
if (flatIn || lands) {
return { flatIn, point: gate };
}
return { flatIn, point: { x: next.cx, y: dy > 0 ? there.y - CHANNEL : there.y + there.height + CHANNEL } };
};
export const tracePoints = function tracePoints(
grid: LearningGrid,
stops: readonly Walker[],
): readonly LearningPoint[] {
const points: LearningPoint[] = [];
const entered = new Map<LaidBlock, boolean | null>();
for (const [index, stop] of stops.entries()) {
const next = stops[index + 1];
points.push({ x: stop.cx, y: stop.cy });
if (next === undefined || next.block === stop.block) {
continue;
}
const leg = legFor(grid, stop, next);
const out = leaveAt(grid, leg, entered);
const into = enterAt(grid, leg);
const lands = leg.dy > 0 ? next.cy > leg.there.cy : next.cy < leg.there.cy;
entered.set(leg.there, into.flatIn || lands ? leg.dx < 0 : null);
points.push(out, ...routeBetween(grid, out, into.point).slice(1, -1), into.point);
}
return points;
};
export const orthogonal = function orthogonal(
grid: LearningGrid,
points: readonly LearningPoint[],
): readonly LearningPoint[] {
const [first] = points;
const straight: LearningPoint[] = first === undefined ? [] : [first];
for (let index = 1; index < points.length; index += 1) {
const before = straight.at(-1);
const here = points[index];
if (before === undefined || here === undefined) {
continue;
}
if (before.x !== here.x && before.y !== here.y) {
const across = { x: here.x, y: before.y };
const down = { x: before.x, y: here.y };
straight.push(isSolid(grid, across) && !isSolid(grid, down) ? down : across);
}
straight.push(here);
}
return straight;
};