import type { CurlingOptions, CurlingStone } from "./charts.types.ts"; /** A team of a game, as the stones and the board name them. */ export type CurlingSide = "home" | "away"; /** The free guards of a layout: how many each team has, and which stones they are. */ export interface CurlingGuards { home: number; away: number; /** The places in the list given of the stones that are free guards. */ indices: number[]; /** A team that is not `home` or `away` is counted under its name. */ others: Record; } /** * The free guards of a layout. A free guard is a stone in play that is between the tee line and the hog line and is not in the house (the free guard zone, World Curling R6 and * Curling Canada Rule 12): its centre is nearer the hog line than the tee line, and no part of it is within the 12-foot circle. Stones out of play are not counted. Places are feet * from the button (in `units`), as `curlingScore` takes them. * @example * ```ts * import { curlingFreeGuards } from "@johnmorrisdotca/kyogijo-stadiums/charts"; * const guards = curlingFreeGuards([ * { x: 0.2, y: -11, team: "home" }, * { x: -2, y: -16, team: "away" }, * { x: 0.5, y: 0.5, team: "home" }, * { x: 3, y: -19, team: "away" }, * ]); * console.log(guards.home, guards.away, guards.indices); * // 1 2 [ 0, 1, 3 ] * ``` */ export declare function curlingFreeGuards(stones: readonly CurlingStone[], options?: { units?: CurlingOptions["units"]; width?: number; }): CurlingGuards; /** The stones in play in each layout of an end, and the most there were. */ export interface CurlingInPlay { /** Stones in play in each layout, in the order given (after each throw). */ counts: number[]; /** The most stones in play at any time of the end, and the first layout that had that many. */ max: number; at: number; } /** * The stones in play after each throw of an end, from the layout after each throw (`curlingScore`'s rules: a stone that touches a sideline, does not come to rest beyond the hog * line or has wholly crossed the back line is out). The most stones in play is the measure of how crowded an end got. * @example * ```ts * import { curlingInPlay } from "@johnmorrisdotca/kyogijo-stadiums/charts"; * const end = curlingInPlay([ * [{ x: 0, y: -12, team: "home" }], * [{ x: 0, y: -12, team: "home" }, { x: 1, y: 0, team: "away" }], * [{ x: 1, y: 0, team: "away" }], * ]); * console.log(end.counts, end.max, end.at); * // [ 1, 2, 1 ] 2 1 * ``` */ export declare function curlingInPlay(layouts: readonly (readonly CurlingStone[])[], options?: { units?: CurlingOptions["units"]; width?: number; }): CurlingInPlay; /** What a team scored in an end: one team scores in an end and the other has nothing, or neither does (a blank end). `hammer` is the team that threw last, when the record says. */ export interface CurlingEndScore { home: number; away: number; hammer?: CurlingSide; } /** One end, worked out. */ export interface CurlingEndRecord { /** The number of the end, from 1. */ end: number; home: number; away: number; /** The team with the hammer, and the team that scored (`null` for a blank end). */ hammer: CurlingSide | null; scoring: CurlingSide | null; points: number; blank: boolean; /** The team without the hammer scored. */ steal: boolean; /** The team with the hammer scored exactly one point (it was forced to take one). */ forced: boolean; /** The score after the end. */ homeTotal: number; awayTotal: number; } /** How a team did in the ends it had the hammer. */ export interface CurlingHammerStats { /** Ends with the hammer. */ ends: number; /** Ends in which the team scored, and its points in them. */ scored: number; points: number; /** Ends it blanked (to keep the hammer), ends it was stolen from, ends it scored exactly one, two or more, and three or more. */ blanked: number; stolen: number; forced: number; multi: number; big: number; /** Points per end with the hammer, the measure of hammer efficiency. */ efficiency: number | null; /** Percent of its hammer ends in which it scored, blanked, was held to one, and was stolen from. */ scoringRate: number | null; blankRate: number | null; forceRate: number | null; stolenRate: number | null; } /** How a team did in the ends it had no hammer. */ export interface CurlingStealStats { ends: number; /** Ends in which it scored (stole), and its points in them. */ steals: number; points: number; /** Percent of its ends without the hammer in which it stole. */ stealRate: number | null; } /** One team's ends. */ export interface CurlingTeamEnds { points: number; /** Ends in which the team scored. */ endsScored: number; withHammer: CurlingHammerStats; withoutHammer: CurlingStealStats; } /** What `curlingEndStats` gives back. */ export interface CurlingEndStats { ends: number; /** Ends in which neither team scored. */ blanks: number; records: CurlingEndRecord[]; home: CurlingTeamEnds; away: CurlingTeamEnds; } /** * The team with the hammer in each end. The team that scored in an end gives up the hammer: the other team has it in the next end. After a blank end the same team keeps it. An end * that names its own `hammer` is taken as it says, and the rule goes on from there. `first` is the team with the hammer in the first end. * @example * ```ts * import { curlingHammers } from "@johnmorrisdotca/kyogijo-stadiums/charts"; * console.log(curlingHammers([{ home: 2, away: 0 }, { home: 0, away: 0 }, { home: 0, away: 1 }, { home: 1, away: 0 }], "home")); * // [ 'home', 'away', 'away', 'home' ] * ``` */ export declare function curlingHammers(ends: readonly CurlingEndScore[], first: CurlingSide): CurlingSide[]; /** * The ends of a game: for each end who scored and how many, who had the hammer, whether it was a blank end, a steal or a forced one; and for each team its hammer efficiency (points * per end with the hammer), how often it scored, blanked, was held to one or was stolen from with the hammer, and how often it stole without it. Each end is what each team scored in it * (one of them nothing: an end is scored by one team, so two scores in an end are an error), as a score board's `ends` give it; `hammer` is the team that threw last in the first end, or * each end names its own. * @throws TypeError when `ends` is not a list; RangeError when an end is not a whole number of points for each team, or both teams scored in it. * @example * ```ts * import { curlingEndStats } from "@johnmorrisdotca/kyogijo-stadiums/charts"; * const game = curlingEndStats( * [{ home: 2, away: 0 }, { home: 0, away: 1 }, { home: 0, away: 1 }, { home: 1, away: 0 }, { home: 0, away: 2 }], * { hammer: "home" }, * ); * console.log(game.home.points, game.away.points, game.home.withHammer.efficiency, game.away.withoutHammer.steals); * // 3 4 1 1 * ``` */ export declare function curlingEndStats(ends: readonly CurlingEndScore[], options?: { hammer?: CurlingSide; }): CurlingEndStats; /** One end for the free guard analysis: the free guards each team had after the first four stones of the end (or whenever the caller counts them), and the points each scored. */ export interface CurlingGuardEnd { hammerGuards: number; otherGuards: number; hammerPoints: number; otherPoints: number; } /** What `curlingGuardScores` gives back. */ export interface CurlingGuardScores { /** The names of the bands of guards: `0`, `1-2` and `3+` by default. */ bands: string[]; /** For each band of guards the hammer team had: the ends, and the average points the hammer team scored in them. */ hammer: { band: string; ends: number; average: number | null; }[]; /** The same for the guards the team without the hammer had and the points it scored. */ other: { band: string; ends: number; average: number | null; }[]; /** Every pair of bands, the hammer team's first: the ends, and the average points each team scored. */ cells: { hammer: string; other: string; ends: number; hammerAverage: number | null; otherAverage: number | null; }[]; } /** * How many points each team scored against how many free guards each had out: the free guard analysis (does the team with the hammer score more when it has put guards out, does the * team without it score more when it has?). The guards are put in bands, `0`, `1-2` and `3+` by default (`bands` are the lowest number of each band). `blanks: false` leaves the * blank ends out of the hammer team's averages, as a team that blanks an end has chosen to score nothing. * @throws TypeError when `ends` is not a list; RangeError for a number of guards or points that is not a whole number, or bands that do not rise from 0. * @example * ```ts * import { curlingGuardScores } from "@johnmorrisdotca/kyogijo-stadiums/charts"; * const table = curlingGuardScores([ * { hammerGuards: 0, otherGuards: 1, hammerPoints: 1, otherPoints: 0 }, * { hammerGuards: 2, otherGuards: 0, hammerPoints: 2, otherPoints: 0 }, * { hammerGuards: 1, otherGuards: 2, hammerPoints: 0, otherPoints: 1 }, * ]); * console.log(table.bands.join(" "), table.hammer.map((b) => b.average ?? "-").join(" ")); * // 0 1-2 3+ 1 1 - * ``` */ export declare function curlingGuardScores(ends: readonly CurlingGuardEnd[], options?: { bands?: readonly number[]; blanks?: boolean; }): CurlingGuardScores; /** One shot, as a shot-by-shot summary gives it: who threw it, what kind it was, and how it was scored from 0 to 4 (4 is the perfect shot). `hit` says whether it was a hit, when the type does not. */ export interface CurlingShot { team?: CurlingSide | (string & {}); /** The kind of shot, as the summary names it: `draw`, `take-out`, `guard`, `clearing`, `hit and roll`... */ type?: string; /** How well it was played, 0 to 4 (World Curling's scale). A shot without a score is counted in the types and left out of the percentage. */ score?: number | null; hit?: boolean; } /** The shot types that are hits: a stone thrown to take out another. Any other type is not a hit, unless the shot says `hit`. */ export declare const CURLING_HIT_TYPES: readonly string[]; /** True for a shot that is a hit: it says so, or its type is one of `CURLING_HIT_TYPES`. */ export declare function curlingIsHit(shot: CurlingShot): boolean; /** A group of shots: how many, how many were scored, and the shot percentage. */ export interface CurlingShotLine { shots: number; /** Shots with a score, and the points they earned out of 4 each. */ scored: number; points: number; /** The shot percentage: the points as a percent of the most they could earn, 4 for each scored shot. */ percent: number | null; hits: number; /** Percent of the shots that were hits. */ hitRate: number | null; } /** What `curlingShotStats` gives back. */ export interface CurlingShotStats extends CurlingShotLine { /** For each team (as the shots name it). */ teams: Record; /** For each type of shot, most played first: the line, and its share of all the shots as a percent. */ types: (CurlingShotLine & { type: string; share: number | null; })[]; /** Shots that were not scored, and shots with no place in `teams` or `types` because they name none. */ unscored: number; } /** * The shots of a game, by team and by type: the shot percentage (the points earned, 0 to 4 for each shot, as a percent of 4 for each scored shot, the way World Curling's shot-by-shot * summaries and broadcasts count it), the hit rate, and how often each type of shot was played. A score outside 0 to 4 is an error. * @throws TypeError when `shots` is not a list; RangeError for a score that is not between 0 and 4. * @example * ```ts * import { curlingShotStats } from "@johnmorrisdotca/kyogijo-stadiums/charts"; * const shots = curlingShotStats([ * { team: "home", type: "draw", score: 4 }, * { team: "home", type: "take-out", score: 3 }, * { team: "away", type: "Hit and Roll", score: 2 }, * { team: "away", type: "guard", score: 4 }, * ]); * console.log(shots.percent, shots.teams.home.percent, shots.hitRate, shots.types[0].type); * // 81.3 87.5 50 draw * ``` */ export declare function curlingShotStats(shots: readonly CurlingShot[]): CurlingShotStats;