Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions gameblocks/modules/behavior/GridPathPlanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ function wrapDelta(delta, size) {
return Math.min(Math.abs(delta), size - Math.abs(delta));
}

function wrapCoord(value, size) {
if (!Number.isFinite(size) || size <= 0) return 0;
let wrapped = value % size;
if (wrapped < 0) wrapped += size;
return wrapped;
}

function wrapCell(cell, board) {
return {
right: wrapCoord(cell.right, board.columns),
forward: wrapCoord(cell.forward, board.rows),
};
}

function priorityInsert(open, entry) {
let index = open.length;
while (index > 0 && open[index - 1].f > entry.f) {
Expand Down Expand Up @@ -124,8 +138,8 @@ export class GridPathPlanner {
) {
if (!start || !goal) return null;

const startCell = cloneCell(start);
const goalCell = cloneCell(goal);
const startCell = wrap ? wrapCell(cloneCell(start), this) : cloneCell(start);
const goalCell = wrap ? wrapCell(cloneCell(goal), this) : cloneCell(goal);
const startKey = gridCellKey(startCell);
const goalKey = gridCellKey(goalCell);
const blockedKeys = normalizeBlockedCells(blocked);
Expand Down Expand Up @@ -198,7 +212,7 @@ export class GridPathPlanner {
return { count: 0, cells: [] };
}

const startCell = cloneCell(start);
const startCell = wrap ? wrapCell(cloneCell(start), this) : cloneCell(start);
const startKey = gridCellKey(startCell);
const blockedKeys = normalizeBlockedCells(blocked);
if (allowStartOccupied) blockedKeys.delete(startKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { GridPathPlanner, gridCellKey } from './GridPathPlanner.js';

const navigation = {
vectors: {
north: { right: 0, forward: 1 },
east: { right: 1, forward: 0 },
south: { right: 0, forward: -1 },
west: { right: -1, forward: 0 },
},
neighborOrder: ['north', 'east', 'south', 'west'],
};

test('wrap findPath maps off-board start and goal onto the torus', () => {
const planner = new GridPathPlanner({ navigation, columns: 10, rows: 10, wrap: true });
const path = planner.findPath({ right: -5, forward: 0 }, { right: 0, forward: 0 });
assert.ok(path);
assert.deepEqual(path[0], { right: 5, forward: 0 });
assert.deepEqual(path.at(-1), { right: 0, forward: 0 });
for (const cell of path) {
assert.ok(cell.right >= 0 && cell.right < 10);
assert.ok(cell.forward >= 0 && cell.forward < 10);
}
assert.equal(gridCellKey(path[0]), '5:0');
});

test('wrap floodFill maps off-board start onto the torus', () => {
const planner = new GridPathPlanner({ navigation, columns: 8, rows: 8, wrap: true });
const fill = planner.floodFill({ right: -1, forward: 0 }, [], true, true, 1);
assert.equal(fill.count, 1);
assert.deepEqual(fill.cells[0], { right: 7, forward: 0 });
});

test('bounded findPath keeps off-board start off-board and fails', () => {
const planner = new GridPathPlanner({ navigation, columns: 10, rows: 10, wrap: false });
const path = planner.findPath({ right: -5, forward: 0 }, { right: 0, forward: 0 });
assert.equal(path, null);
});