Fix double elimination with byes never finishing; hand-built brackets
Byes now cascade through the whole bracket (a slot nobody can fill is a bye or a void), re-run on server start so stuck tournaments recover. New manual bracket builder on the desk: pick round-1 pairings per match. Tests at 5-23 teams for both bracket types. Release 0.5.0. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01MB7nCCAscYsb3zzkT6LHZi
This commit is contained in:
1 parent
a5222953dd
commit
a4bee22d96
13 files changed
+214
-25
No files matched your search
@@ -95,6 +95,7 @@ export function adminTournament({ t, engine }, who, origin, msg, err, events, ke
|
||||
<label><input type="checkbox" name="thirdPlace" value="1" checked> 3rd place</label>
|
||||
<button class="primary small" type="submit" name="what" value="bracket">Generate bracket${poolsDone ? '' : ' (pools not finished)'}</button>
|
||||
</form>` : ''}
|
||||
${!hasBracket && t.stages.includes('bracket') && (t.pools.length || !t.stages.includes('pool')) ? manualBuilder(t) : ''}
|
||||
${generated ? `<details><summary>All matches (${t.matches.length}) — enter or correct any score</summary><table class="adm-table"><tr><th>ID</th><th>Stage</th><th>Match</th><th>Status</th><th>Score</th><th></th></tr>
|
||||
${t.matches.filter(m => !m.conditional || m.teamA).map(m => `<tr><td class="mono">${esc(m.id)}</td><td>${m.stage === 'pool' ? `Pool ${esc(m.poolId)} R${m.round}` : esc(m.label ?? `${m.stage} R${m.round}`)}</td><td>${esc(t.teamName(m.teamA))} vs ${esc(t.teamName(m.teamB))}</td><td>${esc(m.status)}${m.court ? ` C${m.court}` : ''}</td><td class="mono">${m.sets.map(x => x.join('-')).join(', ')}</td>
|
||||
<td>${m.teamA && m.teamB && !['void', 'bye'].includes(m.status) ? `<form method="post" action="/admin/t/${esc(t.slug)}/score" class="inline"><input type="hidden" name="match" value="${esc(m.id)}"><input name="sets" placeholder="21-18" value="${esc(m.sets.map(x => x.join('-')).join(', '))}" style="width:110px"><button class="small" type="submit">Save</button></form>` : ''}</td></tr>`).join('')}</table></details>` : ''}
|
||||
@@ -120,6 +121,26 @@ export function adminTournament({ t, engine }, who, origin, msg, err, events, ke
|
||||
return layout({ title: `${t.name} desk`, admin: who, body, script: '/static/admin.js' });
|
||||
}
|
||||
|
||||
/** Hand-built bracket: one row per first-round match with a team picker on each side. */
|
||||
function manualBuilder(t) {
|
||||
const teams = t.activeTeams();
|
||||
const seeded = t.pools.length ? t.advanceFromPools({ perPool: 2, wildcards: Math.max(0, (1 << Math.ceil(Math.log2(Math.max(2, t.pools.length * 2)))) - t.pools.length * 2) }) : teams.map(x => x.id);
|
||||
const pairs = t.defaultPairs(seeded);
|
||||
const opt = sel => `<option value="">— bye —</option>` + teams.map(x => `<option value="${esc(x.id)}" ${x.id === sel ? 'selected' : ''}>${esc(x.name)}${x.seed ? ` (${x.seed})` : ''}</option>`).join('');
|
||||
return `<details class="manual"><summary>Build the bracket by hand — choose who plays whom in round 1</summary>
|
||||
<form method="post" action="/admin/t/${esc(t.slug)}/generate" class="manualform">
|
||||
<input type="hidden" name="what" value="manual">
|
||||
<div class="inline"><label>Bracket <select name="type"><option value="single">Single elimination</option><option value="double">Double elimination</option></select></label>
|
||||
<label><input type="checkbox" name="thirdPlace" value="1"> 3rd place</label>
|
||||
<span class="muted small">${teams.length} teams in a bracket of ${pairs.length * 2}; ${pairs.length * 2 - teams.length} bye${pairs.length * 2 - teams.length === 1 ? '' : 's'}. Every team must appear exactly once; a side left as "bye" gives the other team a walkover.</span></div>
|
||||
<table class="adm-table pairs"><tr><th>Match</th><th>Side A</th><th></th><th>Side B</th></tr>
|
||||
${pairs.map(([a, b], i) => `<tr><td>${i + 1}</td><td><select name="a${i + 1}">${opt(a)}</select></td><td class="muted">vs</td><td><select name="b${i + 1}">${opt(b)}</select></td></tr>`).join('')}
|
||||
</table>
|
||||
<p class="muted small">Pre-filled with standard seeding (1 v ${pairs.length * 2}, 2 v ${pairs.length * 2 - 1}, …). Winners of matches 1 and 2 meet, 3 and 4 meet, and so on down the sheet.</p>
|
||||
<button class="primary small" type="submit">Build bracket from these pairings</button>
|
||||
</form></details>`;
|
||||
}
|
||||
|
||||
function scorePad(t, m) {
|
||||
const live = m.live ?? [0, 0];
|
||||
return `<div class="scorepad" data-match="${esc(m.id)}">
|
||||
|
||||
Reference in new issue
Block a user