66 */
77
88import { predictMatchSimple } from "./match-prediction" ;
9+ import { DESCRIPTORS } from "../descriptors/descriptor-list" ;
10+ import { Season } from "../Season" ;
911
1012export interface TeamRecord {
1113 teamNumber : number ;
@@ -15,6 +17,11 @@ export interface TeamRecord {
1517 rankingPoints : number ;
1618 totalPoints : number ;
1719 opr : number ;
20+ rpBonusRates ?: {
21+ movement : number ;
22+ goal : number ;
23+ pattern : number ;
24+ } ;
1825}
1926
2027export interface Match {
@@ -26,6 +33,18 @@ export interface Match {
2633 played : boolean ;
2734 redScore ?: number ;
2835 blueScore ?: number ;
36+ scores ?: {
37+ red ?: {
38+ movementRp ?: boolean ;
39+ goalRp ?: boolean ;
40+ patternRp ?: boolean ;
41+ } ;
42+ blue ?: {
43+ movementRp ?: boolean ;
44+ goalRp ?: boolean ;
45+ patternRp ?: boolean ;
46+ } ;
47+ } ;
2948}
3049
3150export interface SimulationResult {
@@ -46,13 +65,51 @@ export interface EventSimulationConfig {
4665 currentSeason ?: number ;
4766}
4867
68+ type RpType = "TotalPoints" | "Record" | "DecodeRP" ;
69+
70+ function getRpType ( currentSeason ?: number ) : RpType {
71+ if ( ! currentSeason || ! ( currentSeason in DESCRIPTORS ) ) return "Record" ;
72+ return DESCRIPTORS [ currentSeason as Season ] . rankings . rp ;
73+ }
74+
75+ function clampRate ( rate : number | undefined ) : number {
76+ if ( typeof rate !== "number" || Number . isNaN ( rate ) ) return 0 ;
77+ if ( rate < 0 ) return 0 ;
78+ if ( rate > 1 ) return 1 ;
79+ return rate ;
80+ }
81+
82+ function getAllianceBonusRate (
83+ teamNumbers : number [ ] ,
84+ teamRpRates : Map < number , TeamRecord [ "rpBonusRates" ] > ,
85+ key : "movement" | "goal" | "pattern"
86+ ) : number {
87+ if ( teamNumbers . length === 0 ) return 0 ;
88+ const rates = teamNumbers . map ( ( team ) => clampRate ( teamRpRates . get ( team ) ?. [ key ] ) ) ;
89+ const total = rates . reduce ( ( sum , value ) => sum + value , 0 ) ;
90+ return clampRate ( total / rates . length ) ;
91+ }
92+
93+ function rollBonus ( rate : number ) : number {
94+ return Math . random ( ) < rate ? 1 : 0 ;
95+ }
96+
4997/**
5098 * Simulate a single match outcome
5199 */
52100function simulateMatch (
53101 match : Match ,
54- teamOPRs : Map < number , number >
55- ) : { redScore : number ; blueScore : number ; redWins : boolean } {
102+ teamOPRs : Map < number , number > ,
103+ teamRpRates : Map < number , TeamRecord [ "rpBonusRates" ] > ,
104+ rpType : RpType
105+ ) : {
106+ redScore : number ;
107+ blueScore : number ;
108+ redWins : boolean ;
109+ tied : boolean ;
110+ redRp : number ;
111+ blueRp : number ;
112+ } {
56113 const redOPR1 = teamOPRs . get ( match . redTeam1 ) || 0 ;
57114 const redOPR2 = teamOPRs . get ( match . redTeam2 ) || 0 ;
58115 const blueOPR1 = teamOPRs . get ( match . blueTeam1 ) || 0 ;
@@ -68,30 +125,50 @@ function simulateMatch(
68125 const redScore = Math . max ( 0 , Math . round ( prediction . predictedRedScore + redNoise ) ) ;
69126 const blueScore = Math . max ( 0 , Math . round ( prediction . predictedBlueScore + blueNoise ) ) ;
70127
128+ const redWon = redScore > blueScore ;
129+ const tied = redScore === blueScore ;
130+
131+ let redRp = 0 ;
132+ let blueRp = 0 ;
133+
134+ if ( rpType === "TotalPoints" ) {
135+ redRp = redScore ;
136+ blueRp = blueScore ;
137+ } else {
138+ if ( rpType === "DecodeRP" ) {
139+ redRp = redWon ? 3 : tied ? 1 : 0 ;
140+ blueRp = ! redWon && ! tied ? 3 : tied ? 1 : 0 ;
141+
142+ const redTeams = [ match . redTeam1 , match . redTeam2 ] . filter ( ( team ) => team ) ;
143+ const blueTeams = [ match . blueTeam1 , match . blueTeam2 ] . filter ( ( team ) => team ) ;
144+
145+ const redBonus =
146+ rollBonus ( getAllianceBonusRate ( redTeams , teamRpRates , "movement" ) ) +
147+ rollBonus ( getAllianceBonusRate ( redTeams , teamRpRates , "goal" ) ) +
148+ rollBonus ( getAllianceBonusRate ( redTeams , teamRpRates , "pattern" ) ) ;
149+ const blueBonus =
150+ rollBonus ( getAllianceBonusRate ( blueTeams , teamRpRates , "movement" ) ) +
151+ rollBonus ( getAllianceBonusRate ( blueTeams , teamRpRates , "goal" ) ) +
152+ rollBonus ( getAllianceBonusRate ( blueTeams , teamRpRates , "pattern" ) ) ;
153+
154+ redRp += redBonus ;
155+ blueRp += blueBonus ;
156+ } else {
157+ redRp = redWon ? 2 : tied ? 1 : 0 ;
158+ blueRp = ! redWon && ! tied ? 2 : tied ? 1 : 0 ;
159+ }
160+ }
161+
71162 return {
72163 redScore,
73164 blueScore,
74- redWins : redScore > blueScore ,
165+ redWins : redWon ,
166+ tied,
167+ redRp,
168+ blueRp,
75169 } ;
76170}
77171
78- /**
79- * Calculate ranking points for a match (season-specific)
80- * This is a simplified version - actual RP calculation varies by season
81- */
82- function calculateRankingPoints (
83- _score : number ,
84- won : boolean ,
85- tied : boolean ,
86- _season ?: number
87- ) : number {
88- // Simplified: 2 RP for win, 1 for tie, 0 for loss
89- // Real implementation would include bonus RPs based on score thresholds
90- if ( won ) return 2 ;
91- if ( tied ) return 1 ;
92- return 0 ;
93- }
94-
95172/**
96173 * Run a single simulation iteration
97174 */
@@ -101,6 +178,12 @@ function runSingleSimulation(
101178 teamOPRs : Map < number , number > ,
102179 season ?: number
103180) : Map < number , TeamRecord > {
181+ const rpType = getRpType ( season ) ;
182+ const teamRpRates = new Map < number , TeamRecord [ "rpBonusRates" ] > ( ) ;
183+ teams . forEach ( ( team ) => {
184+ teamRpRates . set ( team . teamNumber , team . rpBonusRates ) ;
185+ } ) ;
186+
104187 // Clone team records
105188 const simTeams = new Map < number , TeamRecord > ( ) ;
106189 teams . forEach ( ( team ) => {
@@ -111,14 +194,14 @@ function runSingleSimulation(
111194 const unplayedMatches = matches . filter ( ( m ) => ! m . played ) ;
112195
113196 for ( const match of unplayedMatches ) {
114- const result = simulateMatch ( match , teamOPRs ) ;
197+ const result = simulateMatch ( match , teamOPRs , teamRpRates , rpType ) ;
115198
116199 const redTeam1 = simTeams . get ( match . redTeam1 ) ! ;
117200 const redTeam2 = simTeams . get ( match . redTeam2 ) ! ;
118201 const blueTeam1 = simTeams . get ( match . blueTeam1 ) ! ;
119202 const blueTeam2 = simTeams . get ( match . blueTeam2 ) ! ;
120203
121- const tied = result . redScore === result . blueScore ;
204+ const tied = result . tied ;
122205 const redWon = result . redWins ;
123206 const blueWon = ! redWon && ! tied ;
124207
@@ -129,7 +212,7 @@ function runSingleSimulation(
129212 else team . losses ++ ;
130213
131214 team . totalPoints += result . redScore ;
132- team . rankingPoints += calculateRankingPoints ( result . redScore , redWon , tied , season ) ;
215+ team . rankingPoints += result . redRp ;
133216 } ) ;
134217
135218 // Update records for blue alliance
@@ -139,7 +222,7 @@ function runSingleSimulation(
139222 else team . losses ++ ;
140223
141224 team . totalPoints += result . blueScore ;
142- team . rankingPoints += calculateRankingPoints ( result . blueScore , blueWon , tied , season ) ;
225+ team . rankingPoints += result . blueRp ;
143226 } ) ;
144227 }
145228
0 commit comments