@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.5.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 +75 -0
- package/NOTICE.md +4 -0
- package/README.md +71 -16
- package/dist/annotate.js +1 -1
- 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 +4 -2
- package/dist/atlas-entry.js +3 -2
- package/dist/atlas.d.ts +83 -7
- package/dist/atlas.js +108 -8
- 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.curling.heat.d.ts +24 -0
- package/dist/charts.curling.heat.js +116 -0
- package/dist/charts.curling.js +14 -3
- 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.types.d.ts +53 -1
- 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 +154 -0
- package/dist/crowd.draw.d.ts +17 -0
- package/dist/crowd.draw.js +198 -0
- package/dist/crowd.js +465 -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 +318 -35
- package/dist/element-entry.d.ts +1 -1
- package/dist/element-entry.js +1 -1
- package/dist/element.d.ts +13 -2
- package/dist/element.js +77 -3
- package/dist/features.js +131 -0
- package/dist/fields.kit.d.ts +2 -2
- package/dist/fields.types.d.ts +18 -4
- package/dist/frames.providers.js +63 -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/hardwood.d.ts +31 -0
- package/dist/hardwood.js +157 -0
- package/dist/heat.js +4 -3
- package/dist/heat.types.d.ts +5 -0
- package/dist/heat.zones.d.ts +2 -2
- package/dist/heat.zones.js +154 -6
- package/dist/homeside.js +17 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +10 -1
- package/dist/layers.d.ts +1 -1
- package/dist/layers.env.d.ts +3 -1
- package/dist/layers.env.js +4 -2
- package/dist/layers.js +4 -4
- package/dist/leagues/athletics.d.ts +63 -0
- package/dist/leagues/athletics.js +87 -0
- package/dist/leagues/courts.d.ts +269 -4
- package/dist/leagues/courts.js +155 -1
- package/dist/leagues/skating.d.ts +145 -0
- package/dist/leagues/skating.js +73 -0
- package/dist/leagues/sources.js +20 -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 +14 -4
- package/dist/leagues.js +146 -5
- package/dist/look.d.ts +3 -3
- package/dist/look.js +39 -6
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +30 -2
- 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 +207 -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/pool.d.ts +176 -0
- package/dist/pool.figures.d.ts +304 -0
- package/dist/pool.figures.js +144 -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 +257 -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 +47 -3
- package/dist/replay-entry.d.ts +3 -0
- package/dist/replay-entry.js +2 -0
- package/dist/restyle.js +1 -1
- package/dist/score.js +70 -5
- package/dist/sheet.d.ts +3 -0
- package/dist/sheet.js +7 -3
- 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 +296 -0
- package/dist/strings.js +328 -11
- package/dist/style.d.ts +2 -0
- package/dist/style.js +11 -1
- 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 +15 -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 +43 -21
- 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/hockey.js +1 -0
- 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 +44 -0
- package/dist/surfaces/venue.d.ts +15 -0
- package/dist/surfaces/venue.js +95 -0
- package/dist/tenants.d.ts +77 -0
- package/dist/tenants.data.d.ts +8 -0
- package/dist/tenants.data.js +945 -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 +105 -0
- package/dist/turn.camera.js +168 -0
- package/dist/turn.dom.d.ts +40 -0
- package/dist/turn.dom.js +351 -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 +55 -0
- package/dist/types.d.ts +183 -18
- package/dist/units.d.ts +1 -1
- package/dist/units.js +15 -1
- package/dist/uprights.board.d.ts +17 -0
- package/dist/uprights.board.js +59 -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 +160 -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/package.json +19 -2
package/dist/restyle.js
CHANGED
|
@@ -8,7 +8,7 @@ const LINES = new Set(["ky-line", "ky-red", "ky-blue", "ky-band", "ky-crease"]);
|
|
|
8
8
|
/** The dashes in the centre line of a rink are lines too, but they are dashes already. */
|
|
9
9
|
const WIDTH = new Set([...LINES, "ky-dash"]);
|
|
10
10
|
/** The classes of marks painted as a fill, which take an opacity. */
|
|
11
|
-
const FILLS = new Set(["ky-fill-line", "ky-red-fill", "ky-blue-fill", "ky-num"]);
|
|
11
|
+
const FILLS = new Set(["ky-fill-line", "ky-wall-line", "ky-red-fill", "ky-blue-fill", "ky-num"]);
|
|
12
12
|
const finitePositive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
13
13
|
/**
|
|
14
14
|
* The look of the lines the options ask for, or `undefined` when they ask for none of it. A width is a multiple of the width the lines are drawn at (`2`,
|
package/dist/score.js
CHANGED
|
@@ -142,6 +142,27 @@ export function glyph(sport) {
|
|
|
142
142
|
case "tabletennis":
|
|
143
143
|
// A table: the net across it and the centre line.
|
|
144
144
|
return "M-16 -8H16V8H-16ZM0 -10V10M-16 0H16";
|
|
145
|
+
case "padel":
|
|
146
|
+
// A court: the net, the service lines and the central service line.
|
|
147
|
+
return "M-18 -10H18V10H-18ZM0 -10V10M-12.5 -10V10M12.5 -10V10M-12.5 0H12.5";
|
|
148
|
+
case "squash":
|
|
149
|
+
// A court, the front wall at the left: the short line, the half court line and the two service boxes.
|
|
150
|
+
return "M-18 -10H18V10H-18ZM2.3 -10V10M2.3 0H18M8.2 -10V-5H2.3M8.2 10V5H2.3";
|
|
151
|
+
case "racquetball":
|
|
152
|
+
// A court, the front wall at the left: the short line, the service line and the broken receiving line.
|
|
153
|
+
return "M-18 -10H18V10H-18ZM0 -10V10M-4.5 -10V10M4.5 -10V-8M4.5 -6V-4M4.5 -2V0M4.5 2V4M4.5 6V8";
|
|
154
|
+
case "sepaktakraw":
|
|
155
|
+
// A court: the centre line, the quarter circles at its ends and the service circles.
|
|
156
|
+
return "M-18 -10H18V10H-18ZM0 -10V10M-3 -10A3 3 0 0 0 0 -7A3 3 0 0 0 3 -10M-3 10A3 3 0 0 1 0 7A3 3 0 0 1 3 10M-12.9 0A1.5 1.5 0 1 0 -9.9 0A1.5 1.5 0 1 0 -12.9 0M9.9 0A1.5 1.5 0 1 0 12.9 0A1.5 1.5 0 1 0 9.9 0";
|
|
157
|
+
case "teqball":
|
|
158
|
+
// A court: the halfway line and the service lines, and the table.
|
|
159
|
+
return "M-18 -10H18V10H-18ZM0 -10V10M-7.9 -10V10M7.9 -10V10M-3.4 -1.3H3.4V1.3H-3.4Z";
|
|
160
|
+
case "roundnet":
|
|
161
|
+
// A court: the serving line and the no hit zone line round the set, and the marks at the four service positions.
|
|
162
|
+
return "M-10 -10H10V10H-10ZM-8 0A8 8 0 1 0 8 0A8 8 0 1 0 -8 0M-2.8 0A2.8 2.8 0 1 0 2.8 0A2.8 2.8 0 1 0 -2.8 0M-1.2 0A1.2 1.2 0 1 0 1.2 0A1.2 1.2 0 1 0 -1.2 0M-9 0H-7M7 0H9M0 -9V-7M0 7V9";
|
|
163
|
+
case "netball":
|
|
164
|
+
// A court: the transverse lines, the centre circle and the goal circles.
|
|
165
|
+
return "M-18 -10H18V10H-18ZM-6 -10V10M6 -10V10M-1 0A1 1 0 1 0 1 0A1 1 0 1 0 -1 0M-18 -6.4A6.4 6.4 0 0 1 -18 6.4M18 -6.4A6.4 6.4 0 0 0 18 6.4";
|
|
145
166
|
case "lacrosse":
|
|
146
167
|
// A field: the centre line, the goal circles and the restraining lines.
|
|
147
168
|
return "M-18 -10H18V10H-18ZM0 -10V10M-12 0A3 3 0 1 0 -6 0A3 3 0 1 0 -12 0M12 0A3 3 0 1 0 6 0A3 3 0 1 0 12 0M-7 -10V10M7 -10V10";
|
|
@@ -163,6 +184,24 @@ export function glyph(sport) {
|
|
|
163
184
|
case "handball":
|
|
164
185
|
// A court: the centre line, and the goal areas with their arcs.
|
|
165
186
|
return "M-18 -10H18V10H-18ZM0 -10V10M-18 -6A6 6 0 0 1 -12 0A6 6 0 0 1 -18 6M18 -6A6 6 0 0 0 12 0A6 6 0 0 0 18 6";
|
|
187
|
+
case "swimming":
|
|
188
|
+
// A pool: the lane ropes, and the cross lines at the ends of the lane markings.
|
|
189
|
+
return "M-18 -10H18V10H-18ZM-18 -5H18M-18 0H18M-18 5H18M-14 -7.5V-2.5M14 -7.5V-2.5M-14 2.5V7.5M14 2.5V7.5";
|
|
190
|
+
case "waterpolo":
|
|
191
|
+
// A field: the half distance line, the 2 and 6 metre lines, and a goal at each end.
|
|
192
|
+
return "M-18 -10H18V10H-18ZM0 -10V10M-14 -10V10M14 -10V10M-18 -3H-21V3H-18M18 -3H21V3H18M-9 -10V10M9 -10V10";
|
|
193
|
+
case "diving":
|
|
194
|
+
// A well: the boards along one wall, and the lines across the well.
|
|
195
|
+
return "M-18 -10H18V10H-18ZM-18 -7H-9V-5H-18ZM-18 -1H-12V1H-18ZM-18 5H-9V7H-18ZM0 -9V9M6 -9V9M12 -9V9";
|
|
196
|
+
case "artisticswimming":
|
|
197
|
+
// An area: the lines along the floor, and the judges' platform on one side.
|
|
198
|
+
return "M-18 -10H18V10H-18ZM-18 -5H18M-18 0H18M-18 5H18M-10 -13H10";
|
|
199
|
+
case "skating":
|
|
200
|
+
// A rink: the oval of a track inside the boards, and the finishing line.
|
|
201
|
+
return "M-18 -10H18V10H-18ZM-9 -6H9A6 6 0 0 1 9 6H-9A6 6 0 0 1 -9 -6ZM-9 -3.5H9A3.5 3.5 0 0 1 9 3.5H-9A3.5 3.5 0 0 1 -9 -3.5ZM4 3.5V10";
|
|
202
|
+
case "athletics":
|
|
203
|
+
// A track: the oval's outer edge, a lane line and the kerb, and the finish line across the straight.
|
|
204
|
+
return "M-9 -10H9A10 10 0 0 1 9 10H-9A10 10 0 0 1 -9 -10ZM-9 -8.2H9A8.2 8.2 0 0 1 9 8.2H-9A8.2 8.2 0 0 1 -9 -8.2ZM-9 -6.4H9A6.4 6.4 0 0 1 9 6.4H-9A6.4 6.4 0 0 1 -9 -6.4ZM9 6.4V10";
|
|
166
205
|
}
|
|
167
206
|
}
|
|
168
207
|
/** The state each of two scores puts its team in. */
|
|
@@ -171,6 +210,12 @@ export function stateOf(winner, role) {
|
|
|
171
210
|
return "none";
|
|
172
211
|
return winner === role ? "win" : "lose";
|
|
173
212
|
}
|
|
213
|
+
/** The least gap between a team's code and its score in the badge, and the least size of the code and of the score before the flag is left out. */
|
|
214
|
+
const BADGE_GAP = 10;
|
|
215
|
+
const BADGE_MEASURE_SCORE = 1.12;
|
|
216
|
+
const BADGE_MEASURE_CODE = 1.2;
|
|
217
|
+
const BADGE_ABBR_LEAST = 12;
|
|
218
|
+
const BADGE_SCORE_LEAST = 16;
|
|
174
219
|
/** The badge: one strip, [logo abbreviation score] [picture and status] [score abbreviation logo]. */
|
|
175
220
|
export function badgeMarkup(left, right, o) {
|
|
176
221
|
const W = 440;
|
|
@@ -183,18 +228,38 @@ export function badgeMarkup(left, right, o) {
|
|
|
183
228
|
const isLeft = team.side === "left";
|
|
184
229
|
const logoSize = 40;
|
|
185
230
|
const logoX = isLeft ? 12 : W - 12 - logoSize;
|
|
186
|
-
const textStart = isLeft ? (team.logo === undefined ? 14 : 60) : W - (team.logo === undefined ? 14 : 60);
|
|
187
231
|
const scoreEdge = isLeft ? block - 14 : W - block + 14;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
232
|
+
// The code and the score each keep to their own room, with a gap between them: the score is measured, and when the two do not fit the code shrinks to the least that reads, then the score does,
|
|
233
|
+
// and then the flag is left out to give them its room. A short score in a roomy block is laid out as it always was.
|
|
234
|
+
const fit = (withLogo) => {
|
|
235
|
+
const textStart = isLeft ? (withLogo ? 60 : 14) : W - (withLogo ? 60 : 14);
|
|
236
|
+
const avail = Math.abs(scoreEdge - textStart);
|
|
237
|
+
// The measure is an estimate, under what a bold face draws (by a tenth for digits, a fifth for capitals): the layout allows for that.
|
|
238
|
+
const ws = Math.max(1, textWidth(team.score) * BADGE_MEASURE_SCORE);
|
|
239
|
+
const wa = Math.max(1, textWidth(team.abbr) * BADGE_MEASURE_CODE);
|
|
240
|
+
let scoreSize = Math.min(32, (block * 0.42) / ws);
|
|
241
|
+
let abbrSize = Math.min(22, (avail - ws * scoreSize - BADGE_GAP) / wa);
|
|
242
|
+
if (abbrSize < BADGE_ABBR_LEAST) {
|
|
243
|
+
abbrSize = BADGE_ABBR_LEAST;
|
|
244
|
+
scoreSize = Math.min(scoreSize, (avail - BADGE_GAP - abbrSize * wa) / ws);
|
|
245
|
+
}
|
|
246
|
+
return { textStart, scoreSize, abbrSize, ok: scoreSize >= BADGE_SCORE_LEAST };
|
|
247
|
+
};
|
|
248
|
+
const withLogo = team.logo !== undefined;
|
|
249
|
+
let laid = fit(withLogo);
|
|
250
|
+
const logoShown = withLogo && (laid.ok || !fit(false).ok);
|
|
251
|
+
if (withLogo && !logoShown)
|
|
252
|
+
laid = fit(false);
|
|
253
|
+
const { textStart } = laid;
|
|
254
|
+
const scoreSize = Math.max(laid.scoreSize, 8);
|
|
255
|
+
const abbrSize = Math.max(laid.abbrSize, 8);
|
|
191
256
|
// The hammer: a small stone in the corner of the team's block.
|
|
192
257
|
if (team.hammer === true)
|
|
193
258
|
out += el("g", { class: `ky-badge-hammer ky-badge-hammer-${team.side}`, transform: `translate(${isLeft ? block - 11 : W - block + 11} 11)` }, el("circle", { class: "ky-hammer", r: 6 }) + el("circle", { class: "ky-hammer-in", r: 2.6 }));
|
|
194
259
|
// The ball of the team that is serving, in the same corner.
|
|
195
260
|
if (team.serving === true)
|
|
196
261
|
out += el("g", { transform: `translate(${isLeft ? block - 11 : W - block + 11} 11)` }, el("circle", { class: "ky-serve", r: 6 }) + el("path", { class: "ky-serve-seam", d: "M-4.7 -1.8Q0 1.8 4.7 -1.8", "stroke-width": 0.9 }));
|
|
197
|
-
out += el("g", { class: `ky-badge-team ky-badge-${team.side} ky-${team.state}`, part: part(o.parts, `team ${team.role}`) }, (team.logo === undefined
|
|
262
|
+
out += el("g", { class: `ky-badge-team ky-badge-${team.side} ky-${team.state}`, part: part(o.parts, `team ${team.role}`) }, (!logoShown || team.logo === undefined
|
|
198
263
|
? ""
|
|
199
264
|
: team.round === true
|
|
200
265
|
? roundMarkup({ id: `${o.id ?? "ky"}-mk-${team.role}`, cx: logoX + logoSize / 2, cy: 12 + logoSize / 2, r: logoSize / 2, href: team.logo, fit: team.fit, ring: 2 })
|
package/dist/sheet.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export interface SheetPlan {
|
|
|
9
9
|
guardZone: boolean;
|
|
10
10
|
show: SheetShow;
|
|
11
11
|
end: "left" | "right";
|
|
12
|
+
/** `show: "folded"`: what each enlarged panel holds, and the shape of the frame it fills. */
|
|
13
|
+
panels: "half" | "house";
|
|
14
|
+
fit: number;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* The sheet the options ask for. A `league` picks the rulebook (World Curling's by default); `sheet.length` and `sheet.width` are in `units`
|
package/dist/sheet.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CURLING_LEAGUES } from "./leagues/curling.js";
|
|
|
5
5
|
import { lengthUnit } from "./common.js";
|
|
6
6
|
import { parseRange } from "./ranges.names.js";
|
|
7
7
|
import { convertLength } from "./units.js";
|
|
8
|
-
const SHOWS = ["sheet", "half", "house"];
|
|
8
|
+
const SHOWS = ["sheet", "half", "house", "folded"];
|
|
9
9
|
const FEET = (feet) => String(Math.round(feet * 100) / 100);
|
|
10
10
|
/**
|
|
11
11
|
* The sheet the options ask for. A `league` picks the rulebook (World Curling's by default); `sheet.length` and `sheet.width` are in `units`
|
|
@@ -33,6 +33,10 @@ export function sheetFor(o) {
|
|
|
33
33
|
const ranged = o.sport === "curling" ? parseRange(o) : undefined;
|
|
34
34
|
const named = ranged !== undefined && ranged.kind === "name" ? ranged : undefined;
|
|
35
35
|
const show = named !== undefined ? named.name : SHOWS.includes(asked.show) ? asked.show : "sheet";
|
|
36
|
-
const end = named?.asked !== undefined ? named.asked : asked.end === "right" ? "right" : "left";
|
|
37
|
-
|
|
36
|
+
const end = named?.asked !== undefined ? named.asked : asked.end === "right" || asked.end === "bottom" ? "right" : "left";
|
|
37
|
+
const panels = asked.panels === "house" ? "house" : "half";
|
|
38
|
+
if (asked.fit !== undefined && !(typeof asked.fit === "number" && asked.fit >= 0.5 && asked.fit <= 2))
|
|
39
|
+
throw new RangeError(`The folded sheet's fit is its width over its height, 0.5 to 2: ${String(asked.fit)} is not`);
|
|
40
|
+
const fit = asked.fit ?? 1;
|
|
41
|
+
return { league, spec: { ...base, length, width }, pebble: asked.pebble === true, guardZone: asked.guardZone === true, show, end, panels, fit };
|
|
38
42
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Language, SurfaceOptions } from "../types.ts";
|
|
2
|
+
/** The words of a race in a language. */
|
|
3
|
+
export declare const raceName: (id: string, language: Language) => string;
|
|
4
|
+
/** The sentence about the rink, as a fragment: "a 400 m speed skating track with two lanes of 4 m...". */
|
|
5
|
+
export declare function skatingAbout(o: Pick<SurfaceOptions, "sport" | "league" | "skating">, language: Language): string;
|
|
6
|
+
/** The sentences that follow it: the races marked, the furniture of a figure skating rink and the close-up. Each starts after a gap. */
|
|
7
|
+
export declare function skatingSentences(o: Pick<SurfaceOptions, "sport" | "league" | "skating" | "range">, language: Language): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// What a skating drawing says of itself: its sentence about the rink, and what the options mark on it. Cheap (it reads the options, not the plan), so that a title and a description cost nothing.
|
|
2
|
+
import { fill, KYOGIJO_STRINGS } from "../strings.js";
|
|
3
|
+
import { raceById } from "./events.js";
|
|
4
|
+
import { furnitureOf, racesOf, skatingLeague } from "./plan.js";
|
|
5
|
+
import { longOval } from "./track.js";
|
|
6
|
+
/** The words of a race in a language. */
|
|
7
|
+
export const raceName = (id, language) => raceById(id)?.name[language] ?? id;
|
|
8
|
+
/** The sentence about the rink, as a fragment: "a 400 m speed skating track with two lanes of 4 m...". */
|
|
9
|
+
export function skatingAbout(o, language) {
|
|
10
|
+
const words = KYOGIJO_STRINGS[language].skating;
|
|
11
|
+
const league = skatingLeague(o);
|
|
12
|
+
const d = league.dimensions;
|
|
13
|
+
if (d.kind === "long-track") {
|
|
14
|
+
const oval = longOval(o.skating?.innerRadius, o.skating?.laneWidth, d);
|
|
15
|
+
return fill(words.longTrack, { radius: oval.a, outer: oval.w2, straight: Math.round(oval.straight * 100) / 100 });
|
|
16
|
+
}
|
|
17
|
+
if (d.kind === "short-track")
|
|
18
|
+
return fill(words.shortTrack, { length: o.skating?.rink?.length ?? d.rink.length, width: o.skating?.rink?.width ?? d.rink.width, tracks: o.skating?.tracks ?? 5 });
|
|
19
|
+
return fill(words.figure, { length: o.skating?.rink?.length ?? d.length.drawn, width: o.skating?.rink?.width ?? d.width.drawn });
|
|
20
|
+
}
|
|
21
|
+
/** The sentences that follow it: the races marked, the furniture of a figure skating rink and the close-up. Each starts after a gap. */
|
|
22
|
+
export function skatingSentences(o, language) {
|
|
23
|
+
const words = KYOGIJO_STRINGS[language].skating;
|
|
24
|
+
const { gap } = KYOGIJO_STRINGS[language].said;
|
|
25
|
+
const sep = KYOGIJO_STRINGS[language].layers.listSep;
|
|
26
|
+
const discipline = skatingLeague(o).dimensions.kind;
|
|
27
|
+
const out = [];
|
|
28
|
+
if (discipline === "figure") {
|
|
29
|
+
const furniture = furnitureOf(o.skating);
|
|
30
|
+
if (furniture.length > 0)
|
|
31
|
+
out.push(fill(words.furniture, { list: furniture.map((f) => words.furnitureNames[f]).join(sep) }));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const races = racesOf(o.skating, discipline);
|
|
35
|
+
if (races.length > 0)
|
|
36
|
+
out.push(fill(words.races, { list: races.map((r) => raceName(r, language)).join(sep) }));
|
|
37
|
+
}
|
|
38
|
+
const range = typeof o.range === "string" ? o.range : typeof o.range === "object" && o.range !== null && "name" in o.range ? o.range.name : undefined;
|
|
39
|
+
if (range === "start" && discipline !== "figure")
|
|
40
|
+
out.push(fill(words.start, { race: raceName(o.skating?.start ?? "500m", language) }));
|
|
41
|
+
if (range === "finish" && discipline !== "figure")
|
|
42
|
+
out.push(words.finish);
|
|
43
|
+
return out.map((t) => `${gap}${t}`).join("");
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LongTrackRace, RaceInfo, ShortTrackRace, SkatingRace } from "./types.ts";
|
|
2
|
+
/** The races of the long track, in the order a menu lists them. The mass start is 16 laps (6400 m); the team pursuit and the team sprint are marked by their lines in the middle of the straights. */
|
|
3
|
+
export declare const LONG_TRACK_RACES: readonly RaceInfo[];
|
|
4
|
+
/**
|
|
5
|
+
* The races of the short track: the individual 500, 1000 and 1500 m, the 3000 m of the World Cup, and the relays: the women's 3000 m, the men's 5000 m and the mixed 2000 m (Rule 281.1a, which gives the
|
|
6
|
+
* distances of the ISU Championships; the diagram of the tracks gives the laps of each distance).
|
|
7
|
+
*/
|
|
8
|
+
export declare const SHORT_TRACK_RACES: readonly RaceInfo[];
|
|
9
|
+
/** Every race of both tracks, once by id. */
|
|
10
|
+
export declare const RACES: readonly RaceInfo[];
|
|
11
|
+
export declare const LONG_TRACK_RACE_IDS: readonly LongTrackRace[];
|
|
12
|
+
export declare const SHORT_TRACK_RACE_IDS: readonly ShortTrackRace[];
|
|
13
|
+
export declare const RACE_IDS: readonly SkatingRace[];
|
|
14
|
+
/** A race by its id, or `undefined`. */
|
|
15
|
+
export declare const raceById: (id: unknown) => RaceInfo | undefined;
|
|
16
|
+
/** The kinds of element of a figure skating programme a mark can stand for. */
|
|
17
|
+
export declare const ELEMENT_KINDS: readonly ["jump", "spin", "step-sequence", "choreographic-sequence", "lift", "spiral", "twizzle", "death-spiral", "throw"];
|
|
18
|
+
export type ElementKind = (typeof ELEMENT_KINDS)[number];
|
|
19
|
+
/** The words of a kind of element. */
|
|
20
|
+
export declare const ELEMENT_NAMES: Readonly<Record<ElementKind, {
|
|
21
|
+
en: string;
|
|
22
|
+
ja: string;
|
|
23
|
+
fr: string;
|
|
24
|
+
}>>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const race = (r) => Object.freeze(r);
|
|
2
|
+
/** The races of the long track, in the order a menu lists them. The mass start is 16 laps (6400 m); the team pursuit and the team sprint are marked by their lines in the middle of the straights. */
|
|
3
|
+
export const LONG_TRACK_RACES = Object.freeze([
|
|
4
|
+
race({ id: "500m", distance: 500, name: { en: "500 m", ja: "500m", fr: "500\u00a0m" } }),
|
|
5
|
+
race({ id: "1000m", distance: 1000, name: { en: "1000 m", ja: "1000m", fr: "1000\u00a0m" } }),
|
|
6
|
+
race({ id: "1500m", distance: 1500, name: { en: "1500 m", ja: "1500m", fr: "1500\u00a0m" } }),
|
|
7
|
+
race({ id: "3000m", distance: 3000, name: { en: "3000 m", ja: "3000m", fr: "3000\u00a0m" } }),
|
|
8
|
+
race({ id: "5000m", distance: 5000, name: { en: "5000 m", ja: "5000m", fr: "5000\u00a0m" } }),
|
|
9
|
+
race({ id: "10000m", distance: 10000, name: { en: "10,000 m", ja: "10000m", fr: "10\u00a0000\u00a0m" } }),
|
|
10
|
+
race({ id: "mass-start", distance: 6400, name: { en: "Mass start", ja: "マススタート", fr: "Départ groupé" } }),
|
|
11
|
+
race({ id: "team-pursuit", distance: 3200, name: { en: "Team pursuit", ja: "チームパシュート", fr: "Poursuite par équipes" } }),
|
|
12
|
+
race({ id: "team-sprint", distance: 1200, name: { en: "Team sprint", ja: "チームスプリント", fr: "Sprint par équipes" } }),
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* The races of the short track: the individual 500, 1000 and 1500 m, the 3000 m of the World Cup, and the relays: the women's 3000 m, the men's 5000 m and the mixed 2000 m (Rule 281.1a, which gives the
|
|
16
|
+
* distances of the ISU Championships; the diagram of the tracks gives the laps of each distance).
|
|
17
|
+
*/
|
|
18
|
+
export const SHORT_TRACK_RACES = Object.freeze([
|
|
19
|
+
race({ id: "500m", distance: 500, name: { en: "500 m", ja: "500m", fr: "500\u00a0m" } }),
|
|
20
|
+
race({ id: "1000m", distance: 1000, name: { en: "1000 m", ja: "1000m", fr: "1000\u00a0m" } }),
|
|
21
|
+
race({ id: "1500m", distance: 1500, name: { en: "1500 m", ja: "1500m", fr: "1500\u00a0m" } }),
|
|
22
|
+
race({ id: "3000m", distance: 3000, name: { en: "3000 m", ja: "3000m", fr: "3000\u00a0m" } }),
|
|
23
|
+
race({ id: "3000m-relay", distance: 3000, name: { en: "3000 m relay", ja: "3000mリレー", fr: "Relais 3000\u00a0m" } }),
|
|
24
|
+
race({ id: "5000m-relay", distance: 5000, name: { en: "5000 m relay", ja: "5000mリレー", fr: "Relais 5000\u00a0m" } }),
|
|
25
|
+
race({ id: "2000m-mixed-relay", distance: 2000, name: { en: "2000 m mixed relay", ja: "2000m混合リレー", fr: "Relais mixte 2000\u00a0m" } }),
|
|
26
|
+
]);
|
|
27
|
+
/** Every race of both tracks, once by id. */
|
|
28
|
+
export const RACES = Object.freeze([...LONG_TRACK_RACES, ...SHORT_TRACK_RACES.filter((r) => !LONG_TRACK_RACES.some((l) => l.id === r.id))]);
|
|
29
|
+
export const LONG_TRACK_RACE_IDS = Object.freeze(LONG_TRACK_RACES.map((r) => r.id));
|
|
30
|
+
export const SHORT_TRACK_RACE_IDS = Object.freeze(SHORT_TRACK_RACES.map((r) => r.id));
|
|
31
|
+
export const RACE_IDS = Object.freeze(RACES.map((r) => r.id));
|
|
32
|
+
/** A race by its id, or `undefined`. */
|
|
33
|
+
export const raceById = (id) => RACES.find((r) => r.id === id);
|
|
34
|
+
/** The kinds of element of a figure skating programme a mark can stand for. */
|
|
35
|
+
export const ELEMENT_KINDS = Object.freeze(["jump", "spin", "step-sequence", "choreographic-sequence", "lift", "spiral", "twizzle", "death-spiral", "throw"]);
|
|
36
|
+
/** The words of a kind of element. */
|
|
37
|
+
export const ELEMENT_NAMES = Object.freeze({
|
|
38
|
+
jump: { en: "Jump", ja: "ジャンプ", fr: "Saut" },
|
|
39
|
+
spin: { en: "Spin", ja: "スピン", fr: "Pirouette" },
|
|
40
|
+
"step-sequence": { en: "Step sequence", ja: "ステップシークエンス", fr: "Séquence de pas" },
|
|
41
|
+
"choreographic-sequence": { en: "Choreographic sequence", ja: "コレオシークエンス", fr: "Séquence chorégraphique" },
|
|
42
|
+
lift: { en: "Lift", ja: "リフト", fr: "Levée" },
|
|
43
|
+
spiral: { en: "Spiral", ja: "スパイラル", fr: "Spirale" },
|
|
44
|
+
twizzle: { en: "Twizzle", ja: "ツイズル", fr: "Twizzle" },
|
|
45
|
+
"death-spiral": { en: "Death spiral", ja: "デススパイラル", fr: "Spirale de la mort" },
|
|
46
|
+
throw: { en: "Throw jump", ja: "スロージャンプ", fr: "Saut lancé" },
|
|
47
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type FigureRinkFigures } from "../leagues/skating.ts";
|
|
2
|
+
import { type MBox, type MMark, type Parts } from "./parts.ts";
|
|
3
|
+
import type { SkatingFurniture } from "./types.ts";
|
|
4
|
+
/** The rink: its length and width. */
|
|
5
|
+
export interface FigureRink {
|
|
6
|
+
length: number;
|
|
7
|
+
width: number;
|
|
8
|
+
}
|
|
9
|
+
/** The furniture of a figure skating rink, in painter's order. */
|
|
10
|
+
export declare const FURNITURE: readonly SkatingFurniture[];
|
|
11
|
+
/** The seats of a panel of judges, side by side at the rinkboard: nine, as at the Olympic Winter Games (ours: the Rules fix the number elsewhere), 1 m apart. */
|
|
12
|
+
export declare const JUDGES = 9;
|
|
13
|
+
/** Where the furniture stands outside the boards of the rink on its long side at the bottom (and the kiss and cry at the end), as boxes in metres. */
|
|
14
|
+
export declare function furnitureBoxes(r: FigureRink, fig?: FigureRinkFigures): Record<SkatingFurniture, MBox[]>;
|
|
15
|
+
export interface FigureOptions {
|
|
16
|
+
fig: FigureRinkFigures;
|
|
17
|
+
furniture: readonly SkatingFurniture[];
|
|
18
|
+
}
|
|
19
|
+
/** The parts of the rink. */
|
|
20
|
+
export declare function figureParts(r: FigureRink, opt: FigureOptions): {
|
|
21
|
+
parts: Parts;
|
|
22
|
+
box: MBox;
|
|
23
|
+
marks: MMark[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ISU_FIGURE_RINK } from "../leagues/skating.js";
|
|
2
|
+
import { emptyParts } from "./parts.js";
|
|
3
|
+
/** The furniture of a figure skating rink, in painter's order. */
|
|
4
|
+
export const FURNITURE = Object.freeze(["judges", "referee", "technical-panel", "kiss-and-cry"]);
|
|
5
|
+
/** The seats of a panel of judges, side by side at the rinkboard: nine, as at the Olympic Winter Games (ours: the Rules fix the number elsewhere), 1 m apart. */
|
|
6
|
+
export const JUDGES = 9;
|
|
7
|
+
/** Where the furniture stands outside the boards of the rink on its long side at the bottom (and the kiss and cry at the end), as boxes in metres. */
|
|
8
|
+
export function furnitureBoxes(r, fig = ISU_FIGURE_RINK) {
|
|
9
|
+
const y = r.width / 2 + fig.boards + 0.6;
|
|
10
|
+
const seat = (x) => ({ x0: x - 0.4, y0: y, x1: x + 0.4, y1: y + 0.8 });
|
|
11
|
+
return {
|
|
12
|
+
judges: Array.from({ length: JUDGES }, (_, i) => seat((i - (JUDGES - 1) / 2) * 1)),
|
|
13
|
+
referee: [seat(-(JUDGES - 1) / 2 - 2)],
|
|
14
|
+
"technical-panel": [{ x0: (JUDGES - 1) / 2 + 1.5, y0: y + 1.2, x1: (JUDGES - 1) / 2 + 5.5, y1: y + 2.4 }],
|
|
15
|
+
"kiss-and-cry": [{ x0: -r.length / 2 + 3, y0: y, x1: -r.length / 2 + 6.5, y1: y + 1.6 }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** The parts of the rink. */
|
|
19
|
+
export function figureParts(r, opt) {
|
|
20
|
+
const { fig } = opt;
|
|
21
|
+
const parts = emptyParts();
|
|
22
|
+
const Lh = r.length / 2;
|
|
23
|
+
const W = r.width / 2;
|
|
24
|
+
const rd = (n) => Math.round(n * 100) / 100;
|
|
25
|
+
const roundD = (hx, hy, k) => `M${rd(-hx + k)} ${rd(-hy)}H${rd(hx - k)}A${k} ${k} 0 0 1 ${rd(hx)} ${rd(-hy + k)}V${rd(hy - k)}A${k} ${k} 0 0 1 ${rd(hx - k)} ${rd(hy)}H${rd(-hx + k)}A${k} ${k} 0 0 1 ${rd(-hx)} ${rd(hy - k)}V${rd(-hy + k)}A${k} ${k} 0 0 1 ${rd(-hx + k)} ${rd(-hy)}Z`;
|
|
26
|
+
parts.areas.push({ id: "ice", feature: "ice", cls: "ky-ska-ice", d: roundD(Lh, W, fig.corner) });
|
|
27
|
+
parts.areas.push({ id: "boards", feature: "boards", cls: "ky-boards", d: `${roundD(Lh + fig.boards, W + fig.boards, fig.corner + fig.boards)}${roundD(Lh, W, fig.corner)}`, evenodd: true });
|
|
28
|
+
const boxes = furnitureBoxes(r, fig);
|
|
29
|
+
const marks = [];
|
|
30
|
+
for (const kind of opt.furniture) {
|
|
31
|
+
boxes[kind].forEach((b, i) => parts.areas.push({ id: `${kind}-${i + 1}`, feature: kind, cls: kind === "kiss-and-cry" ? "ky-ska-kiss" : "ky-ska-desk", rect: [b.x0, b.y0, b.x1, b.y1] }));
|
|
32
|
+
const b = boxes[kind][Math.floor(boxes[kind].length / 2)];
|
|
33
|
+
marks.push({ id: kind, x: (b.x0 + b.x1) / 2, y: b.y1, side: "bottom" });
|
|
34
|
+
}
|
|
35
|
+
const extra = opt.furniture.length === 0 ? 0 : 3.4;
|
|
36
|
+
return { parts, box: { x0: -Lh - fig.boards, y0: -W - fig.boards, x1: Lh + fig.boards, y1: W + fig.boards + extra }, marks };
|
|
37
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Shape } from "../fields.types.ts";
|
|
2
|
+
import { type LongTrackFigures } from "../leagues/skating.ts";
|
|
3
|
+
import { LONG_TRACK_RACES } from "./events.ts";
|
|
4
|
+
import { addParts, type MBox, type Parts } from "./parts.ts";
|
|
5
|
+
import { laneRadius, type Lane, type LongOval } from "./track.ts";
|
|
6
|
+
import type { LongTrackRace, SkatingInfield } from "./types.ts";
|
|
7
|
+
/** Where a skater starts: the lane (or `both` for a line across both lanes) and the point of the start. */
|
|
8
|
+
export interface StartPlacement {
|
|
9
|
+
lane: Lane | "both";
|
|
10
|
+
/** The coordinate along the skater's own way, from the beginning of the finishing straight of the lap the start is in. */
|
|
11
|
+
v0: number;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
heading: number;
|
|
15
|
+
}
|
|
16
|
+
/** What a race marks: the lines where it starts, the finishing line it ends at, and how its first straight is skated. */
|
|
17
|
+
export interface RaceMarks {
|
|
18
|
+
race: LongTrackRace;
|
|
19
|
+
/** The finishing line: the 400 m mark at the end of the finishing straight or the 1000 m mark in the middle of it. */
|
|
20
|
+
finish: "400m" | "1000m";
|
|
21
|
+
/** True when the first straight keeps its lane (the 1000 m and the 1500 m, Rule 254.5b). */
|
|
22
|
+
noChange: boolean;
|
|
23
|
+
/** The starting lines: one for each lane, or one across both. */
|
|
24
|
+
starts: StartPlacement[];
|
|
25
|
+
}
|
|
26
|
+
/** The marks of a race on the oval. */
|
|
27
|
+
export declare function markRace(o: LongOval, race: LongTrackRace): RaceMarks;
|
|
28
|
+
/** What the long track adds: everything from the ice to the lines. */
|
|
29
|
+
export interface LongOptions {
|
|
30
|
+
fig: LongTrackFigures;
|
|
31
|
+
warmUp: boolean;
|
|
32
|
+
infield: SkatingInfield;
|
|
33
|
+
blocks: boolean;
|
|
34
|
+
coaches: boolean;
|
|
35
|
+
padding: boolean;
|
|
36
|
+
races: readonly LongTrackRace[];
|
|
37
|
+
}
|
|
38
|
+
export declare const LONG_DEFAULTS: {
|
|
39
|
+
warmUp: boolean;
|
|
40
|
+
infield: SkatingInfield;
|
|
41
|
+
blocks: boolean;
|
|
42
|
+
coaches: boolean;
|
|
43
|
+
padding: boolean;
|
|
44
|
+
};
|
|
45
|
+
/** The ice's outer radius: the outer edge of the outer lane and the 3 m the Rules keep clear (Rule 228.2). */
|
|
46
|
+
export declare const iceRadius: (o: LongOval, fig?: LongTrackFigures) => number;
|
|
47
|
+
/** The parts of the long track. */
|
|
48
|
+
export declare function longParts(o: LongOval, opt: LongOptions): {
|
|
49
|
+
parts: Parts;
|
|
50
|
+
marks: RaceMarks[];
|
|
51
|
+
box: MBox;
|
|
52
|
+
};
|
|
53
|
+
export { addParts, laneRadius, LONG_TRACK_RACES };
|
|
54
|
+
export type { Shape };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// The long track: the ice, the padding, the lanes and the warm-up lane, the blocks of the curves, the coaches' area, the finishing lines and the starting lines of each race. Every figure is the ISU's
|
|
2
|
+
// (Special Regulations and Technical Rules Speed Skating, Rules 203, 205, 226, 227, 228, 253, 254 and 256 to 257, and "H. Track"), in metres. Nothing here draws SVG: it makes the shapes.
|
|
3
|
+
import { line, poly } from "../fields.kit.js";
|
|
4
|
+
import { ISU_LONG_TRACK } from "../leagues/skating.js";
|
|
5
|
+
import { LONG_TRACK_RACES } from "./events.js";
|
|
6
|
+
import { addParts, blocksOf, emptyParts, stadiumD } from "./parts.js";
|
|
7
|
+
import { finishAt, laneEdges, laneRadius, outerEdge, pose, startOf } from "./track.js";
|
|
8
|
+
/** How each race starts. `at` is the finishing line's mark, `middle` a start in the middle of the straights (Rules 256 and 257). */
|
|
9
|
+
const SPEC = {
|
|
10
|
+
"500m": { finish: "400m", distance: 500, noChange: false },
|
|
11
|
+
"1000m": { finish: "1000m", distance: 1000, noChange: true },
|
|
12
|
+
"1500m": { finish: "400m", distance: 1500, noChange: true },
|
|
13
|
+
"3000m": { finish: "400m", distance: 3000, noChange: false },
|
|
14
|
+
"5000m": { finish: "400m", distance: 5000, noChange: false },
|
|
15
|
+
"10000m": { finish: "400m", distance: 10000, noChange: false },
|
|
16
|
+
"mass-start": { finish: "400m", distance: 0, noChange: false, start: "middle" },
|
|
17
|
+
"team-pursuit": { finish: "1000m", distance: 0, noChange: false, start: "both-straights" },
|
|
18
|
+
"team-sprint": { finish: "1000m", distance: 0, noChange: false, start: "both-straights" },
|
|
19
|
+
};
|
|
20
|
+
/** The marks of a race on the oval. */
|
|
21
|
+
export function markRace(o, race) {
|
|
22
|
+
const spec = SPEC[race];
|
|
23
|
+
const finish = finishAt(o, spec.finish);
|
|
24
|
+
const place = (lane, v0, p) => ({ lane, v0, x: p.x, y: p.y, heading: p.heading });
|
|
25
|
+
if (spec.start === "middle") {
|
|
26
|
+
const p = pose(o, 0, o.straight / 2);
|
|
27
|
+
return { race, finish: spec.finish, noChange: false, starts: [place("both", o.straight / 2, { ...p, y: o.d })] };
|
|
28
|
+
}
|
|
29
|
+
if (spec.start === "both-straights") {
|
|
30
|
+
const bottom = pose(o, 0, o.straight / 2);
|
|
31
|
+
const top = pose(o, 0, o.straight + Math.PI * o.d + o.crossing / 2);
|
|
32
|
+
return { race, finish: spec.finish, noChange: false, starts: [place(0, o.straight / 2, bottom), place(0, 0, { ...top, y: -o.d })] };
|
|
33
|
+
}
|
|
34
|
+
const starts = [0, 1].map((lane) => {
|
|
35
|
+
const s = startOf(o, lane, spec.distance, finish, spec.noChange);
|
|
36
|
+
return place(lane, s.v0, s.pose);
|
|
37
|
+
});
|
|
38
|
+
// A line across both lanes where the two lanes start at the same place (the 500 m, the 10,000 m).
|
|
39
|
+
const same = Math.abs(starts[0].x - starts[1].x) < 1e-6 && Math.abs(starts[0].y * starts[1].y) > 0 && Math.sign(starts[0].y) === Math.sign(starts[1].y);
|
|
40
|
+
return { race, finish: spec.finish, noChange: spec.noChange, starts: same ? [{ ...starts[0], lane: "both" }] : starts };
|
|
41
|
+
}
|
|
42
|
+
/** The y-range of a lane's band on the top or the bottom side at x, from the edges' radii: on a straight the band is the radii themselves; past the end of a curve's start, the circle's. */
|
|
43
|
+
function bandAt(f, x, rA, rB, side) {
|
|
44
|
+
const out = Math.abs(x) - f / 2;
|
|
45
|
+
const y = (r) => (out <= 0 ? r : Math.sqrt(Math.max(0, r * r - out * out)));
|
|
46
|
+
return [side * y(rA), side * y(rB)];
|
|
47
|
+
}
|
|
48
|
+
export const LONG_DEFAULTS = { warmUp: true, infield: "floor", blocks: true, coaches: true, padding: true };
|
|
49
|
+
/** The ice's outer radius: the outer edge of the outer lane and the 3 m the Rules keep clear (Rule 228.2). */
|
|
50
|
+
export const iceRadius = (o, fig = ISU_LONG_TRACK) => outerEdge(o) + fig.padding.clear;
|
|
51
|
+
/** The parts of the long track. */
|
|
52
|
+
export function longParts(o, opt) {
|
|
53
|
+
const { fig } = opt;
|
|
54
|
+
const f = o.straight;
|
|
55
|
+
const edges = laneEdges(o, opt.warmUp ? fig.warmUpLane : 0);
|
|
56
|
+
const R = iceRadius(o, fig);
|
|
57
|
+
const parts = emptyParts();
|
|
58
|
+
const L = fig.line;
|
|
59
|
+
// The ice, the infield and the padding: the ice out to the padding, the padding a band 0.3 m thick at its edge, and what lies inside the warm-up lane.
|
|
60
|
+
parts.areas.push({ id: "ice", feature: "ice", cls: "ky-ska-ice", d: stadiumD(f, R) });
|
|
61
|
+
if (opt.padding)
|
|
62
|
+
parts.areas.push({ id: "padding", feature: "padding", cls: "ky-ska-pad", d: `${stadiumD(f, R + fig.padding.thick)}${stadiumD(f, R)}`, evenodd: true });
|
|
63
|
+
if (opt.warmUp)
|
|
64
|
+
parts.areas.push({ id: "warm-up", feature: "warm-up", cls: "ky-ska-warm", d: `${stadiumD(f, edges.inner)}${stadiumD(f, edges.warmUp)}`, evenodd: true });
|
|
65
|
+
parts.areas.push({ id: "infield", feature: "infield", cls: opt.infield === "ice" ? "ky-ska-ice" : "ky-ska-floor", d: stadiumD(f, edges.warmUp) });
|
|
66
|
+
// The lanes' lines (Rule 226): fully painted, 5 cm wide, in the straights and in the curves.
|
|
67
|
+
const radii = [...(opt.warmUp ? [edges.warmUp] : []), edges.inner, edges.between, edges.outer];
|
|
68
|
+
radii.forEach((r, i) => parts.shapes.push({ kind: "path", id: `lane-line-${i}`, feature: "lane-lines", d: stadiumD(f, r), w: L, cls: "ky-ska-b" }));
|
|
69
|
+
// The blocks (Rule 226): inside the painted lines of the inner lane's edge and of the line between the lanes, 50 cm apart for the first 15 m of a curve and 2 m after.
|
|
70
|
+
if (opt.blocks) {
|
|
71
|
+
for (const [name, r] of [["inner", edges.inner], ["between", edges.between]]) {
|
|
72
|
+
const rb = r - L - fig.blocks.height;
|
|
73
|
+
for (const side of ["right", "left"])
|
|
74
|
+
parts.shapes.push(...blocksOf(`blocks-${name}-${side}`, f, side, rb, fig.blocks.near, fig.blocks.nearLength, fig.blocks.far, 0.12));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// The coaches' area (Rule 227.3): a line 2 cm wide, 1 m from the outside edge of the track, from 25 m after the end of the curve to 10 m before the next, on the crossing straight.
|
|
78
|
+
if (opt.coaches) {
|
|
79
|
+
const y = -(edges.outer + fig.coaches.offset);
|
|
80
|
+
parts.shapes.push(line("coaches-area", "coaches-area", f / 2 - fig.coaches.after, y, -f / 2 + fig.coaches.before, y, { w: 0.02, cls: "ky-ska-r" }));
|
|
81
|
+
}
|
|
82
|
+
// The finishing lines and the last 5 m before them, marked by the metre (Rule 227.2).
|
|
83
|
+
const marks = opt.races.map((r) => markRace(o, r));
|
|
84
|
+
const finishes = new Set(["400m"]);
|
|
85
|
+
for (const m of marks)
|
|
86
|
+
finishes.add(m.finish);
|
|
87
|
+
const bottom = bandAt(f, 0, edges.inner, edges.outer, 1);
|
|
88
|
+
for (const which of finishes) {
|
|
89
|
+
const x = -f / 2 + finishAt(o, which);
|
|
90
|
+
parts.shapes.push(line(`finish-${which}`, "finish-line", x, bottom[0], x, bottom[1], { w: L, cls: "ky-ska-r" }));
|
|
91
|
+
for (let m = 1; m <= fig.finishMetres; m += 1)
|
|
92
|
+
parts.shapes.push(line(`finish-${which}-metre-${m}`, "finish-marks", x - m, bottom[0], x - m, bottom[1], { w: 0.02, cls: "ky-ska-b" }));
|
|
93
|
+
}
|
|
94
|
+
// The starting lines, each 2 m after a pre-start line (Rule 227.2 and 253.3): across the lane, or across both lanes.
|
|
95
|
+
for (const m of marks) {
|
|
96
|
+
for (const s of m.starts) {
|
|
97
|
+
const side = s.y >= 0 ? 1 : -1;
|
|
98
|
+
const lanes = s.lane === "both" ? [edges.inner, edges.outer] : s.lane === 0 ? [edges.inner, edges.between] : [edges.between, edges.outer];
|
|
99
|
+
const [y0, y1] = bandAt(f, s.x, lanes[0], lanes[1], side);
|
|
100
|
+
parts.shapes.push(line(`start-${m.race}-${String(s.lane)}`, "start-lines", s.x, y0, s.x, y1, { w: L, cls: "ky-ska-r" }));
|
|
101
|
+
// Behind the line: to the left on the finishing straight, to the right on the crossing straight.
|
|
102
|
+
const px = s.x + (side === 1 ? -fig.preStart : fig.preStart);
|
|
103
|
+
const [q0, q1] = bandAt(f, px, lanes[0], lanes[1], side);
|
|
104
|
+
parts.shapes.push(line(`pre-start-${m.race}-${String(s.lane)}`, "pre-start-lines", px, q0, px, q1, { w: 0.02, cls: "ky-ska-b" }));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
void poly;
|
|
108
|
+
const out = R + fig.padding.thick;
|
|
109
|
+
return { parts, marks, box: { x0: -f / 2 - out, y0: -out, x1: f / 2 + out, y1: out } };
|
|
110
|
+
}
|
|
111
|
+
export { addParts, laneRadius, LONG_TRACK_RACES };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { path, line, poly } from "../fields.kit.ts";
|
|
2
|
+
import type { Area, Shape } from "../fields.types.ts";
|
|
3
|
+
/** What a part of a plan adds. */
|
|
4
|
+
export interface Parts {
|
|
5
|
+
areas: Area[];
|
|
6
|
+
shapes: Shape[];
|
|
7
|
+
}
|
|
8
|
+
export declare const emptyParts: () => Parts;
|
|
9
|
+
/** Add `more` to `into`. */
|
|
10
|
+
export declare function addParts(into: Parts, more: Parts): void;
|
|
11
|
+
/** A box in metres. */
|
|
12
|
+
export interface MBox {
|
|
13
|
+
x0: number;
|
|
14
|
+
y0: number;
|
|
15
|
+
x1: number;
|
|
16
|
+
y1: number;
|
|
17
|
+
}
|
|
18
|
+
/** A named part of the plan to crop to, in metres: one box, or one at each end (`left` and `right` as the rink is drawn wide). */
|
|
19
|
+
export type MRange = MBox | {
|
|
20
|
+
left: MBox;
|
|
21
|
+
right: MBox;
|
|
22
|
+
};
|
|
23
|
+
/** A part of the plan to label (`landmarks`): where it is, in metres, and the side of the drawing its label stands on. */
|
|
24
|
+
export interface MMark {
|
|
25
|
+
id: string;
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
side: "top" | "bottom";
|
|
29
|
+
}
|
|
30
|
+
export declare const grow: (b: MBox, m: number) => MBox;
|
|
31
|
+
export declare const union: (boxes: readonly MBox[]) => MBox;
|
|
32
|
+
/** The path of a stadium: two semicircles of radius `r` joined by straights of length `s`, about the origin, drawn from the top left, clockwise as the eye sees it. */
|
|
33
|
+
export declare const stadiumD: (s: number, r: number) => string;
|
|
34
|
+
/** A point of a curve of the oval: `side` is the right-hand curve (centre at x = s/2, entered at the bottom) or the left-hand one (centre at x = -s/2, entered at the top); `t` the angle from the entrance. */
|
|
35
|
+
export declare function curvePoint(s: number, side: "right" | "left", r: number, t: number): [number, number];
|
|
36
|
+
/** The path of an arc of a curve of the oval from the angle `t0` to `t1`, in the way the skaters go (counter-clockwise as the eye sees it). */
|
|
37
|
+
export declare function curveD(s: number, side: "right" | "left", r: number, t0: number, t1: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* The blocks of a curve as dots: a dashed path with a zero dash and round caps, so that a dot of the line's width stands every `near` metres for the first `nearLength` metres from the entrance and
|
|
40
|
+
* every `far` metres after, the cone at the entrance a dot of its own (Rule 226).
|
|
41
|
+
*/
|
|
42
|
+
export declare function blocksOf(id: string, s: number, side: "right" | "left", r: number, near: number, nearLength: number, far: number, width: number): Shape[];
|
|
43
|
+
export { line, poly, path };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// What a part of a skating plan adds, as shapes and areas in painter's order, in metres, and the small functions the parts are made of. Nothing here draws SVG.
|
|
2
|
+
import { path, line, poly } from "../fields.kit.js";
|
|
3
|
+
import { num } from "../format.js";
|
|
4
|
+
export const emptyParts = () => ({ areas: [], shapes: [] });
|
|
5
|
+
/** Add `more` to `into`. */
|
|
6
|
+
export function addParts(into, more) {
|
|
7
|
+
into.areas.push(...more.areas);
|
|
8
|
+
into.shapes.push(...more.shapes);
|
|
9
|
+
}
|
|
10
|
+
export const grow = (b, m) => ({ x0: b.x0 - m, y0: b.y0 - m, x1: b.x1 + m, y1: b.y1 + m });
|
|
11
|
+
export const union = (boxes) => ({ x0: Math.min(...boxes.map((b) => b.x0)), y0: Math.min(...boxes.map((b) => b.y0)), x1: Math.max(...boxes.map((b) => b.x1)), y1: Math.max(...boxes.map((b) => b.y1)) });
|
|
12
|
+
/** The path of a stadium: two semicircles of radius `r` joined by straights of length `s`, about the origin, drawn from the top left, clockwise as the eye sees it. */
|
|
13
|
+
export const stadiumD = (s, r) => `M${num(-s / 2)} ${num(-r)}H${num(s / 2)}A${num(r)} ${num(r)} 0 0 1 ${num(s / 2)} ${num(r)}H${num(-s / 2)}A${num(r)} ${num(r)} 0 0 1 ${num(-s / 2)} ${num(-r)}Z`;
|
|
14
|
+
/** A point of a curve of the oval: `side` is the right-hand curve (centre at x = s/2, entered at the bottom) or the left-hand one (centre at x = -s/2, entered at the top); `t` the angle from the entrance. */
|
|
15
|
+
export function curvePoint(s, side, r, t) {
|
|
16
|
+
return side === "right" ? [s / 2 + r * Math.sin(t), r * Math.cos(t)] : [-s / 2 - r * Math.sin(t), -r * Math.cos(t)];
|
|
17
|
+
}
|
|
18
|
+
/** The path of an arc of a curve of the oval from the angle `t0` to `t1`, in the way the skaters go (counter-clockwise as the eye sees it). */
|
|
19
|
+
export function curveD(s, side, r, t0, t1) {
|
|
20
|
+
const [x0, y0] = curvePoint(s, side, r, t0);
|
|
21
|
+
const [x1, y1] = curvePoint(s, side, r, t1);
|
|
22
|
+
return `M${num(x0)} ${num(y0)}A${num(r)} ${num(r)} 0 ${t1 - t0 > Math.PI ? 1 : 0} 0 ${num(x1)} ${num(y1)}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The blocks of a curve as dots: a dashed path with a zero dash and round caps, so that a dot of the line's width stands every `near` metres for the first `nearLength` metres from the entrance and
|
|
26
|
+
* every `far` metres after, the cone at the entrance a dot of its own (Rule 226).
|
|
27
|
+
*/
|
|
28
|
+
export function blocksOf(id, s, side, r, near, nearLength, far, width) {
|
|
29
|
+
const out = [];
|
|
30
|
+
const tNear = Math.min(Math.PI, nearLength / r);
|
|
31
|
+
out.push(path(`${id}-near`, "blocks", curveD(s, side, r, 0, tNear), { w: width, dash: [0, near], cls: "ky-ska-blocks", least: 0.4 }));
|
|
32
|
+
// The far part starts a step after the near one stops, so that no block stands twice.
|
|
33
|
+
const start = tNear + far / r;
|
|
34
|
+
if (start < Math.PI + 1e-9)
|
|
35
|
+
out.push(path(`${id}-far`, "blocks", curveD(s, side, r, start, Math.PI), { w: width, dash: [0, far], cls: "ky-ska-blocks", least: 0.4 }));
|
|
36
|
+
const [cx, cy] = curvePoint(s, side, r, 0);
|
|
37
|
+
out.push({ kind: "dot", id: `${id}-cone`, feature: "cones", cx, cy, r: width * 1.3, paint: "cone" });
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
export { line, poly, path };
|