Average player age at registration; age-matched round 1 for bracket-only tournaments

Optional age on signup and the desk. Auto-generated brackets pair neighbours by
age with byes spread down the sheet; teams without an age are placed at random,
fully random when no ages exist. Release 0.6.0.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MB7nCCAscYsb3zzkT6LHZi
This commit is contained in:
bkvargyasandClaude Fable 5.1 committed 2026-09-05 16:28:12 +00:00
1 parent 3f60a39e45
commit 103c221a12
10 files changed
+114 -19

No files matched your search

+33
View File
@@ -242,3 +242,36 @@ test('withdrawal mid-bracket shows as forfeits (not byes) and play continues to
assert.ok(theirs.every(m => m.status === 'forfeit' && m.loser === quitter), 'every match of the quitter is a forfeit against them');
assert.equal(t.matches.filter(m => m.status === 'bye' && m.round > 1).length, 0, 'no silent byes created by the withdrawal');
});
test('age-matched round 1: neighbours in age play each other, byes spread evenly, everyone placed once', () => {
const t = setup(0, { stages: ['bracket'] });
const ages = [52, 23, 41, 38, 19, 60, 44, 25, 33, 29, null, 47];
ages.forEach((a, i) => t.registerTeam({ name: `T${i + 1}`, players: 2, avgAge: a }));
t.closeRegistration();
const pairs = t.pairsByAge();
assert.equal(pairs.length, 8); // 12 teams -> 16 bracket -> 8 matches
assert.equal(pairs.filter(([a, b]) => a && b).length, 4); // 4 real matches, 4 byes
const placed = pairs.flat().filter(Boolean);
assert.equal(new Set(placed).size, 12);
// every real match pairs two teams within 10 years of each other
for (const [a, b] of pairs) if (a && b) {
const x = t.teams.get(a).avgAge, y = t.teams.get(b).avgAge;
if (x != null && y != null) assert.ok(Math.abs(x - y) <= 10, `${x} vs ${y}`);
}
// byes are spread, not bunched at the bottom
const byeRows = pairs.map(([a, b], i) => (!a || !b) ? i : -1).filter(i => i >= 0);
assert.ok(byeRows[0] < 4 && byeRows.at(-1) >= 4);
t.generateBracketFromPairs(pairs, { type: 'double' });
assert.throws(() => t.registerTeam({ name: 'Bad', players: 2, avgAge: 150 }, { force: true }));
});
test('no ages at all: round-1 pairing is random, still valid', () => {
const t = setup(8, { stages: ['bracket'] });
t.closeRegistration();
let seed = 3; const rnd = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 2 ** 32);
const pairs = t.pairsByAge(undefined, rnd);
assert.equal(pairs.length, 4);
assert.equal(new Set(pairs.flat()).size, 8);
assert.notDeepEqual(pairs.flat(), ids(t), 'order should not simply be seed order');
t.generateBracketFromPairs(pairs, { type: 'single' });
});