@johnmorrisdotca/kyogijo-stadiums 0.5.0 → 0.6.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/CHANGELOG.md +64 -0
- package/NOTICE.md +179 -3
- package/README.md +43 -11
- package/dist/arenas-entry.d.ts +4 -0
- package/dist/arenas-entry.js +3 -0
- package/dist/arenas.d.ts +125 -0
- package/dist/arenas.data.d.ts +2 -0
- package/dist/arenas.data.js +3763 -0
- package/dist/arenas.js +72 -0
- package/dist/atlas-entry.d.ts +3 -1
- package/dist/atlas-entry.js +3 -2
- package/dist/atlas.d.ts +37 -19
- package/dist/atlas.js +92 -7
- package/dist/background.js +2 -1
- package/dist/budo/describe.d.ts +13 -0
- package/dist/budo/describe.js +120 -0
- package/dist/budo/dojo.api.d.ts +64 -0
- package/dist/budo/dojo.api.js +46 -0
- package/dist/budo/dojo.d.ts +72 -0
- package/dist/budo/dojo.js +300 -0
- package/dist/budo/judo.d.ts +61 -0
- package/dist/budo/judo.js +109 -0
- package/dist/budo/karate.d.ts +29 -0
- package/dist/budo/karate.js +80 -0
- package/dist/budo/kendo.d.ts +18 -0
- package/dist/budo/kendo.js +44 -0
- package/dist/budo/kimarite.d.ts +19 -0
- package/dist/budo/kimarite.js +33 -0
- package/dist/budo/kit.d.ts +16 -0
- package/dist/budo/kit.js +24 -0
- package/dist/budo/kyudo.d.ts +28 -0
- package/dist/budo/kyudo.js +66 -0
- package/dist/budo/looks.d.ts +28 -0
- package/dist/budo/looks.js +58 -0
- package/dist/budo/parts.d.ts +32 -0
- package/dist/budo/parts.js +10 -0
- package/dist/budo/plan.d.ts +64 -0
- package/dist/budo/plan.js +139 -0
- package/dist/budo/room.d.ts +133 -0
- package/dist/budo/room.js +111 -0
- package/dist/budo/sumo.d.ts +31 -0
- package/dist/budo/sumo.js +71 -0
- package/dist/budo/tatami.d.ts +55 -0
- package/dist/budo/tatami.js +156 -0
- package/dist/budo/types.d.ts +192 -0
- package/dist/budo/types.js +2 -0
- package/dist/budo-entry.d.ts +22 -0
- package/dist/budo-entry.js +13 -0
- package/dist/budo.board.d.ts +28 -0
- package/dist/budo.board.js +81 -0
- package/dist/budo.score.d.ts +18 -0
- package/dist/budo.score.js +52 -0
- package/dist/budo.style.d.ts +6 -0
- package/dist/budo.style.js +67 -0
- package/dist/charts.baseball.js +1 -1
- package/dist/charts.js +4 -4
- package/dist/charts.soccer.js +3 -3
- package/dist/charts.types.d.ts +10 -0
- package/dist/colour.d.ts +4 -4
- package/dist/colour.js +4 -4
- package/dist/crowd.d.ts +11 -1
- package/dist/crowd.draw.js +15 -9
- package/dist/crowd.js +17 -2
- package/dist/draw.js +156 -31
- package/dist/element-entry.d.ts +2 -0
- package/dist/element-entry.js +1 -0
- package/dist/element.d.ts +2 -2
- package/dist/element.js +2 -1
- package/dist/emblems-source.d.ts +50 -0
- package/dist/emblems-source.js +56 -0
- package/dist/eras.d.ts +3 -1
- package/dist/eras.js +8 -3
- package/dist/features.js +73 -0
- package/dist/field.data.d.ts +16 -0
- package/dist/field.data.js +50 -0
- package/dist/fields.kit.d.ts +2 -2
- package/dist/fields.types.d.ts +9 -3
- package/dist/formations.js +18 -7
- package/dist/formations.sports.d.ts +8 -0
- package/dist/formations.sports.js +251 -0
- package/dist/frames.providers.js +9 -1
- package/dist/halls.d.ts +65 -0
- package/dist/halls.js +122 -0
- package/dist/heat.js +17 -10
- package/dist/heat.zones.d.ts +4 -2
- package/dist/heat.zones.js +55 -3
- package/dist/homeside.js +7 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +8 -1
- package/dist/layers-entry.d.ts +2 -1
- package/dist/layers-entry.js +1 -0
- package/dist/layers.d.ts +2 -1
- package/dist/layers.env.d.ts +10 -1
- package/dist/layers.env.js +33 -3
- package/dist/layers.js +30 -11
- package/dist/layers.types.d.ts +9 -2
- package/dist/leagues/budo.d.ts +196 -0
- package/dist/leagues/budo.js +105 -0
- package/dist/leagues/hockey.js +41 -19
- package/dist/leagues/sources.js +21 -0
- package/dist/leagues/types.d.ts +1 -1
- package/dist/leagues.d.ts +4 -1
- package/dist/leagues.js +102 -2
- package/dist/lineup.codes.d.ts +23 -0
- package/dist/lineup.codes.js +122 -0
- package/dist/logo.d.ts +25 -0
- package/dist/logo.js +47 -0
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +17 -3
- package/dist/pageant.js +7 -3
- package/dist/pointer-entry.d.ts +2 -0
- package/dist/pointer-entry.js +2 -0
- package/dist/pointer.d.ts +113 -0
- package/dist/pointer.js +151 -0
- package/dist/pool.d.ts +7 -7
- package/dist/pool.figures.d.ts +57 -55
- package/dist/pool.figures.js +59 -55
- package/dist/pool.js +7 -7
- package/dist/pool.labels.js +2 -1
- package/dist/ranges.names.js +6 -0
- package/dist/road.draw.js +3 -2
- package/dist/road.js +2 -1
- package/dist/road.types.d.ts +3 -0
- package/dist/rules.d.ts +138 -0
- package/dist/rules.js +511 -0
- package/dist/rules.types.d.ts +4 -0
- package/dist/rules.types.js +64 -0
- package/dist/score.d.ts +3 -0
- package/dist/score.js +23 -1
- package/dist/situation.d.ts +135 -0
- package/dist/situation.draw.d.ts +34 -0
- package/dist/situation.draw.js +202 -0
- package/dist/situation.js +337 -0
- package/dist/situation.style.d.ts +2 -0
- package/dist/situation.style.js +45 -0
- package/dist/situation.types.d.ts +77 -0
- package/dist/situation.types.js +3 -0
- package/dist/situation.widgets.d.ts +39 -0
- package/dist/situation.widgets.js +129 -0
- package/dist/strings.d.ts +175 -1
- package/dist/strings.js +364 -20
- package/dist/style.js +9 -2
- package/dist/surfaces/artistic.d.ts +3 -3
- package/dist/surfaces/artistic.js +3 -3
- package/dist/surfaces/baseball.js +2 -0
- package/dist/surfaces/basketball.js +1 -1
- package/dist/surfaces/budo.d.ts +6 -0
- package/dist/surfaces/budo.js +104 -0
- package/dist/surfaces/dive.d.ts +3 -3
- package/dist/surfaces/dive.js +3 -3
- package/dist/surfaces/football.js +7 -0
- package/dist/surfaces/hockey.js +3 -2
- package/dist/surfaces/polo.d.ts +4 -4
- package/dist/surfaces/polo.js +5 -5
- package/dist/surfaces/pool.d.ts +1 -1
- package/dist/surfaces/pool.js +3 -3
- package/dist/surfaces/surface.d.ts +32 -0
- package/dist/surfaces/venue.d.ts +1 -1
- package/dist/surfaces/venue.js +1 -1
- package/dist/target-entry.d.ts +2 -0
- package/dist/target-entry.js +2 -0
- package/dist/target.d.ts +200 -0
- package/dist/target.js +525 -0
- package/dist/team.common.d.ts +1 -1
- package/dist/team.common.js +3 -3
- package/dist/team.formation.d.ts +3 -1
- package/dist/team.formation.js +3 -3
- package/dist/team.layers.d.ts +2 -2
- package/dist/team.layers.js +4 -1
- package/dist/team.lineup.d.ts +27 -0
- package/dist/team.lineup.js +207 -0
- package/dist/team.network.js +6 -5
- package/dist/team.shape.js +5 -5
- package/dist/team.types.d.ts +40 -1
- package/dist/tenants.data.js +848 -0
- package/dist/turn-entry.d.ts +2 -2
- package/dist/turn-entry.js +1 -1
- package/dist/turn.camera.d.ts +16 -3
- package/dist/turn.camera.js +53 -7
- package/dist/turn.dom.js +11 -9
- package/dist/turn.kit.d.ts +2 -2
- package/dist/turn.style.js +4 -3
- package/dist/types.d.ts +43 -7
- package/dist/units.js +7 -0
- package/dist/uprights.budo.d.ts +17 -0
- package/dist/uprights.budo.js +87 -0
- package/dist/uprights.js +10 -1
- package/dist/uprights.pools.js +4 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/zones-entry.d.ts +2 -0
- package/dist/zones-entry.js +2 -0
- package/package.json +39 -3
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { DojoFigures } from "../leagues/budo.ts";
|
|
2
|
+
import { type MBox, type MMark, type Parts } from "./parts.ts";
|
|
3
|
+
import { type MatSize, type Piece } from "./tatami.ts";
|
|
4
|
+
import type { Compass, DojoEra, DojoPattern, DojoSpec, Side4 } from "./types.ts";
|
|
5
|
+
/** The usual sizes of a room counted in jō, in the mats' widths across and along: 8 jō is 4 by 4 widths (3.64 m square of Chūkyōma mats), 20 jō 5 by 8. */
|
|
6
|
+
export declare const DOJO_SIZES: Readonly<Record<number, readonly [number, number]>>;
|
|
7
|
+
/** The mats' widths across and along of a floor of `jo` mats: the usual shape where there is one, else the whole-number shape nearest to 1 to 1.6. @throws RangeError for a count that is not a whole number of 2 or more. */
|
|
8
|
+
export declare function shapeOfJo(jo: number): [number, number];
|
|
9
|
+
/** The floor's size in metres from the options: its width (across, x) and its length (along, y). */
|
|
10
|
+
export declare function floorOf(spec: DojoSpec, mat: MatSize): {
|
|
11
|
+
width: number;
|
|
12
|
+
length: number;
|
|
13
|
+
};
|
|
14
|
+
/** The compass side a name means: north is the top of the drawing. @throws Error for a name that is not a side. */
|
|
15
|
+
export declare function compassOf(side: Side4 | undefined, fallback: Compass): Compass;
|
|
16
|
+
/** What a dojo is: its floor, its mats, its walls and what is in them. */
|
|
17
|
+
export interface DojoLayout {
|
|
18
|
+
parts: Parts;
|
|
19
|
+
floor: MBox;
|
|
20
|
+
/** The floor and the walls round it. */
|
|
21
|
+
extent: MBox;
|
|
22
|
+
width: number;
|
|
23
|
+
length: number;
|
|
24
|
+
mat: MatSize;
|
|
25
|
+
pattern: DojoPattern;
|
|
26
|
+
pieces: Piece[];
|
|
27
|
+
/** The mats counted as whole ones (a half mat is a half): the area in jō. */
|
|
28
|
+
jo: number;
|
|
29
|
+
counts: {
|
|
30
|
+
full: number;
|
|
31
|
+
half: number;
|
|
32
|
+
cut: number;
|
|
33
|
+
};
|
|
34
|
+
kamiza: Compass;
|
|
35
|
+
wall: number;
|
|
36
|
+
/** The floor and the wood surround round it: the room inside the walls. */
|
|
37
|
+
room: MBox;
|
|
38
|
+
/** The width of the wood surround, 0 for none. */
|
|
39
|
+
surround: number;
|
|
40
|
+
entrances: {
|
|
41
|
+
wall: Compass;
|
|
42
|
+
box: MBox;
|
|
43
|
+
}[];
|
|
44
|
+
windows: {
|
|
45
|
+
wall: Compass;
|
|
46
|
+
box: MBox;
|
|
47
|
+
}[];
|
|
48
|
+
shrine: MBox | undefined;
|
|
49
|
+
/** The contest areas laid on the floor. */
|
|
50
|
+
contests: MBox[];
|
|
51
|
+
/** The raised platform of the seat of honour, and the banner on its wall. */
|
|
52
|
+
dais: MBox | undefined;
|
|
53
|
+
banner: MBox | undefined;
|
|
54
|
+
/** The era of the floor, where it has them. */
|
|
55
|
+
era: DojoEra | undefined;
|
|
56
|
+
marks: MMark[];
|
|
57
|
+
}
|
|
58
|
+
/** The league of mats a spec asks for. */
|
|
59
|
+
export declare function dojoLeague(standard: string | undefined): DojoFigures;
|
|
60
|
+
/**
|
|
61
|
+
* The era a spec asks for, and the spec as it was then: the era's look, colours, pattern, contest areas and outline laid over the spec's own. `era` picks an era by name, `year` the first whose
|
|
62
|
+
* years hold it (an era with an unknown year, `null`, never holds one), and with neither the last era, the floor as it is now. A spec without eras is its own era.
|
|
63
|
+
* @throws Error for an `era` that no era is named, RangeError for a `year` no era holds.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveEra(spec: DojoSpec | undefined): {
|
|
66
|
+
spec: DojoSpec;
|
|
67
|
+
era: DojoEra | undefined;
|
|
68
|
+
};
|
|
69
|
+
/** The dojo's spec as drawn: the era's changes laid over it. */
|
|
70
|
+
export declare const dojoSpecOf: (spec: DojoSpec | undefined) => DojoSpec;
|
|
71
|
+
/** The plan of the dojo a spec asks for. */
|
|
72
|
+
export declare function dojoLayout(asked: DojoSpec, league: DojoFigures): DojoLayout;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { DOJO_LEAGUES } from "../leagues/budo.js";
|
|
2
|
+
import { boxD, rectArea } from "./kit.js";
|
|
3
|
+
import { emptyParts, grow } from "./parts.js";
|
|
4
|
+
import { checker, heri, lay, matCount, seams, tatamiAreas, tint } from "./tatami.js";
|
|
5
|
+
/** The usual sizes of a room counted in jō, in the mats' widths across and along: 8 jō is 4 by 4 widths (3.64 m square of Chūkyōma mats), 20 jō 5 by 8. */
|
|
6
|
+
export const DOJO_SIZES = Object.freeze({ 8: [4, 4], 12: [4, 6], 20: [5, 8], 50: [10, 10], 100: [10, 20], 200: [20, 20] });
|
|
7
|
+
/** The mats' widths across and along of a floor of `jo` mats: the usual shape where there is one, else the whole-number shape nearest to 1 to 1.6. @throws RangeError for a count that is not a whole number of 2 or more. */
|
|
8
|
+
export function shapeOfJo(jo) {
|
|
9
|
+
if (!Number.isInteger(jo) || jo < 2 || jo > 1000)
|
|
10
|
+
throw new RangeError("The area of a dojo is a whole number of jō, 2 to 1000");
|
|
11
|
+
const known = DOJO_SIZES[jo];
|
|
12
|
+
if (known !== undefined)
|
|
13
|
+
return [known[0], known[1]];
|
|
14
|
+
const units = jo * 2;
|
|
15
|
+
let best = [1, units];
|
|
16
|
+
let score = Infinity;
|
|
17
|
+
for (let a = 1; a * a <= units; a += 1) {
|
|
18
|
+
if (units % a !== 0)
|
|
19
|
+
continue;
|
|
20
|
+
const b = units / a;
|
|
21
|
+
const s = Math.abs(Math.log(b / a / 1.6));
|
|
22
|
+
if (s < score) {
|
|
23
|
+
score = s;
|
|
24
|
+
best = [a, b];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
}
|
|
29
|
+
/** The floor's size in metres from the options: its width (across, x) and its length (along, y). */
|
|
30
|
+
export function floorOf(spec, mat) {
|
|
31
|
+
const size = spec.mats ?? spec.jo ?? 20;
|
|
32
|
+
const fromJo = (jo) => {
|
|
33
|
+
const [a, b] = shapeOfJo(jo);
|
|
34
|
+
return { width: a * mat.width, length: b * mat.width };
|
|
35
|
+
};
|
|
36
|
+
if (typeof size === "number")
|
|
37
|
+
return fromJo(size);
|
|
38
|
+
if (Array.isArray(size)) {
|
|
39
|
+
const [across, along] = size;
|
|
40
|
+
for (const n of [across, along])
|
|
41
|
+
if (!Number.isInteger(n) || n < 1 || n > 60)
|
|
42
|
+
throw new RangeError("dojo.mats is [across, along]: whole numbers of mats, 1 to 60");
|
|
43
|
+
return { width: across * mat.width, length: along * mat.length };
|
|
44
|
+
}
|
|
45
|
+
const o = size;
|
|
46
|
+
if (o.jo !== undefined)
|
|
47
|
+
return fromJo(o.jo);
|
|
48
|
+
const unit = o.unit ?? "m";
|
|
49
|
+
if (unit === "jo")
|
|
50
|
+
return fromJo(o.width ?? o.length ?? 20);
|
|
51
|
+
const w = o.width;
|
|
52
|
+
const l = o.length;
|
|
53
|
+
if (typeof w !== "number" || typeof l !== "number" || !(w > 0) || !(l > 0) || !Number.isFinite(w) || !Number.isFinite(l))
|
|
54
|
+
throw new RangeError("dojo.mats is { width, length, unit } with a width and a length above 0");
|
|
55
|
+
if (unit === "mat")
|
|
56
|
+
return { width: w * mat.width, length: l * mat.length };
|
|
57
|
+
const k = unit === "ft" ? 0.3048 : 1;
|
|
58
|
+
if (unit !== "m" && unit !== "ft")
|
|
59
|
+
throw new RangeError('dojo.mats.unit is "m", "ft", "mat" or "jo"');
|
|
60
|
+
return { width: w * k, length: l * k };
|
|
61
|
+
}
|
|
62
|
+
/** The compass side a name means: north is the top of the drawing. @throws Error for a name that is not a side. */
|
|
63
|
+
export function compassOf(side, fallback) {
|
|
64
|
+
if (side === undefined)
|
|
65
|
+
return fallback;
|
|
66
|
+
const map = { north: "north", south: "south", east: "east", west: "west", top: "north", bottom: "south", left: "west", right: "east" };
|
|
67
|
+
const found = map[String(side)];
|
|
68
|
+
if (found === undefined)
|
|
69
|
+
throw new Error(`Unknown side "${String(side)}": use north, south, east or west (or top, bottom, left or right)`);
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
72
|
+
const OPPOSITE = { north: "south", south: "north", east: "west", west: "east" };
|
|
73
|
+
/** The league of mats a spec asks for. */
|
|
74
|
+
export function dojoLeague(standard) {
|
|
75
|
+
const id = standard ?? "chukyoma";
|
|
76
|
+
const found = DOJO_LEAGUES.find((l) => l.id === id);
|
|
77
|
+
if (found === undefined)
|
|
78
|
+
throw new Error(`Unknown mat standard "${id}": use one of ${DOJO_LEAGUES.map((l) => l.id).join(", ")}`);
|
|
79
|
+
return found.dimensions;
|
|
80
|
+
}
|
|
81
|
+
const along = (wall, box, at, width, depth) => wall === "north" ? { x0: box.x0 + at - width / 2, y0: box.y0 - depth, x1: box.x0 + at + width / 2, y1: box.y0 } : wall === "south" ? { x0: box.x0 + at - width / 2, y0: box.y1, x1: box.x0 + at + width / 2, y1: box.y1 + depth } : wall === "west" ? { x0: box.x0 - depth, y0: box.y0 + at - width / 2, x1: box.x0, y1: box.y0 + at + width / 2 } : { x0: box.x1, y0: box.y0 + at - width / 2, x1: box.x1 + depth, y1: box.y0 + at + width / 2 };
|
|
82
|
+
/** The length of a wall of the floor, and the distance along it a thing at `at` (0 to 1) stands, held so that it keeps clear of the corners. */
|
|
83
|
+
function place(wall, box, at, width) {
|
|
84
|
+
const len = wall === "north" || wall === "south" ? box.x1 - box.x0 : box.y1 - box.y0;
|
|
85
|
+
const w = Math.min(width, len * 0.8);
|
|
86
|
+
const t = typeof at === "number" && Number.isFinite(at) ? Math.min(1, Math.max(0, at)) : 0.5;
|
|
87
|
+
return { at: Math.min(len - w / 2 - 0.1, Math.max(w / 2 + 0.1, t * len)), width: w };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The era a spec asks for, and the spec as it was then: the era's look, colours, pattern, contest areas and outline laid over the spec's own. `era` picks an era by name, `year` the first whose
|
|
91
|
+
* years hold it (an era with an unknown year, `null`, never holds one), and with neither the last era, the floor as it is now. A spec without eras is its own era.
|
|
92
|
+
* @throws Error for an `era` that no era is named, RangeError for a `year` no era holds.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveEra(spec) {
|
|
95
|
+
const s = spec ?? {};
|
|
96
|
+
const eras = s.eras;
|
|
97
|
+
if (eras === undefined || eras.length === 0)
|
|
98
|
+
return { spec: s, era: undefined };
|
|
99
|
+
let era;
|
|
100
|
+
if (s.era !== undefined) {
|
|
101
|
+
era = eras.find((e) => e.name === s.era);
|
|
102
|
+
if (era === undefined)
|
|
103
|
+
throw new Error(`Unknown era "${String(s.era)}": use one of ${eras.map((e) => e.name).join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
else if (s.year !== undefined) {
|
|
106
|
+
if (typeof s.year !== "number" || !Number.isFinite(s.year))
|
|
107
|
+
throw new RangeError("dojo.year is a year");
|
|
108
|
+
const year = s.year;
|
|
109
|
+
era = eras.find((e) => typeof e.from === "number" && year >= e.from && (typeof e.to !== "number" || year <= e.to));
|
|
110
|
+
if (era === undefined)
|
|
111
|
+
throw new RangeError(`No era of this dojo holds the year ${year}: ${eras.map((e) => `${e.name} (${e.from ?? "?"} to ${e.to ?? "?"})`).join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
era = eras[eras.length - 1];
|
|
115
|
+
}
|
|
116
|
+
const without = (o, keys) => Object.fromEntries(Object.entries(o).filter(([k, v]) => !keys.includes(k) && v !== undefined));
|
|
117
|
+
const colours = era.colours;
|
|
118
|
+
const merged = { ...without(s, ["eras", "era", "year"]), ...without(era, ["name", "nameJa", "from", "to", "colours"]) };
|
|
119
|
+
const own = s.colours ?? s.colors;
|
|
120
|
+
if (colours !== undefined || own !== undefined)
|
|
121
|
+
merged.colours = { ...own, ...colours };
|
|
122
|
+
delete merged.colors;
|
|
123
|
+
return { spec: merged, era };
|
|
124
|
+
}
|
|
125
|
+
/** The dojo's spec as drawn: the era's changes laid over it. */
|
|
126
|
+
export const dojoSpecOf = (spec) => resolveEra(spec).spec;
|
|
127
|
+
/** The plan of the dojo a spec asks for. */
|
|
128
|
+
export function dojoLayout(asked, league) {
|
|
129
|
+
const spec = dojoSpecOf(asked);
|
|
130
|
+
const mat = league.mat;
|
|
131
|
+
const dims = floorOf(spec, mat);
|
|
132
|
+
// The floor lies wide, as every surface does: its longer side is x, and the mats run along it.
|
|
133
|
+
const width = Math.max(dims.width, dims.length);
|
|
134
|
+
const length = Math.min(dims.width, dims.length);
|
|
135
|
+
const pattern = spec.pattern ?? "traditional";
|
|
136
|
+
if (pattern !== "traditional" && pattern !== "competition" && pattern !== "checker" && pattern !== "border")
|
|
137
|
+
throw new Error(`Unknown pattern "${String(pattern)}": use traditional, competition, checker or border`);
|
|
138
|
+
const floor = { x0: 0, y0: 0, x1: width, y1: length };
|
|
139
|
+
const alongAxis = "x";
|
|
140
|
+
const kamiza = compassOf(spec.kamiza, "north");
|
|
141
|
+
const wallsOn = spec.walls !== false;
|
|
142
|
+
const wall = wallsOn ? (typeof spec.walls === "object" && spec.walls !== null && typeof spec.walls.thickness === "number" && spec.walls.thickness > 0 ? spec.walls.thickness : 0.3) : 0;
|
|
143
|
+
const parts = emptyParts();
|
|
144
|
+
const raw = lay(floor, mat, alongAxis, pattern !== "checker");
|
|
145
|
+
// Contest areas laid on the floor: squares of 8 m, centred along the longer side and 4 m apart, the mats inside them the first colour and the rest the second.
|
|
146
|
+
const contests = [];
|
|
147
|
+
const grid = spec.areas === undefined ? undefined : typeof spec.areas === "number" ? { across: spec.areas, down: 1 } : spec.areas;
|
|
148
|
+
const a = spec.areaSize === undefined ? 8 : spec.areaSize;
|
|
149
|
+
if (typeof a !== "number" || !Number.isFinite(a) || a < 8 || a > 10)
|
|
150
|
+
throw new RangeError("dojo.areaSize is 8 m to 10 m (the IJF's contest area)");
|
|
151
|
+
if (grid !== undefined && !(grid.across === 0 && grid.down === 0)) {
|
|
152
|
+
for (const k of [grid.across, grid.down])
|
|
153
|
+
if (!Number.isInteger(k) || k < 1 || k > 6)
|
|
154
|
+
throw new RangeError("dojo.areas is a whole number of 1 to 6, or { across, down } of whole numbers of 1 to 6");
|
|
155
|
+
const gap = 4;
|
|
156
|
+
const margin = 3;
|
|
157
|
+
const runX = grid.across * a + (grid.across - 1) * gap;
|
|
158
|
+
const runY = grid.down * a + (grid.down - 1) * gap;
|
|
159
|
+
const needX = runX + 2 * margin;
|
|
160
|
+
const needY = runY + 2 * margin;
|
|
161
|
+
if (width < needX - 1e-9 || length < needY - 1e-9)
|
|
162
|
+
throw new RangeError(`A floor of ${Math.round(width * 100) / 100} m by ${Math.round(length * 100) / 100} m is too small for ${grid.across * grid.down} contest area${grid.across * grid.down === 1 ? "" : "s"}: it needs ${needX} m by ${needY} m`);
|
|
163
|
+
for (let j = 0; j < grid.down; j += 1) {
|
|
164
|
+
for (let i = 0; i < grid.across; i += 1) {
|
|
165
|
+
const x0 = (width - runX) / 2 + i * (a + gap);
|
|
166
|
+
const y0 = (length - runY) / 2 + j * (a + gap);
|
|
167
|
+
contests.push({ x0, y0, x1: x0 + a, y1: y0 + a });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// The tones: the contest areas laid on the floor are the first, the rest of the floor the second; a border puts a band of the third round the floor (and the floor inside it is the first, or the second when contest areas lie on it).
|
|
172
|
+
let pieces;
|
|
173
|
+
// An outline puts a band one mat wide in the third colour round the inside of each contest area.
|
|
174
|
+
const zones = spec.outline === true && contests.length > 0 ? [...contests.map((b) => ({ box: grow(b, -mat.width), tone: 1 })), ...contests.map((b) => ({ box: b, tone: 3 }))] : contests.map((b) => ({ box: b, tone: 1 }));
|
|
175
|
+
let rest = contests.length > 0 ? 2 : 1;
|
|
176
|
+
if (pattern === "border") {
|
|
177
|
+
zones.push({ box: grow(floor, -mat.width), tone: contests.length > 0 ? 2 : 1 });
|
|
178
|
+
rest = 3;
|
|
179
|
+
}
|
|
180
|
+
else if (pattern === "competition" && contests.length === 0) {
|
|
181
|
+
// A floor in the colours of a contest area: a safety area of up to 3 m round the first colour.
|
|
182
|
+
const m = Math.min(3, Math.max(mat.width, Math.min(width, length) * 0.2));
|
|
183
|
+
if (Math.min(width, length) > 2 * m + mat.width) {
|
|
184
|
+
zones.push({ box: grow(floor, -m), tone: 1 });
|
|
185
|
+
rest = 2;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (pattern === "checker" && contests.length === 0)
|
|
189
|
+
pieces = checker(raw, floor, mat);
|
|
190
|
+
else
|
|
191
|
+
pieces = zones.length > 0 || rest !== 1 ? tint(raw, zones, rest, mat) : raw;
|
|
192
|
+
const named = { 1: "mats", 2: "mats-alt", 3: "border" };
|
|
193
|
+
parts.areas.push(...tatamiAreas("mats", pieces, { 1: "ky-bud-t1", 2: "ky-bud-t2", 3: "ky-bud-t3" }, named));
|
|
194
|
+
parts.shapes.push(...seams("mats", pieces, 0.02));
|
|
195
|
+
if (pattern === "traditional" || pattern === "border")
|
|
196
|
+
parts.areas.push(...heri("mats", pieces, Math.min(0.05, mat.width * 0.06), mat));
|
|
197
|
+
// The walls: a band of the thickness round the floor; the doors and the windows are cut in it.
|
|
198
|
+
const entrances = [];
|
|
199
|
+
const windows = [];
|
|
200
|
+
const marks = [];
|
|
201
|
+
const surround = spec.surround === undefined ? 0 : spec.surround;
|
|
202
|
+
if (typeof surround !== "number" || !Number.isFinite(surround) || surround < 0 || surround > 5)
|
|
203
|
+
throw new RangeError("dojo.surround is the wood round the mats, 0 m to 5 m");
|
|
204
|
+
const room = grow(floor, surround);
|
|
205
|
+
const frame = grow(room, wall);
|
|
206
|
+
if (surround > 0)
|
|
207
|
+
parts.areas.unshift({ id: "surround", feature: "floor", cls: "ky-bud-floor", d: `${boxD(room)}${boxD(floor)}`, evenodd: true });
|
|
208
|
+
if (wallsOn) {
|
|
209
|
+
parts.areas.push({ id: "walls", feature: "walls", cls: "ky-bud-wall", d: `${boxD(frame)}${boxD(room)}`, evenodd: true });
|
|
210
|
+
const doors = spec.entrances ?? [{ wall: OPPOSITE[kamiza] }];
|
|
211
|
+
doors.forEach((e, i) => {
|
|
212
|
+
const w = compassOf(e.wall, OPPOSITE[kamiza]);
|
|
213
|
+
const p = place(w, room, e.at, e.width ?? 1.8);
|
|
214
|
+
const b = along(w, room, p.at, p.width, wall);
|
|
215
|
+
entrances.push({ wall: w, box: b });
|
|
216
|
+
parts.areas.push(rectArea(`door-${i + 1}`, "doors", "ky-bud-door", b));
|
|
217
|
+
});
|
|
218
|
+
const windowList = windowsOf(spec.windows, floor);
|
|
219
|
+
windowList.forEach((e, i) => {
|
|
220
|
+
const w = compassOf(e.wall, "east");
|
|
221
|
+
const p = place(w, room, e.at, e.width ?? 1.8);
|
|
222
|
+
const b = along(w, room, p.at, p.width, wall);
|
|
223
|
+
const mid = wall / 2;
|
|
224
|
+
const thin = w === "north" || w === "south" ? { x0: b.x0, y0: b.y0 + mid * 0.7, x1: b.x1, y1: b.y1 - mid * 0.7 } : { x0: b.x0 + mid * 0.7, y0: b.y0, x1: b.x1 - mid * 0.7, y1: b.y1 };
|
|
225
|
+
windows.push({ wall: w, box: thin });
|
|
226
|
+
parts.areas.push(rectArea(`window-${i + 1}`, "windows", "ky-bud-window", thin));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// The dais: the seat of honour's platform, against the middle of the kamiza wall, 1.5 m deep (ours: no source gives its size) and a third of the wall wide, up to 5 m.
|
|
230
|
+
let dais;
|
|
231
|
+
if (spec.dais === true) {
|
|
232
|
+
const w = Math.min(5, (kamiza === "north" || kamiza === "south" ? width : length) * 0.34);
|
|
233
|
+
const p = place(kamiza, room, 0.5, w);
|
|
234
|
+
const d = Math.min(1.5, (kamiza === "north" || kamiza === "south" ? length : width) * 0.2);
|
|
235
|
+
const edge = along(kamiza, room, p.at, w, d);
|
|
236
|
+
dais = kamiza === "north" ? { ...edge, y0: room.y0, y1: room.y0 + d } : kamiza === "south" ? { ...edge, y0: room.y1 - d, y1: room.y1 } : kamiza === "west" ? { ...edge, x0: room.x0, x1: room.x0 + d } : { ...edge, x0: room.x1 - d, x1: room.x1 };
|
|
237
|
+
parts.areas.push(rectArea("dais", "dais", "ky-bud-dais", dais));
|
|
238
|
+
}
|
|
239
|
+
// The banner: a plain dark band 0.9 m wide on the kamiza wall, in the wall's thickness, over the dais or the shrine.
|
|
240
|
+
let banner;
|
|
241
|
+
if (spec.banner === true && wallsOn) {
|
|
242
|
+
const p = place(kamiza, room, 0.5, 0.9);
|
|
243
|
+
banner = along(kamiza, room, p.at, 0.9, wall * 0.7);
|
|
244
|
+
parts.areas.push(rectArea("banner", "banner", "ky-bud-banner", banner));
|
|
245
|
+
}
|
|
246
|
+
// The shinzen (神前): the shrine at the middle of the kamiza wall, on the floor against it.
|
|
247
|
+
let shrine;
|
|
248
|
+
if (spec.shinzen === true) {
|
|
249
|
+
const w = Math.min(1.8, (kamiza === "north" || kamiza === "south" ? width : length) * 0.3);
|
|
250
|
+
const p = place(kamiza, room, 0.5, w);
|
|
251
|
+
const d = 0.35;
|
|
252
|
+
const edge = along(kamiza, room, p.at, w, d);
|
|
253
|
+
shrine = kamiza === "north" ? { ...edge, y0: room.y0, y1: room.y0 + d } : kamiza === "south" ? { ...edge, y0: room.y1 - d, y1: room.y1 } : kamiza === "west" ? { ...edge, x0: room.x0, x1: room.x0 + d } : { ...edge, x0: room.x1 - d, x1: room.x1 };
|
|
254
|
+
parts.areas.push(rectArea("shinzen", "shrine", "ky-bud-shrine", shrine));
|
|
255
|
+
}
|
|
256
|
+
const cx = width / 2;
|
|
257
|
+
const cy = length / 2;
|
|
258
|
+
const inset = (c) => (c === "north" ? { x: cx, y: Math.min(1.2, length * 0.1) } : c === "south" ? { x: cx, y: length - Math.min(1.2, length * 0.1) } : c === "west" ? { x: Math.min(1.2, width * 0.1), y: cy } : { x: width - Math.min(1.2, width * 0.1), y: cy });
|
|
259
|
+
const k = inset(kamiza);
|
|
260
|
+
const s = inset(OPPOSITE[kamiza]);
|
|
261
|
+
marks.push({ id: "kamiza", x: k.x, y: k.y, side: kamiza === "south" ? "bottom" : "top" });
|
|
262
|
+
marks.push({ id: "shimoza", x: s.x, y: s.y, side: kamiza === "north" ? "bottom" : "top" });
|
|
263
|
+
if (dais !== undefined)
|
|
264
|
+
marks.push({ id: "dais", x: (dais.x0 + dais.x1) / 2, y: (dais.y0 + dais.y1) / 2, side: kamiza === "south" ? "bottom" : "top" });
|
|
265
|
+
if (banner !== undefined)
|
|
266
|
+
marks.push({ id: "banner", x: (banner.x0 + banner.x1) / 2, y: (banner.y0 + banner.y1) / 2, side: kamiza === "south" ? "bottom" : "top" });
|
|
267
|
+
if (shrine !== undefined)
|
|
268
|
+
marks.push({ id: "shinzen", x: (shrine.x0 + shrine.x1) / 2, y: (shrine.y0 + shrine.y1) / 2, side: kamiza === "south" ? "bottom" : "top" });
|
|
269
|
+
const door = entrances[0];
|
|
270
|
+
if (door !== undefined)
|
|
271
|
+
marks.push({ id: "entrance", x: (door.box.x0 + door.box.x1) / 2, y: (door.box.y0 + door.box.y1) / 2, side: door.wall === "north" ? "top" : "bottom" });
|
|
272
|
+
const win = windows[0];
|
|
273
|
+
if (win !== undefined)
|
|
274
|
+
marks.push({ id: "window", x: (win.box.x0 + win.box.x1) / 2, y: (win.box.y0 + win.box.y1) / 2, side: win.wall === "north" ? "top" : "bottom" });
|
|
275
|
+
if (contests.length > 0)
|
|
276
|
+
marks.push({ id: "contest-area", x: (contests[0].x0 + contests[0].x1) / 2, y: contests[0].y0 + 1.2, side: "top" });
|
|
277
|
+
marks.push({ id: "tatami", x: cx, y: cy, side: "bottom" });
|
|
278
|
+
const edge = pieces.find((p) => p.kind === "full");
|
|
279
|
+
if (edge !== undefined && (pattern === "traditional" || pattern === "border"))
|
|
280
|
+
marks.push({ id: "heri", x: (edge.x0 + edge.x1) / 2, y: edge.y0 + 0.02, side: "top" });
|
|
281
|
+
if (wallsOn)
|
|
282
|
+
marks.push({ id: "wall", x: cx * 0.4, y: -wall / 2, side: "top" });
|
|
283
|
+
const counts = { full: pieces.filter((p) => p.kind === "full").length, half: pieces.filter((p) => p.kind === "half").length, cut: pieces.filter((p) => p.kind === "cut").length };
|
|
284
|
+
return { parts, floor, extent: grow(room, wall + 0.2), width, length, mat, pattern, pieces, jo: Math.round(matCount(pieces, mat) * 100) / 100, counts, kamiza, wall, entrances, windows, room, surround, shrine, contests, dais, banner, era: resolveEra(asked).era, marks };
|
|
285
|
+
}
|
|
286
|
+
/** The windows a spec asks for: the list, or `count` of them spread along the long walls. */
|
|
287
|
+
function windowsOf(w, floor) {
|
|
288
|
+
if (w === undefined)
|
|
289
|
+
return [];
|
|
290
|
+
if (typeof w === "number") {
|
|
291
|
+
if (!Number.isInteger(w) || w < 0 || w > 24)
|
|
292
|
+
throw new RangeError("dojo.windows is a list or a whole number of 0 to 24");
|
|
293
|
+
const long = floor.x1 - floor.x0 >= floor.y1 - floor.y0 ? ["north", "south"] : ["west", "east"];
|
|
294
|
+
const out = [];
|
|
295
|
+
for (let i = 0; i < w; i += 1)
|
|
296
|
+
out.push({ wall: long[i % 2], at: (Math.floor(i / 2) + 1) / (Math.ceil(w / 2) + 1) });
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
return [...w];
|
|
300
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { JudoFigures } from "../leagues/budo.ts";
|
|
2
|
+
import { boxD } from "./kit.ts";
|
|
3
|
+
import { type MBox, type MMark, type Parts } from "./parts.ts";
|
|
4
|
+
import { type MatSize, type Piece } from "./tatami.ts";
|
|
5
|
+
import type { BudoStyle } from "./types.ts";
|
|
6
|
+
/** The mats a `tatami` option names. */
|
|
7
|
+
export declare const TATAMI: Readonly<Record<NonNullable<BudoStyle["tatami"]>, MatSize>>;
|
|
8
|
+
/** What a judo plan is laid out from. */
|
|
9
|
+
export interface JudoOptions {
|
|
10
|
+
fig: JudoFigures;
|
|
11
|
+
/** The contest area's side, the safety area's width and the number of areas across and down. */
|
|
12
|
+
contest: number;
|
|
13
|
+
safety: number;
|
|
14
|
+
across: number;
|
|
15
|
+
down: number;
|
|
16
|
+
mat: MatSize;
|
|
17
|
+
platform: boolean;
|
|
18
|
+
officials: boolean;
|
|
19
|
+
start: boolean;
|
|
20
|
+
seams: boolean;
|
|
21
|
+
/** The colours of the tape: the contestant to the referee's right, then to his left (blue and white in the IJF's rules, red and white in the AJJF's). */
|
|
22
|
+
tape: readonly ["blue" | "red" | "white", "white"];
|
|
23
|
+
}
|
|
24
|
+
/** What a judo plan is. */
|
|
25
|
+
export interface JudoLayout {
|
|
26
|
+
parts: Parts;
|
|
27
|
+
/** The tatami (the contest and the safety areas together) and everything that lies beyond it. */
|
|
28
|
+
tatami: MBox;
|
|
29
|
+
extent: MBox;
|
|
30
|
+
/** The contest areas, left to right and top to bottom. */
|
|
31
|
+
contests: MBox[];
|
|
32
|
+
/** The distance between two areas that adjoin. */
|
|
33
|
+
gap: number;
|
|
34
|
+
pieces: Piece[];
|
|
35
|
+
/** The mat the area is laid with. */
|
|
36
|
+
mat: MatSize;
|
|
37
|
+
marks: MMark[];
|
|
38
|
+
/** The width of the red band of a danger zone, or 0. */
|
|
39
|
+
danger: number;
|
|
40
|
+
}
|
|
41
|
+
/** The mats of a `tatami` option, or the league's own when none. @throws Error for a name that is not a size of mat. */
|
|
42
|
+
export declare function matOf(asked: BudoStyle["tatami"], fig: JudoFigures): MatSize;
|
|
43
|
+
/**
|
|
44
|
+
* A size made a whole number of mats' widths, for mats that do not divide the rule's metres (the AJJF's 91 cm): the next whole number of mats up, so that a least the rules give is kept, or the last that
|
|
45
|
+
* is within the greatest. 8 m of 91 cm mats is 9 mats, 8.19 m; 3 m is 4 mats, 3.64 m (two ken, as the AJJF's guidebook has it).
|
|
46
|
+
*/
|
|
47
|
+
export declare function snapTo(size: number, width: number, least: number, greatest: number): number;
|
|
48
|
+
/** The areas across and down that the options ask for. @throws RangeError for a number outside 1 to 6. */
|
|
49
|
+
export declare function gridOf(areas: BudoStyle["areas"]): {
|
|
50
|
+
across: number;
|
|
51
|
+
down: number;
|
|
52
|
+
};
|
|
53
|
+
/** The size of a contest area the options ask for, held to what the rules allow. */
|
|
54
|
+
export declare const contestOf: (asked: number | undefined, fig: JudoFigures) => number;
|
|
55
|
+
/** The width of the safety area the options ask for. */
|
|
56
|
+
export declare const safetyOf: (asked: number | undefined, fig: JudoFigures) => number;
|
|
57
|
+
/** The plan of the competition area the options ask for. */
|
|
58
|
+
export declare function judoParts(o: JudoOptions): JudoLayout;
|
|
59
|
+
/** The number of mats of the competition area: a half mat counts a half. */
|
|
60
|
+
export declare const mats: (layout: JudoLayout, mat: MatSize) => number;
|
|
61
|
+
export { boxD };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { boxD, rectArea, tapeBar, within } from "./kit.js";
|
|
2
|
+
import { emptyParts, grow, union } from "./parts.js";
|
|
3
|
+
import { lay, matCount, seams, tatamiAreas, tint } from "./tatami.js";
|
|
4
|
+
/** The mats a `tatami` option names. */
|
|
5
|
+
export const TATAMI = Object.freeze({ "2x1": { length: 2, width: 1 }, "1x1": { length: 1, width: 1 }, "182x91": { length: 1.82, width: 0.91 } });
|
|
6
|
+
/** The mats of a `tatami` option, or the league's own when none. @throws Error for a name that is not a size of mat. */
|
|
7
|
+
export function matOf(asked, fig) {
|
|
8
|
+
if (asked === undefined)
|
|
9
|
+
return { length: fig.tatami.length, width: fig.tatami.width };
|
|
10
|
+
const mat = TATAMI[asked];
|
|
11
|
+
if (mat === undefined)
|
|
12
|
+
throw new Error(`Unknown tatami "${String(asked)}": use one of ${Object.keys(TATAMI).join(", ")}`);
|
|
13
|
+
return mat;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A size made a whole number of mats' widths, for mats that do not divide the rule's metres (the AJJF's 91 cm): the next whole number of mats up, so that a least the rules give is kept, or the last that
|
|
17
|
+
* is within the greatest. 8 m of 91 cm mats is 9 mats, 8.19 m; 3 m is 4 mats, 3.64 m (two ken, as the AJJF's guidebook has it).
|
|
18
|
+
*/
|
|
19
|
+
export function snapTo(size, width, least, greatest) {
|
|
20
|
+
if (Math.abs(width - 1) < 1e-9 || Math.abs(Math.round(size / width) * width - size) < 1e-9)
|
|
21
|
+
return size;
|
|
22
|
+
const up = Math.ceil(size / width - 1e-9) * width;
|
|
23
|
+
return up <= greatest + 1e-9 ? up : Math.max(least, Math.floor(size / width + 1e-9) * width);
|
|
24
|
+
}
|
|
25
|
+
/** The areas across and down that the options ask for. @throws RangeError for a number outside 1 to 6. */
|
|
26
|
+
export function gridOf(areas) {
|
|
27
|
+
const a = areas === undefined ? { across: 1, down: 1 } : typeof areas === "number" ? { across: areas, down: 1 } : areas;
|
|
28
|
+
for (const n of [a.across, a.down])
|
|
29
|
+
if (!Number.isInteger(n) || n < 1 || n > 6)
|
|
30
|
+
throw new RangeError("budo.areas is a whole number of 1 to 6, or { across, down } of whole numbers of 1 to 6");
|
|
31
|
+
return a;
|
|
32
|
+
}
|
|
33
|
+
/** The size of a contest area the options ask for, held to what the rules allow. */
|
|
34
|
+
export const contestOf = (asked, fig) => (asked === undefined ? fig.contest.drawn : within(asked, fig.contest.min, fig.contest.max, "The contest area", "the IJF's Sport and Organisation Rules (8.7)"));
|
|
35
|
+
/** The width of the safety area the options ask for. */
|
|
36
|
+
export const safetyOf = (asked, fig) => (asked === undefined ? fig.safety.drawn : within(asked, fig.safety.min, fig.safety.max, "The safety area", "the IJF's Sport and Organisation Rules (8.7)"));
|
|
37
|
+
/** The technical table: 3 m by 0.8 m (ours: the rules give no size), centred on its contest area and set well outside the free zone. */
|
|
38
|
+
const TABLE = { length: 3, depth: 0.8, clear: 1.5 };
|
|
39
|
+
/** The plan of the competition area the options ask for. */
|
|
40
|
+
export function judoParts(o) {
|
|
41
|
+
const { fig, contest: c, safety: s, across, down, mat } = o;
|
|
42
|
+
const gap = Math.max(fig.common, s);
|
|
43
|
+
const width = across * c + (across - 1) * gap + 2 * s;
|
|
44
|
+
const height = down * c + (down - 1) * gap + 2 * s;
|
|
45
|
+
const tatami = { x0: 0, y0: 0, x1: width, y1: height };
|
|
46
|
+
const contests = [];
|
|
47
|
+
for (let j = 0; j < down; j += 1)
|
|
48
|
+
for (let i = 0; i < across; i += 1)
|
|
49
|
+
contests.push({ x0: s + i * (c + gap), y0: s + j * (c + gap), x1: s + i * (c + gap) + c, y1: s + j * (c + gap) + c });
|
|
50
|
+
const band = fig.danger ? mat.width : 0;
|
|
51
|
+
const parts = emptyParts();
|
|
52
|
+
// The free zone and the platform lie under the tatami: the platform one metre wider and longer than it, the free zone 50 cm beyond whichever is the larger.
|
|
53
|
+
const platform = o.platform ? grow(tatami, fig.platform) : undefined;
|
|
54
|
+
const free = grow(platform ?? tatami, fig.freeZone);
|
|
55
|
+
parts.areas.push(rectArea("free-zone", "free-zone", "ky-bud-free", free));
|
|
56
|
+
if (platform !== undefined)
|
|
57
|
+
parts.areas.push(rectArea("platform", "platform", "ky-bud-platform", platform));
|
|
58
|
+
// The mats of the whole competition area, then cut where a colour changes: the contest area inside the safety area, and inside it, for the domestic rules, the red band of the danger zone.
|
|
59
|
+
const zones = [...(band > 0 ? contests.map((b) => ({ box: grow(b, -band), tone: 1 })) : []), ...contests.map((b) => ({ box: b, tone: (band > 0 ? 3 : 1) }))];
|
|
60
|
+
const pieces = tint(lay(tatami, mat, "x", true), zones, 2, mat);
|
|
61
|
+
parts.areas.push(...tatamiAreas("tatami", pieces, { 1: "ky-bud-t1", 2: "ky-bud-t2", 3: "ky-bud-t3" }, { 1: "contest-area", 2: "safety-area", 3: "danger-zone" }));
|
|
62
|
+
if (o.seams)
|
|
63
|
+
parts.shapes.push(...seams("tatami", pieces, 0.02));
|
|
64
|
+
const marks = [];
|
|
65
|
+
// The tape that marks where the contestants start, in the middle of each contest area, across the line between them: the one to the referee's right, who faces the table at the top, is to the east.
|
|
66
|
+
if (o.start && fig.marks !== null) {
|
|
67
|
+
contests.forEach((b, k) => {
|
|
68
|
+
const cx = (b.x0 + b.x1) / 2;
|
|
69
|
+
const cy = (b.y0 + b.y1) / 2;
|
|
70
|
+
const m = fig.marks;
|
|
71
|
+
const at = (x) => ({ x0: x - m.width / 2, y0: cy - m.length / 2, x1: x + m.width / 2, y1: cy + m.length / 2 });
|
|
72
|
+
parts.shapes.push(tapeBar(`start-${k + 1}-right`, "start-right", `ky-bud-${o.tape[0]}-fill`, at(cx + m.fromCentre)));
|
|
73
|
+
parts.shapes.push(tapeBar(`start-${k + 1}-left`, "start-left", `ky-bud-${o.tape[1]}-fill`, at(cx - m.fromCentre)));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// The technical table of each area, outside the free zone, at the top.
|
|
77
|
+
const desks = [];
|
|
78
|
+
if (o.officials) {
|
|
79
|
+
for (const b of contests.filter((_, k) => k < across)) {
|
|
80
|
+
const cx = (b.x0 + b.x1) / 2;
|
|
81
|
+
const y1 = free.y0 - TABLE.clear;
|
|
82
|
+
desks.push({ x0: cx - TABLE.length / 2, y0: y1 - TABLE.depth, x1: cx + TABLE.length / 2, y1 });
|
|
83
|
+
}
|
|
84
|
+
desks.forEach((d, k) => parts.areas.push(rectArea(`table-${k + 1}`, "officials", "ky-bud-desk", d)));
|
|
85
|
+
}
|
|
86
|
+
const first = contests[0];
|
|
87
|
+
const fx = (first.x0 + first.x1) / 2;
|
|
88
|
+
marks.push({ id: "contest-area", x: fx, y: first.y0 + c * 0.3, side: "top" });
|
|
89
|
+
marks.push({ id: "safety-area", x: fx, y: s / 2, side: "top" });
|
|
90
|
+
if (band > 0)
|
|
91
|
+
marks.push({ id: "danger-zone", x: first.x0 + c * 0.22, y: first.y1 - band / 2, side: "bottom" });
|
|
92
|
+
if (o.start && fig.marks !== null)
|
|
93
|
+
marks.push({ id: "start-mark", x: fx + fig.marks.fromCentre, y: (first.y0 + first.y1) / 2, side: "bottom" });
|
|
94
|
+
if (across > 1)
|
|
95
|
+
marks.push({ id: "common-safe-zone", x: first.x1 + gap / 2, y: (first.y0 + first.y1) / 2, side: "bottom" });
|
|
96
|
+
marks.push({ id: "free-zone", x: width * 0.12, y: free.y0 + fig.freeZone / 2, side: "top" });
|
|
97
|
+
// The referee stands at the middle of the tatami, 2 m back from the line the contestants start on, facing the table (Article 9).
|
|
98
|
+
marks.push({ id: "referee-position", x: fx, y: (first.y0 + first.y1) / 2 + 2, side: "bottom" });
|
|
99
|
+
marks.push({ id: "tatami", x: first.x0 + 0.5 * c, y: first.y0 + c * 0.7, side: "bottom" });
|
|
100
|
+
if (platform !== undefined)
|
|
101
|
+
marks.push({ id: "platform", x: width * 0.88, y: platform.y1 - fig.platform / 2, side: "bottom" });
|
|
102
|
+
if (desks.length > 0)
|
|
103
|
+
marks.push({ id: "technical-table", x: (desks[0].x0 + desks[0].x1) / 2, y: desks[0].y0 + TABLE.depth / 2, side: "top" });
|
|
104
|
+
const extent = union([free, ...desks.map((d) => grow(d, 0.3))]);
|
|
105
|
+
return { parts, tatami, extent, contests, gap, pieces, mat, marks, danger: band };
|
|
106
|
+
}
|
|
107
|
+
/** The number of mats of the competition area: a half mat counts a half. */
|
|
108
|
+
export const mats = (layout, mat) => Math.round(matCount(layout.pieces, mat) * 100) / 100;
|
|
109
|
+
export { boxD };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { KarateFigures } from "../leagues/budo.ts";
|
|
2
|
+
import { type MBox, type MMark, type Parts } from "./parts.ts";
|
|
3
|
+
import { type MatSize, type Piece } from "./tatami.ts";
|
|
4
|
+
/** The mat of a karate area: 1 m by 1 m, a WKF-approved mat's size (ours: the rules do not give it). */
|
|
5
|
+
export declare const KARATE_MAT: MatSize;
|
|
6
|
+
/** What a karate plan is laid out from. */
|
|
7
|
+
export interface KarateOptions {
|
|
8
|
+
fig: KarateFigures;
|
|
9
|
+
across: number;
|
|
10
|
+
down: number;
|
|
11
|
+
officials: boolean;
|
|
12
|
+
judges: boolean;
|
|
13
|
+
seams: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** What a karate plan is. */
|
|
16
|
+
export interface KarateLayout {
|
|
17
|
+
parts: Parts;
|
|
18
|
+
/** The mats, the competition areas and the safety area together. */
|
|
19
|
+
mats: MBox;
|
|
20
|
+
extent: MBox;
|
|
21
|
+
/** The competition areas (the matted squares of 8 m), left to right and top to bottom, and the inner square of each (without the outer band). */
|
|
22
|
+
areas: MBox[];
|
|
23
|
+
inner: MBox[];
|
|
24
|
+
gap: number;
|
|
25
|
+
pieces: Piece[];
|
|
26
|
+
marks: MMark[];
|
|
27
|
+
}
|
|
28
|
+
/** The plan of the karate area the options ask for. */
|
|
29
|
+
export declare function karateParts(o: KarateOptions): KarateLayout;
|