@johnmorrisdotca/taisen-brackets 0.6.1 → 0.8.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.
@@ -0,0 +1,19 @@
1
+ import type { JudoMethod, JudoSide } from "./judo-model.ts";
2
+ import type { Tie } from "./types.ts";
3
+ import { type Voice } from "./locales.ts";
4
+ type Filled = Required<JudoSide>;
5
+ /** A side's score as the box writes it: `I` for ippon, `W` a waza-ari (`W2` two), `Y` a yuko (`Y3` three), "0" for none. The marks are the language's (`judoIppon`, `judoWazaAri`, `judoYuko`). */
6
+ export declare function judoMark(side: Filled, v: Voice): string;
7
+ /** A side's score in words, for a sentence: "ippon", "waza-ari yuko", "2 waza-ari 3 yuko", "0". */
8
+ export declare function judoSideLong(side: Filled, v: Voice): string;
9
+ /** How a contest was won, in the language. */
10
+ export declare function judoMethodWord(method: JudoMethod, v: Voice): string;
11
+ /** The pieces of a judo contest's note, short or in a sentence: how it was won, golden score, the contest time, and (long only) each side's shido. */
12
+ export declare function judoNotes(tie: Tie, v: Voice, long: boolean): string[];
13
+ /** What a row of a box writes for a side's score: a judo contest's marks, else the number. `""` when nothing was played. */
14
+ export declare function sideScore(tie: Tie, k: 0 | 1, v: Voice): string;
15
+ /** A side's score in words for a sentence (a judo contest's, else the number). */
16
+ export declare function sideScoreLong(tie: Tie, k: 0 | 1, v: Voice): string;
17
+ /** The cards beside a side's score: a yellow one for each shido, and a red one for the third (hansoku-make) or for a direct hansoku-make: `["y", "y", "r"]`. */
18
+ export declare function shidoCards(tie: Tie, k: 0 | 1): ("y" | "r")[];
19
+ export {};
@@ -0,0 +1,84 @@
1
+ import { judoClock } from "./judo-model.js";
2
+ import { fill } from "./locales.js";
3
+ /** A side's score as the box writes it: `I` for ippon, `W` a waza-ari (`W2` two), `Y` a yuko (`Y3` three), "0" for none. The marks are the language's (`judoIppon`, `judoWazaAri`, `judoYuko`). */
4
+ export function judoMark(side, v) {
5
+ const w = v.words;
6
+ if (side.ippon)
7
+ return w.judoIppon;
8
+ const parts = [];
9
+ if (side.wazaAri > 0)
10
+ parts.push(w.judoWazaAri + (side.wazaAri > 1 ? v.number(side.wazaAri) : ""));
11
+ if (side.yuko > 0)
12
+ parts.push(w.judoYuko + (side.yuko > 1 ? v.number(side.yuko) : ""));
13
+ return parts.length ? parts.join(" ") : v.number(0);
14
+ }
15
+ /** A side's score in words, for a sentence: "ippon", "waza-ari yuko", "2 waza-ari 3 yuko", "0". */
16
+ export function judoSideLong(side, v) {
17
+ const w = v.words;
18
+ if (side.ippon)
19
+ return w.judoIpponLong;
20
+ const parts = [];
21
+ if (side.wazaAri > 0)
22
+ parts.push(side.wazaAri > 1 ? `${v.number(side.wazaAri)} ${w.judoWazaAriLong}` : w.judoWazaAriLong);
23
+ if (side.yuko > 0)
24
+ parts.push(side.yuko > 1 ? `${v.number(side.yuko)} ${w.judoYukoLong}` : w.judoYukoLong);
25
+ return parts.length ? parts.join(" ") : v.number(0);
26
+ }
27
+ /** How a contest was won, in the language. */
28
+ export function judoMethodWord(method, v) {
29
+ const w = v.words;
30
+ switch (method) {
31
+ case "ippon": return w.judoMethodIppon;
32
+ case "waza-ari-awasete-ippon": return w.judoMethodAwasete;
33
+ case "score": return w.judoMethodScore;
34
+ case "hansoku-make": return w.judoMethodHansoku;
35
+ case "fusen-gachi": return w.judoMethodFusen;
36
+ case "kiken-gachi": return w.judoMethodKiken;
37
+ }
38
+ }
39
+ /** The pieces of a judo contest's note, short or in a sentence: how it was won, golden score, the contest time, and (long only) each side's shido. */
40
+ export function judoNotes(tie, v, long) {
41
+ const j = tie.judo;
42
+ if (!j)
43
+ return [];
44
+ const w = v.words;
45
+ const out = [];
46
+ if (j.method !== null)
47
+ out.push(judoMethodWord(j.method, v));
48
+ if (j.goldenScore)
49
+ out.push(long ? w.judoGoldenLong : w.judoGolden);
50
+ if (j.seconds !== null)
51
+ out.push(fill(w.judoTime, { t: judoClock(j.seconds) }));
52
+ if (long) {
53
+ [0, 1].forEach((k) => {
54
+ const team = tie.sides[k];
55
+ if (j.sides[k].shido > 0 && team)
56
+ out.push(fill(w.judoShidoOf, { name: v.team(team), n: v.number(j.sides[k].shido) }));
57
+ });
58
+ }
59
+ return out;
60
+ }
61
+ /** What a row of a box writes for a side's score: a judo contest's marks, else the number. `""` when nothing was played. */
62
+ export function sideScore(tie, k, v) {
63
+ if (tie.judo)
64
+ return tie.result.score ? judoMark(tie.judo.sides[k], v) : "";
65
+ return tie.result.score ? v.number(tie.result.score[k]) : "";
66
+ }
67
+ /** A side's score in words for a sentence (a judo contest's, else the number). */
68
+ export function sideScoreLong(tie, k, v) {
69
+ if (tie.judo)
70
+ return judoSideLong(tie.judo.sides[k], v);
71
+ return tie.result.score ? v.number(tie.result.score[k]) : "";
72
+ }
73
+ /** The cards beside a side's score: a yellow one for each shido, and a red one for the third (hansoku-make) or for a direct hansoku-make: `["y", "y", "r"]`. */
74
+ export function shidoCards(tie, k) {
75
+ const j = tie.judo;
76
+ if (!j)
77
+ return [];
78
+ const n = j.sides[k].shido;
79
+ if (n >= 3)
80
+ return ["y", "y", "r"];
81
+ // A direct hansoku-make is the loser's red card, when it came with no third shido.
82
+ const penalised = j.method === "hansoku-make" && j.winner !== null && j.winner !== k;
83
+ return penalised ? [...Array.from({ length: n }, () => "y"), "r"] : Array.from({ length: n }, () => "y");
84
+ }
package/dist/judo.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { BracketInput, BracketLayout, Matchup, Team } from "./types.ts";
2
+ export { JUDO_WEIGHT_CLASSES, judoOutcome, judoSeconds, judoClock } from "./judo-model.ts";
3
+ export type { JudoMethod, JudoOutcome, JudoResult, JudoSide } from "./judo-model.ts";
4
+ /** How a judo tournament is drawn: `repechage` (the IJF's quarter-final repechage, with its two bronze medal contests), or `direct` (a direct knockout in which both semi-final losers are bronze medallists). */
5
+ export type JudoSystem = "repechage" | "direct";
6
+ export interface JudoBracketOptions {
7
+ /** Default `repechage` for eight athletes or more, else `direct`. */
8
+ system?: JudoSystem;
9
+ /** The ids of the contests are `${prefix}1-1`, `${prefix}1-2`… (round, then place in the round), `${prefix}rep-1`, `${prefix}bronze-1`. Default "J". */
10
+ prefix?: string;
11
+ }
12
+ /**
13
+ * The contests of one judo weight category for any number of athletes, seeded in the standard way (the top seeds have the byes when the number is
14
+ * not a power of two). The athlete drawn first is white and the second blue (`Matchup.sides`: first white).
15
+ *
16
+ * With `repechage` (IJF Sport and Organisation Rules, 2.5.2 "Quarter-final Repechage", the system of the Olympic Games): the athletes are in two tables
17
+ * and an elimination produces two finalists. "The athletes defeated in the quarter-final will compete in two (2) repechage contests. The winners of each of
18
+ * these two (2) repechage contests will compete in bronze medal contests against the loser of the semi-final contest of the respective opposite table.
19
+ * The winners (2) of those contests are placed third." The repechage and bronze contests are `stage: "repechage"` and `stage: "bronze"` matchups, fed by
20
+ * the losers of the tree's, and are drawn under it. Needs eight athletes or more. With `direct`, there are no such contests: the two losing semi-finalists are
21
+ * the bronze medallists (`judoPodium`).
22
+ *
23
+ * Double repechage and full repechage (2.5.3 and 2.5.4) are not drawn: their pools are laid out in the rules' Appendix G charts.
24
+ */
25
+ export declare function judoBracket(athletes: readonly Team[], options?: JudoBracketOptions): {
26
+ teams: Team[];
27
+ matchups: Matchup[];
28
+ };
29
+ /** The medallists of a judo tournament, as far as it has been fought. */
30
+ export interface JudoPodium {
31
+ gold: Team | null;
32
+ silver: Team | null;
33
+ /** The two bronze medallists: the winners of the bronze medal contests, or the losing semi-finalists of a direct knockout. Only those decided. */
34
+ bronze: Team[];
35
+ }
36
+ /** The gold, silver and two bronze medals of a bracket (matchups, input or layout), from the final, the bronze medal contests or the semi-finals. */
37
+ export declare function judoPodium(input: BracketLayout | readonly Matchup[] | BracketInput): JudoPodium;
38
+ export interface JudoCategory {
39
+ sex: "men" | "women";
40
+ /** `"-66"` (up to and including 66 kg), `"+100"` (over 100 kg): see `JUDO_WEIGHT_CLASSES`. */
41
+ weight: string;
42
+ }
43
+ /** A weight category as a heading in a language: "Men -66 kg", "Women +78 kg". */
44
+ export declare function judoCategoryLabel(category: JudoCategory, locale?: string): string;
package/dist/judo.js ADDED
@@ -0,0 +1,75 @@
1
+ import { layoutBracket } from "./layout.js";
2
+ import { singleElimination } from "./seeding.js";
3
+ import { fill, voiceOf } from "./locales.js";
4
+ export { JUDO_WEIGHT_CLASSES, judoOutcome, judoSeconds, judoClock } from "./judo-model.js";
5
+ /**
6
+ * The contests of one judo weight category for any number of athletes, seeded in the standard way (the top seeds have the byes when the number is
7
+ * not a power of two). The athlete drawn first is white and the second blue (`Matchup.sides`: first white).
8
+ *
9
+ * With `repechage` (IJF Sport and Organisation Rules, 2.5.2 "Quarter-final Repechage", the system of the Olympic Games): the athletes are in two tables
10
+ * and an elimination produces two finalists. "The athletes defeated in the quarter-final will compete in two (2) repechage contests. The winners of each of
11
+ * these two (2) repechage contests will compete in bronze medal contests against the loser of the semi-final contest of the respective opposite table.
12
+ * The winners (2) of those contests are placed third." The repechage and bronze contests are `stage: "repechage"` and `stage: "bronze"` matchups, fed by
13
+ * the losers of the tree's, and are drawn under it. Needs eight athletes or more. With `direct`, there are no such contests: the two losing semi-finalists are
14
+ * the bronze medallists (`judoPodium`).
15
+ *
16
+ * Double repechage and full repechage (2.5.3 and 2.5.4) are not drawn: their pools are laid out in the rules' Appendix G charts.
17
+ */
18
+ export function judoBracket(athletes, options = {}) {
19
+ const system = options.system ?? (athletes.length >= 8 ? "repechage" : "direct");
20
+ if (system !== "repechage" && system !== "direct")
21
+ throw new RangeError(`taisen-brackets: a judo system is "repechage" or "direct", not ${JSON.stringify(system)}`);
22
+ if (athletes.length < 4)
23
+ throw new RangeError("taisen-brackets: a judo bracket needs four athletes or more (the two losing semi-finalists are bronze medallists)");
24
+ if (system === "repechage" && athletes.length < 8)
25
+ throw new RangeError("taisen-brackets: a quarter-final repechage needs eight athletes or more; use system: \"direct\" for fewer");
26
+ const prefix = options.prefix ?? "J";
27
+ const made = singleElimination(athletes, { prefix });
28
+ if (system === "direct")
29
+ return made;
30
+ const rounds = Math.max(...made.matchups.map((m) => m.round ?? 0));
31
+ const semis = made.matchups.filter((m) => m.round === rounds - 1);
32
+ const quarters = (semi) => {
33
+ const [a, b] = semi.feeds ?? [];
34
+ if (typeof a !== "string" || typeof b !== "string")
35
+ throw new Error("taisen-brackets: a semi-final of a repechage bracket needs two quarter-finals feeding it");
36
+ return [a, b];
37
+ };
38
+ const loser = (tie) => ({ tie, result: "loser" });
39
+ const extra = [];
40
+ semis.forEach((semi, k) => {
41
+ const [q1, q2] = quarters(semi);
42
+ extra.push({ id: `${prefix}rep-${k + 1}`, sides: [null, null], feeds: [loser(q1), loser(q2)], stage: "repechage" });
43
+ });
44
+ semis.forEach((semi, k) => {
45
+ // The repechage winner of one table meets the semi-final loser of the other.
46
+ const other = semis[1 - k];
47
+ extra.push({ id: `${prefix}bronze-${k + 1}`, sides: [null, null], feeds: [`${prefix}rep-${k + 1}`, loser(other.id)], stage: "bronze" });
48
+ });
49
+ return { teams: made.teams, matchups: [...made.matchups, ...extra] };
50
+ }
51
+ /** The gold, silver and two bronze medals of a bracket (matchups, input or layout), from the final, the bronze medal contests or the semi-finals. */
52
+ export function judoPodium(input) {
53
+ const layout = "ties" in input && "sides" in input ? input : layoutBracket(input);
54
+ const fin = layout.finalIndex === null ? null : layout.ties[layout.finalIndex];
55
+ const gold = fin && fin.winner !== null ? fin.sides[fin.winner] : null;
56
+ const silver = fin && fin.winner !== null ? fin.sides[(1 - fin.winner)] : null;
57
+ const bronzeTies = layout.ties.filter((t) => t.stage === "bronze");
58
+ let bronze;
59
+ if (bronzeTies.length)
60
+ bronze = bronzeTies.flatMap((t) => (t.winner === null || !t.sides[t.winner] ? [] : [t.sides[t.winner]]));
61
+ else {
62
+ const semis = layout.finalIndex === null ? [] : layout.ties.filter((t) => t.round === layout.rounds - 1 && t.stage === undefined && !t.third && layout.place[t.index] !== "o");
63
+ bronze = semis.flatMap((t) => (t.winner === null || !t.sides[(1 - t.winner)] ? [] : [t.sides[(1 - t.winner)]]));
64
+ }
65
+ return { gold, silver, bronze };
66
+ }
67
+ /** A weight category as a heading in a language: "Men -66 kg", "Women +78 kg". */
68
+ export function judoCategoryLabel(category, locale) {
69
+ if (category.sex !== "men" && category.sex !== "women")
70
+ throw new RangeError(`taisen-brackets: a judo category's sex is "men" or "women", not ${JSON.stringify(category.sex)}`);
71
+ if (!/^[-+]\d{2,3}$/.test(category.weight))
72
+ throw new RangeError(`taisen-brackets: a judo weight is "-66" or "+100", not ${JSON.stringify(category.weight)}`);
73
+ const w = voiceOf({ locale }).words;
74
+ return fill(category.weight.startsWith("+") ? w.judoWeightOver : w.judoWeightClass, { sex: category.sex === "men" ? w.judoMen : w.judoWomen, w: category.weight.slice(1) });
75
+ }
package/dist/layout.js CHANGED
@@ -17,8 +17,8 @@ export function layoutBracket(input, options = {}) {
17
17
  throw new Error(`taisen-brackets: two matchups have the id ${JSON.stringify(t.id)}`);
18
18
  byId.set(t.id, t.index);
19
19
  }
