tournament-brackets-ui 0.1.0
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/.storybook/main.js +23 -0
- package/.storybook/preview.js +13 -0
- package/README.md +70 -0
- package/README.old.md +1 -0
- package/commands.txt +41 -0
- package/dist/esm/CollapsedLeft-B5z4pKZL.js +1042 -0
- package/dist/esm/CollapsedLeft-B5z4pKZL.js.map +1 -0
- package/dist/esm/CollapsedLeft-B6FNo8Dp.js +727 -0
- package/dist/esm/CollapsedLeft-B6FNo8Dp.js.map +1 -0
- package/dist/esm/CollapsedLeft-BB1dXff3.js +1215 -0
- package/dist/esm/CollapsedLeft-BB1dXff3.js.map +1 -0
- package/dist/esm/CollapsedLeft-BMZwFt9m.js +879 -0
- package/dist/esm/CollapsedLeft-BMZwFt9m.js.map +1 -0
- package/dist/esm/CollapsedLeft-BYfnV5-9.js +1228 -0
- package/dist/esm/CollapsedLeft-BYfnV5-9.js.map +1 -0
- package/dist/esm/CollapsedLeft-C3BOW8Vh.js +977 -0
- package/dist/esm/CollapsedLeft-C3BOW8Vh.js.map +1 -0
- package/dist/esm/CollapsedLeft-CBPTeMn9.js +922 -0
- package/dist/esm/CollapsedLeft-CBPTeMn9.js.map +1 -0
- package/dist/esm/CollapsedLeft-CNXxlyEt.js +887 -0
- package/dist/esm/CollapsedLeft-CNXxlyEt.js.map +1 -0
- package/dist/esm/CollapsedLeft-DN7qy7MO.js +961 -0
- package/dist/esm/CollapsedLeft-DN7qy7MO.js.map +1 -0
- package/dist/esm/CollapsedLeft-DO5DyVjM.js +870 -0
- package/dist/esm/CollapsedLeft-DO5DyVjM.js.map +1 -0
- package/dist/esm/CollapsedLeft-DP4ftgM6.js +796 -0
- package/dist/esm/CollapsedLeft-DP4ftgM6.js.map +1 -0
- package/dist/esm/CollapsedLeft-DQO8oQL_.js +800 -0
- package/dist/esm/CollapsedLeft-DQO8oQL_.js.map +1 -0
- package/dist/esm/CollapsedLeft-DXyWTNAC.js +1047 -0
- package/dist/esm/CollapsedLeft-DXyWTNAC.js.map +1 -0
- package/dist/esm/CollapsedLeft-DZGhG_-q.js +728 -0
- package/dist/esm/CollapsedLeft-DZGhG_-q.js.map +1 -0
- package/dist/esm/CollapsedLeft-bAhAurv-.js +908 -0
- package/dist/esm/CollapsedLeft-bAhAurv-.js.map +1 -0
- package/dist/esm/CollapsedLeft-hF26CY1G.js +1003 -0
- package/dist/esm/CollapsedLeft-hF26CY1G.js.map +1 -0
- package/dist/esm/CollapsedRight-1CTx2ozt.js +860 -0
- package/dist/esm/CollapsedRight-1CTx2ozt.js.map +1 -0
- package/dist/esm/CollapsedRight-2mcguPWm.js +715 -0
- package/dist/esm/CollapsedRight-2mcguPWm.js.map +1 -0
- package/dist/esm/CollapsedRight-BCLyZXf2.js +863 -0
- package/dist/esm/CollapsedRight-BCLyZXf2.js.map +1 -0
- package/dist/esm/CollapsedRight-C13Vq_e3.js +1293 -0
- package/dist/esm/CollapsedRight-C13Vq_e3.js.map +1 -0
- package/dist/esm/CollapsedRight-C3-pe8RB.js +799 -0
- package/dist/esm/CollapsedRight-C3-pe8RB.js.map +1 -0
- package/dist/esm/CollapsedRight-CPLDkLx5.js +678 -0
- package/dist/esm/CollapsedRight-CPLDkLx5.js.map +1 -0
- package/dist/esm/CollapsedRight-CdwTany1.js +1388 -0
- package/dist/esm/CollapsedRight-CdwTany1.js.map +1 -0
- package/dist/esm/CollapsedRight-DGOaJHqo.js +1123 -0
- package/dist/esm/CollapsedRight-DGOaJHqo.js.map +1 -0
- package/dist/esm/CollapsedRight-DH1HTBg_.js +1034 -0
- package/dist/esm/CollapsedRight-DH1HTBg_.js.map +1 -0
- package/dist/esm/CollapsedRight-D_0End0L.js +1013 -0
- package/dist/esm/CollapsedRight-D_0End0L.js.map +1 -0
- package/dist/esm/CollapsedRight-Da0UMNrq.js +1547 -0
- package/dist/esm/CollapsedRight-Da0UMNrq.js.map +1 -0
- package/dist/esm/CollapsedRight-DjoLogzE.js +916 -0
- package/dist/esm/CollapsedRight-DjoLogzE.js.map +1 -0
- package/dist/esm/CollapsedRight-DoOykWbk.js +1219 -0
- package/dist/esm/CollapsedRight-DoOykWbk.js.map +1 -0
- package/dist/esm/CollapsedRight-RCxUtg7-.js +1140 -0
- package/dist/esm/CollapsedRight-RCxUtg7-.js.map +1 -0
- package/dist/esm/CollapsedRight-U7cf6t10.js +946 -0
- package/dist/esm/CollapsedRight-U7cf6t10.js.map +1 -0
- package/dist/esm/CollapsedRight-WXy5eWFq.js +1177 -0
- package/dist/esm/CollapsedRight-WXy5eWFq.js.map +1 -0
- package/dist/esm/Expanded-BAxI7aFR.js +1540 -0
- package/dist/esm/Expanded-BAxI7aFR.js.map +1 -0
- package/dist/esm/Expanded-BCEVV7zK.js +1572 -0
- package/dist/esm/Expanded-BCEVV7zK.js.map +1 -0
- package/dist/esm/Expanded-BJtgsFxf.js +2474 -0
- package/dist/esm/Expanded-BJtgsFxf.js.map +1 -0
- package/dist/esm/Expanded-C8Bxoixp.js +1374 -0
- package/dist/esm/Expanded-C8Bxoixp.js.map +1 -0
- package/dist/esm/Expanded-CBu5Dj05.js +1838 -0
- package/dist/esm/Expanded-CBu5Dj05.js.map +1 -0
- package/dist/esm/Expanded-CDYmOWYL.js +1589 -0
- package/dist/esm/Expanded-CDYmOWYL.js.map +1 -0
- package/dist/esm/Expanded-CUg2EMDY.js +2242 -0
- package/dist/esm/Expanded-CUg2EMDY.js.map +1 -0
- package/dist/esm/Expanded-CZX_MCio.js +1406 -0
- package/dist/esm/Expanded-CZX_MCio.js.map +1 -0
- package/dist/esm/Expanded-CmtBl1ZS.js +1626 -0
- package/dist/esm/Expanded-CmtBl1ZS.js.map +1 -0
- package/dist/esm/Expanded-Co8V5jgl.js +2024 -0
- package/dist/esm/Expanded-Co8V5jgl.js.map +1 -0
- package/dist/esm/Expanded-D2_h7YPI.js +1032 -0
- package/dist/esm/Expanded-D2_h7YPI.js.map +1 -0
- package/dist/esm/Expanded-DFP1nUBY.js +1381 -0
- package/dist/esm/Expanded-DFP1nUBY.js.map +1 -0
- package/dist/esm/Expanded-DPFndGqw.js +1118 -0
- package/dist/esm/Expanded-DPFndGqw.js.map +1 -0
- package/dist/esm/Expanded-Jrq0Bh65.js +1739 -0
- package/dist/esm/Expanded-Jrq0Bh65.js.map +1 -0
- package/dist/esm/Expanded-RJigoSw3.js +1552 -0
- package/dist/esm/Expanded-RJigoSw3.js.map +1 -0
- package/dist/esm/Expanded-vqC8qzDP.js +1388 -0
- package/dist/esm/Expanded-vqC8qzDP.js.map +1 -0
- package/dist/esm/ExpandedBase-BUpZvswi.js +88 -0
- package/dist/esm/ExpandedBase-BUpZvswi.js.map +1 -0
- package/dist/esm/NameRow-B-GWjlou.js +50 -0
- package/dist/esm/NameRow-B-GWjlou.js.map +1 -0
- package/dist/esm/Team-CLk1m4Vj.js +25 -0
- package/dist/esm/Team-CLk1m4Vj.js.map +1 -0
- package/dist/esm/TeamsBase-moB9MaNw.js +44 -0
- package/dist/esm/TeamsBase-moB9MaNw.js.map +1 -0
- package/dist/esm/index.css +1 -0
- package/dist/esm/index.js +112 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.cjs +57871 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1 -0
- package/package.json +87 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/rollup.config.mjs +41 -0
- package/src/allocatePlayers.js +235 -0
- package/src/components/Facades/CollapsedLeft.jsx +26 -0
- package/src/components/Facades/CollapsedRight.jsx +27 -0
- package/src/components/Facades/Expanded.jsx +27 -0
- package/src/components/Individuals/08-Player/CollapsedLeft.jsx +340 -0
- package/src/components/Individuals/08-Player/CollapsedRight.jsx +276 -0
- package/src/components/Individuals/08-Player/Expanded.jsx +407 -0
- package/src/components/Individuals/09-Player/CollapsedLeft.jsx +386 -0
- package/src/components/Individuals/09-Player/CollapsedRight.jsx +343 -0
- package/src/components/Individuals/09-Player/Expanded.jsx +614 -0
- package/src/components/Individuals/10-Player/CollapsedLeft.jsx +315 -0
- package/src/components/Individuals/10-Player/CollapsedRight.jsx +367 -0
- package/src/components/Individuals/10-Player/Expanded.jsx +611 -0
- package/src/components/Individuals/11-Player/CollapsedLeft.jsx +352 -0
- package/src/components/Individuals/11-Player/CollapsedRight.jsx +412 -0
- package/src/components/Individuals/11-Player/Expanded.jsx +616 -0
- package/src/components/Individuals/12-Player/CollapsedLeft.jsx +396 -0
- package/src/components/Individuals/12-Player/CollapsedRight.jsx +451 -0
- package/src/components/Individuals/12-Player/Expanded.jsx +622 -0
- package/src/components/Individuals/13-Player/CollapsedLeft.jsx +426 -0
- package/src/components/Individuals/13-Player/CollapsedRight.jsx +490 -0
- package/src/components/Individuals/13-Player/Expanded.jsx +701 -0
- package/src/components/Individuals/14-Player/CollapsedLeft.jsx +458 -0
- package/src/components/Individuals/14-Player/CollapsedRight.jsx +509 -0
- package/src/components/Individuals/14-Player/Expanded.jsx +801 -0
- package/src/components/Individuals/16-Player/CollapsedLeft.jsx +514 -0
- package/src/components/Individuals/16-Player/CollapsedRight.jsx +608 -0
- package/src/components/Individuals/16-Player/Expanded.jsx +963 -0
- package/src/components/Individuals/stylesheet.module.css +360 -0
- package/src/components/NameRow.jsx +21 -0
- package/src/components/NameRowFillable.jsx +30 -0
- package/src/components/ScoreRow.jsx +29 -0
- package/src/components/ScoreRowFillable.jsx +64 -0
- package/src/components/Team.jsx +20 -0
- package/src/components/TeamFillable.jsx +25 -0
- package/src/components/Teams/08-Team/CollapsedLeft.jsx +304 -0
- package/src/components/Teams/08-Team/CollapsedRight.jsx +232 -0
- package/src/components/Teams/08-Team/Expanded.jsx +356 -0
- package/src/components/Teams/09-Team/CollapsedLeft.jsx +335 -0
- package/src/components/Teams/09-Team/CollapsedRight.jsx +282 -0
- package/src/components/Teams/09-Team/Expanded.jsx +486 -0
- package/src/components/Teams/10-Team/CollapsedLeft.jsx +269 -0
- package/src/components/Teams/10-Team/CollapsedRight.jsx +304 -0
- package/src/components/Teams/10-Team/Expanded.jsx +483 -0
- package/src/components/Teams/11-Team/CollapsedLeft.jsx +292 -0
- package/src/components/Teams/11-Team/CollapsedRight.jsx +335 -0
- package/src/components/Teams/11-Team/Expanded.jsx +491 -0
- package/src/components/Teams/12-Team/CollapsedLeft.jsx +328 -0
- package/src/components/Teams/12-Team/CollapsedRight.jsx +363 -0
- package/src/components/Teams/12-Team/Expanded.jsx +497 -0
- package/src/components/Teams/13-Team/CollapsedLeft.jsx +355 -0
- package/src/components/Teams/13-Team/CollapsedRight.jsx +399 -0
- package/src/components/Teams/13-Team/Expanded.jsx +569 -0
- package/src/components/Teams/14-Team/CollapsedLeft.jsx +382 -0
- package/src/components/Teams/14-Team/CollapsedRight.jsx +420 -0
- package/src/components/Teams/14-Team/Expanded.jsx +661 -0
- package/src/components/Teams/16-Team/CollapsedLeft.jsx +437 -0
- package/src/components/Teams/16-Team/CollapsedRight.jsx +496 -0
- package/src/components/Teams/16-Team/Expanded.jsx +795 -0
- package/src/components/Teams/stylesheet.module.css +300 -0
- package/src/components/_internal/ExpandedBase.jsx +60 -0
- package/src/components/_internal/TeamsBase.jsx +45 -0
- package/src/components/_internal/VariantFacade.jsx +26 -0
- package/src/index.js +3 -0
- package/src/scripts/convert-html-to-jsx.js +46 -0
- package/src/scripts/inject-players.js +45 -0
- package/src/scripts/scoreUtils.js +25 -0
- package/src/scripts/update-classnames.js +26 -0
- package/src/seeding.js +488 -0
- package/src/stories/Button.jsx +39 -0
- package/src/stories/Button.stories.js +49 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.jsx +56 -0
- package/src/stories/Header.stories.js +29 -0
- package/src/stories/Individuals/08-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/08-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/08-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/09-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/09-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/09-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/10-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/10-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/10-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/11-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/11-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/11-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/12-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/12-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/12-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/13-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/13-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/13-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/14-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/14-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/14-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Individuals/16-Player/CollapsedLeft.stories.jsx +30 -0
- package/src/stories/Individuals/16-Player/CollapsedRight.stories.jsx +30 -0
- package/src/stories/Individuals/16-Player/Expanded.stories.jsx +30 -0
- package/src/stories/Page.jsx +69 -0
- package/src/stories/Page.stories.js +28 -0
- package/src/stories/Teams/08-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/08-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/08-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/09-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/09-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/09-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/10-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/10-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/10-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/11-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/11-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/11-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/12-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/12-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/12-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/13-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/13-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/13-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/14-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/14-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/14-Team/Expanded.stories.jsx +33 -0
- package/src/stories/Teams/16-Team/CollapsedLeft.stories.jsx +33 -0
- package/src/stories/Teams/16-Team/CollapsedRight.stories.jsx +33 -0
- package/src/stories/Teams/16-Team/Expanded.stories.jsx +33 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/stories/button.css +30 -0
- package/src/stories/data/players.js +116 -0
- package/src/stories/data/teams.js +32 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +68 -0
package/src/seeding.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
// ---- 1) Static config ----
|
|
2
|
+
export const BRACKET_BYES = {
|
|
3
|
+
8: 0,
|
|
4
|
+
9: 1,
|
|
5
|
+
10: 6,
|
|
6
|
+
11: 5,
|
|
7
|
+
12: 4,
|
|
8
|
+
13: 3,
|
|
9
|
+
14: 2,
|
|
10
|
+
16: 0,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const ALLOWED_SIZES = [8, 9, 10, 11, 12, 13, 14, 16];
|
|
14
|
+
const MIN = ALLOWED_SIZES[0];
|
|
15
|
+
const MAX = ALLOWED_SIZES[ALLOWED_SIZES.length - 1];
|
|
16
|
+
|
|
17
|
+
// ---- 2) Pick bracket sizes (same logic) ----
|
|
18
|
+
export function pickBracketSizes(N) {
|
|
19
|
+
let count = 1;
|
|
20
|
+
while (count * MAX < N) count <<= 1;
|
|
21
|
+
|
|
22
|
+
if (N <= MIN * count) {
|
|
23
|
+
return Array(count).fill(MIN);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const base = Math.floor(N / count);
|
|
27
|
+
const sizes = Array(count).fill(Math.max(MIN, Math.min(MAX, base)));
|
|
28
|
+
let extra = N - sizes.reduce((sum, s) => sum + s, 0);
|
|
29
|
+
|
|
30
|
+
// round-robin distribute the extra slots
|
|
31
|
+
for (let i = 0; extra > 0; i = (i + 1) % count) {
|
|
32
|
+
if (sizes[i] < MAX) {
|
|
33
|
+
sizes[i]++;
|
|
34
|
+
extra--;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return sizes;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---- 3) Utilities ----
|
|
41
|
+
function byeCountForSize(size) {
|
|
42
|
+
return BRACKET_BYES[size] ?? 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function neighborsOf(i, total) {
|
|
46
|
+
const left = (i - 1 + total) % total;
|
|
47
|
+
const right = (i + 1) % total;
|
|
48
|
+
return [left, right];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function countByClub(players) {
|
|
52
|
+
const m = new Map();
|
|
53
|
+
for (const p of players) {
|
|
54
|
+
m.set(p.club, (m.get(p.club) || 0) + 1);
|
|
55
|
+
}
|
|
56
|
+
return m;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sumByesForSizes(sizes) {
|
|
60
|
+
return sizes.reduce((acc, sz) => acc + byeCountForSize(sz), 0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Largest remainder apportionment for bye quotas: proportional fairness
|
|
64
|
+
function computeByeQuotaByClub(players, sizes) {
|
|
65
|
+
const totalByes = sumByesForSizes(sizes);
|
|
66
|
+
const totals = countByClub(players);
|
|
67
|
+
const N = players.length;
|
|
68
|
+
|
|
69
|
+
const entries = [...totals.entries()].map(([club, cnt]) => {
|
|
70
|
+
const exact = (totalByes * cnt) / N;
|
|
71
|
+
const floor = Math.floor(exact);
|
|
72
|
+
const remainder = exact - floor;
|
|
73
|
+
return { club, floor, remainder };
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let allocated = entries.reduce((s, e) => s + e.floor, 0);
|
|
77
|
+
const remaining = totalByes - allocated;
|
|
78
|
+
|
|
79
|
+
entries.sort((a, b) => b.remainder - a.remainder); // biggest remainder first
|
|
80
|
+
for (let i = 0; i < remaining; i++) entries[i].floor += 1;
|
|
81
|
+
|
|
82
|
+
const quota = new Map();
|
|
83
|
+
for (const e of entries) quota.set(e.club, e.floor);
|
|
84
|
+
return quota; // Map<club, targetByesForTournament>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---- 5) Strong-player aware + club-balanced group distribution ----
|
|
88
|
+
// - Place strong first (avoid neighbor same-club strong).
|
|
89
|
+
// - Then place others, preferring the group that has the FEWEST of that club so far.
|
|
90
|
+
function distributeIntoGroups(players, sizes) {
|
|
91
|
+
const bracketCount = sizes.length;
|
|
92
|
+
|
|
93
|
+
const brackets = sizes.map((sz, i) => ({
|
|
94
|
+
group: i + 1,
|
|
95
|
+
size: sz,
|
|
96
|
+
players: [],
|
|
97
|
+
}));
|
|
98
|
+
const capacityLeft = sizes.map((s) => s);
|
|
99
|
+
|
|
100
|
+
const strong = players.filter((p) => p.bias);
|
|
101
|
+
const others = players.filter((p) => !p.bias);
|
|
102
|
+
|
|
103
|
+
// Track strong clubs per group for neighbor rule
|
|
104
|
+
const strongInGroupClub = Array.from({ length: bracketCount }, () => new Set());
|
|
105
|
+
|
|
106
|
+
// Track club counts per group for balancing
|
|
107
|
+
const clubCountInGroup = Array.from({ length: bracketCount }, () => new Map());
|
|
108
|
+
|
|
109
|
+
const incClub = (g, club) => {
|
|
110
|
+
const m = clubCountInGroup[g];
|
|
111
|
+
m.set(club, (m.get(club) || 0) + 1);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Place strong first with scoring: capacity, no neighbor same-club strong, fewer club counts
|
|
115
|
+
for (const p of strong) {
|
|
116
|
+
let best = -1;
|
|
117
|
+
let bestScore = -Infinity;
|
|
118
|
+
|
|
119
|
+
for (let g = 0; g < bracketCount; g++) {
|
|
120
|
+
if (capacityLeft[g] <= 0) continue;
|
|
121
|
+
|
|
122
|
+
const neigh = neighborsOf(g, bracketCount);
|
|
123
|
+
const neighborConflict = neigh.some((ng) => strongInGroupClub[ng].has(p.club)) ? 1 : 0;
|
|
124
|
+
const sameGroupConflict = strongInGroupClub[g].has(p.club) ? 1 : 0;
|
|
125
|
+
const clubLoad = (clubCountInGroup[g].get(p.club) || 0);
|
|
126
|
+
|
|
127
|
+
// Heuristic score:
|
|
128
|
+
// +100 * capacity
|
|
129
|
+
// -20 if neighbor has same-club strong
|
|
130
|
+
// -10 if same group already has same-club strong
|
|
131
|
+
// -3 per existing members of this club in the group
|
|
132
|
+
const score =
|
|
133
|
+
capacityLeft[g] * 100 - neighborConflict * 20 - sameGroupConflict * 10 - clubLoad * 3;
|
|
134
|
+
|
|
135
|
+
if (score > bestScore) {
|
|
136
|
+
bestScore = score;
|
|
137
|
+
best = g;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (best !== -1) {
|
|
142
|
+
brackets[best].players.push(p);
|
|
143
|
+
capacityLeft[best]--;
|
|
144
|
+
strongInGroupClub[best].add(p.club);
|
|
145
|
+
incClub(best, p.club);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Place others: choose group with lowest club load, tie-break by capacity
|
|
150
|
+
for (const p of others) {
|
|
151
|
+
let best = -1;
|
|
152
|
+
let bestScore = -Infinity;
|
|
153
|
+
|
|
154
|
+
for (let g = 0; g < bracketCount; g++) {
|
|
155
|
+
if (capacityLeft[g] <= 0) continue;
|
|
156
|
+
const clubLoad = (clubCountInGroup[g].get(p.club) || 0);
|
|
157
|
+
const score = -clubLoad * 100 + capacityLeft[g]; // prefer groups with fewer of this club
|
|
158
|
+
if (score > bestScore) {
|
|
159
|
+
bestScore = score;
|
|
160
|
+
best = g;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (best !== -1) {
|
|
165
|
+
brackets[best].players.push(p);
|
|
166
|
+
capacityLeft[best]--;
|
|
167
|
+
incClub(best, p.club);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return brackets;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---- 6) In-bracket ordering with fair byes + constraints (no nulls) ----
|
|
175
|
+
function orderBracket(players, size, byeAssignedPerClub, byeQuotaByClub) {
|
|
176
|
+
const byeCount = byeCountForSize(size);
|
|
177
|
+
const byeStart = size - byeCount; // indexes [byeStart .. size-1] are bye positions
|
|
178
|
+
|
|
179
|
+
const strong = players.filter((p) => p.bias);
|
|
180
|
+
const nonStrong = players.filter((p) => !p.bias);
|
|
181
|
+
|
|
182
|
+
// Helper: can this club still receive a bye (global fairness)?
|
|
183
|
+
const canClubTakeMoreByes = (club) => {
|
|
184
|
+
const used = byeAssignedPerClub.get(club) || 0;
|
|
185
|
+
const quota = byeQuotaByClub.get(club) || 0;
|
|
186
|
+
return used < quota;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Sort candidates by "need" = (assigned/quota) ascending, then by frequency (optional)
|
|
190
|
+
const needScore = (club) => {
|
|
191
|
+
const used = byeAssignedPerClub.get(club) || 0;
|
|
192
|
+
const quota = byeQuotaByClub.get(club) || 0;
|
|
193
|
+
// If quota is zero, treat as high needScore so it's picked only if forced later
|
|
194
|
+
return quota > 0 ? used / quota : 1e9;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// We'll avoid giving multiple byes in the SAME bracket to the SAME club if alternatives exist
|
|
198
|
+
const bracketByeClubs = new Set();
|
|
199
|
+
|
|
200
|
+
// Pick K players for bye slots using fairness:
|
|
201
|
+
const byePicks = [];
|
|
202
|
+
|
|
203
|
+
// 6a) Prefer strong players that can still take byes (respect quotas)
|
|
204
|
+
const strongEligible = strong
|
|
205
|
+
.filter((p) => canClubTakeMoreByes(p.club))
|
|
206
|
+
.sort((a, b) => needScore(a.club) - needScore(b.club));
|
|
207
|
+
|
|
208
|
+
for (const s of strongEligible) {
|
|
209
|
+
if (byePicks.length >= byeCount) break;
|
|
210
|
+
// avoid duplicate club in this bracket bye region if possible
|
|
211
|
+
if (!bracketByeClubs.has(s.club)) {
|
|
212
|
+
byePicks.push(s);
|
|
213
|
+
bracketByeClubs.add(s.club);
|
|
214
|
+
byeAssignedPerClub.set(s.club, (byeAssignedPerClub.get(s.club) || 0) + 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 6b) If not enough, allow remaining strong regardless of quota (but still try to avoid duplicates)
|
|
219
|
+
if (byePicks.length < byeCount) {
|
|
220
|
+
const strongFallback = strong
|
|
221
|
+
.filter((p) => !byePicks.includes(p))
|
|
222
|
+
.sort((a, b) => needScore(a.club) - needScore(b.club));
|
|
223
|
+
for (const s of strongFallback) {
|
|
224
|
+
if (byePicks.length >= byeCount) break;
|
|
225
|
+
if (!bracketByeClubs.has(s.club)) {
|
|
226
|
+
byePicks.push(s);
|
|
227
|
+
bracketByeClubs.add(s.club);
|
|
228
|
+
// Only increment assigned if they were actually under quota
|
|
229
|
+
if (canClubTakeMoreByes(s.club)) {
|
|
230
|
+
byeAssignedPerClub.set(s.club, (byeAssignedPerClub.get(s.club) || 0) + 1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 6c) Still not enough? Fill with non-strong fairly
|
|
237
|
+
if (byePicks.length < byeCount) {
|
|
238
|
+
const nonStrongEligible = nonStrong
|
|
239
|
+
.filter((p) => canClubTakeMoreByes(p.club))
|
|
240
|
+
.sort((a, b) => needScore(a.club) - needScore(b.club));
|
|
241
|
+
for (const p of nonStrongEligible) {
|
|
242
|
+
if (byePicks.length >= byeCount) break;
|
|
243
|
+
if (!bracketByeClubs.has(p.club)) {
|
|
244
|
+
byePicks.push(p);
|
|
245
|
+
bracketByeClubs.add(p.club);
|
|
246
|
+
byeAssignedPerClub.set(p.club, (byeAssignedPerClub.get(p.club) || 0) + 1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 6d) If STILL short (tight quotas), take anyone left (minimize duplicates if possible)
|
|
252
|
+
if (byePicks.length < byeCount) {
|
|
253
|
+
const rest = players.filter((p) => !byePicks.includes(p));
|
|
254
|
+
// prefer clubs not yet present in this bye region
|
|
255
|
+
rest.sort((a, b) => {
|
|
256
|
+
const aDup = bracketByeClubs.has(a.club) ? 1 : 0;
|
|
257
|
+
const bDup = bracketByeClubs.has(b.club) ? 1 : 0;
|
|
258
|
+
if (aDup !== bDup) return aDup - bDup;
|
|
259
|
+
return needScore(a.club) - needScore(b.club);
|
|
260
|
+
});
|
|
261
|
+
for (const p of rest) {
|
|
262
|
+
if (byePicks.length >= byeCount) break;
|
|
263
|
+
byePicks.push(p);
|
|
264
|
+
bracketByeClubs.add(p.club);
|
|
265
|
+
if (canClubTakeMoreByes(p.club)) {
|
|
266
|
+
byeAssignedPerClub.set(p.club, (byeAssignedPerClub.get(p.club) || 0) + 1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Build slots: players only, no nulls; put bye picks into tail indexes
|
|
272
|
+
const slots = Array(size);
|
|
273
|
+
const byeSet = new Set(byePicks);
|
|
274
|
+
let tail = byeStart;
|
|
275
|
+
for (const p of byePicks) {
|
|
276
|
+
slots[tail++] = p;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Remaining players
|
|
280
|
+
const remaining = players.filter((p) => !byeSet.has(p));
|
|
281
|
+
|
|
282
|
+
// Place remaining strong spaced in non-bye region
|
|
283
|
+
const nonByeLen = size - byeCount;
|
|
284
|
+
const spreadOrder = (() => {
|
|
285
|
+
const mid = Math.floor(nonByeLen / 2);
|
|
286
|
+
const order = [];
|
|
287
|
+
let l = 0, r = mid;
|
|
288
|
+
while (order.length < nonByeLen) {
|
|
289
|
+
if (l < mid) order.push(l++);
|
|
290
|
+
if (r < nonByeLen) order.push(r++);
|
|
291
|
+
}
|
|
292
|
+
return order;
|
|
293
|
+
})();
|
|
294
|
+
|
|
295
|
+
// First, place remaining strong on spread positions
|
|
296
|
+
const remainingStrong = remaining.filter((p) => p.bias);
|
|
297
|
+
let so = 0;
|
|
298
|
+
for (const s of remainingStrong) {
|
|
299
|
+
while (so < spreadOrder.length && slots[spreadOrder[so]]) so++;
|
|
300
|
+
if (so >= spreadOrder.length) break;
|
|
301
|
+
slots[spreadOrder[so]] = s;
|
|
302
|
+
so++;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Then fill rest with non-strong avoiding first-round same-club if possible
|
|
306
|
+
const pool = remaining.filter((p) => !p.bias);
|
|
307
|
+
|
|
308
|
+
function pickNotClub(club) {
|
|
309
|
+
if (!club) return pool.shift() || null;
|
|
310
|
+
const idx = pool.findIndex((p) => p.club !== club);
|
|
311
|
+
if (idx === -1) return pool.shift() || null;
|
|
312
|
+
return pool.splice(idx, 1)[0] || null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for (let i = 0; i < nonByeLen; i += 2) {
|
|
316
|
+
const a = i, b = i + 1;
|
|
317
|
+
const clubA = slots[a]?.club ?? null;
|
|
318
|
+
const clubB = slots[b]?.club ?? null;
|
|
319
|
+
|
|
320
|
+
const fill = (idx, avoidClub) => {
|
|
321
|
+
if (slots[idx]) return;
|
|
322
|
+
const pick = pickNotClub(avoidClub);
|
|
323
|
+
if (pick) slots[idx] = pick;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
if (clubA && !clubB) {
|
|
327
|
+
fill(b, clubA);
|
|
328
|
+
} else if (!clubA && clubB) {
|
|
329
|
+
fill(a, clubB);
|
|
330
|
+
} else if (!clubA && !clubB) {
|
|
331
|
+
const first = pickNotClub(null);
|
|
332
|
+
if (first) slots[a] = first;
|
|
333
|
+
const second = pickNotClub(first ? first.club : null);
|
|
334
|
+
if (second) slots[b] = second;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Any leftover holes in non-bye region (rare): fill sequentially
|
|
339
|
+
for (let i = 0; i < nonByeLen && pool.length > 0; i++) {
|
|
340
|
+
if (!slots[i]) slots[i] = pool.shift();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Sanity: fill any remaining empties (shouldn’t happen)
|
|
344
|
+
const leftovers = remaining.filter((p) => !slots.includes(p) && !byeSet.has(p));
|
|
345
|
+
for (let i = 0; i < size && leftovers.length > 0; i++) {
|
|
346
|
+
if (!slots[i]) slots[i] = leftovers.shift();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return slots;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ---- 7) Main entry ----
|
|
353
|
+
export function assignBrackets(players) {
|
|
354
|
+
const sizes = pickBracketSizes(players.length);
|
|
355
|
+
|
|
356
|
+
// (A) Fair bye quota per club across the whole tournament
|
|
357
|
+
const byeQuotaByClub = computeByeQuotaByClub(players, sizes); // Map<club, quota>
|
|
358
|
+
const byeAssignedPerClub = new Map(); // Map<club, used>
|
|
359
|
+
|
|
360
|
+
// (B) Place players into groups with strong + club balancing
|
|
361
|
+
const grouped = distributeIntoGroups(players, sizes);
|
|
362
|
+
|
|
363
|
+
// (C) Order inside each bracket; assign byes fairly; no nulls
|
|
364
|
+
for (const br of grouped) {
|
|
365
|
+
br.players = orderBracket(br.players, br.size, byeAssignedPerClub, byeQuotaByClub);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return grouped;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/*
|
|
372
|
+
Bye quotas (global fairness): We compute how many byes each club should get overall, proportional to that club’s population, and then we respect that quota when assigning byes in each bracket. This prevents one big club from hogging all byes.
|
|
373
|
+
|
|
374
|
+
No duplicate club byes in the same bracket (if alternatives exist): inside a bracket’s bye region we avoid giving multiple byes to the same club to keep it fair round-by-round.
|
|
375
|
+
|
|
376
|
+
Club-balanced grouping: when placing players into groups, we always pick the group that currently has the fewest of that player’s club (subject to capacity), which evens out club distribution across all groups.
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
const divisionAPlayers = [
|
|
381
|
+
{ id: "P001", name: "Amina Yusuf", club: "TKC", bias: false },
|
|
382
|
+
{ id: "P002", name: "Hiroki Tanaka", club: "TKC", bias: false },
|
|
383
|
+
{ id: "P003", name: "Lucas Oliveira", club: "TKC", bias: true },
|
|
384
|
+
{ id: "P004", name: "Priya Singh", club: "TKC", bias: false },
|
|
385
|
+
{ id: "P005", name: "Li Wei", club: "TKC", bias: false },
|
|
386
|
+
{ id: "P006", name: "Jamal Thompson", club: "TKC", bias: false },
|
|
387
|
+
{ id: "P007", name: "Sofia García", club: "TKC", bias: true },
|
|
388
|
+
{ id: "P008", name: "Viktor Johansson", club: "TKC", bias: false },
|
|
389
|
+
{ id: "P009", name: "Zuri Ndlovu", club: "TKC", bias: false },
|
|
390
|
+
{ id: "P010", name: "Amara Diop", club: "TKC", bias: false },
|
|
391
|
+
{ id: "P011", name: "Kenji Watanabe", club: "TKC", bias: false },
|
|
392
|
+
{ id: "P012", name: "María Estévez", club: "TKC", bias: false },
|
|
393
|
+
{ id: "P013", name: "Oskar Lindgren", club: "TKC", bias: false },
|
|
394
|
+
{ id: "P014", name: "Aisha El-Sayed", club: "TKC", bias: false },
|
|
395
|
+
{ id: "P015", name: "Linh Tran", club: "TKC", bias: false },
|
|
396
|
+
{ id: "P016", name: "Diego Castillo", club: "ETO", bias: false },
|
|
397
|
+
{ id: "P017", name: "Fatima Al-Hassan", club: "ETO", bias: false },
|
|
398
|
+
{ id: "P018", name: "Olivia Montgomery", club: "ETO", bias: false },
|
|
399
|
+
{ id: "P019", name: "Thiago Oliveira", club: "ETO", bias: false },
|
|
400
|
+
{ id: "P020", name: "Zara Novak", club: "ETO", bias: false },
|
|
401
|
+
{ id: "P021", name: "Amina Yusuf", club: "ETO", bias: false },
|
|
402
|
+
{ id: "P022", name: "Hiroki Tanaka", club: "ETO", bias: true },
|
|
403
|
+
{ id: "P023", name: "Lucas Oliveira", club: "ETO", bias: false },
|
|
404
|
+
{ id: "P024", name: "Priya Singh", club: "ETO", bias: false },
|
|
405
|
+
{ id: "P025", name: "Li Wei", club: "ETO", bias: false },
|
|
406
|
+
{ id: "P026", name: "Jamal Thompson", club: "ETO", bias: false },
|
|
407
|
+
{ id: "P027", name: "Sofia García", club: "ETO", bias: false },
|
|
408
|
+
{ id: "P028", name: "Viktor Johansson", club: "UOT", bias: false },
|
|
409
|
+
{ id: "P029", name: "Zuri Ndlovu", club: "UOT", bias: true },
|
|
410
|
+
{ id: "P030", name: "Amara Diop", club: "UOT", bias: false },
|
|
411
|
+
{ id: "P031", name: "Kenji Watanabe", club: "UOT", bias: false },
|
|
412
|
+
{ id: "P032", name: "María Estévez", club: "UOT", bias: false },
|
|
413
|
+
{ id: "P033", name: "Oskar Lindgren", club: "UOT", bias: true },
|
|
414
|
+
{ id: "P034", name: "Aisha El-Sayed", club: "UOT", bias: false },
|
|
415
|
+
{ id: "P035", name: "Linh Tran", club: "UOT", bias: false },
|
|
416
|
+
{ id: "P036", name: "Diego Castillo", club: "UOT", bias: false },
|
|
417
|
+
{ id: "P037", name: "Fatima Al-Hassan", club: "UOT", bias: false },
|
|
418
|
+
{ id: "P038", name: "Olivia Montgomery", club: "UOT", bias: false },
|
|
419
|
+
{ id: "P039", name: "Thiago Oliveira", club: "JCC", bias: false },
|
|
420
|
+
{ id: "P040", name: "Zara Novak", club: "JCC", bias: false },
|
|
421
|
+
{ id: "P041", name: "Amina Yusuf", club: "JCC", bias: false },
|
|
422
|
+
{ id: "P042", name: "Hiroki Tanaka", club: "JCC", bias: false },
|
|
423
|
+
{ id: "P043", name: "Lucas Oliveira", club: "JCC", bias: false },
|
|
424
|
+
{ id: "P044", name: "Priya Singh", club: "JCC", bias: true },
|
|
425
|
+
{ id: "P045", name: "Li Wei", club: "JCC", bias: false },
|
|
426
|
+
{ id: "P046", name: "Jamal Thompson", club: "JCC", bias: false },
|
|
427
|
+
{ id: "P047", name: "Sofia García", club: "JCC", bias: false },
|
|
428
|
+
{ id: "P048", name: "Viktor Johansson", club: "JCC", bias: false },
|
|
429
|
+
{ id: "P049", name: "Zuri Ndlovu", club: "UWA", bias: false },
|
|
430
|
+
{ id: "P050", name: "Amara Diop", club: "UWA", bias: false },
|
|
431
|
+
{ id: "P051", name: "Kenji Watanabe", club: "UWA", bias: false },
|
|
432
|
+
{ id: "P052", name: "María Estévez", club: "UWA", bias: false },
|
|
433
|
+
{ id: "P053", name: "Oskar Lindgren", club: "UWA", bias: false },
|
|
434
|
+
{ id: "P054", name: "Aisha El-Sayed", club: "UWA", bias: false },
|
|
435
|
+
{ id: "P055", name: "Linh Tran", club: "UWA", bias: false },
|
|
436
|
+
{ id: "P056", name: "Diego Castillo", club: "UWA", bias: false },
|
|
437
|
+
{ id: "P057", name: "Fatima Al-Hassan", club: "UWA", bias: false },
|
|
438
|
+
{ id: "P058", name: "Olivia Montgomery", club: "UWA", bias: false },
|
|
439
|
+
{ id: "P059", name: "Thiago Oliveira", club: "TMU", bias: false },
|
|
440
|
+
{ id: "P060", name: "Zara Novak", club: "TMU", bias: false },
|
|
441
|
+
{ id: "P061", name: "Amina Yusuf", club: "TMU", bias: false },
|
|
442
|
+
{ id: "P062", name: "Hiroki Tanaka", club: "TMU", bias: false },
|
|
443
|
+
{ id: "P063", name: "Lucas Oliveira", club: "TMU", bias: false },
|
|
444
|
+
{ id: "P064", name: "Priya Singh", club: "TMU", bias: false },
|
|
445
|
+
{ id: "P065", name: "Li Wei", club: "TMU", bias: false },
|
|
446
|
+
{ id: "P066", name: "Jamal Thompson", club: "TMU", bias: false },
|
|
447
|
+
{ id: "P067", name: "Sofia García", club: "TMU", bias: false },
|
|
448
|
+
{ id: "P068", name: "Viktor Johansson", club: "MAR", bias: false },
|
|
449
|
+
{ id: "P069", name: "Zuri Ndlovu", club: "MAR", bias: false },
|
|
450
|
+
{ id: "P070", name: "Amara Diop", club: "MAR", bias: false },
|
|
451
|
+
{ id: "P071", name: "Kenji Watanabe", club: "MAR", bias: false },
|
|
452
|
+
{ id: "P072", name: "María Estévez", club: "MAR", bias: true },
|
|
453
|
+
{ id: "P073", name: "Oskar Lindgren", club: "MAR", bias: false },
|
|
454
|
+
{ id: "P074", name: "Aisha El-Sayed", club: "MAR", bias: false },
|
|
455
|
+
{ id: "P075", name: "Linh Tran", club: "MAR", bias: false },
|
|
456
|
+
{ id: "P076", name: "Diego Castillo", club: "MAR", bias: false },
|
|
457
|
+
{ id: "P077", name: "Fatima Al-Hassan", club: "ARC", bias: false },
|
|
458
|
+
{ id: "P078", name: "Olivia Montgomery", club: "ARC", bias: false },
|
|
459
|
+
{ id: "P079", name: "Thiago Oliveira", club: "ARC", bias: false },
|
|
460
|
+
{ id: "P080", name: "Zara Novak", club: "ARC", bias: false },
|
|
461
|
+
{ id: "P081", name: "Amina Yusuf", club: "ARC", bias: false },
|
|
462
|
+
{ id: "P082", name: "Hiroki Tanaka", club: "ARC", bias: false },
|
|
463
|
+
{ id: "P083", name: "Lucas Oliveira", club: "ARC", bias: false },
|
|
464
|
+
{ id: "P084", name: "Priya Singh", club: "ARC", bias: false },
|
|
465
|
+
{ id: "P085", name: "Li Wei", club: "ARC", bias: false },
|
|
466
|
+
{ id: "P086", name: "Jamal Thompson", club: "BLU", bias: false },
|
|
467
|
+
{ id: "P087", name: "Sofia García", club: "BLU", bias: false },
|
|
468
|
+
{ id: "P088", name: "Viktor Johansson", club: "BLU", bias: false },
|
|
469
|
+
{ id: "P089", name: "Zuri Ndlovu", club: "BLU", bias: false },
|
|
470
|
+
{ id: "P090", name: "Amara Diop", club: "BLU", bias: false },
|
|
471
|
+
{ id: "P091", name: "Kenji Watanabe", club: "BLU", bias: false },
|
|
472
|
+
{ id: "P092", name: "María Estévez", club: "BLU", bias: false },
|
|
473
|
+
{ id: "P093", name: "Oskar Lindgren", club: "BLU", bias: false },
|
|
474
|
+
{ id: "P094", name: "Aisha El-Sayed", club: "BLU", bias: false },
|
|
475
|
+
{ id: "P095", name: "Linh Tran", club: "GRN", bias: false },
|
|
476
|
+
{ id: "P096", name: "Diego Castillo", club: "GRN", bias: false },
|
|
477
|
+
{ id: "P097", name: "Fatima Al-Hassan", club: "GRN", bias: false },
|
|
478
|
+
{ id: "P098", name: "Olivia Montgomery", club: "GRN", bias: false },
|
|
479
|
+
{ id: "P099", name: "Thiago Oliveira", club: "GRN", bias: false },
|
|
480
|
+
{ id: "P100", name: "Zara Novak", club: "GRN", bias: false },
|
|
481
|
+
{ id: "P101", name: "Amina Yusuf", club: "GRN", bias: false },
|
|
482
|
+
{ id: "P102", name: "Hiroki Tanaka", club: "GRN", bias: false },
|
|
483
|
+
{ id: "P103", name: "Lucas Oliveira", club: "GRN", bias: false },
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
const divisionABrackets = assignBrackets(divisionAPlayers);
|
|
487
|
+
console.log("total players:", divisionAPlayers.length);
|
|
488
|
+
console.log(JSON.stringify(divisionABrackets, null, 2));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
|
|
5
|
+
import './button.css';
|
|
6
|
+
|
|
7
|
+
/** Primary UI component for user interaction */
|
|
8
|
+
export const Button = ({
|
|
9
|
+
primary = false,
|
|
10
|
+
backgroundColor = null,
|
|
11
|
+
size = 'medium',
|
|
12
|
+
label,
|
|
13
|
+
...props
|
|
14
|
+
}) => {
|
|
15
|
+
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
|
|
16
|
+
return (
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
|
|
20
|
+
style={backgroundColor && { backgroundColor }}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{label}
|
|
24
|
+
</button>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Button.propTypes = {
|
|
29
|
+
/** Is this the principal call to action on the page? */
|
|
30
|
+
primary: PropTypes.bool,
|
|
31
|
+
/** What background color to use */
|
|
32
|
+
backgroundColor: PropTypes.string,
|
|
33
|
+
/** How large should the button be? */
|
|
34
|
+
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
|
35
|
+
/** Button contents */
|
|
36
|
+
label: PropTypes.string.isRequired,
|
|
37
|
+
/** Optional click handler */
|
|
38
|
+
onClick: PropTypes.func,
|
|
39
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { fn } from '@storybook/test';
|
|
2
|
+
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
|
|
5
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
6
|
+
export default {
|
|
7
|
+
title: 'Example/Button',
|
|
8
|
+
component: Button,
|
|
9
|
+
parameters: {
|
|
10
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
|
16
|
+
argTypes: {
|
|
17
|
+
backgroundColor: { control: 'color' },
|
|
18
|
+
},
|
|
19
|
+
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
|
20
|
+
args: { onClick: fn() },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
|
24
|
+
export const Primary = {
|
|
25
|
+
args: {
|
|
26
|
+
primary: true,
|
|
27
|
+
label: 'Button',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Secondary = {
|
|
32
|
+
args: {
|
|
33
|
+
label: 'Button',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Large = {
|
|
38
|
+
args: {
|
|
39
|
+
size: 'large',
|
|
40
|
+
label: 'Button',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Small = {
|
|
45
|
+
args: {
|
|
46
|
+
size: 'small',
|
|
47
|
+
label: 'Button',
|
|
48
|
+
},
|
|
49
|
+
};
|