1
1
import { nanoid } from "nanoid" ;
2
2
import { GameData , Song , Chart } from "./models/SongData" ;
3
- import { chunkInPieces , pickRandomItem , shuffle , times } from "./utils" ;
3
+ import { pickRandomItem , shuffle , times } from "./utils" ;
4
4
import { CountingSet } from "./utils/counting-set" ;
5
5
import { DefaultingMap } from "./utils/defaulting-set" ;
6
+ import { Fraction } from "./utils/fraction" ;
6
7
import { DrawnChart , EligibleChart , Drawing } from "./models/Drawing" ;
7
8
import { ConfigState } from "./config-state" ;
8
9
import { getDifficultyColor } from "./hooks/useDifficultyColor" ;
@@ -12,6 +13,15 @@ import {
12
13
getDiffAbbr ,
13
14
} from "./game-data-utils" ;
14
15
16
+ function clampToNearest ( incr : number , n : number , clamp : ( n : number ) => number ) {
17
+ const multor = Math . round ( 1 / incr ) ;
18
+ let ret = clamp ( n * multor ) / multor ;
19
+ if ( Number . isInteger ( n ) && clamp === Math . floor ) {
20
+ ret -= incr ;
21
+ }
22
+ return ret ;
23
+ }
24
+
15
25
export function getDrawnChart (
16
26
gameData : GameData ,
17
27
currentSong : Song ,
@@ -67,9 +77,8 @@ export function chartIsValid(
67
77
}
68
78
69
79
export function * eligibleCharts ( config : ConfigState , gameData : GameData ) {
70
- const buckets = getBuckets (
71
- config ,
72
- getAvailableLevels ( gameData , config . useGranularLevels ) ,
80
+ const buckets = Array . from (
81
+ getBuckets ( config , getAvailableLevels ( gameData , config . useGranularLevels ) ) ,
73
82
) ;
74
83
for ( const currentSong of gameData . songs ) {
75
84
if ( ! songIsValid ( config , currentSong ) ) {
@@ -105,37 +114,50 @@ export function* eligibleCharts(config: ConfigState, gameData: GameData) {
105
114
106
115
export type LevelRangeBucket = [ low : number , high : number ] ;
107
116
export type BucketLvlRanges = Array < LevelRangeBucket > ;
108
- export type LvlRanges = Array < number > | BucketLvlRanges ;
117
+ export type LvlRanges = Array < number | LevelRangeBucket > ;
109
118
110
119
/**
111
120
*
112
121
* @param cfg
113
122
* @param availableLvls prefer granular
114
123
* @returns
115
124
*/
116
- export function getBuckets (
125
+ export function * getBuckets (
117
126
cfg : Pick <
118
127
ConfigState ,
119
128
"useWeights" | "probabilityBucketCount" | "upperBound" | "lowerBound"
120
129
> ,
121
130
availableLvls : Array < number > ,
122
- ) : LvlRanges {
131
+ ) : Generator < LevelRangeBucket | number > {
123
132
const { useWeights, probabilityBucketCount, upperBound, lowerBound } = cfg ;
124
133
const absoluteRangeSize = upperBound - lowerBound + 1 ;
125
134
if ( ! useWeights || ! probabilityBucketCount ) {
126
- return times ( absoluteRangeSize , ( n ) => n - 1 + lowerBound ) ;
135
+ for ( let n = lowerBound ; n <= upperBound ; n ++ ) {
136
+ yield n ;
137
+ }
138
+ return ;
127
139
}
128
- const lowerIndex = availableLvls . indexOf ( lowerBound ) ;
140
+ const bucketWidth = new Fraction ( absoluteRangeSize , probabilityBucketCount ) ;
129
141
let upperIndex : number | undefined = availableLvls . indexOf ( upperBound + 1 ) ;
130
142
if ( upperIndex === - 1 ) {
131
143
upperIndex = undefined ;
132
144
}
133
- const levelsInRange = availableLvls . slice ( lowerIndex , upperIndex ) ;
134
- return Array . from ( chunkInPieces ( probabilityBucketCount , levelsInRange ) ) . map (
135
- ( levels ) : LevelRangeBucket => {
136
- return [ levels [ 0 ] , levels [ levels . length - 1 ] ] ;
137
- } ,
138
- ) ;
145
+ // TODO add this to the data file spec
146
+ const incrementGuess = availableLvls [ 1 ] - availableLvls [ 0 ] ;
147
+ const lowerBoundF = new Fraction ( lowerBound ) ;
148
+ const nudge = new Fraction ( 1 , 1000 ) ;
149
+ for ( let i = 0 ; i < probabilityBucketCount ; i ++ ) {
150
+ const bucketBottom = bucketWidth . mult ( new Fraction ( i ) ) . add ( lowerBoundF ) ;
151
+ const bucketTop = bucketBottom . add ( bucketWidth ) ;
152
+ yield [
153
+ clampToNearest ( incrementGuess , bucketBottom . valueOf ( ) , Math . ceil ) ,
154
+ clampToNearest (
155
+ incrementGuess ,
156
+ bucketTop . sub ( nudge ) . valueOf ( ) ,
157
+ Math . floor ,
158
+ ) ,
159
+ ] ;
160
+ }
139
161
}
140
162
141
163
/**
@@ -184,7 +206,10 @@ export function draw(gameData: GameData, configData: ConfigState): Drawing {
184
206
185
207
for ( const chart of eligibleCharts ( configData , gameData ) ) {
186
208
const bucketIdx = useWeights
187
- ? bucketIndexForLvl ( chartLevelOrTier ( chart , useGranularLevels ) , buckets )
209
+ ? bucketIndexForLvl (
210
+ chartLevelOrTier ( chart , useGranularLevels ) ,
211
+ Array . from ( buckets ) ,
212
+ )
188
213
: 0 ; // outside of weights mode we just put all songs into one shared bucket
189
214
if ( bucketIdx === null ) continue ;
190
215
validCharts . get ( bucketIdx ) . push ( chart ) ;
0 commit comments