@opndev/tennis-match-center 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changes +5 -0
- package/LICENSE +235 -0
- package/README.md +218 -0
- package/lib/auto-planner.mjs +1297 -0
- package/lib/bracket-autofill.mjs +174 -0
- package/lib/bracket-single-elimination.mjs +496 -0
- package/lib/busy-button.mjs +58 -0
- package/lib/components/bracket-board.mjs +586 -0
- package/lib/components/planning-board.mjs +1179 -0
- package/lib/components/roster-board.mjs +273 -0
- package/lib/components/roster-team.mjs +92 -0
- package/lib/components/schedule-board.mjs +1193 -0
- package/lib/court-bands.mjs +185 -0
- package/lib/court-runs.mjs +76 -0
- package/lib/day-rows.mjs +116 -0
- package/lib/division-hues.mjs +52 -0
- package/lib/five-team-planner.mjs +21 -0
- package/lib/flags.mjs +60 -0
- package/lib/four-team-planner.mjs +216 -0
- package/lib/group-schedule.mjs +175 -0
- package/lib/index.mjs +9 -0
- package/lib/knockout-draw.mjs +209 -0
- package/lib/knockout-planner.mjs +573 -0
- package/lib/knockout-waves.mjs +156 -0
- package/lib/lighting.mjs +50 -0
- package/lib/match-time.mjs +13 -0
- package/lib/occupancy.mjs +74 -0
- package/lib/paired-court-planner.mjs +236 -0
- package/lib/plan-placement.mjs +219 -0
- package/lib/plan.mjs +133 -0
- package/lib/planning-day.mjs +177 -0
- package/lib/roster-seeding.mjs +65 -0
- package/lib/run-queue-planner.mjs +603 -0
- package/lib/sessions.mjs +48 -0
- package/lib/three-team-planner.mjs +122 -0
- package/lib/webcomponents.mjs +25 -0
- package/opndev-tennis-match-center-0.0.1.tgz +0 -0
- package/package.json +39 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesley@opndev.io>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { GROUP_TEMPLATES, templateMatches } from './group-schedule.mjs'
|
|
6
|
+
import { buildUnits, placeLayout, unitLayout } from './paired-court-planner.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Plans genuine 3-team groups: A v B, B v C, C v A.
|
|
10
|
+
*
|
|
11
|
+
* A 3-team round robin never needs more than one court at once (every
|
|
12
|
+
* one of its three matches shares a team with the other two, so none
|
|
13
|
+
* of them can run alongside each other) - unlike a 4-team group, which
|
|
14
|
+
* needs a pair. But a lone 3-team group still runs its whole round
|
|
15
|
+
* robin back to back with no rest between a team's own matches, the
|
|
16
|
+
* same problem 4-team bundling solves. So a singles/doubles division
|
|
17
|
+
* drawn in threes takes the very same bundling: two such groups share
|
|
18
|
+
* a court set and take turns, one group's turn a slot at a time,
|
|
19
|
+
* giving each a rest through the other's - it only ever costs one
|
|
20
|
+
* court doing it, not a pair.
|
|
21
|
+
*
|
|
22
|
+
* Team Cup is a genuine 3-team shape too (its own division's teams
|
|
23
|
+
* numbering 4 players a side, not 1 or 2) but does NOT bundle -
|
|
24
|
+
* group-schedule.mjs's own GROUP_TEMPLATES[3] comment already says
|
|
25
|
+
* why ("one court per group"). auto-planner.mjs's buildThreeTeamUnits
|
|
26
|
+
* is what tells the two apart (division.team_size, sent through as
|
|
27
|
+
* each group's own team_size) and keeps Team Cup groups out of this
|
|
28
|
+
* module's bundling entirely, each on its own dedicated unit of one -
|
|
29
|
+
* this module itself has no opinion on team_size at all, only on the
|
|
30
|
+
* groups it is actually handed.
|
|
31
|
+
*
|
|
32
|
+
* The bundling and layout mechanics live in paired-court-planner.mjs,
|
|
33
|
+
* shared with four-team-planner.mjs. This module owns only what is
|
|
34
|
+
* genuinely 3-team's own: its template (group-schedule.mjs's
|
|
35
|
+
* GROUP_TEMPLATES[3]) and the public names callers depend on.
|
|
36
|
+
*
|
|
37
|
+
* Groups are pooled across divisions on purpose - a division with a
|
|
38
|
+
* single 3-team group has nobody of its own to share with.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {Object} params
|
|
43
|
+
* @param {Array<Object>} params.groups
|
|
44
|
+
* {id, competitionId, divisionId, divisionName, divisionGender, groupName, teamIds}
|
|
45
|
+
* @param {Array<Object>} params.courts in the order they should be used
|
|
46
|
+
* @param {Array<Object>} params.slots the day's slots, with start/end minutes
|
|
47
|
+
* @param {string} params.date
|
|
48
|
+
* @param {Object} params.occupancy shared with the other planners
|
|
49
|
+
* @param {?number} params.darkFrom minutes past midnight
|
|
50
|
+
* @returns {{blocks: Array<Object>, unplaced: Array<Object>}}
|
|
51
|
+
*/
|
|
52
|
+
export function planThreeTeamGroups({
|
|
53
|
+
groups,
|
|
54
|
+
courts,
|
|
55
|
+
slots,
|
|
56
|
+
date,
|
|
57
|
+
occupancy,
|
|
58
|
+
darkFrom = null,
|
|
59
|
+
}) {
|
|
60
|
+
const blocks = []
|
|
61
|
+
const unplaced = []
|
|
62
|
+
|
|
63
|
+
// buildUnits leaves a solitary group (nobody to bundle with) out
|
|
64
|
+
// entirely - the caller's to decide what happens to it. Here, that
|
|
65
|
+
// is placing it on its own: still one continuous run on one court,
|
|
66
|
+
// same as it always was, just with nobody to trade rest with.
|
|
67
|
+
const units = groups.length === 1 ? [groups] : threeTeamUnits(groups)
|
|
68
|
+
|
|
69
|
+
for (const unit of units) {
|
|
70
|
+
const placed = placeUnit({ unit, courts, slots, date, occupancy, darkFrom, blocks })
|
|
71
|
+
|
|
72
|
+
if (!placed) {
|
|
73
|
+
unplaced.push(...unit)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { blocks, unplaced }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Splits the groups into units sharing a court set. A lone group (no
|
|
82
|
+
* dense/sparse choice for 3-team - see paired-court-planner.mjs's
|
|
83
|
+
* TODO) is left for the caller to place on its own, the same as
|
|
84
|
+
* four-team-planner.mjs's buildUnits does.
|
|
85
|
+
*/
|
|
86
|
+
export function threeTeamUnits(groups) {
|
|
87
|
+
return buildUnits(groups)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function placeUnit({ unit, courts, slots, date, occupancy, darkFrom, blocks }) {
|
|
91
|
+
const layout = threeTeamUnitLayout(unit.map(group => ({
|
|
92
|
+
id: group.id,
|
|
93
|
+
teamIds: group.teamIds,
|
|
94
|
+
})))
|
|
95
|
+
|
|
96
|
+
return placeLayout({
|
|
97
|
+
layout,
|
|
98
|
+
lookup: groupId => unit.find(entry => entry.id === groupId),
|
|
99
|
+
courts,
|
|
100
|
+
slots,
|
|
101
|
+
date,
|
|
102
|
+
occupancy,
|
|
103
|
+
darkFrom,
|
|
104
|
+
blocks,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Lays out a set of 3-team groups sharing one court, turn by turn - see
|
|
110
|
+
* paired-court-planner.mjs's unitLayout. A single group's own layout
|
|
111
|
+
* (unit of one, no bundling) is the degenerate case: three turns of
|
|
112
|
+
* one match each, back to back on whichever one court hosts it.
|
|
113
|
+
*
|
|
114
|
+
* @param {Array<{id: number, teamIds: number[]}>} groups
|
|
115
|
+
* @returns {Array<Array<{groupId: number, index: number}|null>>} slot by court
|
|
116
|
+
*/
|
|
117
|
+
export function threeTeamUnitLayout(groups) {
|
|
118
|
+
return unitLayout(groups, {
|
|
119
|
+
template: GROUP_TEMPLATES[3],
|
|
120
|
+
matchesFor: group => templateMatches({ teamIds: group.teamIds, groupSize: 3 }),
|
|
121
|
+
})
|
|
122
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesley@opndev.io>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Registers every component under its own tag.
|
|
7
|
+
*
|
|
8
|
+
* Imported for its side effect and nothing else:
|
|
9
|
+
* `import '@opndev/tennis-match-center/webcomponents'`. A component
|
|
10
|
+
* never registers itself - whether a tag is claimed, and under which
|
|
11
|
+
* name, is the page's decision. A caller wanting one component, or its
|
|
12
|
+
* own tag for it, imports the component's module and registers it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { TennisPlanningBoardElement } from './components/planning-board.mjs'
|
|
16
|
+
import { TennisRosterTeamElement } from './components/roster-team.mjs'
|
|
17
|
+
import { TennisRosterBoardElement } from './components/roster-board.mjs'
|
|
18
|
+
import { TennisBracketBoardElement } from './components/bracket-board.mjs'
|
|
19
|
+
import { TennisScheduleBoardElement } from './components/schedule-board.mjs'
|
|
20
|
+
|
|
21
|
+
TennisPlanningBoardElement.register()
|
|
22
|
+
TennisRosterTeamElement.register()
|
|
23
|
+
TennisRosterBoardElement.register()
|
|
24
|
+
TennisBracketBoardElement.register()
|
|
25
|
+
TennisScheduleBoardElement.register()
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": {
|
|
3
|
+
"email": "wesley@opndev.io",
|
|
4
|
+
"name": "Wesley Schwengle"
|
|
5
|
+
},
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@twemoji/api": ">=17.0.0"
|
|
8
|
+
},
|
|
9
|
+
"description": "Match Center",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"jsdoc": "latest",
|
|
12
|
+
"jsdom": "latest",
|
|
13
|
+
"tap": "latest"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./lib/index.mjs",
|
|
17
|
+
"./webcomponents": "./lib/webcomponents.mjs"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tennis",
|
|
21
|
+
"aruba-open"
|
|
22
|
+
],
|
|
23
|
+
"license": "AGPL-3.0-or-later",
|
|
24
|
+
"name": "@opndev/tennis-match-center",
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@skirbi/sugar": ">=0.1.20"
|
|
27
|
+
},
|
|
28
|
+
"private": false,
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "rzil build",
|
|
31
|
+
"jsdoc": "jsdoc -c .jsdoc.json",
|
|
32
|
+
"pkg": "rzil pkg",
|
|
33
|
+
"release": "rzil release",
|
|
34
|
+
"test": "tap"
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": true,
|
|
37
|
+
"type": "module",
|
|
38
|
+
"version": "0.0.1"
|
|
39
|
+
}
|