20
- // A play-in game is outside the tree; so are a third-place match and a round-robin game.
21
- const playIns = ties.filter((t) => matchups[t.index].stage === "play-in").map((t) => t.index);
20
+ // A play-in game is outside the tree, and so are a judo repechage and bronze medal contest (a stage's ties are fed by the tree's losers); so are a third-place match and a round-robin game.
21
+ const playIns = ties.filter((t) => matchups[t.index].stage !== undefined).map((t) => t.index);
22
22
  const isPlayIn = new Set(playIns);
23
23
  const thirds = ties.filter((t) => t.third && !isPlayIn.has(t.index)).map((t) => t.index);
24
24
  const robin = ties.filter((t) => !t.third && !isPlayIn.has(t.index) && matchups[t.index].round === 0).map((t) => t.index);
package/dist/layouts.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { abbrOf } from "./matchup.js";
2
+ import { sideScore } from "./judo-text.js";
2
3
  import { drawMark } from "./marks.js";
3
4
  import { emWidth, esc, fit, num } from "./text.js";
4
5
  const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
5
6
  /** The width of a text set in bold at a size: the package's generous estimate, so that a text stays inside its place in any font. */
6
7
  const bw = (text, size) => emWidth(text) * size;
7
- const scoreOf = (e, k) => (e.scoresOn && e.tie.result.score ? e.v.number(e.tie.result.score[k]) : "");
8
+ const scoreOf = (e, k) => (e.scoresOn ? sideScore(e.tie, k, e.v) : "");
8
9
  const pensOf = (e, k) => (e.scoresOn && e.tie.result.pens ? `(${e.v.number(e.tie.result.pens[k])})` : "");
