@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.6.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/CHANGELOG.md +133 -0
- package/NOTICE.md +182 -2
- package/README.md +106 -19
- package/dist/annotate.js +1 -1
- 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/athletics/describe.d.ts +8 -0
- package/dist/athletics/describe.js +41 -0
- package/dist/athletics/events.d.ts +17 -0
- package/dist/athletics/events.js +48 -0
- package/dist/athletics/facilities.d.ts +80 -0
- package/dist/athletics/facilities.js +176 -0
- package/dist/athletics/infield.d.ts +79 -0
- package/dist/athletics/infield.js +166 -0
- package/dist/athletics/layout.d.ts +13 -0
- package/dist/athletics/layout.js +98 -0
- package/dist/athletics/oval.d.ts +57 -0
- package/dist/athletics/oval.js +116 -0
- package/dist/athletics/plan.d.ts +97 -0
- package/dist/athletics/plan.js +355 -0
- package/dist/athletics/races.d.ts +102 -0
- package/dist/athletics/races.js +355 -0
- package/dist/athletics/steeplechase.d.ts +72 -0
- package/dist/athletics/steeplechase.js +196 -0
- package/dist/athletics/stencil.d.ts +13 -0
- package/dist/athletics/stencil.js +85 -0
- package/dist/athletics/track.d.ts +54 -0
- package/dist/athletics/track.js +105 -0
- package/dist/athletics/types.d.ts +77 -0
- package/dist/athletics/types.js +1 -0
- package/dist/athletics-entry.d.ts +17 -0
- package/dist/athletics-entry.js +11 -0
- package/dist/athletics.style.d.ts +16 -0
- package/dist/athletics.style.js +51 -0
- package/dist/atlas-entry.d.ts +6 -2
- package/dist/atlas-entry.js +4 -2
- package/dist/atlas.d.ts +117 -23
- package/dist/atlas.js +197 -12
- 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-entry.d.ts +6 -1
- package/dist/charts-entry.js +3 -0
- package/dist/charts.athletics.d.ts +183 -0
- package/dist/charts.athletics.js +455 -0
- package/dist/charts.baseball.js +1 -1
- package/dist/charts.curling.heat.d.ts +24 -0
- package/dist/charts.curling.heat.js +116 -0
- package/dist/charts.curling.js +14 -3
- package/dist/charts.js +4 -4
- package/dist/charts.pool.d.ts +67 -0
- package/dist/charts.pool.js +76 -0
- package/dist/charts.skating.d.ts +204 -0
- package/dist/charts.skating.js +426 -0
- package/dist/charts.soccer.js +3 -3
- package/dist/charts.types.d.ts +63 -1
- package/dist/colour.d.ts +4 -4
- package/dist/colour.js +4 -4
- package/dist/court.d.ts +85 -2
- package/dist/court.js +102 -7
- package/dist/court.more.d.ts +46 -0
- package/dist/court.more.js +219 -0
- package/dist/courts.style.d.ts +14 -2
- package/dist/courts.style.js +51 -4
- package/dist/crowd.d.ts +164 -0
- package/dist/crowd.draw.d.ts +17 -0
- package/dist/crowd.draw.js +204 -0
- package/dist/crowd.js +480 -0
- package/dist/crowd.park.d.ts +19 -0
- package/dist/crowd.park.js +153 -0
- package/dist/curling.stats.d.ts +271 -0
- package/dist/curling.stats.js +301 -0
- package/dist/draw.d.ts +16 -0
- package/dist/draw.js +462 -54
- package/dist/element-entry.d.ts +3 -1
- package/dist/element-entry.js +2 -1
- package/dist/element.d.ts +13 -2
- package/dist/element.js +79 -4
- 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 +204 -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 +24 -4
- 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 +71 -8
- package/dist/geojson.js +3 -0
- package/dist/geometry-entry.d.ts +2 -1
- package/dist/geometry-entry.js +2 -1
- package/dist/geometry.d.ts +20 -0
- package/dist/geometry.js +78 -1
- package/dist/grounds.d.ts +4 -0
- package/dist/grounds.data.js +12 -0
- package/dist/halls.d.ts +65 -0
- package/dist/halls.js +122 -0
- package/dist/hardwood.d.ts +31 -0
- package/dist/hardwood.js +157 -0
- package/dist/heat.js +21 -13
- package/dist/heat.types.d.ts +5 -0
- package/dist/heat.zones.d.ts +4 -2
- package/dist/heat.zones.js +207 -7
- package/dist/homeside.js +24 -0
- package/dist/index.d.ts +30 -4
- package/dist/index.js +18 -2
- 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 +12 -1
- package/dist/layers.env.js +36 -4
- package/dist/layers.js +32 -13
- package/dist/layers.types.d.ts +9 -2
- package/dist/leagues/athletics.d.ts +63 -0
- package/dist/leagues/athletics.js +87 -0
- package/dist/leagues/budo.d.ts +196 -0
- package/dist/leagues/budo.js +105 -0
- package/dist/leagues/courts.d.ts +269 -4
- package/dist/leagues/courts.js +155 -1
- package/dist/leagues/hockey.js +41 -19
- package/dist/leagues/skating.d.ts +145 -0
- package/dist/leagues/skating.js +73 -0
- package/dist/leagues/sources.js +41 -0
- package/dist/leagues/types.d.ts +1 -1
- package/dist/leagues-entry.d.ts +4 -2
- package/dist/leagues-entry.js +2 -1
- package/dist/leagues.d.ts +17 -4
- package/dist/leagues.js +246 -5
- 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/look.d.ts +3 -3
- package/dist/look.js +39 -6
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +46 -4
- package/dist/pageant.d.ts +120 -0
- package/dist/pageant.draw.d.ts +21 -0
- package/dist/pageant.draw.js +171 -0
- package/dist/pageant.js +211 -0
- package/dist/pageant.style.d.ts +8 -0
- package/dist/pageant.style.js +42 -0
- package/dist/png.d.ts +8 -1
- package/dist/png.js +21 -1
- 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 +176 -0
- package/dist/pool.figures.d.ts +306 -0
- package/dist/pool.figures.js +148 -0
- package/dist/pool.js +304 -0
- package/dist/pool.kit.d.ts +48 -0
- package/dist/pool.kit.js +141 -0
- package/dist/pool.labels.d.ts +20 -0
- package/dist/pool.labels.js +258 -0
- package/dist/pool.race.d.ts +65 -0
- package/dist/pool.race.js +140 -0
- package/dist/pool.replay.d.ts +46 -0
- package/dist/pool.replay.js +127 -0
- package/dist/pool.section.d.ts +13 -0
- package/dist/pool.section.js +83 -0
- package/dist/pool.style.d.ts +20 -0
- package/dist/pool.style.js +169 -0
- package/dist/pool.types.d.ts +145 -0
- package/dist/pool.types.js +3 -0
- package/dist/pool.words.d.ts +13 -0
- package/dist/pool.words.js +101 -0
- package/dist/ranges.names.js +53 -3
- package/dist/replay-entry.d.ts +3 -0
- package/dist/replay-entry.js +2 -0
- package/dist/restyle.js +1 -1
- 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 +93 -6
- package/dist/sheet.d.ts +3 -0
- package/dist/sheet.js +7 -3
- 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/skating/describe.d.ts +7 -0
- package/dist/skating/describe.js +44 -0
- package/dist/skating/events.d.ts +24 -0
- package/dist/skating/events.js +47 -0
- package/dist/skating/figure.d.ts +24 -0
- package/dist/skating/figure.js +37 -0
- package/dist/skating/long.d.ts +54 -0
- package/dist/skating/long.js +111 -0
- package/dist/skating/parts.d.ts +43 -0
- package/dist/skating/parts.js +40 -0
- package/dist/skating/plan.d.ts +55 -0
- package/dist/skating/plan.js +197 -0
- package/dist/skating/short.d.ts +47 -0
- package/dist/skating/short.js +89 -0
- package/dist/skating/track.d.ts +66 -0
- package/dist/skating/track.js +94 -0
- package/dist/skating/types.d.ts +57 -0
- package/dist/skating/types.js +3 -0
- package/dist/skating-entry.d.ts +17 -0
- package/dist/skating-entry.js +11 -0
- package/dist/skating.style.d.ts +4 -0
- package/dist/skating.style.js +24 -0
- package/dist/strings.d.ts +470 -0
- package/dist/strings.js +674 -13
- package/dist/style.d.ts +2 -0
- package/dist/style.js +19 -2
- package/dist/surfaces/artistic.d.ts +9 -0
- package/dist/surfaces/artistic.js +94 -0
- package/dist/surfaces/athletics.d.ts +16 -0
- package/dist/surfaces/athletics.js +132 -0
- package/dist/surfaces/baseball.js +17 -0
- package/dist/surfaces/basketball.floor.d.ts +27 -0
- package/dist/surfaces/basketball.floor.js +83 -0
- package/dist/surfaces/basketball.gear.d.ts +27 -0
- package/dist/surfaces/basketball.gear.js +90 -0
- package/dist/surfaces/basketball.js +44 -22
- package/dist/surfaces/budo.d.ts +6 -0
- package/dist/surfaces/budo.js +104 -0
- package/dist/surfaces/curling.d.ts +16 -0
- package/dist/surfaces/curling.fold.d.ts +67 -0
- package/dist/surfaces/curling.fold.js +107 -0
- package/dist/surfaces/curling.js +37 -3
- package/dist/surfaces/dive.d.ts +9 -0
- package/dist/surfaces/dive.js +128 -0
- package/dist/surfaces/field.d.ts +7 -2
- package/dist/surfaces/field.js +15 -9
- package/dist/surfaces/football.js +7 -0
- package/dist/surfaces/hockey.js +4 -2
- package/dist/surfaces/net.d.ts +2 -1
- package/dist/surfaces/net.js +160 -15
- package/dist/surfaces/polo.d.ts +12 -0
- package/dist/surfaces/polo.js +159 -0
- package/dist/surfaces/pool.d.ts +27 -0
- package/dist/surfaces/pool.js +198 -0
- package/dist/surfaces/skating.d.ts +6 -0
- package/dist/surfaces/skating.js +62 -0
- package/dist/surfaces/soccer.js +2 -1
- package/dist/surfaces/surface.d.ts +76 -0
- package/dist/surfaces/venue.d.ts +15 -0
- package/dist/surfaces/venue.js +95 -0
- 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.d.ts +77 -0
- package/dist/tenants.data.d.ts +8 -0
- package/dist/tenants.data.js +1793 -0
- package/dist/tenants.js +46 -0
- package/dist/turn-entry.d.ts +12 -0
- package/dist/turn-entry.js +7 -0
- package/dist/turn.camera.d.ts +116 -0
- package/dist/turn.camera.js +184 -0
- package/dist/turn.dom.d.ts +40 -0
- package/dist/turn.dom.js +353 -0
- package/dist/turn.kit.d.ts +86 -0
- package/dist/turn.kit.js +110 -0
- package/dist/turn.markup.d.ts +48 -0
- package/dist/turn.markup.js +108 -0
- package/dist/turn.model.d.ts +78 -0
- package/dist/turn.model.js +91 -0
- package/dist/turn.style.d.ts +5 -0
- package/dist/turn.style.js +56 -0
- package/dist/types.d.ts +221 -20
- package/dist/units.d.ts +1 -1
- package/dist/units.js +22 -1
- package/dist/uprights.board.d.ts +17 -0
- package/dist/uprights.board.js +59 -0
- package/dist/uprights.budo.d.ts +17 -0
- package/dist/uprights.budo.js +87 -0
- package/dist/uprights.d.ts +37 -0
- package/dist/uprights.fields.d.ts +32 -0
- package/dist/uprights.fields.js +172 -0
- package/dist/uprights.goal.d.ts +49 -0
- package/dist/uprights.goal.js +69 -0
- package/dist/uprights.js +169 -0
- package/dist/uprights.more.d.ts +7 -0
- package/dist/uprights.more.js +183 -0
- package/dist/uprights.net.d.ts +12 -0
- package/dist/uprights.net.js +109 -0
- package/dist/uprights.netkit.d.ts +28 -0
- package/dist/uprights.netkit.js +67 -0
- package/dist/uprights.pools.d.ts +8 -0
- package/dist/uprights.pools.js +190 -0
- package/dist/uprights.rinks.d.ts +27 -0
- package/dist/uprights.rinks.js +106 -0
- package/dist/uprights.track.d.ts +50 -0
- package/dist/uprights.track.js +246 -0
- 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 +56 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { SurfaceOptions } from "../types.ts";
|
|
2
|
+
import type { MBox } from "./parts.ts";
|
|
3
|
+
import type { Piece } from "./tatami.ts";
|
|
4
|
+
import type { Compass, DojoPattern, DojoSpec } from "./types.ts";
|
|
5
|
+
/** One mat of a dojo's floor, or a part of one, in metres from the floor's top left corner. */
|
|
6
|
+
export type Tatami = Piece;
|
|
7
|
+
/** A dojo made from a spec: the floor as data. */
|
|
8
|
+
export interface Dojo {
|
|
9
|
+
/** The spec it was made from. */
|
|
10
|
+
spec: DojoSpec;
|
|
11
|
+
/** The mat standard: a league of the `dojo` sport (`chukyoma`, `kyoma`, `edoma`, `danchima`, `judo-2x1`) and the mat's size in metres. */
|
|
12
|
+
standard: string;
|
|
13
|
+
mat: {
|
|
14
|
+
length: number;
|
|
15
|
+
width: number;
|
|
16
|
+
};
|
|
17
|
+
/** The floor in metres: its width (x, the longer side) and its length (y). */
|
|
18
|
+
width: number;
|
|
19
|
+
length: number;
|
|
20
|
+
/** The floor's area in jō: the area over the area of a mat, a half mat counting a half. */
|
|
21
|
+
jo: number;
|
|
22
|
+
/** How many whole mats, half mats (hanjō) and cut pieces the floor holds. */
|
|
23
|
+
counts: {
|
|
24
|
+
full: number;
|
|
25
|
+
half: number;
|
|
26
|
+
cut: number;
|
|
27
|
+
};
|
|
28
|
+
pattern: DojoPattern;
|
|
29
|
+
/** Every mat, in metres, with the tone it is painted in (1, 2 or 3). */
|
|
30
|
+
mats: readonly Tatami[];
|
|
31
|
+
/** The seat of honour's wall, and the doors and windows with where each is. */
|
|
32
|
+
kamiza: Compass;
|
|
33
|
+
entrances: readonly {
|
|
34
|
+
wall: Compass;
|
|
35
|
+
box: MBox;
|
|
36
|
+
}[];
|
|
37
|
+
windows: readonly {
|
|
38
|
+
wall: Compass;
|
|
39
|
+
box: MBox;
|
|
40
|
+
}[];
|
|
41
|
+
shrine: MBox | undefined;
|
|
42
|
+
/** The thickness of the walls round the floor, 0 for none. */
|
|
43
|
+
wall: number;
|
|
44
|
+
/** The contest areas laid on the floor, in metres. */
|
|
45
|
+
contests: readonly MBox[];
|
|
46
|
+
/** The options that draw it: `drawKyogijo(dojo.options)`. */
|
|
47
|
+
options: SurfaceOptions;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A dojo (道場): the floor in mats, laid as real tatami units, with the walls, doors, windows, shrine and kamiza. `mats` is the floor: a number is its area in jō (8, 12, 20, 50, 100 and 200 give the usual shapes), `[across, along]`
|
|
51
|
+
* the whole mats across and along, `{ width, length, unit }` a size in metres, feet, mats or jō; `pattern` is how it is laid (`traditional`: no four corners meet; `competition`, `checker`, `border`).
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { dojo } from "@johnmorrisdotca/kyogijo-stadiums/budo";
|
|
55
|
+
* const d = dojo({ jo: 20 });
|
|
56
|
+
* console.log(d.jo, d.width.toFixed(2), d.length.toFixed(2), d.counts.full, d.counts.half, d.pattern);
|
|
57
|
+
* // 20 7.28 4.55 18 4 traditional
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function dojo(spec?: DojoSpec): Dojo;
|
|
61
|
+
/** Draw a dojo: `drawKyogijo` with the spec as the options' `dojo`, and any other option the caller gives. */
|
|
62
|
+
export declare function drawDojo(spec?: DojoSpec, more?: Partial<SurfaceOptions>): string;
|
|
63
|
+
/** The mat standards a dojo can be laid with. */
|
|
64
|
+
export declare const DOJO_STANDARDS: readonly string[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// The dojo builder's public face: `dojo(spec)` lays a floor of tatami out as real units and returns it as plain data (every mat, the floor in metres and in jō, where the kamiza, the doors and the windows are),
|
|
2
|
+
// and `drawDojo(spec)` draws it. The same spec always gives the same floor.
|
|
3
|
+
import { drawKyogijo } from "../draw.js";
|
|
4
|
+
import { DOJO_LEAGUES } from "../leagues/budo.js";
|
|
5
|
+
import { dojoLayout, dojoLeague } from "./dojo.js";
|
|
6
|
+
/**
|
|
7
|
+
* A dojo (道場): the floor in mats, laid as real tatami units, with the walls, doors, windows, shrine and kamiza. `mats` is the floor: a number is its area in jō (8, 12, 20, 50, 100 and 200 give the usual shapes), `[across, along]`
|
|
8
|
+
* the whole mats across and along, `{ width, length, unit }` a size in metres, feet, mats or jō; `pattern` is how it is laid (`traditional`: no four corners meet; `competition`, `checker`, `border`).
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { dojo } from "@johnmorrisdotca/kyogijo-stadiums/budo";
|
|
12
|
+
* const d = dojo({ jo: 20 });
|
|
13
|
+
* console.log(d.jo, d.width.toFixed(2), d.length.toFixed(2), d.counts.full, d.counts.half, d.pattern);
|
|
14
|
+
* // 20 7.28 4.55 18 4 traditional
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function dojo(spec = {}) {
|
|
18
|
+
const standard = spec.standard ?? "chukyoma";
|
|
19
|
+
const league = dojoLeague(standard);
|
|
20
|
+
const L = dojoLayout(spec, league);
|
|
21
|
+
return {
|
|
22
|
+
spec,
|
|
23
|
+
standard,
|
|
24
|
+
mat: { length: L.mat.length, width: L.mat.width },
|
|
25
|
+
width: L.width,
|
|
26
|
+
length: L.length,
|
|
27
|
+
jo: L.jo,
|
|
28
|
+
counts: L.counts,
|
|
29
|
+
pattern: L.pattern,
|
|
30
|
+
mats: L.pieces,
|
|
31
|
+
kamiza: L.kamiza,
|
|
32
|
+
entrances: L.entrances,
|
|
33
|
+
windows: L.windows,
|
|
34
|
+
shrine: L.shrine,
|
|
35
|
+
wall: L.wall,
|
|
36
|
+
contests: L.contests,
|
|
37
|
+
options: { sport: "dojo", ...(standard === "chukyoma" ? {} : { league: standard }), dojo: spec },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Draw a dojo: `drawKyogijo` with the spec as the options' `dojo`, and any other option the caller gives. */
|
|
41
|
+
export function drawDojo(spec = {}, more = {}) {
|
|
42
|
+
const d = dojo(spec);
|
|
43
|
+
return drawKyogijo({ ...d.options, ...more, sport: "dojo", dojo: spec });
|
|
44
|
+
}
|
|
45
|
+
/** The mat standards a dojo can be laid with. */
|
|
46
|
+
export const DOJO_STANDARDS = DOJO_LEAGUES.map((l) => l.id);
|
|
@@ -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 };
|