mirror of
https://github.com/jcwimer/wrestlingApp
synced 2026-04-11 16:01:56 +00:00
345 lines
12 KiB
JavaScript
345 lines
12 KiB
JavaScript
/*
|
|
State page config contract
|
|
==========================
|
|
|
|
The state page responds to these top-level config objects:
|
|
|
|
1. `wrestler_actions`
|
|
Drives the wrestler-side UI from top to bottom inside each wrestler panel.
|
|
The controller renders these sections in order, so the order in this object
|
|
controls the visual order underneath each wrestler's name, school, and score.
|
|
Supported sections:
|
|
- `match_actions`
|
|
- `timers`
|
|
- `extra_actions`
|
|
|
|
Each section may define:
|
|
- `title`
|
|
- `description`
|
|
- `items`
|
|
|
|
How the state page uses it:
|
|
- `match_actions.items`
|
|
Each item is either:
|
|
- a literal action key, or
|
|
- a special alias such as `global` or `position`
|
|
The state page expands those aliases into the currently legal actions for
|
|
that wrestler and renders them as buttons.
|
|
- `timers.items`
|
|
Each item is a timer key. The state page renders the timer display plus
|
|
start/stop/reset buttons for each listed timer.
|
|
- `extra_actions.items`
|
|
Each item is a literal action key rendered as an always-visible button
|
|
underneath the timer section.
|
|
|
|
2. `actionsByKey`
|
|
Canonical definitions for match actions and history actions.
|
|
This is the source of truth for how a button behaves and how an action
|
|
should appear in the event log.
|
|
Each action may define:
|
|
- `label`
|
|
- `availability`
|
|
- `statCode`
|
|
- `effect`
|
|
- `progression`
|
|
|
|
How the state page uses it:
|
|
- `label`
|
|
Used for button text and event log text.
|
|
- `availability`
|
|
Used when `wrestler_actions.match_actions.items` includes aliases like
|
|
`global` or `position`.
|
|
- `effect`
|
|
Used by the rules engine to update score and match position when replaying
|
|
the event list.
|
|
- `statCode`
|
|
Used when rewriting the hidden `w1_stat` / `w2_stat` fields from the
|
|
structured event log for websocket sync and match submission.
|
|
- `progression`
|
|
Used for progressive actions like stalling, caution, and penalty to decide
|
|
if the opponent should automatically receive a linked point-scoring event.
|
|
|
|
Supported `availability` values used by the wrestler-side UI:
|
|
- `global`
|
|
- `neutral`
|
|
- `top`
|
|
- `bottom`
|
|
- `extra`
|
|
|
|
3. `timers`
|
|
Canonical timer definitions keyed by timer name.
|
|
This controls both the timer controls in the wrestler panel and how timer
|
|
usage is labeled in the event log.
|
|
|
|
How the state page uses it:
|
|
- `label`
|
|
Displayed next to the running timer value in the wrestler panel.
|
|
- `maxSeconds`
|
|
Used to initialize, reset, clamp, and render the timer.
|
|
- `historyLabel`
|
|
Used when a timer stop event is recorded in history.
|
|
- `statCode`
|
|
Used when rewriting the hidden `w1_stat` / `w2_stat` fields for timer-used
|
|
events.
|
|
|
|
4. `phases`
|
|
Defines the period / choice sequence for this wrestling style.
|
|
The active phase drives:
|
|
- the main match clock
|
|
- phase labels
|
|
- start-of-period position behavior
|
|
- choice button behavior
|
|
- event grouping in the history list
|
|
|
|
How the state page uses it:
|
|
- chooses which phase sequence to use from bracket position
|
|
- builds the main match clock state for timed phases
|
|
- determines whether the current phase is a period or a choice phase
|
|
- determines how a period starts (`neutral` or from a prior choice)
|
|
*/
|
|
|
|
const RULESETS = {
|
|
folkstyle_usa: {
|
|
id: "folkstyle_usa",
|
|
|
|
wrestler_actions: {
|
|
match_actions: {
|
|
title: "Match Actions",
|
|
description: "Scoring and match-state actions available based on current position.",
|
|
items: ["global", "position"]
|
|
},
|
|
timers: {
|
|
title: "Wrestler Timers",
|
|
description: "Track blood, injury, recovery, and head/neck time for this wrestler.",
|
|
items: ["blood", "injury", "recovery", "head_neck"]
|
|
},
|
|
extra_actions: {
|
|
title: "Extra Actions",
|
|
description: "Force the match into a specific position and record it in history.",
|
|
items: ["position_neutral", "position_top", "position_bottom"]
|
|
}
|
|
},
|
|
|
|
actionsByKey: {
|
|
stalling: {
|
|
label: "Stalling",
|
|
availability: "global",
|
|
statCode: "S",
|
|
effect: { points: 0 },
|
|
progression: [0, 1, 1, 2]
|
|
},
|
|
caution: {
|
|
label: "Caution",
|
|
availability: "global",
|
|
statCode: "C",
|
|
effect: { points: 0 },
|
|
progression: [0, 0, 1]
|
|
},
|
|
penalty: {
|
|
label: "Penalty",
|
|
availability: "global",
|
|
statCode: "P",
|
|
effect: { points: 0 },
|
|
progression: [1, 1, 2]
|
|
},
|
|
minus_1: {
|
|
label: "-1 Point",
|
|
availability: "global",
|
|
statCode: "-1",
|
|
effect: { points: -1 }
|
|
},
|
|
plus_1: {
|
|
label: "+1 Point",
|
|
availability: "global",
|
|
statCode: "+1",
|
|
effect: { points: 1 }
|
|
},
|
|
plus_2: {
|
|
label: "+2 Points",
|
|
statCode: "+2",
|
|
effect: { points: 2 }
|
|
},
|
|
takedown_3: {
|
|
label: "Takedown +3",
|
|
availability: "neutral",
|
|
statCode: "T3",
|
|
effect: { points: 3, nextPosition: "top" }
|
|
},
|
|
nearfall_2: {
|
|
label: "Nearfall +2",
|
|
availability: "top",
|
|
statCode: "N2",
|
|
effect: { points: 2 }
|
|
},
|
|
nearfall_3: {
|
|
label: "Nearfall +3",
|
|
availability: "top",
|
|
statCode: "N3",
|
|
effect: { points: 3 }
|
|
},
|
|
nearfall_4: {
|
|
label: "Nearfall +4",
|
|
availability: "top",
|
|
statCode: "N4",
|
|
effect: { points: 4 }
|
|
},
|
|
nearfall_5: {
|
|
label: "Nearfall +5",
|
|
availability: "top",
|
|
statCode: "N5",
|
|
effect: { points: 5 }
|
|
},
|
|
escape_1: {
|
|
label: "Escape +1",
|
|
availability: "bottom",
|
|
statCode: "E1",
|
|
effect: { points: 1, nextPosition: "neutral" }
|
|
},
|
|
reversal_2: {
|
|
label: "Reversal +2",
|
|
availability: "bottom",
|
|
statCode: "R2",
|
|
effect: { points: 2, nextPosition: "top" }
|
|
},
|
|
position_neutral: {
|
|
label: "Neutral",
|
|
availability: "extra",
|
|
statCode: "|Neutral|",
|
|
effect: { points: 0, nextPosition: "neutral" }
|
|
},
|
|
position_top: {
|
|
label: "Top",
|
|
availability: "extra",
|
|
statCode: "|Top|",
|
|
effect: { points: 0, nextPosition: "top" }
|
|
},
|
|
position_bottom: {
|
|
label: "Bottom",
|
|
availability: "extra",
|
|
statCode: "|Bottom|",
|
|
effect: { points: 0, nextPosition: "bottom" }
|
|
},
|
|
choice_top: {
|
|
label: "Choice: Top",
|
|
statCode: "|Chose Top|"
|
|
},
|
|
choice_bottom: {
|
|
label: "Choice: Bottom",
|
|
statCode: "|Chose Bottom|"
|
|
},
|
|
choice_neutral: {
|
|
label: "Choice: Neutral",
|
|
statCode: "|Chose Neutral|"
|
|
},
|
|
choice_defer: {
|
|
label: "Choice: Defer",
|
|
statCode: "|Deferred|"
|
|
}
|
|
},
|
|
|
|
timers: {
|
|
blood: { maxSeconds: 300, label: "Blood", historyLabel: "Blood Time Used", statCode: "Blood Time" },
|
|
injury: { maxSeconds: 90, label: "Injury", historyLabel: "Injury Time Used", statCode: "Injury Time" },
|
|
recovery: { maxSeconds: 120, label: "Recovery", historyLabel: "Recovery Time Used", statCode: "Recovery Time" },
|
|
head_neck: { maxSeconds: 300, label: "Head/Neck", historyLabel: "Head/Neck Time Used", statCode: "Head/Neck Time" }
|
|
},
|
|
|
|
phases: {
|
|
championship: {
|
|
label: "Championship Format",
|
|
sequence: [
|
|
{ key: "period_1", label: "Period 1", type: "period", startsIn: "neutral", clockSeconds: 120 },
|
|
{ key: "choice_1", label: "Choice 1", type: "choice", chooser: "either", options: ["top", "bottom", "neutral", "defer"] },
|
|
{ key: "period_2", label: "Period 2", type: "period", startsFromChoice: "choice_1", clockSeconds: 120 },
|
|
{ key: "choice_2", label: "Choice 2", type: "choice", chooser: "other", options: ["top", "bottom", "neutral"] },
|
|
{ key: "period_3", label: "Period 3", type: "period", startsFromChoice: "choice_2", clockSeconds: 120 },
|
|
{ key: "sv_1", label: "SV-1", type: "period", startsIn: "neutral", clockSeconds: 60, overtimeType: "SV-1" },
|
|
{ key: "choice_3", label: "Choice 3", type: "choice", chooser: "either", options: ["top", "bottom", "defer"] },
|
|
{ key: "tb_1a", label: "TB-1A", type: "period", startsFromChoice: "choice_3", clockSeconds: 30, overtimeType: "TB-1" },
|
|
{ key: "choice_4", label: "Choice 4", type: "choice", chooser: "other", options: ["top", "bottom"] },
|
|
{ key: "tb_1b", label: "TB-1B", type: "period", startsFromChoice: "choice_4", clockSeconds: 30, overtimeType: "TB-1" },
|
|
{ key: "choice_5", label: "Choice 5", type: "choice", chooser: "either", options: ["top", "bottom"] },
|
|
{ key: "utb", label: "UTB", type: "period", startsFromChoice: "choice_5", clockSeconds: 30, overtimeType: "UTB" }
|
|
]
|
|
},
|
|
consolation: {
|
|
label: "Consolation Format",
|
|
sequence: [
|
|
{ key: "period_1", label: "Period 1", type: "period", startsIn: "neutral", clockSeconds: 60 },
|
|
{ key: "choice_1", label: "Choice 1", type: "choice", chooser: "either", options: ["top", "bottom", "neutral", "defer"] },
|
|
{ key: "period_2", label: "Period 2", type: "period", startsFromChoice: "choice_1", clockSeconds: 120 },
|
|
{ key: "choice_2", label: "Choice 2", type: "choice", chooser: "other", options: ["top", "bottom", "neutral"] },
|
|
{ key: "period_3", label: "Period 3", type: "period", startsFromChoice: "choice_2", clockSeconds: 120 },
|
|
{ key: "sv_1", label: "SV-1", type: "period", startsIn: "neutral", clockSeconds: 60, overtimeType: "SV-1" },
|
|
{ key: "choice_3", label: "Choice 3", type: "choice", chooser: "either", options: ["top", "bottom", "defer"] },
|
|
{ key: "tb_1a", label: "TB-1A", type: "period", startsFromChoice: "choice_3", clockSeconds: 30, overtimeType: "TB-1" },
|
|
{ key: "choice_4", label: "Choice 4", type: "choice", chooser: "other", options: ["top", "bottom"] },
|
|
{ key: "tb_1b", label: "TB-1B", type: "period", startsFromChoice: "choice_4", clockSeconds: 30, overtimeType: "TB-1" },
|
|
{ key: "choice_5", label: "Choice 5", type: "choice", chooser: "either", options: ["top", "bottom"] },
|
|
{ key: "utb", label: "UTB", type: "period", startsFromChoice: "choice_5", clockSeconds: 30, overtimeType: "UTB" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function phaseStyleKeyForBracketPosition(bracketPosition) {
|
|
if (!bracketPosition) return "championship"
|
|
|
|
if (
|
|
bracketPosition.includes("Conso") ||
|
|
["3/4", "5/6", "7/8"].includes(bracketPosition)
|
|
) {
|
|
return "consolation"
|
|
}
|
|
|
|
return "championship"
|
|
}
|
|
|
|
function buildActionEffects(actionsByKey) {
|
|
return Object.fromEntries(
|
|
Object.entries(actionsByKey)
|
|
.filter(([, action]) => action.effect)
|
|
.map(([key, action]) => [key, action.effect])
|
|
)
|
|
}
|
|
|
|
function buildActionLabels(actionsByKey, timers) {
|
|
const actionLabels = Object.fromEntries(
|
|
Object.entries(actionsByKey)
|
|
.filter(([, action]) => action.label)
|
|
.map(([key, action]) => [key, action.label])
|
|
)
|
|
|
|
Object.entries(timers || {}).forEach(([timerKey, timer]) => {
|
|
if (timer.historyLabel) {
|
|
actionLabels[`timer_used_${timerKey}`] = timer.historyLabel
|
|
}
|
|
})
|
|
|
|
return actionLabels
|
|
}
|
|
|
|
function buildProgressionRules(actionsByKey) {
|
|
return Object.fromEntries(
|
|
Object.entries(actionsByKey)
|
|
.filter(([, action]) => Array.isArray(action.progression))
|
|
.map(([key, action]) => [key, action.progression])
|
|
)
|
|
}
|
|
|
|
export function getMatchStateConfig(rulesetId, bracketPosition) {
|
|
const ruleset = RULESETS[rulesetId] || RULESETS.folkstyle_usa
|
|
const phaseStyleKey = phaseStyleKeyForBracketPosition(bracketPosition)
|
|
const phaseStyle = ruleset.phases[phaseStyleKey]
|
|
|
|
return {
|
|
...ruleset,
|
|
actionEffects: buildActionEffects(ruleset.actionsByKey),
|
|
actionLabels: buildActionLabels(ruleset.actionsByKey, ruleset.timers),
|
|
progressionRules: buildProgressionRules(ruleset.actionsByKey),
|
|
matchFormat: { id: phaseStyleKey, label: phaseStyle.label },
|
|
phaseSequence: phaseStyle.sequence
|
|
}
|
|
}
|