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
88 changes: 79 additions & 9 deletions resources/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,16 +264,16 @@ const output16Dir: DirectionOutput16[] = [
const outputCardinalDir: DirectionOutputCardinal[] = ['dirN', 'dirE', 'dirS', 'dirW'];
const outputIntercardDir: DirectionOutputIntercard[] = ['dirNE', 'dirSE', 'dirSW', 'dirNW'];

const compareDirectionOutput = (a: DirectionOutput16, b: DirectionOutput16): number => {
const getIndex = (n: DirectionOutput16) => {
const index = output16Dir.indexOf(n);
// Values outside of output16Dir (i.e. 'unknown') sort last
if (index < 0)
return output16Dir.length;
return index;
};
const getDirectionIndex = (n: DirectionOutput16) => {
const index = output16Dir.indexOf(n);
// Values outside of output16Dir (i.e. 'unknown') sort last
if (index < 0)
return output16Dir.length;
return index;
};

return getIndex(a) - getIndex(b);
const compareDirectionOutput = (a: DirectionOutput16, b: DirectionOutput16): number => {
return getDirectionIndex(a) - getDirectionIndex(b);
};

const outputStrings16Dir: OutputStrings = {
Expand Down Expand Up @@ -383,6 +383,76 @@ const outputFromIntercardNum = (dirNum: number): DirectionOutputIntercard => {
return outputIntercardDir[dirNum] ?? 'unknown';
};

export type AnyDirection =
| DirectionOutputCardinal
| DirectionOutputIntercard
| DirectionOutput8
| DirectionOutput16;

// Example usage:
// const dirs: DirectionOutputCardinal[] = ['dirN', 'dirW'];
// dirs.sort(getSortDirectionsClockwiseFunction('dirE'));
// `dirs` should equal `['dirW', 'dirN']`
export const getSortDirectionsClockwiseFunction = (
from?: AnyDirection,
): (left: AnyDirection, right: AnyDirection) => number => {
// Default to dirN
let offset = 0;
if (from !== undefined && from !== 'unknown')
offset = getDirectionIndex(from);

const count = output16Dir.length;

return (left: AnyDirection, right: AnyDirection) => {
if (left === 'unknown' || right === 'unknown') {
return left === right ? 0 : left === 'unknown' ? 1 : -1;
}
const rightIndex = (count + getDirectionIndex(right) - offset) % count;
const leftIndex = (count + getDirectionIndex(left) - offset) % count;
return leftIndex - rightIndex;
};
};

type Point = {
x: number;
y: number;
};

const xyToHeading = (x: number, y: number, centerX: number, centerY: number) => {
x = x - centerX;
y = y - centerY;
return Math.atan2(x, y);
};

// Example usage:
// getSortPointsClockwiseFunction
// const points = [{ x: 101, y: 101 }, { x: 99, y: 99 }];
// points.sort(getSortPointsClockwiseFunction({x: 100, y: 100}, {x: 99, y: 101}));
// `points` should now equal `[{ x: 99, y: 99 }, { x: 101, y: 101 }]`
export const getSortPointsClockwiseFunction = <T extends Point>(
center: T,
reference: number | T = Math.PI, // Default to north
): (left: T, right: T) => number => {
// Convert point to heading if needed
const offset = typeof reference === 'object'
? xyToHeading(reference.x, reference.y, center.x, center.y)
: reference;

const twoPI = Math.PI * 2;

return (left: T, right: T) => {
// Get our base headings for the two points
const rightHeading = xyToHeading(right.x, right.y, center.x, center.y);
const leftHeading = xyToHeading(left.x, left.y, center.x, center.y);

// Adjust by reference offset
const rightHeadingOffset = (twoPI + (offset - rightHeading)) % twoPI;
const leftHeadingOffset = (twoPI + (offset - leftHeading)) % twoPI;

return leftHeadingOffset - rightHeadingOffset;
};
};

export const Directions = {
output8Dir: output8Dir,
output16Dir: output16Dir,
Expand Down
96 changes: 96 additions & 0 deletions test/unittests/util_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { assert } from 'chai';

import Util, {
allJobs,
AnyDirection,
casterDpsJobs,
craftingJobs,
gatheringJobs,
getSortDirectionsClockwiseFunction,
getSortPointsClockwiseFunction,
healerJobs,
limitedJobs,
meleeDpsJobs,
Expand Down Expand Up @@ -93,4 +96,97 @@ describe('util tests', () => {
assert(!Util.isLimitedJob(job))
);
});

it('sorts directions clockwise from a reference direction and undefined reference', () => {
const dirs: AnyDirection[] = [
'dirNE',
'dirSW',
'dirN',
'dirNW',
'dirS',
'dirW',
'dirSE',
'dirE',
];

let expected = ['dirNW', 'dirN', 'dirNE', 'dirE', 'dirSE', 'dirS', 'dirSW', 'dirW'];
let sorted = dirs.sort(getSortDirectionsClockwiseFunction('dirNW'));
assert.deepEqual(sorted, expected);

expected = ['dirN', 'dirNE', 'dirE', 'dirSE', 'dirS', 'dirSW', 'dirW', 'dirNW'];
sorted = dirs.sort(getSortDirectionsClockwiseFunction());
assert.deepEqual(sorted, expected);
});

it('sorts directions and unknowns, putting unknowns at the end', () => {
const dirs1: AnyDirection[] = [
'dirNE',
'unknown',
'dirN',
'unknown',
'dirNW',
'dirS',
];

const expected1 = ['dirN', 'dirNE', 'dirS', 'dirNW', 'unknown', 'unknown'];
assert.deepEqual(dirs1.sort(getSortDirectionsClockwiseFunction()), expected1);

const dirs2: AnyDirection[] = ['dirNE', 'unknown', 'dirN', 'dirNW'];
const expected2 = ['dirNW', 'dirN', 'dirNE', 'unknown'];
assert.deepEqual(
dirs2.sort(getSortDirectionsClockwiseFunction('dirNW')),
expected2,
);
});

it('sorts points clockwise from a reference point', () => {
const getPoints = () => [
{ id: 'NE', x: 1, y: -1 },
{ id: 'SW', x: -1, y: 1 },
{ id: 'N', x: 0, y: -1 },
{ id: 'NW', x: -1, y: -1 },
{ id: 'S', x: 0, y: 1 },
{ id: 'W', x: -1, y: 0 },
{ id: 'SE', x: 3, y: 3 },
{ id: 'E', x: 2, y: 0 },
];

const expected = ['NW', 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W'];

let [refX, refY] = [-1, -1]; // same as NW
let sorted = getPoints().sort(
getSortPointsClockwiseFunction({ x: 0, y: 0 }, { x: refX, y: refY }),
);
assert.deepEqual(sorted.map((point) => point.id), expected);

[refX, refY] = [-1.2, -0.8];
sorted = getPoints().sort(getSortPointsClockwiseFunction({ x: 0, y: 0 }, { x: refX, y: refY }));
assert.deepEqual(sorted.map((point) => point.id), expected);
});

it('sorts points clockwise with numeric reference', () => {
const points = [
{ id: 'NE', x: 1, y: -1 },
{ id: 'NW', x: -1, y: -1 },
{ id: 'N', x: 0, y: -1 },
];

const sorted = points.sort(
getSortPointsClockwiseFunction({ x: 0, y: 0 }, 0), // reference is south
);

assert.deepEqual(sorted.map((point) => point.id), ['NW', 'N', 'NE']);
});

it('keeps points in input order when all angles are equal', () => {
const points = [
{ id: '1', x: 0, y: -3 },
{ id: '2', x: 0, y: -4 },
{ id: '3', x: 0, y: -1 },
{ id: '4', x: 0, y: -2 },
];

const sorted = points.sort(getSortPointsClockwiseFunction({ x: 0, y: 0 }));
assert.deepEqual(sorted.map((point) => point.id), ['1', '2', '3', '4']);
});
});
Loading