9
10
  /** A small pill with a seed tag, its left edge at `x`, centred on `cy`. */
10
11
  function tagPill(text, x, cy, size) {
@@ -260,7 +261,7 @@ export function seriesLineWidth(tie, v, size, scoresOn) {
260
261
  const score = scoresOn && tie.result.score ? fillScore(tie, v) : v.words.vs;
261
262
  return bw(code(0), size) + bw(code(1), size) + bw(score, size) + 4 * size * 0.6 + 8 * 2 + 2 * bw("00", size * 0.72);
262
263
  }
263
- const fillScore = (tie, v) => (tie.result.score ? v.words.scoreText.replace("{a}", v.number(tie.result.score[0])).replace("{b}", v.number(tie.result.score[1])) : "");
264
+ const fillScore = (tie, v, swap = false) => (tie.result.score ? v.words.scoreText.replace("{a}", sideScore(tie, swap ? 1 : 0, v)).replace("{b}", sideScore(tie, swap ? 0 : 1, v)) : "");
264
265
  /** One line a series: "(1) BOS 4–0 MIA (8)", the winner in bold and the loser quiet. Where the box is too narrow for a line, two compact rows. */
265
266
  function series(e) {
266
267
  const { x, y, w, h, tie, v } = e;
@@ -274,7 +275,7 @@ function series(e) {
274
275
  const [a, b] = e.order;
275
276
  const cy = y + h / 2;
276
277
  const score = e.scoresOn && tie.result.score ? fillScore(tie, v) : v.words.vs;
277
- const scoreOrdered = e.scoresOn && tie.result.score && a === 1 ? v.words.scoreText.replace("{a}", v.number(tie.result.score[1])).replace("{b}", v.number(tie.result.score[0])) : score;
278
+ const scoreOrdered = e.scoresOn && tie.result.score && a === 1 ? fillScore(tie, v, true) : score;
278
279
  const sw = bw(scoreOrdered, size);
279
280
  const cx = x + w / 2;
280
281
  const ss = Math.max(size * 0.72, 7.5);
package/dist/locales.js CHANGED
@@ -21,6 +21,12 @@ const EN = {
21
21
  roadFinal: "The final: {a} {score} {b} ({how}).", roadChampion: "{name} won the final.", roadToPlay: "The final is still to be played.",
22
22
  vs: "vs", at: "AT", groupName: "Group {n}", colPlayed: "P", colWon: "W", colDrawn: "D", colLost: "L", colDiff: "GD", colPoints: "Pts",
23
23
  playIn: "Play-in", playInWinner: "Winner {a}/{b}", playInLoser: "Loser {a}/{b}", seedSlot: "Seed {n}", winnerOf: "Winner of {name}", loserOf: "Loser of {name}",
24
+ judoIppon: "I", judoWazaAri: "W", judoYuko: "Y", judoIpponLong: "ippon", judoWazaAriLong: "waza-ari", judoYukoLong: "yuko",
25
+ judoMethodIppon: "ippon", judoMethodAwasete: "waza-ari awasete ippon", judoMethodScore: "decision", judoMethodHansoku: "hansoku-make", judoMethodFusen: "fusen-gachi", judoMethodKiken: "kiken-gachi",
26
+ judoGolden: "GS", judoGoldenLong: "golden score", judoTime: "{t}", judoShidoOf: "{name}: {n} shido", judoShidoShort: "shido {a}–{b}",
27
+ judoRepechage: "Repechage", judoBronze: "Bronze medal contest", judoRepechageBronze: "Repechage and bronze medal contests", judoWhite: "white", judoBlue: "blue",
28
+ judoWeightClass: "{sex} -{w} kg", judoWeightOver: "{sex} +{w} kg", judoMen: "Men", judoWomen: "Women",
29
+ judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Mat {n}", judoContest: "Contest {n}", judoCoach: "Coach: {name}", judoBoardTitle: "Judo contest scoreboard",
24
30
  };
25
31
  const FR = {
26
32
  tbd: "À déterminer", bye: "exempt", walkover: "forfait", champion: "Champion", thirdPlace: "Match pour la troisième place", other: "Autres rencontres",
@@ -38,6 +44,12 @@ const FR = {
38
44
  roadFinal: "La finale : {a} {score} {b} ({how}).", roadChampion: "{name} a remporté la finale.", roadToPlay: "La finale reste à jouer.",
39
45
  vs: "vs", at: "À", groupName: "Groupe {n}", colPlayed: "PJ", colWon: "V", colDrawn: "N", colLost: "D", colDiff: "+/-", colPoints: "Pts",
40
46
  playIn: "Play-in", playInWinner: "Gagnant {a}/{b}", playInLoser: "Perdant {a}/{b}", seedSlot: "N°\u00a0{n}", winnerOf: "Gagnant du match {name}", loserOf: "Perdant du match {name}",
47
+ judoIppon: "I", judoWazaAri: "W", judoYuko: "Y", judoIpponLong: "ippon", judoWazaAriLong: "waza-ari", judoYukoLong: "yuko",
48
+ judoMethodIppon: "ippon", judoMethodAwasete: "waza-ari awasete ippon", judoMethodScore: "décision", judoMethodHansoku: "hansoku-make", judoMethodFusen: "fusen-gachi", judoMethodKiken: "kiken-gachi",
49
+ judoGolden: "GS", judoGoldenLong: "golden score", judoTime: "{t}", judoShidoOf: "{name}\u00a0: {n}\u00a0shido", judoShidoShort: "shido {a}–{b}",
50
+ judoRepechage: "Repêchage", judoBronze: "Combat pour la médaille de bronze", judoRepechageBronze: "Repêchages et combats pour la médaille de bronze", judoWhite: "blanc", judoBlue: "bleu",
51
+ judoWeightClass: "{sex} -{w}\u00a0kg", judoWeightOver: "{sex} +{w}\u00a0kg", judoMen: "Hommes", judoWomen: "Femmes",
52
+ judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Tapis {n}", judoContest: "Combat {n}", judoCoach: "Entraîneur\u00a0: {name}", judoBoardTitle: "Tableau d’affichage d’un combat de judo",
41
53
  };
42
54
  const JA = {
43
55
  tbd: "未定", bye: "シード", walkover: "不戦勝", champion: "優勝", thirdPlace: "3位決定戦", other: "その他の対戦",
@@ -55,6 +67,12 @@ const JA = {
55
67
  roadFinal: "決勝は{a} {score} {b}({how})でした。", roadChampion: "{name}が優勝しました。", roadToPlay: "決勝はまだ行われていません。",
56
68
  vs: "対", at: "@", groupName: "グループ{n}", colPlayed: "試合", colWon: "勝", colDrawn: "分", colLost: "敗", colDiff: "得失差", colPoints: "勝点",
57
69
  playIn: "プレイイン", playInWinner: "{a}/{b}の勝者", playInLoser: "{a}/{b}の敗者", seedSlot: "第{n}シード", winnerOf: "{name}の勝者", loserOf: "{name}の敗者",
70
+ judoIppon: "一", judoWazaAri: "技", judoYuko: "有", judoIpponLong: "一本", judoWazaAriLong: "技あり", judoYukoLong: "有効",
71
+ judoMethodIppon: "一本", judoMethodAwasete: "合わせ技一本", judoMethodScore: "優勢勝ち", judoMethodHansoku: "反則勝ち", judoMethodFusen: "不戦勝", judoMethodKiken: "棄権勝ち",
72
+ judoGolden: "GS", judoGoldenLong: "ゴールデンスコア", judoTime: "{t}", judoShidoOf: "{name}:指導{n}", judoShidoShort: "指導{a}–{b}",
73
+ judoRepechage: "敗者復活戦", judoBronze: "銅メダル決定戦", judoRepechageBronze: "敗者復活戦と銅メダル決定戦", judoWhite: "白", judoBlue: "青",
74
+ judoWeightClass: "{sex}{w}kg級", judoWeightOver: "{sex}{w}kg超級", judoMen: "男子", judoWomen: "女子",
75
+ judoOsaekomi: "抑え込み", judoShido: "指導", judoMat: "第{n}試合場", judoContest: "試合 {n}", judoCoach: "コーチ:{name}", judoBoardTitle: "柔道試合のスコアボード",
58
76
  };
59
77
  const ES = {
60
78
  tbd: "Por definir", bye: "exento", walkover: "incomparecencia", champion: "Campeón", thirdPlace: "Partido por el tercer puesto", other: "Otros partidos",
@@ -72,6 +90,12 @@ const ES = {
72
90
  roadFinal: "La final: {a} {score} {b} ({how}).", roadChampion: "{name} ganó la final.", roadToPlay: "La final aún no se ha jugado.",
73
91
  vs: "vs.", at: "EN", groupName: "Grupo {n}", colPlayed: "PJ", colWon: "G", colDrawn: "E", colLost: "P", colDiff: "DG", colPoints: "Pts",
74
92
  playIn: "Play-in", playInWinner: "Ganador {a}/{b}", playInLoser: "Perdedor {a}/{b}", seedSlot: "Puesto {n}", winnerOf: "Ganador de {name}", loserOf: "Perdedor de {name}",
93
+ judoIppon: "I", judoWazaAri: "W", judoYuko: "Y", judoIpponLong: "ippon", judoWazaAriLong: "waza-ari", judoYukoLong: "yuko",
94
+ judoMethodIppon: "ippon", judoMethodAwasete: "waza-ari awasete ippon", judoMethodScore: "decisión", judoMethodHansoku: "hansoku-make", judoMethodFusen: "fusen-gachi", judoMethodKiken: "kiken-gachi",
95
+ judoGolden: "GS", judoGoldenLong: "golden score", judoTime: "{t}", judoShidoOf: "{name}: {n} shido", judoShidoShort: "shido {a}–{b}",
96
+ judoRepechage: "Repesca", judoBronze: "Combate por la medalla de bronce", judoRepechageBronze: "Repesca y combates por la medalla de bronce", judoWhite: "blanco", judoBlue: "azul",
97
+ judoWeightClass: "{sex} -{w} kg", judoWeightOver: "{sex} +{w} kg", judoMen: "Hombres", judoWomen: "Mujeres",
98
+ judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Tatami {n}", judoContest: "Combate {n}", judoCoach: "Entrenador: {name}", judoBoardTitle: "Marcador de un combate de judo",
75
99
  };
76
100
  const DE = {
77
101
  tbd: "offen", bye: "Freilos", walkover: "kampflos", champion: "Turniersieger", thirdPlace: "Spiel um Platz 3", other: "Weitere Begegnungen",
@@ -89,6 +113,12 @@ const DE = {
89
113
  roadFinal: "Das Finale: {a} {score} {b} ({how}).", roadChampion: "{name} gewann das Finale.", roadToPlay: "Das Finale steht noch aus.",
90
114
  vs: "vs.", at: "BEI", groupName: "Gruppe {n}", colPlayed: "Sp", colWon: "S", colDrawn: "U", colLost: "N", colDiff: "Diff.", colPoints: "Pkt",
91
115
  playIn: "Play-in", playInWinner: "Sieger {a}/{b}", playInLoser: "Verlierer {a}/{b}", seedSlot: "Platz {n}", winnerOf: "Sieger von {name}", loserOf: "Verlierer von {name}",
116
+ judoIppon: "I", judoWazaAri: "W", judoYuko: "Y", judoIpponLong: "Ippon", judoWazaAriLong: "Waza-ari", judoYukoLong: "Yuko",
117
+ judoMethodIppon: "Ippon", judoMethodAwasete: "Waza-ari awasete Ippon", judoMethodScore: "Punktsieg", judoMethodHansoku: "Hansoku-make", judoMethodFusen: "Fusen-gachi", judoMethodKiken: "Kiken-gachi",
118
+ judoGolden: "GS", judoGoldenLong: "Golden Score", judoTime: "{t}", judoShidoOf: "{name}: {n} Shido", judoShidoShort: "Shido {a}–{b}",
119
+ judoRepechage: "Hoffnungsrunde", judoBronze: "Kampf um Bronze", judoRepechageBronze: "Hoffnungsrunde und Kämpfe um Bronze", judoWhite: "weiß", judoBlue: "blau",
120
+ judoWeightClass: "{sex} -{w} kg", judoWeightOver: "{sex} +{w} kg", judoMen: "Herren", judoWomen: "Damen",
121
+ judoOsaekomi: "Osaekomi", judoShido: "Shido", judoMat: "Matte {n}", judoContest: "Kampf {n}", judoCoach: "Trainer: {name}", judoBoardTitle: "Anzeigetafel eines Judokampfes",
92
122
  };
93
123
  /**
94
124
  * The round names, English name first. The football rounds are the terms of each language's Wikipedia article on the World Cup;
package/dist/matchup.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { judoHasScore, judoOutcome } from "./judo-model.js";
2
+ import { judoNotes, sideScoreLong } from "./judo-text.js";
1
3
  import { abbreviate } from "./text.js";
2
4
  import { fill } from "./locales.js";
3
5
  /** A bracket's teams: the list given, plus a team made from every id that a matchup names and the list does not. */
@@ -77,18 +79,31 @@ export function resolveMatchup(m, index, teams) {
77
79
  if (ids[0] !== null && ids[0] === ids[1])
78
80
  throw new Error(`taisen-brackets: matchup ${index} has ${JSON.stringify(ids[0])} on both sides`);
79
81
  const kind = kindOf(m);
80
- const { result, winner: settled } = settle(m, kind);
81
- let winner = settled;
82
+ let named;
82
83
  if (m.winner !== undefined) {
83
84
  if (m.winner === null)
84
- winner = null;
85
+ named = null;
85
86
  else if (ids[0] === m.winner)
86
- winner = 0;
87
+ named = 0;
87
88
  else if (ids[1] === m.winner)
88
- winner = 1;
89
+ named = 1;
89
90
  else
90
91
  throw new Error(`taisen-brackets: matchup ${index}'s winner ${JSON.stringify(m.winner)} is not one of its sides`);
91
92
  }
93
+ let judo;
94
+ let result;
95
+ let winner;
96
+ if (m.judo) {
97
+ judo = judoOutcome(m.judo, named ?? null);
98
+ if (judo.winner !== null && named !== undefined && named !== null && named !== judo.winner)
99
+ throw new RangeError(`taisen-brackets: matchup ${index}'s judo scores make side ${judo.winner} the winner, but its winner is side ${named}`);
100
+ winner = judo.winner;
101
+ result = { kind: "game", score: judoHasScore(judo) || winner !== null ? judo.points : null, played: judoHasScore(judo) || winner !== null ? 1 : 0, bestOf: null, pens: null, extraTime: false, replays: 0, games: [judo.points], decided: winner !== null };
102
+ }
103
+ else
104
+ ({ result, winner } = settle(m, kind));
105
+ if (named !== undefined)
106
+ winner = named;
92
107
  const sides = ids.map((id) => (id === null ? null : teams.get(id) ?? null));
93
108
  return {
94
109
  index,
@@ -102,6 +117,8 @@ export function resolveMatchup(m, index, teams) {
102
117
  third: !!m.third,
103
118
  walkover: !!m.walkover,
104
119
  note: m.note ?? null,
120
+ ...(judo ? { judo } : {}),
121
+ ...(m.stage ? { stage: m.stage } : {}),
105
122
  ...(m.seeds ? { seeds: m.seeds } : {}),
106
123
  ...(m.home !== undefined ? { home: m.home } : {}),
107
124
  };
@@ -112,7 +129,16 @@ export const abbrOf = (team, v) => (team ? team.abbr ?? abbreviate(v.team(team))
112
129
  export function noteParts(tie, v) {
113
130
  const r = tie.result;
114
131
  const w = v.words;
115
- const out = [];
132
+ const out = tie.judo ? judoNotes(tie, v, false) : [];
133
+ if (tie.stage === "repechage" || tie.stage === "bronze")
134
+ out.unshift(tie.stage === "repechage" ? w.judoRepechage : w.judoBronze);
135
+ if (tie.judo) {
136
+ if (tie.walkover)
137
+ out.push(w.walkover);
138
+ if (tie.note)
139
+ out.push(tie.note);
140
+ return out;
141
+ }
116
142
  if (r.kind === "series") {
117
143
  if (r.decided && r.played > 0)
118
144
  out.push(fill(w.inGames, { n: v.number(r.played) }));
@@ -139,7 +165,14 @@ export function noteParts(tie, v) {
139
165
  export function noteLong(tie, v) {
140
166
  const r = tie.result;
141
167
  const w = v.words;
142
- const out = [];
168
+ const out = tie.judo ? judoNotes(tie, v, true) : [];
169
+ if (tie.judo) {
170
+ if (tie.walkover)
171
+ out.push(w.walkover);
172
+ if (tie.note)
173
+ out.push(tie.note);
174
+ return out;
175
+ }
143
176
  if (r.kind === "series") {
144
177
  if (r.decided && r.played > 0)
145
178
  out.push(fill(w.inGames, { n: v.number(r.played) }));
@@ -169,7 +202,7 @@ export function describeTie(tie, v) {
169
202
  const [a, b] = tie.sides;
170
203
  const r = tie.result;
171
204
  let text = r.score
172
- ? fill(w.tieScore, { a: name(a), b: name(b), sa: v.number(r.score[0]), sb: v.number(r.score[1]) })
205
+ ? fill(w.tieScore, { a: name(a), b: name(b), sa: sideScoreLong(tie, 0, v), sb: sideScoreLong(tie, 1, v) })
173
206
  : fill(w.tieVs, { a: name(a), b: name(b) });
174
207
  const notes = noteLong(tie, v);
175
208
  if (notes.length)
package/dist/playin.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { fill } from "./locales.js";
2
2
  import { abbrOf, describeTie } from "./matchup.js";
3
+ import { sideScore } from "./judo-text.js";
3
4
  import { drawMark } from "./marks.js";
4
5
  import { bold, headTile, headingTexts, titleHeight } from "./furniture.js";
5
6
  import { esc, fit, num } from "./text.js";
@@ -147,7 +148,7 @@ export function drawPlayIn(layout, o, v, size, ctx, road, linked) {
147
148
  const team = t.sides[k];
148
149
  const ry = rect.y + k * rowH;
149
150
  const cy = ry + rowH / 2;
150
- const score = o.scores !== false && t.result.score ? v.number(t.result.score[k]) : "";
151
+ const score = o.scores !== false ? sideScore(t, k, v) : "";
151
152
  const cls = t.winner === null ? "tb-pending" : t.winner === k ? "tb-won" : "tb-lost";
152
153
  const items = [];
153
154
  const slotX = rect.x + rect.w - 5 - 26;