mirror of
https://github.com/jcwimer/wrestlingApp
synced 2026-04-17 05:15:36 +00:00
New stats page, scoreboard, and live scores pages.
This commit is contained in:
344
app/assets/javascripts/lib/match_state/config.js
Normal file
344
app/assets/javascripts/lib/match_state/config.js
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user