Files
Fabric.Backend.Guild/src/channels/turn-shuffle.ts
hzhang 8c41d23a9c refactor: migrate to ES modules
package.json type=module, tsconfig module/moduleResolution=NodeNext,
target es2022, explicit .js on all relative imports. Center: jsonwebtoken
& bcryptjs switched to default imports (ESM/CJS interop). Verified:
builds, boots, full auth + plugin round-trip work under ESM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:47:36 +01:00

53 lines
2.0 KiB
TypeScript

import { RoundEvent } from '../entities/channel-turn-state.entity.js';
export type ShuffleResult = { paused: true } | { paused: false; newOrder: string[] };
function shuffleInPlace<T>(arr: T[]): T[] {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// End-of-round shuffle.
// - tail = the *last contiguous run* of /no-reply in the round's turn events
// (anchor = first of that run; kept in event order)
// - head = every current member not in tail, shuffled randomly
// - constraint: head[0] !== D, where D = the last member who delivered a
// normal message in the round
// - if the constraint is unsatisfiable (head empty, or head === [D]) the
// rotation pauses instead of shuffling (per spec B.3)
export function computeShuffle(roundEvents: RoundEvent[], currentMembers: string[]): ShuffleResult {
const memberSet = new Set(currentMembers);
// trailing contiguous /no-reply run, in event order, limited to current members
const tail: string[] = [];
for (let i = roundEvents.length - 1; i >= 0; i--) {
if (roundEvents[i].a !== 'noreply') break;
if (memberSet.has(roundEvents[i].u)) tail.unshift(roundEvents[i].u);
}
const tailSet = new Set(tail);
// D = last normal speaker in the round (the one right before the trailing run)
let d: string | null = null;
for (let i = roundEvents.length - 1; i >= 0; i--) {
if (roundEvents[i].a === 'normal' && memberSet.has(roundEvents[i].u)) {
d = roundEvents[i].u;
break;
}
}
const head = currentMembers.filter((u) => !tailSet.has(u));
if (head.length === 0) return { paused: true };
if (head.length === 1 && head[0] === d) return { paused: true };
shuffleInPlace(head);
if (head[0] === d) {
// length >= 2 here (the [d] singleton case returned paused above)
[head[0], head[1]] = [head[1], head[0]];
}
return { paused: false, newOrder: [...head, ...tail] };
}