├── .gitignore ├── .travis.yml ├── MAINTAINER.md ├── MANIFEST.in ├── README.rst ├── UNLICENSE ├── pyttanko.py ├── run_test ├── setup.cfg ├── setup.py └── test ├── __init__.py ├── download_suite ├── gentest.py ├── suite.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.json 3 | *.tar.xz 4 | *.pyc 5 | tags 6 | /dist 7 | /build 8 | /test/test_suite 9 | *.egg-info 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | - "3.4" 6 | - "3.5" 7 | - "3.5-dev" 8 | - "3.6" 9 | - "3.6-dev" 10 | - "3.7-dev" 11 | - "nightly" 12 | - "pypy" 13 | - "pypy3" 14 | - "pypy-5.3.1" 15 | 16 | cache: 17 | directories: 18 | - test/test_suite 19 | 20 | install: pip install . 21 | 22 | script: 23 | - cd test 24 | - ./download_suite 25 | - cd .. 26 | - ./run_test 27 | -------------------------------------------------------------------------------- /MAINTAINER.md: -------------------------------------------------------------------------------- 1 | requirements for making releases 2 | 3 | ``` 4 | python3 -m pip install --user --upgrade twine setuptools wheel 5 | ``` 6 | 7 | to make a new release, bump version in pyttanko.py and run 8 | 9 | ``` 10 | python3 setup.py sdist bdist_wheel 11 | twine upload dist/* 12 | ``` 13 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include UNLICENSE 2 | recursive-exclude test * 3 | exclude run_test 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/Francesco149/pyttanko.svg?branch=master 2 | :target: https://travis-ci.org/Francesco149/pyttanko 3 | 4 | osu! pp and difficulty calculator. pure python implementation of 5 | https://github.com/Francesco149/oppai-ng 6 | 7 | this is meant to be a standalone single-file module that's as 8 | portable as possible using only python 2.6+ builtins with no 9 | extra dependencies 10 | 11 | if you need a command line interface, check out 12 | `oppai-ng `_ 13 | 14 | if you need a more object oriented implementation, check out 15 | `oppadc `_ 16 | 17 | usage 18 | =========== 19 | pyttanko is a single-file module, so the simplest way to use it 20 | is to simply drop it in your project's folder: 21 | 22 | .. code-block:: sh 23 | 24 | cd my/project 25 | curl https://raw.githubusercontent.com/Francesco149/pyttanko/master/pyttanko.py > pyttanko.py 26 | 27 | this way, anyone who clones your project won't have to install 28 | pyttanko as it will be bundled 29 | 30 | if you prefer, it's also available on pip 31 | 32 | .. code-block:: sh 33 | 34 | pip install pyttanko 35 | 36 | or you can also manually install like so: 37 | 38 | .. code-block:: sh 39 | 40 | curl -L https://github.com/Francesco149/pyttanko/archive/HEAD.tar.gz -o HEAD.tar.gz 41 | cd pyttanko-* 42 | python setup.py install --user 43 | 44 | check out 45 | 46 | .. code-block:: sh 47 | 48 | pydoc pyttanko 49 | 50 | or 51 | 52 | .. code-block:: sh 53 | 54 | python -c "help('pyttanko')" 55 | 56 | for the full documentation 57 | 58 | minimal example: 59 | 60 | .. code-block:: python 61 | 62 | #!/usr/bin/env python 63 | 64 | import sys 65 | import pyttanko as osu 66 | 67 | p = osu.parser() 68 | bmap = p.map(sys.stdin) 69 | 70 | stars = osu.diff_calc().calc(bmap) 71 | print("%g stars" % stars.total) 72 | 73 | pp, _, _, _, _ = osu.ppv2(stars.aim, stars.speed, bmap=bmap) 74 | print("%g pp" % pp) 75 | 76 | 77 | which you can run with: 78 | 79 | .. code-block:: sh 80 | 81 | cat /path/to/file.osu | ./example.py 82 | 83 | 84 | performance 85 | =========== 86 | pyttanko runs the test suite over 10 times slower than the original 87 | C implementation and uses ~8 times more memory, so if you need 88 | to batch process thousands of scores, you should consider writing 89 | native bindings for the C version. 90 | 91 | tests were performed on linux 4.9.38, python 2.7.10 on a i7-4790k 92 | 93 | this is still a pretty respectable speed considering python is 94 | interpreted 95 | 96 | .. code-block:: sh 97 | 98 | $ cd ~/src/pyttanko/ 99 | $ time -v ./run_test 100 | ... 101 | Command being timed: "./run_test" 102 | User time (seconds): 101.68 103 | System time (seconds): 0.61 104 | Percent of CPU this job got: 99% 105 | Elapsed (wall clock) time (h:mm:ss or m:ss): 1m 42.34s 106 | Average shared text size (kbytes): 0 107 | Average unshared data size (kbytes): 0 108 | Average stack size (kbytes): 0 109 | Average total size (kbytes): 0 110 | Maximum resident set size (kbytes): 88688 111 | Average resident set size (kbytes): 0 112 | Major (requiring I/O) page faults: 0 113 | Minor (reclaiming a frame) page faults: 631637 114 | Voluntary context switches: 1 115 | Involuntary context switches: 4116 116 | Swaps: 0 117 | File system inputs: 0 118 | File system outputs: 56 119 | Socket messages sent: 0 120 | Socket messages received: 0 121 | Signals delivered: 0 122 | Page size (bytes): 4096 123 | Exit status: 0 124 | 125 | $ cd ~/src/oppai-ng/test 126 | $ ./build 127 | $ time -v ./oppai_test 128 | ... 129 | Command being timed: "./oppai_test" 130 | User time (seconds): 9.09 131 | System time (seconds): 0.06 132 | Percent of CPU this job got: 99% 133 | Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 9.15s 134 | Average shared text size (kbytes): 0 135 | Average unshared data size (kbytes): 0 136 | Average stack size (kbytes): 0 137 | Average total size (kbytes): 0 138 | Maximum resident set size (kbytes): 11840 139 | Average resident set size (kbytes): 0 140 | Major (requiring I/O) page faults: 0 141 | Minor (reclaiming a frame) page faults: 304 142 | Voluntary context switches: 1 143 | Involuntary context switches: 39 144 | Swaps: 0 145 | File system inputs: 0 146 | File system outputs: 0 147 | Socket messages sent: 0 148 | Socket messages received: 0 149 | Signals delivered: 0 150 | Page size (bytes): 4096 151 | Exit status: 0 152 | 153 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /pyttanko.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | osu! pp and difficulty calculator. 5 | 6 | pure python implementation of oppai-ng 7 | this is meant to be used as a completely standalone python module, 8 | more portable and easier to use than bindings. 9 | 10 | this is over 10 times slower than the C version, so if you need to 11 | calculate hundreds or thousands of scores you should consider 12 | using the C version or making python bindings for it. 13 | 14 | for comparison, the C version runs through the test suite (~12k 15 | unique scores) in ~9-10 seconds on my i7-4790k while pyttanko takes 16 | 100+ seconds 17 | 18 | if you want a command line interface, check out 19 | https://github.com/Francesco149/oppai-ng 20 | ------------------------------------------------------------------- 21 | usage: 22 | put pyttanko.py in your project's folder and import pyttanko 23 | 24 | for example usage, check out the __main__ at the bottom of the file 25 | you can run it like: 26 | 27 | cat /path/to/map.osu | ./pyttanko.py +HDDT 200x 1m 95% 28 | 29 | also, check out "pydoc pyttanko" for full documentation 30 | ------------------------------------------------------------------- 31 | this is free and unencumbered software released into the public 32 | domain. check the attached UNLICENSE or http://unlicense.org/ 33 | """ 34 | 35 | __author__ = "Franc[e]sco " 36 | __version__ = "2.1.0" 37 | 38 | import sys 39 | import math 40 | 41 | if sys.version_info[0] < 3: 42 | # hack to force utf-8 43 | reload(sys) 44 | sys.setdefaultencoding("utf-8") 45 | 46 | info = sys.stderr.write 47 | 48 | class v2f: 49 | """2D vector with float values""" 50 | def __init__(self, x=0.0, y=0.0): 51 | self.x = x 52 | self.y = y 53 | 54 | 55 | def __sub__(self, other): 56 | return v2f(self.x - other.x, self.y - other.y) 57 | 58 | def __mul__(self, other): 59 | return v2f(self.x * other, self.y * other) 60 | 61 | def len(self): 62 | return math.sqrt(self.x * self.x + self.y * self.y) 63 | 64 | def __str__(self): 65 | return str(self.__dict__) 66 | 67 | def __repr__(self): 68 | return str(self) 69 | 70 | def dot(self, other): 71 | return self.x * other.x + self.y * other.y 72 | 73 | 74 | # ------------------------------------------------------------------------- 75 | # beatmap utils 76 | 77 | MODE_STD = 0 78 | 79 | class circle: 80 | def __init__(self, pos=None): 81 | """ 82 | initializes a circle object. 83 | if pos is None, it will be set to v2f() 84 | """ 85 | if pos == None: 86 | pos = v2f() 87 | 88 | self.pos = pos 89 | 90 | 91 | def __str__(self): 92 | return str(self.__dict__) 93 | 94 | def __repr__(self): 95 | return str(self) 96 | 97 | 98 | class slider: 99 | def __init__(self, pos=None, distance=0.0, repetitions=0): 100 | """ 101 | initializes a slider object. 102 | 103 | distance: distance travelled by one repetition (float) 104 | pos: instance of v2f. if None, it will be set to v2f() 105 | """ 106 | if pos == None: 107 | pos = v2f() 108 | 109 | self.pos = pos 110 | self.distance = distance 111 | self.repetitions = repetitions 112 | 113 | 114 | def __str__(self): 115 | return str(self.__dict__) 116 | 117 | def __repr__(self): 118 | return str(self) 119 | 120 | 121 | OBJ_CIRCLE = 1<<0 122 | OBJ_SLIDER = 1<<1 123 | OBJ_SPINNER = 1<<3 124 | 125 | class hitobject: 126 | def __init__(self, time=0.0, objtype=OBJ_CIRCLE, data=None): 127 | """ 128 | initializes a new hitobject. 129 | 130 | time: start time in milliseconds (float) 131 | data: an instance of circle, slider or None 132 | """ 133 | self.time = time 134 | self.objtype = objtype 135 | self.data = data 136 | self.normpos = v2f() 137 | self.angle = 0.0 138 | self.strains = [ 0.0, 0.0 ] 139 | self.is_single = False 140 | self.delta_time = 0.0 141 | self.d_distance = 0.0 142 | 143 | 144 | def typestr(self): 145 | res = "" 146 | 147 | if self.objtype & OBJ_CIRCLE != 0: 148 | res += "circle | " 149 | if self.objtype & OBJ_SLIDER != 0: 150 | res += "slider | " 151 | if self.objtype & OBJ_SPINNER != 0: 152 | res += "spinner | " 153 | 154 | return res[0:-3] 155 | 156 | 157 | def __str__(self): 158 | return ( 159 | """hitobject(time=%g, objtype=%s, data=%s, 160 | normpos=%s, strains=%s, is_single=%s)""" % ( 161 | self.time, self.typestr(), str(self.data), 162 | str(self.normpos), str(self.strains), 163 | str(self.is_single) 164 | ) 165 | ) 166 | 167 | 168 | def __repr__(self): 169 | return str(self) 170 | 171 | 172 | class timing: 173 | def __init__(self, time=0.0, ms_per_beat=-100.0, change=False): 174 | """ 175 | initializes a timing point 176 | time: start time in milliseconds (float) 177 | ms_per_beat: float 178 | change: if False, ms_per_beat is -100.0 * bpm_multiplier 179 | """ 180 | self.time = time 181 | self.ms_per_beat = ms_per_beat 182 | self.change = change 183 | 184 | 185 | def __str__(self): 186 | return str(self.__dict__) 187 | 188 | def __repr__(self): 189 | return str(self) 190 | 191 | 192 | class beatmap: 193 | """ 194 | the bare minimum amount of data about a beatmap to perform 195 | difficulty and pp calculation 196 | 197 | fields: 198 | mode: gamemode, see MODE_* constants (integer) 199 | title title_unicode artist artist_unicode 200 | creator: mapper name 201 | version: difficulty name 202 | ncircles, nsliders, nspinners 203 | hp cs od ar (float) 204 | sv tick_rate (float) 205 | hitobjects: list (hitobject) 206 | timing_points: list (timing) 207 | """ 208 | def __init__(self): 209 | # i tried pre-allocating hitobjects and timing_points 210 | # as well as object data. 211 | # it didn't show any appreciable performance improvement 212 | self.format_version = 1 213 | self.hitobjects = [] 214 | self.timing_points = [] 215 | # these are assumed to be ordered by time low to high 216 | self.reset() 217 | 218 | 219 | def reset(self): 220 | """ 221 | resets fields to prepare the object to store a new 222 | beatmap. used internally by the parser 223 | """ 224 | self.mode = MODE_STD 225 | 226 | self.title = "" 227 | self.title_unicode = "" 228 | self.artist = "" 229 | self.artist_unicode = "" 230 | self.creator = "" 231 | self.version = "" 232 | 233 | self.ncircles = self.nsliders = self.nspinners = 0 234 | self.hp = self.cs = self.od = 5 235 | self.ar = None 236 | self.sv = self.tick_rate = 1.0 237 | 238 | self.hitobjects[:] = [] 239 | self.timing_points[:] = [] 240 | 241 | 242 | def __str__(self): 243 | s = self 244 | return """beatmap( 245 | title="%s", title_unicode="%s" 246 | artist="%s", artist_unicode="%s", 247 | creator="%s", version="%s", 248 | hitobjects=[ %s ], 249 | timing_points=[ %s ], 250 | ncircles=%d, nsliders=%d, nspinners=%d, 251 | hp=%g, cs=%g, od=%g, ar=%g, 252 | sv=%g, tick_rate=%g\n)""" % ( 253 | s.title, s.title_unicode, s.artist, s.artist_unicode, 254 | s.creator, s.version, 255 | ",\n ".join([str(x) for x in s.hitobjects]), 256 | ",\n ".join([str(x) for x in s.timing_points]), 257 | s.ncircles, s.nsliders, s.nspinners, s.hp, s.cs, s.od, 258 | s.ar, s.sv, s.tick_rate 259 | ) 260 | 261 | 262 | def __repr__(self): 263 | return str(self) 264 | 265 | def max_combo(self): 266 | res = 0 267 | 268 | points = self.timing_points 269 | tindex = -1 270 | tnext = -float("inf") 271 | 272 | px_per_beat = None 273 | 274 | for obj in self.hitobjects: 275 | if obj.objtype & OBJ_SLIDER == 0: 276 | res += 1 277 | continue 278 | 279 | 280 | # keep track of the current timing point without 281 | # looping through all of the timing points for every 282 | # object 283 | while tnext != None and obj.time >= tnext: 284 | tindex += 1 285 | if len(points) > tindex + 1: 286 | tnext = points[tindex + 1].time 287 | else: 288 | tnext = None 289 | 290 | t = points[tindex] 291 | sv_multiplier = 1.0 292 | 293 | if not t.change and t.ms_per_beat < 0: 294 | sv_multiplier = (-100.0 / t.ms_per_beat) 295 | 296 | px_per_beat = self.sv * 100.0 * sv_multiplier 297 | if self.format_version < 8: 298 | px_per_beat /= sv_multiplier 299 | 300 | 301 | # slider ticks 302 | sl = obj.data 303 | 304 | num_beats = ( 305 | (sl.distance * sl.repetitions) / px_per_beat 306 | ) 307 | 308 | ticks = int( 309 | math.ceil( 310 | (num_beats - 0.1) / 311 | sl.repetitions * self.tick_rate 312 | ) 313 | ) 314 | 315 | ticks -= 1 316 | ticks *= sl.repetitions 317 | ticks += sl.repetitions + 1 318 | 319 | res += max(0, ticks) 320 | 321 | 322 | return res 323 | 324 | 325 | 326 | # ------------------------------------------------------------------------- 327 | # beatmap parser 328 | 329 | class parser: 330 | """ 331 | beatmap parser. 332 | 333 | fields: 334 | lastline lastpos: last line and token touched (strings) 335 | nline: last line number touched 336 | done: True if the parsing completed successfully 337 | """ 338 | def __init__(self): 339 | self.lastline = "" 340 | self.lastpos = "" 341 | self.nline = 0 342 | self.done = False 343 | 344 | 345 | def __str__(self): 346 | """formats parser status if the parsing failed""" 347 | if self.done: 348 | return "parsing successful" 349 | 350 | return ( 351 | "in line %d\n%s\n> %s\n" % ( 352 | self.nline, self.lastline, self.lastpos 353 | ) 354 | ) 355 | 356 | 357 | def __repr__(self): 358 | return str(self) 359 | 360 | def setlastpos(self, v): 361 | # sets lastpos to v and returns v 362 | # should be used to access any string that can make the 363 | # parser fail 364 | self.lastpos = v 365 | return v 366 | 367 | 368 | def property(self, line): 369 | # parses PropertyName:Value into a tuple 370 | s = line.split(":") 371 | if len(s) < 2: 372 | raise SyntaxError( 373 | "property must be a pair of ':'-separated values" 374 | ) 375 | return (s[0], "".join(s[1:])) 376 | 377 | 378 | def metadata(self, b, line): 379 | p = self.property(line) 380 | if p[0] == "Title": 381 | b.title = p[1] 382 | elif p[0] == "TitleUnicode": 383 | b.title_unicode = p[1] 384 | elif p[0] == "Artist": 385 | b.artist = p[1] 386 | elif p[0] == "ArtistUnicode": 387 | b.artist_unicode = p[1] 388 | elif p[0] == "Creator": 389 | b.creator = p[1] 390 | elif p[0] == "Version": 391 | b.version = p[1] 392 | 393 | 394 | def general(self, b, line): 395 | p = self.property(line) 396 | if p[0] == "Mode": 397 | b.mode = int(self.setlastpos(p[1])) 398 | 399 | 400 | def difficulty(self, b, line): 401 | p = self.property(line) 402 | if p[0] == "CircleSize": 403 | b.cs = float(self.setlastpos(p[1])) 404 | elif p[0] == "OverallDifficulty": 405 | b.od = float(self.setlastpos(p[1])) 406 | elif p[0] == "ApproachRate": 407 | b.ar = float(self.setlastpos(p[1])) 408 | elif p[0] == "HPDrainRate": 409 | b.hp = float(self.setlastpos(p[1])) 410 | elif p[0] == "SliderMultiplier": 411 | b.sv = float(self.setlastpos(p[1])) 412 | elif p[0] == "SliderTickRate": 413 | b.tick_rate = float(self.setlastpos(p[1])) 414 | 415 | 416 | def timing(self, b, line): 417 | s = line.split(",") 418 | 419 | if len(s) > 8: 420 | info("W: timing point with trailing values\n") 421 | 422 | elif len(s) < 2: 423 | raise SyntaxError( 424 | "timing point must have at least two fields" 425 | ) 426 | 427 | 428 | t = timing( 429 | time=float(self.setlastpos(s[0])), 430 | ms_per_beat=float(self.setlastpos(s[1])) 431 | ) 432 | 433 | if len(s) >= 7: 434 | t.change = int(s[6]) != 0 435 | 436 | b.timing_points.append(t) 437 | 438 | 439 | def objects_std(self, b, line): 440 | s = line.split(",") 441 | if len(s) > 11: 442 | info("W: object with trailing values\n") 443 | 444 | if len(s) < 5: 445 | raise SyntaxError( 446 | "hitobject must have at least 5 fields" 447 | ) 448 | 449 | 450 | # I already tried calling the constructor with all of the 451 | # values on the fly and it wasn't any faster, don't bother 452 | obj = hitobject() 453 | obj.time = float(self.setlastpos(s[2])) 454 | obj.objtype = int(self.setlastpos(s[3])) 455 | if obj.objtype < 0 or obj.objtype > 255: 456 | raise SyntaxError("invalid hitobject type") 457 | 458 | # x,y,... 459 | if obj.objtype & OBJ_CIRCLE != 0: 460 | b.ncircles += 1 461 | c = circle() 462 | c.pos.x = float(self.setlastpos(s[0])) 463 | c.pos.y = float(self.setlastpos(s[1])) 464 | obj.data = c 465 | 466 | # ?,?,?,?,?,end_time,custom_sample_banks 467 | elif obj.objtype & OBJ_SPINNER != 0: 468 | b.nspinners += 1 469 | 470 | # x,y,time,type,sound_type,points,repetitions,distance, 471 | # per_node_sounds,per_node_samples,custom_sample_banks 472 | elif obj.objtype & OBJ_SLIDER != 0: 473 | if len(s) < 7: 474 | raise SyntaxError( 475 | "slider must have at least 7 fields" 476 | ) 477 | 478 | b.nsliders += 1 479 | sli = slider() 480 | sli.pos.x = float(self.setlastpos(s[0])) 481 | sli.pos.y = float(self.setlastpos(s[1])) 482 | sli.repetitions = int(self.setlastpos(s[6])) 483 | sli.distance = float(self.setlastpos(s[7])) 484 | obj.data = sli 485 | 486 | 487 | b.hitobjects.append(obj) 488 | 489 | 490 | def objects(self, b, line): 491 | if b.mode == MODE_STD: 492 | self.objects_std(b, line) 493 | 494 | # TODO: other modes 495 | 496 | else: 497 | raise NotImplementedError 498 | 499 | 500 | def map(self, osu_file, bmap = None): 501 | """ 502 | reads a file object and parses it into a beatmap object 503 | which is then returned. 504 | 505 | if bmap is specified, it will be reused as a pre-allocated 506 | beatmap object instead of building a new one, speeding 507 | up parsing slightly because of less allocations 508 | """ 509 | f = osu_file 510 | self.done = False 511 | 512 | section = "" 513 | b = bmap 514 | if b == None: 515 | b = beatmap() 516 | else: 517 | b.reset() 518 | 519 | for line in osu_file: 520 | self.nline += 1 521 | self.lastline = line 522 | 523 | # comments (according to lazer) 524 | if line.startswith(" ") or line.startswith("_"): 525 | continue 526 | 527 | line = line.strip() 528 | if line == "": 529 | continue 530 | 531 | # c++ style comments 532 | if line.startswith("//"): 533 | continue 534 | 535 | # [SectionName] 536 | if line.startswith("["): 537 | section = line[1:-1] 538 | continue 539 | 540 | try: 541 | if section == "Metadata": 542 | self.metadata(b, line) 543 | elif section == "General": 544 | self.general(b, line) 545 | elif section == "Difficulty": 546 | self.difficulty(b, line) 547 | elif section == "TimingPoints": 548 | self.timing(b, line) 549 | elif section == "HitObjects": 550 | self.objects(b, line) 551 | else: 552 | OSU_MAGIC = "file format v" 553 | findres = line.strip().find(OSU_MAGIC) 554 | if findres > 0: 555 | b.format_version = int( 556 | line[findres+len(OSU_MAGIC):] 557 | ) 558 | 559 | except (ValueError, SyntaxError) as e: 560 | info("W: %s\n%s\n" % (e, self)) 561 | 562 | 563 | 564 | if b.ar is None: 565 | b.ar = b.od 566 | 567 | self.done = True 568 | return b 569 | 570 | 571 | 572 | # ------------------------------------------------------------------------- 573 | # mods utils 574 | 575 | MODS_NOMOD = 0 576 | MODS_NF = 1<<0 577 | MODS_EZ = 1<<1 578 | MODS_TD = MODS_TOUCH_DEVICE = 1<<2 579 | MODS_HD = 1<<3 580 | MODS_HR = 1<<4 581 | MODS_DT = 1<<6 582 | MODS_HT = 1<<8 583 | MODS_NC = 1<<9 584 | MODS_FL = 1<<10 585 | MODS_SO = 1<<12 586 | 587 | def mods_str(mods): 588 | """ 589 | gets string representation of mods, such as HDDT. 590 | returns "nomod" for nomod 591 | """ 592 | if mods == 0: 593 | return "nomod" 594 | 595 | res = "" 596 | 597 | if mods & MODS_HD != 0: res += "HD" 598 | if mods & MODS_HT != 0: res += "HT" 599 | if mods & MODS_HR != 0: res += "HR" 600 | if mods & MODS_EZ != 0: res += "EZ" 601 | if mods & MODS_TOUCH_DEVICE != 0: res += "TD" 602 | if mods & MODS_NC != 0: res += "NC" 603 | elif mods & MODS_DT != 0: res += "DT" 604 | if mods & MODS_FL != 0: res += "FL" 605 | if mods & MODS_SO != 0: res += "SO" 606 | if mods & MODS_NF != 0: res += "NF" 607 | 608 | return res 609 | 610 | 611 | def mods_from_str(string): 612 | """ 613 | get mods bitmask from their string representation 614 | (touch device is TD) 615 | """ 616 | 617 | res = 0 618 | 619 | while string != "": 620 | if string.startswith("HD"): res |= MODS_HD 621 | elif string.startswith("HT"): res |= MODS_HT 622 | elif string.startswith("HR"): res |= MODS_HR 623 | elif string.startswith("EZ"): res |= MODS_EZ 624 | elif string.startswith("TD"): res |= MODS_TOUCH_DEVICE 625 | elif string.startswith("NC"): res |= MODS_NC 626 | elif string.startswith("DT"): res |= MODS_DT 627 | elif string.startswith("FL"): res |= MODS_FL 628 | elif string.startswith("SO"): res |= MODS_SO 629 | elif string.startswith("NF"): res |= MODS_NF 630 | else: 631 | string = string[1:] 632 | continue 633 | 634 | string = string[2:] 635 | 636 | 637 | return res 638 | 639 | 640 | def mods_apply(mods, ar = None, od = None, cs = None, hp = None): 641 | """ 642 | calculates speed multiplier, ar, od, cs, hp with the given 643 | mods applied. returns (speed_mul, ar, od, cs, hp). 644 | 645 | the base stats are all optional and default to None. if a base 646 | stat is None, then it won't be calculated and will also be 647 | returned as None. 648 | """ 649 | 650 | OD0_MS = 80 651 | OD10_MS = 20 652 | AR0_MS = 1800 653 | AR5_MS = 1200 654 | AR10_MS = 450 655 | 656 | OD_MS_STEP = (OD0_MS - OD10_MS) / 10.0 657 | AR_MS_STEP1 = (AR0_MS - AR5_MS) / 5.0 658 | AR_MS_STEP2 = (AR5_MS - AR10_MS) / 5.0 659 | 660 | MODS_SPEED_CHANGING = MODS_DT | MODS_HT | MODS_NC 661 | MODS_MAP_CHANGING = MODS_HR | MODS_EZ | MODS_SPEED_CHANGING 662 | 663 | if mods & MODS_MAP_CHANGING == 0: 664 | return (1.0, ar, od, cs, hp) 665 | 666 | speed_mul = 1.0 667 | 668 | if mods & (MODS_DT | MODS_NC) != 0: 669 | speed_mul = 1.5 670 | 671 | if mods & MODS_HT != 0: 672 | speed_mul *= 0.75 673 | 674 | od_ar_hp_multiplier = 1.0 675 | 676 | if mods & MODS_HR != 0: 677 | od_ar_hp_multiplier = 1.4 678 | 679 | if mods & MODS_EZ: 680 | od_ar_hp_multiplier *= 0.5 681 | 682 | if ar != None: 683 | ar *= od_ar_hp_multiplier 684 | 685 | # convert AR into milliseconds 686 | arms = AR5_MS 687 | 688 | if ar < 5.0: 689 | arms = AR0_MS - AR_MS_STEP1 * ar 690 | else: 691 | arms = AR5_MS - AR_MS_STEP2 * (ar - 5) 692 | 693 | # stats must be capped to 0-10 before HT/DT which brings 694 | # them to a range of -4.42-11.08 for OD and -5-11 for AR 695 | arms = min(AR0_MS, max(AR10_MS, arms)) 696 | arms /= speed_mul 697 | 698 | # convert back to AR 699 | if arms > AR5_MS: 700 | ar = (AR0_MS - arms) / AR_MS_STEP1 701 | else: 702 | ar = 5.0 + (AR5_MS - arms) / AR_MS_STEP2 703 | 704 | 705 | if od != None: 706 | od *= od_ar_hp_multiplier 707 | odms = OD0_MS - math.ceil(OD_MS_STEP * od) 708 | odms = min(OD0_MS, max(OD10_MS, odms)) 709 | odms /= speed_mul 710 | od = (OD0_MS - odms) / OD_MS_STEP 711 | 712 | 713 | if cs != None: 714 | if mods & MODS_HR != 0: 715 | cs *= 1.3 716 | 717 | if mods & MODS_EZ != 0: 718 | cs *= 0.5 719 | 720 | cs = min(10.0, cs) 721 | 722 | 723 | if hp != None: 724 | hp = min(10.0, hp * od_ar_hp_multiplier) 725 | 726 | return (speed_mul, ar, od, cs, hp) 727 | 728 | 729 | # ------------------------------------------------------------------------- 730 | # difficulty calculator 731 | 732 | DIFF_SPEED = 0 733 | DIFF_AIM = 1 734 | 735 | def d_spacing_weight(difftype, distance, delta_time, prev_distance, 736 | prev_delta_time, angle): 737 | 738 | # calculates spacing weight and returns (weight, is_single) 739 | # NOTE: is_single is only computed for DIFF_SPEED 740 | 741 | MIN_SPEED_BONUS = 75.0 # ~200BPM 1/4 streams 742 | MAX_SPEED_BONUS = 45.0 # ~330BPM 1/4 streams 743 | ANGLE_BONUS_SCALE = 90 744 | AIM_TIMING_THRESHOLD = 107 745 | SPEED_ANGLE_BONUS_BEGIN = 5 * math.pi / 6 746 | AIM_ANGLE_BONUS_BEGIN = math.pi / 3 747 | 748 | # arbitrary thresholds to determine when a stream is spaced 749 | # enough that it becomes hard to alternate 750 | SINGLE_SPACING = 125.0 751 | 752 | strain_time = max(delta_time, 50.0) 753 | prev_strain_time = max(prev_delta_time, 50.0) 754 | 755 | if difftype == DIFF_AIM: 756 | result = 0.0 757 | if angle is not None and angle > AIM_ANGLE_BONUS_BEGIN: 758 | angle_bonus = math.sqrt( 759 | max(prev_distance - ANGLE_BONUS_SCALE, 0.0) * 760 | pow(math.sin(angle - AIM_ANGLE_BONUS_BEGIN), 2.0) * 761 | max(distance - ANGLE_BONUS_SCALE, 0.0) 762 | ) 763 | result = ( 764 | 1.5 * pow(max(0.0, angle_bonus), 0.99) / 765 | max(AIM_TIMING_THRESHOLD, prev_strain_time) 766 | ) 767 | weighted_distance = pow(distance, 0.99) 768 | res = max(result + 769 | weighted_distance / max(AIM_TIMING_THRESHOLD, strain_time), 770 | weighted_distance / strain_time) 771 | return (res, False) 772 | 773 | elif difftype == DIFF_SPEED: 774 | is_single = distance > SINGLE_SPACING 775 | distance = min(distance, SINGLE_SPACING) 776 | delta_time = max(delta_time, MAX_SPEED_BONUS) 777 | speed_bonus = 1.0 778 | if delta_time < MIN_SPEED_BONUS: 779 | speed_bonus += pow((MIN_SPEED_BONUS - delta_time) / 40.0, 2) 780 | angle_bonus = 1.0 781 | if angle is not None and angle < SPEED_ANGLE_BONUS_BEGIN: 782 | s = math.sin(1.5 * (SPEED_ANGLE_BONUS_BEGIN - angle)) 783 | angle_bonus += s * s / 3.57 784 | if angle < math.pi / 2.0: 785 | angle_bonus = 1.28 786 | if distance < ANGLE_BONUS_SCALE and angle < math.pi / 4.0: 787 | angle_bonus += ( 788 | (1.0 - angle_bonus) * 789 | min((ANGLE_BONUS_SCALE - distance) / 10.0, 1.0) 790 | ) 791 | elif distance < ANGLE_BONUS_SCALE: 792 | angle_bonus += ( 793 | (1.0 - angle_bonus) * 794 | min((ANGLE_BONUS_SCALE - distance) / 10.0, 1.0) * 795 | math.sin((math.pi / 2.0 - angle) * 4.0 / math.pi) 796 | ) 797 | res = ( 798 | (1 + (speed_bonus - 1) * 0.75) * angle_bonus * 799 | (0.95 + speed_bonus * pow(distance / SINGLE_SPACING, 3.5)) 800 | ) / strain_time 801 | return (res, is_single) 802 | 803 | 804 | raise NotImplementedError 805 | 806 | 807 | DECAY_BASE = [ 0.3, 0.15 ] # strain decay per interval 808 | 809 | def d_strain(difftype, obj, prevobj, speed_mul): 810 | # calculates the difftype strain value for a hitobject. stores 811 | # the result in obj.strains[difftype] 812 | # this assumes that normpos is already computed 813 | 814 | WEIGHT_SCALING = [ 1400.0, 26.25 ] # balances speed and aim 815 | 816 | t = difftype 817 | value = 0.0 818 | time_elapsed = (obj.time - prevobj.time) / speed_mul 819 | obj.delta_time = time_elapsed 820 | decay = pow(DECAY_BASE[t], time_elapsed / 1000.0) 821 | 822 | # this implementation doesn't account for sliders 823 | if obj.objtype & (OBJ_SLIDER | OBJ_CIRCLE) != 0: 824 | distance = (obj.normpos - prevobj.normpos).len() 825 | obj.d_distance = distance 826 | value, is_single = d_spacing_weight(t, distance, time_elapsed, 827 | prevobj.d_distance, prevobj.delta_time, obj.angle) 828 | value *= WEIGHT_SCALING[t] 829 | if t == DIFF_SPEED: 830 | obj.is_single = is_single 831 | 832 | 833 | obj.strains[t] = prevobj.strains[t] * decay + value 834 | 835 | 836 | class diff_calc: 837 | """ 838 | difficulty calculator. 839 | 840 | fields: 841 | total: star rating 842 | aim: aim stars 843 | speed: speed stars 844 | nsingles: number of notes that are considered singletaps by 845 | the difficulty calculator 846 | nsingles_threshold: number of taps slower or equal to the 847 | singletap threshold value 848 | """ 849 | 850 | def __init__(self): 851 | self.strains = [] 852 | # NOTE: i tried pre-allocating this to 600 elements or so 853 | # and it didn't show appreciable performance improvements 854 | 855 | self.reset() 856 | 857 | 858 | def reset(self): 859 | self.total = 0.0 860 | self.aim = self.aim_difficulty = self.aim_length_bonus = 0.0 861 | self.speed = self.speed_difficulty = self.speed_length_bonus = 0.0 862 | self.nsingles = self.nsingles_threshold = 0 863 | 864 | 865 | def __str__(self): 866 | return """%g stars (%g aim, %g speed) 867 | %d spacing singletaps 868 | %d taps within singletap threshold""" % ( 869 | self.total, self.aim, self.speed, self.nsingles, 870 | self.nsingles_threshold 871 | ) 872 | 873 | 874 | def calc_individual(self, difftype, bmap, speed_mul): 875 | # calculates total strain for difftype. this assumes the 876 | # normalized positions for hitobjects are already present 877 | 878 | # max strains are weighted from highest to lowest. 879 | # this is how much the weight decays 880 | DECAY_WEIGHT = 0.9 881 | 882 | # strains are calculated by analyzing the map in chunks 883 | # and taking the peak strains in each chunk. this is the 884 | # length of a strain interval in milliseconds 885 | strain_step = 400.0 * speed_mul 886 | 887 | objs = bmap.hitobjects 888 | self.strains[:] = [] 889 | # first object doesn't generate a strain so we begin with 890 | # an incremented interval end 891 | interval_end = ( 892 | math.ceil(objs[0].time / strain_step) * strain_step 893 | ) 894 | max_strain = 0.0 895 | 896 | t = difftype 897 | 898 | for i, obj in enumerate(objs[1:]): 899 | prev = objs[i] 900 | 901 | d_strain(difftype, obj, prev, speed_mul) 902 | 903 | while obj.time > interval_end: 904 | # add max strain for this interval 905 | self.strains.append(max_strain) 906 | 907 | # decay last object's strains until the next 908 | # interval and use that as the initial max strain 909 | decay = pow( 910 | DECAY_BASE[t], 911 | (interval_end - prev.time) / 1000.0 912 | ) 913 | 914 | max_strain = prev.strains[t] * decay 915 | interval_end += strain_step 916 | 917 | 918 | max_strain = max(max_strain, obj.strains[t]) 919 | 920 | 921 | # don't forget to add the last strain 922 | self.strains.append(max_strain) 923 | 924 | # weight the top strains sorted from highest to lowest 925 | weight = 1.0 926 | total = 0.0 927 | difficulty = 0.0 928 | 929 | strains = self.strains 930 | strains.sort(reverse=True) 931 | 932 | for strain in strains: 933 | total += pow(strain, 1.2) 934 | difficulty += strain * weight 935 | weight *= DECAY_WEIGHT 936 | 937 | 938 | return ( difficulty, total ) 939 | 940 | 941 | def calc(self, bmap, mods=MODS_NOMOD, singletap_threshold=125): 942 | """ 943 | calculates difficulty and stores results in self.total, 944 | self.aim, self.speed, self.nsingles, 945 | self.nsingles_threshold. 946 | 947 | returns self. 948 | 949 | singletap_threshold is the smallest milliseconds interval 950 | that will be considered singletappable, defaults to 125ms 951 | which is 240 bpm 1/2 ((60000 / 240) / 2) 952 | """ 953 | 954 | # non-normalized diameter where the small circle size buff 955 | # starts 956 | CIRCLESIZE_BUFF_THRESHOLD = 30.0 957 | STAR_SCALING_FACTOR = 0.0675 # global stars multiplier 958 | 959 | # 50% of the difference between aim and speed is added to 960 | # star rating to compensate aim only or speed only maps 961 | EXTREME_SCALING_FACTOR = 0.5 962 | 963 | PLAYFIELD_WIDTH = 512.0 # in osu!pixels 964 | playfield_center = v2f( 965 | PLAYFIELD_WIDTH / 2, PLAYFIELD_WIDTH / 2 966 | ) 967 | 968 | if bmap.mode != MODE_STD: 969 | raise NotImplementedError 970 | 971 | self.reset() 972 | 973 | # calculate CS with mods 974 | speed_mul, _, _, cs, _ = mods_apply(mods, cs=bmap.cs) 975 | 976 | # circle radius 977 | radius = ( 978 | (PLAYFIELD_WIDTH / 16.0) * 979 | (1.0 - 0.7 * (cs - 5.0) / 5.0) 980 | ) 981 | 982 | # positions are normalized on circle radius so that we can 983 | # calc as if everything was the same circlesize 984 | scaling_factor = 52.0 / radius 985 | 986 | # low cs buff (credits to osuElements) 987 | if radius < CIRCLESIZE_BUFF_THRESHOLD: 988 | scaling_factor *= ( 989 | 1.0 + min(CIRCLESIZE_BUFF_THRESHOLD - radius, 5.0) / 50.0 990 | ) 991 | 992 | 993 | playfield_center *= scaling_factor 994 | 995 | # calculate normalized positions 996 | objs = bmap.hitobjects 997 | prev1 = None 998 | prev2 = None 999 | i = 0 1000 | for obj in objs: 1001 | if obj.objtype & OBJ_SPINNER != 0: 1002 | obj.normpos = v2f( 1003 | playfield_center.x, playfield_center.y 1004 | ) 1005 | else: 1006 | obj.normpos = obj.data.pos * scaling_factor 1007 | 1008 | if i >= 2: 1009 | v1 = prev2.normpos - prev1.normpos 1010 | v2 = obj.normpos - prev1.normpos 1011 | dot = v1.dot(v2) 1012 | det = v1.x * v2.y - v1.y * v2.x 1013 | obj.angle = abs(math.atan2(det, dot)) 1014 | else: 1015 | obj.angle = None 1016 | 1017 | prev2 = prev1 1018 | prev1 = obj 1019 | i+=1 1020 | 1021 | b = bmap 1022 | 1023 | # speed and aim stars 1024 | speed = self.calc_individual(DIFF_SPEED, b, speed_mul) 1025 | self.speed = speed[0] 1026 | self.speed_difficulty = speed[1] 1027 | 1028 | aim = self.calc_individual(DIFF_AIM, b, speed_mul) 1029 | self.aim = aim[0] 1030 | self.aim_difficulty = aim[1] 1031 | 1032 | def length_bonus(star, diff): 1033 | return ( 1034 | 0.32 + 0.5 * (math.log10(diff + star) - math.log10(star)) 1035 | ) 1036 | 1037 | self.aim_length_bonus = length_bonus(self.aim, self.aim_difficulty) 1038 | self.speed_length_bonus = ( 1039 | length_bonus(self.speed, self.speed_difficulty) 1040 | ) 1041 | self.aim = math.sqrt(self.aim) * STAR_SCALING_FACTOR 1042 | self.speed = math.sqrt(self.speed) * STAR_SCALING_FACTOR 1043 | if mods & MODS_TOUCH_DEVICE != 0: 1044 | self.aim = pow(self.aim, 0.8) 1045 | 1046 | # total stars 1047 | self.total = self.aim + self.speed 1048 | self.total += ( 1049 | abs(self.speed - self.aim) * 1050 | EXTREME_SCALING_FACTOR 1051 | ) 1052 | 1053 | # singletap stats 1054 | for i, obj in enumerate(objs[1:]): 1055 | prev = objs[i] 1056 | 1057 | if obj.is_single: 1058 | self.nsingles += 1 1059 | 1060 | if obj.objtype & (OBJ_CIRCLE | OBJ_SLIDER) == 0: 1061 | continue 1062 | 1063 | interval = (obj.time - prev.time) / speed_mul 1064 | 1065 | if interval >= singletap_threshold: 1066 | self.nsingles_threshold += 1 1067 | 1068 | 1069 | return self 1070 | 1071 | 1072 | 1073 | # ------------------------------------------------------------------------- 1074 | # pp calculator 1075 | 1076 | def acc_calc(n300, n100, n50, misses): 1077 | """calculates accuracy (0.0-1.0)""" 1078 | h = n300 + n100 + n50 + misses 1079 | 1080 | if h <= 0: 1081 | return 0.0 1082 | 1083 | return (n50 * 50.0 + n100 * 100.0 + n300 * 300.0) / (h * 300.0) 1084 | 1085 | 1086 | def acc_round(acc_percent, nobjects, misses): 1087 | """ 1088 | rounds to the closest amount of 300s, 100s, 50s 1089 | returns (n300, n100, n50) 1090 | """ 1091 | 1092 | misses = min(nobjects, misses) 1093 | max300 = nobjects - misses 1094 | maxacc = acc_calc(max300, 0, 0, misses) * 100.0 1095 | acc_percent = max(0.0, min(maxacc, acc_percent)) 1096 | 1097 | n50 = n300 = 0 1098 | 1099 | # just some black magic maths from wolfram alpha 1100 | n100 = round( 1101 | -3.0 * 1102 | ((acc_percent * 0.01 - 1.0) * nobjects + misses) * 0.5 1103 | ) 1104 | 1105 | n100 = int(n100) 1106 | 1107 | if n100 > nobjects - misses: 1108 | # acc lower than all 100s, use 50s 1109 | n100 = 0 1110 | n50 = round( 1111 | -6.0 * ( 1112 | (acc_percent * 0.01 - 1.0) * nobjects 1113 | + misses 1114 | ) * 0.5 1115 | ) 1116 | 1117 | n50 = int(n50) 1118 | n50 = min(max300, n50) 1119 | 1120 | else: 1121 | n100 = min(max300, n100) 1122 | 1123 | n300 = nobjects - n100 - n50 - misses 1124 | 1125 | return (n300, n100, n50) 1126 | 1127 | 1128 | def pp_base(stars): 1129 | # base pp value for stars, used internally by ppv2 1130 | return ( 1131 | pow(5.0 * max(1.0, stars / 0.0675) - 4.0, 3.0) / 100000.0 1132 | ) 1133 | 1134 | 1135 | def ppv2( 1136 | aim_stars=None, speed_stars=None, max_combo=None, 1137 | nsliders=None, ncircles=None, nobjects=None, base_ar=5.0, 1138 | base_od=5.0, mode=MODE_STD, mods=MODS_NOMOD, combo=None, 1139 | n300=None, n100=0, n50=0, nmiss=0, score_version=1, bmap=None 1140 | ): 1141 | """ 1142 | calculates ppv2 1143 | 1144 | returns (pp, aim_pp, speed_pp, acc_pp, acc_percent) 1145 | 1146 | if bmap is provided, mode, base_ar, base_od, max_combo, 1147 | nsliders, ncircles and nobjects are taken from it. otherwise 1148 | they must be provided. 1149 | 1150 | if combo is None, max_combo is used. 1151 | if n300 is None, max_combo - n100 - n50 - nmiss is used. 1152 | """ 1153 | if mode != MODE_STD: 1154 | info( 1155 | "ppv2 is only implemented for osu!std at the moment\n" 1156 | ) 1157 | raise NotImplementedError 1158 | 1159 | 1160 | if bmap != None: 1161 | mode = bmap.mode 1162 | base_ar = bmap.ar 1163 | base_od = bmap.od 1164 | max_combo = bmap.max_combo() 1165 | nsliders = bmap.nsliders 1166 | ncircles = bmap.ncircles 1167 | nobjects = len(bmap.hitobjects) 1168 | 1169 | else: 1170 | if aim_stars == None: 1171 | raise ValueError("missing aim_stars or bmap") 1172 | 1173 | if speed_stars == None: 1174 | raise ValueError("missing speed_stars") 1175 | 1176 | if max_combo == None: 1177 | raise ValueError("missing max_combo or bmap") 1178 | 1179 | if nsliders == None: 1180 | raise ValueError("missing nsliders or bmap") 1181 | 1182 | if ncircles == None: 1183 | raise ValueError("missing ncircles or bmap") 1184 | 1185 | if nobjects == None: 1186 | raise ValueError("missing nobjects or bmap") 1187 | 1188 | 1189 | if max_combo <= 0: 1190 | info("W: max_combo <= 0, changing to 1\n") 1191 | max_combo = 1 1192 | 1193 | if combo == None: 1194 | combo = max_combo - nmiss 1195 | 1196 | if n300 == None: 1197 | n300 = nobjects - n100 - n50 - nmiss 1198 | 1199 | # accuracy ---------------------------------------------------- 1200 | accuracy = acc_calc(n300, n100, n50, nmiss) 1201 | real_acc = accuracy 1202 | nspinners = nobjects - nsliders - ncircles 1203 | 1204 | if score_version == 1: 1205 | # scorev1 ignores sliders since they are free 300s 1206 | # for whatever reason it also ignores spinners 1207 | real_acc = acc_calc( 1208 | n300 - nsliders - nspinners, n100, n50, nmiss 1209 | ) 1210 | 1211 | # can go negative if we miss everything 1212 | real_acc = max(0.0, real_acc) 1213 | 1214 | elif score_version == 2: 1215 | ncircles = nobjects 1216 | 1217 | else: 1218 | info("unsupported scorev%d\n" % (score_version)) 1219 | raise NotImplementedError 1220 | 1221 | # global values ----------------------------------------------- 1222 | nobjects_over_2k = nobjects / 2000.0 1223 | 1224 | length_bonus = 0.95 + 0.4 * min(1.0, nobjects_over_2k) 1225 | 1226 | if nobjects > 2000: 1227 | length_bonus += math.log10(nobjects_over_2k) * 0.5 1228 | 1229 | miss_penality_aim = 0.97 * pow(1 - pow(float(nmiss) / nobjects, 0.775), nmiss) 1230 | miss_penality_speed = ( 1231 | 0.97 * pow(1 - pow(float(nmiss) / nobjects, 0.775), pow(nmiss, 0.875)) 1232 | ) 1233 | combo_break = pow(combo, 0.8) / pow(max_combo, 0.8) 1234 | 1235 | # calculate stats with mods 1236 | speed_mul, ar, od, _, _ = ( 1237 | mods_apply(mods, ar=base_ar, od=base_od) 1238 | ) 1239 | 1240 | # ar bonus ---------------------------------------------------- 1241 | ar_bonus = 0.0 1242 | 1243 | if ar > 10.33: 1244 | ar_bonus += 0.4 * (ar - 10.33) 1245 | 1246 | elif ar < 8.0: 1247 | ar_bonus += 0.01 * (8.0 - ar) 1248 | 1249 | 1250 | # aim pp ------------------------------------------------------ 1251 | aim = pp_base(aim_stars) 1252 | aim *= length_bonus 1253 | if nmiss > 0: 1254 | aim *= miss_penality_aim 1255 | aim *= combo_break 1256 | aim *= 1.0 + min(ar_bonus, ar_bonus * (nobjects / 1000.0)) 1257 | 1258 | hd_bonus = 1.0 1259 | if mods & MODS_HD != 0: 1260 | hd_bonus *= 1.0 + 0.04 * (12.0 - ar) 1261 | 1262 | aim *= hd_bonus 1263 | 1264 | if mods & MODS_FL != 0: 1265 | fl_bonus = 1.0 + 0.35 * min(1.0, nobjects / 200.0) 1266 | if nobjects > 200: 1267 | fl_bonus += 0.3 * min(1, (nobjects - 200) / 300.0) 1268 | if nobjects > 500: 1269 | fl_bonus += (nobjects - 500) / 1200.0 1270 | aim *= fl_bonus 1271 | 1272 | acc_bonus = 0.5 + accuracy / 2.0 1273 | od_squared = od * od; 1274 | od_bonus = 0.98 + od_squared / 2500.0 1275 | 1276 | aim *= acc_bonus 1277 | aim *= od_bonus 1278 | 1279 | # speed pp ---------------------------------------------------- 1280 | speed = pp_base(speed_stars) 1281 | speed *= length_bonus 1282 | if nmiss > 0: 1283 | speed *= miss_penality_speed 1284 | speed *= combo_break 1285 | if ar > 10.33: 1286 | speed *= 1.0 + min(ar_bonus, ar_bonus * (nobjects / 1000.0)) 1287 | speed *= hd_bonus 1288 | 1289 | speed *= (0.95 + od_squared / 750.0) * pow(accuracy, (14.5 - max(od, 8.0)) / 2.0) 1290 | if n50 >= nobjects / 500.0: 1291 | speed *= pow(0.98, n50 - nobjects / 500.0) 1292 | 1293 | # acc pp ------------------------------------------------------ 1294 | acc = pow(1.52163, od) * pow(real_acc, 24.0) * 2.83 1295 | 1296 | # length bonus (not the same as speed/aim length bonus) 1297 | acc *= min(1.15, pow(ncircles / 1000.0, 0.3)) 1298 | 1299 | if mods & MODS_HD != 0: 1300 | acc *= 1.08 1301 | 1302 | if mods & MODS_FL != 0: 1303 | acc *= 1.02 1304 | 1305 | # total pp ---------------------------------------------------- 1306 | final_multiplier = 1.12 1307 | 1308 | if mods & MODS_NF != 0: 1309 | final_multiplier *= max(0.9, 1.0 - 0.2 * nmiss) 1310 | 1311 | if mods & MODS_SO != 0: 1312 | final_multiplier *= 1.0 - pow(float(nspinners) / nobjects, 0.85) 1313 | 1314 | total = ( 1315 | pow( 1316 | pow(aim, 1.1) + pow(speed, 1.1) + pow(acc, 1.1), 1317 | 1.0 / 1.1 1318 | ) * final_multiplier 1319 | ) 1320 | 1321 | return (total, aim, speed, acc, accuracy * 100.0) 1322 | 1323 | 1324 | # ------------------------------------------------------------------------- 1325 | # usage example 1326 | 1327 | if __name__ == "__main__": 1328 | import traceback 1329 | 1330 | mods = 0 1331 | acc_percent = 100.0 1332 | combo = -1 1333 | nmiss = 0 1334 | 1335 | # get mods, acc, combo, misses from command line arguments 1336 | # format: +HDDT 95% 300x 1m 1337 | for arg in sys.argv: 1338 | if arg.startswith("+"): 1339 | mods = mods_from_str(arg[1:]) 1340 | elif arg.endswith("%"): 1341 | acc_percent = float(arg[:-1]) 1342 | elif arg.endswith("x"): 1343 | combo = int(arg[:-1]) 1344 | elif arg.endswith("m"): 1345 | nmiss = int(arg[:-1]) 1346 | 1347 | 1348 | try: 1349 | p = parser() 1350 | bmap = p.map(sys.stdin) 1351 | if combo < 0: 1352 | combo = bmap.max_combo() 1353 | 1354 | print("%s - %s [%s] +%s" % (bmap.artist, bmap.title, 1355 | bmap.version, mods_str(mods))) 1356 | print("OD%g AR%g CS%g HP%g" % (bmap.od, bmap.ar, bmap.cs, 1357 | bmap.hp)) 1358 | stars = diff_calc().calc(bmap, mods) 1359 | print("max combo: %d\n" % (bmap.max_combo())) 1360 | print(stars) 1361 | 1362 | # round acc percent to the closest 300/100/50 count 1363 | n300, n100, n50 = acc_round(acc_percent, 1364 | len(bmap.hitobjects), nmiss) 1365 | 1366 | # ppv2 returns a tuple (pp, aim, speed, acc, percent) 1367 | print("%g pp (%g aim, %g speed, %g acc) for %g%%" % ( 1368 | ppv2( 1369 | aim_stars=stars.aim, 1370 | speed_stars=stars.speed, 1371 | bmap=bmap, 1372 | n300=n300, n100=n100, n50=n50, nmiss=nmiss, 1373 | mods=mods, 1374 | combo=combo, 1375 | ) 1376 | )) 1377 | 1378 | except KeyboardInterrupt: 1379 | pass 1380 | except Exception as e: 1381 | if p.done: 1382 | raise 1383 | else: # beatmap parsing error, print parser state 1384 | info("%s\n%s\n" % (traceback.format_exc(), str(p))) 1385 | -------------------------------------------------------------------------------- /run_test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from test import test 4 | 5 | if __name__ == "__main__": 6 | test.run() 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | import pyttanko 7 | 8 | pyttanko_classifiers = [ 9 | "Programming Language :: Python :: 2", 10 | "Programming Language :: Python :: 3", 11 | "Intended Audience :: Developers", 12 | "License :: Public Domain", 13 | "Topic :: Software Development :: Libraries", 14 | "Topic :: Utilities", 15 | ] 16 | 17 | with open("README.rst", "r") as f: 18 | pyttanko_readme = f.read() 19 | 20 | setup( 21 | name="pyttanko", 22 | version=pyttanko.__version__, 23 | author="Franc[e]sco", 24 | author_email="lolisamurai@tfwno.gf", 25 | url="https://github.com/Francesco149/pyttanko", 26 | py_modules=["pyttanko"], 27 | description="osu! pp and difficulty calculator", 28 | long_description=pyttanko_readme, 29 | license="Unlicense", 30 | classifiers=pyttanko_classifiers, 31 | keywords="osu! osu" 32 | ) 33 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Francesco149/pyttanko/6e01fb63aee804c88ba29c2af206800aea5232b4/test/__init__.py -------------------------------------------------------------------------------- /test/download_suite: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir="$(dirname "$0")" 4 | if command -v realpath 2>&1 >/dev/null; then 5 | wdir="$(realpath "$dir")" 6 | else 7 | wdir="$dir" 8 | fi 9 | olddir="$(pwd)" 10 | cd "$wdir" || exit $? 11 | 12 | url="https://github.com/Francesco149/oppai-ng/releases/download/4.0.0/test_suite_2021-02-03.tar.gz" 13 | 14 | if [ $(find test_suite 2>/dev/null | tail -n +2 | wc -l) = "0" ]; then 15 | curl -LO "$url" || exit $? 16 | tar xf "$(basename $url)" || exit $? 17 | else 18 | echo "using existing test_suite" 19 | fi 20 | 21 | cd "$olddir" 22 | -------------------------------------------------------------------------------- /test/gentest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | import time 6 | import json 7 | import traceback 8 | import argparse 9 | import hashlib 10 | 11 | if sys.version_info[0] < 3: 12 | # hack to force utf-8 13 | reload(sys) 14 | sys.setdefaultencoding('utf-8') 15 | 16 | try: 17 | import httplib 18 | except ImportError: 19 | import http.client as httplib 20 | 21 | try: 22 | import urllib 23 | except ImportError: 24 | import urllib.parse as urllib 25 | 26 | # ------------------------------------------------------------------------- 27 | 28 | parser = argparse.ArgumentParser( 29 | description = ( 30 | 'generates the oppai test suite. outputs python code to ' + 31 | 'stdout and the json dump to a file.' 32 | ) 33 | ) 34 | 35 | parser.add_argument( 36 | '-key', 37 | default = None, 38 | help = ( 39 | 'osu! api key. required if -input-file is not present. ' + 40 | 'can also be specified through the OSU_API_KEY ' + 41 | 'environment variable' 42 | ) 43 | ) 44 | 45 | parser.add_argument( 46 | '-output-file', 47 | default = 'test_suite.json', 48 | help = 'dumps json to this file' 49 | ) 50 | 51 | parser.add_argument( 52 | '-input-file', 53 | default = None, 54 | help = ( 55 | 'loads test suite from this json file instead of ' 56 | 'fetching it from osu api. if set to "-", json will be ' 57 | 'read from standard input' 58 | ) 59 | ) 60 | 61 | args = parser.parse_args() 62 | 63 | if args.key == None and 'OSU_API_KEY' in os.environ: 64 | args.key = os.environ['OSU_API_KEY'] 65 | 66 | # ------------------------------------------------------------------------- 67 | 68 | osu_treset = time.time() + 60 69 | osu_ncalls = 0 70 | 71 | def osu_get(conn, endpoint, paramsdict=None): 72 | # GETs /api/endpoint?paramsdict&k=args.key from conn. 73 | # return json object, exits process on api errors 74 | global osu_treset, osu_ncalls, args 75 | 76 | sys.stderr.write('%s %s\n' % (endpoint, str(paramsdict))) 77 | 78 | paramsdict['k'] = args.key 79 | path = '/api/%s?%s' % (endpoint, urllib.urlencode(paramsdict)) 80 | 81 | while True: 82 | while True: 83 | if time.time() >= osu_treset: 84 | osu_ncalls = 0 85 | osu_treset = time.time() + 60 86 | sys.stderr.write('\napi ready\n') 87 | 88 | if osu_ncalls < 60: 89 | break 90 | else: 91 | sys.stderr.write('waiting for api cooldown...\r') 92 | time.sleep(1) 93 | 94 | 95 | try: 96 | conn.request('GET', path) 97 | osu_ncalls += 1 98 | r = conn.getresponse() 99 | 100 | raw = '' 101 | 102 | while True: 103 | try: 104 | raw += r.read() 105 | break 106 | except httplib.IncompleteRead as e: 107 | raw += e.partial 108 | 109 | j = json.loads(raw) 110 | 111 | if 'error' in j: 112 | sys.stderr.write('%s\n' % j['error']) 113 | sys.exit(1) 114 | 115 | return j 116 | 117 | except (httplib.HTTPException, ValueError) as e: 118 | sys.stderr.write('%s\n' % (traceback.format_exc())) 119 | 120 | try: 121 | # prevents exceptions on next request if the 122 | # response wasn't previously read due to errors 123 | conn.getresponse().read() 124 | 125 | except httplib.HTTPException: 126 | pass 127 | 128 | time.sleep(5) 129 | 130 | 131 | def gen_modstr(bitmask): 132 | # generates code for a mod combination's bitmask 133 | mods = [] 134 | 135 | allmods = { 136 | (1<< 0, 'nf'), (1<< 1, 'ez'), (1<< 2, 'td'), (1<< 3, 'hd'), 137 | (1<< 4, 'hr'), (1<< 6, 'dt'), (1<< 8, 'ht'), 138 | (1<< 9, 'nc'), (1<<10, 'fl'), (1<<12, 'so') 139 | } 140 | 141 | for bit, string in allmods: 142 | if bitmask & bit != 0: 143 | mods.append(string) 144 | 145 | if len(mods) == 0: 146 | return 'nomod' 147 | 148 | return ' | '.join(mods) 149 | 150 | # ------------------------------------------------------------------------- 151 | 152 | if args.key == None: 153 | sys.stderr.write( 154 | 'please set OSU_API_KEY or pass it as a parameter\n' 155 | ) 156 | sys.exit(1) 157 | 158 | 159 | scores = [] 160 | 161 | if args.input_file == None: 162 | # fetch a fresh test suite from osu api 163 | top_players = [ 164 | 4504101, 7562902, 6447454, 4787150, 11367222, 5339515, 8179335, 4196808, 4650315 165 | ] 166 | 167 | osu = httplib.HTTPSConnection('osu.ppy.sh') 168 | 169 | for u in top_players: 170 | params = { 'u': u, 'limit': 100, 'type': 'id' } 171 | scores += osu_get(osu, 'get_user_best', params) 172 | 173 | # TODO: uncomment when everything is recalced 174 | #params = { 'm': 0, 'since': '2015-11-26' } 175 | #maps = osu_get(osu, 'get_beatmaps', params) 176 | 177 | #for m in maps: 178 | # params = { 'b': m['beatmap_id'] } 179 | # map_scores = osu_get(osu, 'get_scores', params) 180 | 181 | # if len(map_scores) == 0: 182 | # sys.stderr.write('W: map has no scores???\n') 183 | # continue 184 | 185 | # # note: api also returns qualified and loved, so ignore 186 | # # maps that don't have pp in rankings 187 | # if not 'pp' in map_scores[0]: 188 | # sys.stderr.write('W: ignoring loved/qualified map\n') 189 | # continue 190 | 191 | # for s in map_scores: 192 | # s['beatmap_id'] = m['beatmap_id'] 193 | 194 | # scores += map_scores 195 | 196 | 197 | with open(args.output_file, 'w+') as f: 198 | f.write(json.dumps(scores)) 199 | 200 | else: 201 | # load existing test suite from json file 202 | with open(args.input_file, 'r') as f: 203 | scores = json.loads(f.read()) 204 | 205 | 206 | print('#!/usr/bin/env python\n') 207 | print('# this code was automatically generated by gentest.py') 208 | print('') 209 | print('import pyttanko') 210 | 211 | # make code a little nicer by shortening mods 212 | allmods = { 213 | 'nf', 'ez', 'td', 'hd', 'hr', 'dt', 'ht', 'nc', 'fl', 'so', 'nomod' 214 | } 215 | 216 | for mod in allmods: 217 | print('%s = pyttanko.MODS_%s' % (mod, mod.upper())) 218 | 219 | print(''' 220 | from collections import namedtuple 221 | 222 | s = namedtuple( 223 | 'score', 'id max_combo n300 n100 n50 nmiss mods pp' 224 | ) 225 | 226 | suite = [ 227 | ''') 228 | 229 | seen_hashes = [] 230 | 231 | for s in scores: 232 | # why is every value returned by osu api a string? 233 | line = ( 234 | ' s(%s, %s, %s, %s, %s, %s, %s, %s),' % 235 | ( 236 | s['beatmap_id'], s['maxcombo'], s['count300'], 237 | s['count100'], s['count50'], s['countmiss'], 238 | gen_modstr(int(s['enabled_mods'])), s['pp'] 239 | ) 240 | ) 241 | 242 | # don't include identical scores by different people 243 | s = hashlib.sha1(line.encode('utf-8')).digest() 244 | if s in seen_hashes: 245 | continue 246 | 247 | print(line) 248 | seen_hashes.append(s) 249 | 250 | print(']\n') 251 | 252 | for mod in allmods: 253 | print('del %s' % (mod)) 254 | 255 | print('del s') 256 | 257 | -------------------------------------------------------------------------------- /test/suite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # this code was automatically generated by gentest.py 4 | 5 | import pyttanko 6 | fl = pyttanko.MODS_FL 7 | hd = pyttanko.MODS_HD 8 | td = pyttanko.MODS_TD 9 | nf = pyttanko.MODS_NF 10 | nc = pyttanko.MODS_NC 11 | ht = pyttanko.MODS_HT 12 | nomod = pyttanko.MODS_NOMOD 13 | hr = pyttanko.MODS_HR 14 | ez = pyttanko.MODS_EZ 15 | dt = pyttanko.MODS_DT 16 | so = pyttanko.MODS_SO 17 | 18 | from collections import namedtuple 19 | 20 | s = namedtuple( 21 | 'score', 'id max_combo n300 n100 n50 nmiss mods pp' 22 | ) 23 | 24 | suite = [ 25 | 26 | s(2469345, 464, 330, 7, 0, 0, dt | nc | hr | hd, 1089.33), 27 | s(2444148, 495, 325, 8, 0, 0, dt | hr | hd, 1076.84), 28 | s(1805627, 548, 427, 8, 0, 2, dt | hd, 1075.32), 29 | s(2628691, 1318, 954, 6, 0, 0, dt | hd, 1068), 30 | s(2486881, 1646, 1153, 3, 0, 0, dt | hd, 1063.5), 31 | s(2097898, 427, 308, 8, 0, 0, dt | hr | hd, 1044.35), 32 | s(2164997, 619, 448, 12, 0, 0, dt | hr | hd, 1018.18), 33 | s(2410945, 519, 329, 10, 0, 2, dt | nc | hr | hd, 1011.91), 34 | s(1919312, 1811, 1208, 19, 0, 0, dt | hd, 1002.4), 35 | s(2201460, 551, 353, 8, 0, 0, dt | hr | hd, 992.285), 36 | s(2185824, 1479, 978, 8, 0, 0, dt | hd, 991.049), 37 | s(869222, 2494, 2017, 8, 1, 0, hr, 973.637), 38 | s(1816113, 4079, 2979, 36, 0, 0, nomod, 971.477), 39 | s(2022718, 617, 407, 9, 0, 0, dt | hr | hd, 967.44), 40 | s(2058788, 550, 346, 10, 0, 0, dt | hr | hd, 966.971), 41 | s(1759814, 1323, 850, 10, 0, 0, dt | hd, 962.862), 42 | s(1690355, 639, 419, 3, 0, 0, dt | hd, 955.764), 43 | s(1860433, 541, 329, 20, 1, 2, dt | hr | hd, 954.618), 44 | s(1955170, 464, 355, 6, 0, 2, dt | nc | hr | hd, 941.151), 45 | s(1861487, 3163, 2355, 16, 0, 0, nomod, 933.19), 46 | s(1843653, 431, 239, 3, 0, 0, dt | hr | hd, 932.365), 47 | s(2128030, 2242, 1571, 43, 0, 0, nomod, 928.548), 48 | s(1594266, 1268, 895, 12, 0, 0, dt | hd, 924.452), 49 | s(2404778, 481, 371, 12, 1, 0, dt | hr | hd, 924.092), 50 | s(2505956, 478, 329, 0, 0, 1, dt | hd, 924.036), 51 | s(2502643, 3088, 2542, 21, 0, 0, nomod, 921.575), 52 | s(1655981, 468, 358, 17, 0, 1, dt | hd, 916.683), 53 | s(1849580, 2383, 2109, 17, 3, 4, hr, 915.798), 54 | s(1778560, 1247, 917, 6, 0, 1, dt | hd, 913.554), 55 | s(2083422, 1030, 770, 11, 0, 0, dt | hd, 909.957), 56 | s(1875396, 844, 605, 6, 0, 0, dt | hd, 908.444), 57 | s(1944926, 769, 535, 16, 0, 0, dt | hd, 899.764), 58 | s(1872396, 1537, 1232, 21, 1, 2, dt | nc | hd, 899.044), 59 | s(1869933, 386, 250, 11, 0, 0, dt | hr | hd, 896.115), 60 | s(2231706, 1003, 688, 11, 0, 0, dt | hd, 891.721), 61 | s(1857519, 393, 256, 5, 0, 2, dt | hr | hd, 891.239), 62 | s(2129143, 624, 452, 10, 0, 0, dt | hd, 890.584), 63 | s(1258642, 1721, 1218, 21, 0, 0, dt | hd, 885.764), 64 | s(1788628, 1433, 928, 21, 0, 0, hr | hd, 885.392), 65 | s(1911308, 547, 291, 12, 0, 0, dt | hr | hd, 884.191), 66 | s(1828346, 488, 362, 23, 7, 3, dt | nc | hd, 883.463), 67 | s(1642274, 530, 339, 19, 0, 0, dt | hr | hd, 882.663), 68 | s(1892257, 506, 373, 6, 0, 0, dt | hd, 882.594), 69 | s(1626537, 727, 517, 14, 0, 0, dt | hd, 879.853), 70 | s(1969946, 410, 278, 8, 0, 0, dt | hr | hd, 877.66), 71 | s(2625911, 1910, 1327, 8, 0, 0, hr | hd, 877.556), 72 | s(1097543, 1629, 1092, 5, 0, 0, dt | hd, 873.203), 73 | s(2368880, 1597, 1120, 16, 0, 0, dt | hd, 871.856), 74 | s(1983406, 788, 524, 9, 0, 0, dt | hd, 870.586), 75 | s(1610745, 253, 191, 3, 0, 0, dt | hr | hd, 870.533), 76 | s(2484994, 514, 381, 10, 1, 1, dt | hd, 863.108), 77 | s(2237466, 521, 364, 17, 0, 0, dt | hr | hd, 863.064), 78 | s(2022204, 542, 372, 8, 0, 0, dt | hd, 852.088), 79 | s(2069841, 806, 661, 19, 0, 1, dt | hd, 842.611), 80 | s(1554502, 1702, 1190, 26, 0, 0, dt | hd, 838.482), 81 | s(795627, 1327, 944, 53, 1, 0, dt | hd, 828.13), 82 | s(2231831, 562, 395, 15, 1, 0, dt | hd, 827.413), 83 | s(2049467, 6229, 5652, 9, 0, 1, nomod, 826.042), 84 | s(2014389, 1532, 1066, 12, 0, 0, hr | hd, 825.771), 85 | s(1432943, 2639, 1663, 21, 0, 0, hr | hd, 818.958), 86 | s(999944, 1026, 718, 25, 0, 0, dt | nc | hd, 818.469), 87 | s(2485438, 427, 380, 14, 0, 0, dt | hr | hd, 817.063), 88 | s(2034983, 403, 358, 5, 0, 1, dt | hr | hd, 815.868), 89 | s(1763013, 613, 393, 2, 0, 0, dt | hd, 811.794), 90 | s(2047629, 673, 419, 9, 0, 0, dt | hd, 810.162), 91 | s(820619, 651, 467, 7, 0, 0, dt | hd, 809.579), 92 | s(1977951, 1130, 758, 7, 1, 0, hr, 808.174), 93 | s(2518847, 231, 161, 7, 0, 0, dt | hr | hd, 807.63), 94 | s(1619555, 472, 383, 2, 0, 0, dt | hd, 804.689), 95 | s(2084862, 462, 380, 11, 0, 3, dt | hd, 803.914), 96 | s(724015, 1393, 1008, 7, 0, 0, hr | hd, 803.477), 97 | s(2118444, 538, 339, 3, 0, 0, dt | hd, 799.397), 98 | s(1451282, 1367, 1057, 9, 0, 0, hr | hd, 799.179), 99 | s(1537817, 1636, 1153, 28, 0, 0, dt | hd, 797.287), 100 | s(2149819, 504, 333, 32, 0, 1, dt | hr | hd, 793.618), 101 | s(1784189, 697, 513, 7, 0, 0, dt | hd, 791.564), 102 | s(949029, 1206, 890, 22, 0, 1, dt | hd, 789.105), 103 | s(2468866, 507, 344, 9, 0, 0, dt | hd, 788.743), 104 | s(1571309, 802, 558, 11, 0, 0, dt | hd, 787.9), 105 | s(1995061, 913, 620, 2, 0, 0, hr | hd, 787.286), 106 | s(1312156, 421, 330, 3, 0, 0, dt | hd, 786.984), 107 | s(2245774, 192, 133, 14, 0, 0, dt | hr | hd, 784.02), 108 | s(1867520, 1201, 894, 13, 2, 1, dt | hd, 782.899), 109 | s(1473301, 809, 537, 6, 0, 0, dt | hd, 782.714), 110 | s(1501472, 611, 394, 3, 0, 0, dt | hd, 779.067), 111 | s(1570203, 443, 346, 9, 0, 1, dt | hd, 775.741), 112 | s(2111505, 1777, 1592, 16, 0, 0, hr, 774.41), 113 | s(1612329, 386, 308, 6, 0, 0, dt | hd, 774.323), 114 | s(1815397, 3160, 2354, 20, 0, 0, hd, 767.244), 115 | s(2044362, 528, 371, 9, 0, 0, dt | hd, 767.042), 116 | s(1385397, 479, 368, 11, 0, 2, dt | hd, 764.905), 117 | s(774965, 1774, 1147, 26, 0, 0, dt | hd, 761.777), 118 | s(2410948, 517, 366, 24, 0, 3, dt | hd, 757.739), 119 | s(954692, 574, 404, 3, 0, 0, dt | hd, 746.974), 120 | s(1826438, 359, 303, 16, 0, 4, dt | hd, 744.751), 121 | s(791274, 2911, 2198, 13, 2, 0, hr | hd, 743.377), 122 | s(1627148, 261, 197, 9, 0, 0, dt | hd, 741.555), 123 | s(2026320, 453, 300, 6, 0, 0, dt | hd, 740.03), 124 | s(2044366, 519, 354, 7, 0, 0, dt | hd, 736.888), 125 | s(827488, 1230, 825, 6, 0, 0, dt | hd, 733.151), 126 | s(1892257, 506, 368, 11, 0, 0, dt | hr | hd, 1058.31), 127 | s(2315558, 1352, 996, 32, 1, 0, dt | hd, 977.006), 128 | s(2469345, 446, 324, 12, 0, 1, dt | hr | hd, 969.496), 129 | s(2486881, 1642, 1132, 22, 2, 0, dt | hd, 967.252), 130 | s(1826438, 396, 306, 17, 0, 0, dt | nc | hd, 957.582), 131 | s(2628691, 1291, 943, 15, 0, 2, dt | hd, 956.711), 132 | s(1919312, 1765, 1203, 23, 0, 1, dt | nc | hd, 937.162), 133 | s(2616989, 1465, 1069, 28, 1, 0, dt | hd, 929.574), 134 | s(1642274, 532, 347, 11, 0, 0, dt | nc | hr | hd, 928.579), 135 | s(1860433, 508, 334, 16, 0, 2, dt | nc | hr | hd, 924.87), 136 | s(1872396, 1677, 1206, 49, 1, 0, dt | hd, 922.224), 137 | s(2505956, 400, 320, 9, 0, 1, dt | hr | hd, 921.472), 138 | s(1701404, 512, 395, 27, 1, 1, dt | hd, 916.557), 139 | s(1626537, 727, 523, 8, 0, 0, dt | hd, 915.834), 140 | s(1983406, 769, 507, 24, 0, 2, dt | hr | hd, 915.827), 141 | s(1610745, 253, 193, 1, 0, 0, dt | nc | hr | hd, 906.219), 142 | s(2468866, 506, 333, 19, 1, 0, dt | hr | hd, 903.882), 143 | s(2185824, 1476, 957, 29, 0, 0, dt | hd, 902.499), 144 | s(970331, 706, 542, 19, 0, 0, dt | hd, 901.979), 145 | s(1925409, 544, 401, 15, 0, 0, dt | hd, 901.966), 146 | s(1849433, 394, 320, 5, 0, 0, dt | hd, 899.055), 147 | s(2034057, 600, 454, 9, 0, 0, dt | hd, 898.983), 148 | s(2546306, 508, 355, 1, 0, 0, dt | hd, 898.194), 149 | s(2404778, 521, 363, 18, 2, 1, dt | hr | hd, 896.535), 150 | s(2058788, 540, 342, 13, 0, 1, dt | nc | hr | hd, 889.647), 151 | s(1759814, 1321, 831, 28, 1, 0, dt | hd, 889.498), 152 | s(2083422, 1027, 766, 15, 0, 0, dt | hd, 888.418), 153 | s(1843653, 428, 236, 6, 0, 0, dt | hr | hd, 888.371), 154 | s(2034429, 1206, 855, 16, 0, 0, dt | hd, 888.204), 155 | s(1487213, 834, 655, 19, 1, 3, dt | hd, 885.302), 156 | s(2596015, 333, 248, 1, 0, 0, dt | nc | hr | hd, 885.035), 157 | s(2466406, 1892, 1310, 51, 0, 0, dt | hd, 882.059), 158 | s(2474574, 536, 360, 14, 0, 0, dt | hr | hd, 879.862), 159 | s(1655981, 462, 356, 18, 0, 2, dt | hd, 876.995), 160 | s(2389623, 1412, 1062, 20, 0, 1, dt | hd, 873.038), 161 | s(2231831, 562, 402, 9, 0, 0, dt | hd, 872.913), 162 | s(2628166, 545, 382, 7, 0, 0, dt | hd, 872.225), 163 | s(1749322, 427, 318, 6, 0, 0, dt | hr | hd, 868.105), 164 | s(1955170, 445, 348, 13, 0, 2, dt | hr | hd, 866.96), 165 | s(1627157, 253, 189, 1, 0, 0, dt | hr | hd, 866.116), 166 | s(2201460, 551, 360, 1, 0, 0, dt | nc | hd, 862.93), 167 | s(1969946, 411, 276, 10, 0, 0, dt | hr | hd, 862.101), 168 | s(2684029, 1349, 939, 1, 0, 0, dt | hd, 860.527), 169 | s(2444148, 495, 325, 8, 0, 0, dt | hd, 858.947), 170 | s(1769144, 610, 444, 25, 0, 0, dt | hd, 856.07), 171 | s(1570203, 393, 340, 14, 0, 2, dt | hr | hd, 853.631), 172 | s(2410945, 516, 333, 7, 0, 1, dt | hd, 853.61), 173 | s(2047629, 673, 424, 4, 0, 0, dt | nc | hd, 849.812), 174 | s(1875396, 854, 591, 20, 0, 0, dt | hd, 846.457), 175 | s(2603107, 497, 315, 5, 0, 0, dt | hr | hd, 843.961), 176 | s(2684033, 1114, 966, 24, 2, 4, dt | hd, 840.548), 177 | s(1807735, 456, 384, 8, 0, 1, dt | hd, 839.68), 178 | s(1675085, 1483, 914, 37, 0, 0, dt | hd, 839.281), 179 | s(1911308, 527, 293, 9, 0, 1, dt | nc | hr | hd, 835.796), 180 | s(2245774, 192, 140, 7, 0, 0, dt | hr | hd, 835.499), 181 | s(1690355, 636, 407, 14, 0, 1, dt | hd, 832.736), 182 | s(999944, 1010, 731, 11, 0, 1, dt | hd, 832.124), 183 | s(1537817, 1636, 1165, 16, 0, 0, dt | hd, 830.447), 184 | s(1828346, 499, 359, 29, 2, 5, dt | hd, 829.187), 185 | s(1818428, 566, 392, 7, 0, 0, dt | hd, 828.36), 186 | s(1941712, 1782, 1293, 14, 0, 0, dt | hd, 827.326), 187 | s(1828342, 505, 340, 15, 2, 2, dt | hd, 826.952), 188 | s(2410948, 527, 375, 16, 0, 2, dt | hd, 826.469), 189 | s(2330357, 1424, 913, 40, 0, 0, dt | hd, 822.444), 190 | s(1829131, 512, 357, 12, 3, 3, dt | hd, 822.043), 191 | s(1805627, 534, 412, 19, 0, 6, dt | hd, 822.003), 192 | s(2518847, 232, 162, 6, 0, 0, dt | nc | hr | hd, 820.945), 193 | s(2530755, 537, 372, 14, 2, 1, dt | hr | hd, 820.55), 194 | s(687303, 2266, 1876, 46, 0, 2, dt | hd, 819.989), 195 | s(1659421, 516, 363, 12, 0, 0, dt | hr | hd, 819.902), 196 | s(1919344, 1084, 754, 17, 0, 0, dt | hd, 819.37), 197 | s(2559126, 588, 400, 13, 0, 0, dt | hd, 818.703), 198 | s(2129143, 623, 437, 25, 0, 0, dt | hd, 816.859), 199 | s(2428358, 747, 541, 12, 0, 0, dt | hd, 815.618), 200 | s(1992711, 173, 136, 2, 0, 0, dt | hr | hd, 813.688), 201 | s(2360153, 504, 352, 8, 0, 0, dt | hr | hd, 813.586), 202 | s(2026320, 448, 291, 14, 0, 1, dt | hr | hd, 812.089), 203 | s(2461085, 563, 397, 12, 0, 0, dt | hr | hd, 811.765), 204 | s(1554502, 1703, 1179, 37, 0, 0, dt | hd, 809.057), 205 | s(1874119, 548, 397, 4, 0, 0, dt | hd, 807.838), 206 | s(1737169, 626, 487, 10, 0, 0, dt | hd, 803.963), 207 | s(1700568, 2012, 1286, 35, 0, 0, dt | hd, 803.542), 208 | s(2164997, 618, 449, 11, 0, 0, dt | hd, 803.192), 209 | s(2620911, 281, 207, 8, 0, 0, dt | hd, 799.836), 210 | s(1594266, 1259, 882, 21, 0, 4, dt | hd, 798.962), 211 | s(1610746, 239, 196, 2, 0, 0, dt | hd, 798.682), 212 | s(1144515, 1848, 1186, 25, 0, 0, dt | hd, 795.289), 213 | s(2726492, 606, 422, 2, 0, 0, dt | hd, 794.569), 214 | s(1450423, 483, 369, 13, 0, 0, dt | hd, 793.715), 215 | s(2169604, 638, 454, 30, 0, 0, dt | hd, 792.624), 216 | s(142954, 1160, 856, 20, 0, 3, dt | hd, 791.454), 217 | s(2368880, 1594, 1090, 46, 0, 0, dt | hd, 791.155), 218 | s(2596586, 595, 412, 6, 0, 0, dt | hd, 787.63), 219 | s(816600, 550, 409, 7, 0, 0, dt | hd, 787.143), 220 | s(865344, 1542, 1029, 23, 0, 0, dt | hd, 784.748), 221 | s(1869933, 379, 251, 7, 0, 3, dt | hr | hd, 782.899), 222 | s(2485438, 527, 385, 8, 1, 0, dt | hd, 780.599), 223 | s(2245777, 197, 150, 2, 0, 0, dt | hr | hd, 778.1), 224 | s(1605598, 581, 511, 17, 0, 4, dt | hd, 777.663), 225 | s(1385398, 386, 363, 17, 0, 5, dt | hd, 777.301), 226 | s(1695382, 530, 540, 14, 0, 1, dt | nc | hd, 1149.83), 227 | s(111680, 1109, 710, 138, 3, 0, dt | hr | hd, 1144.15), 228 | s(1842043, 1555, 1110, 7, 0, 0, dt | hd, 1092.05), 229 | s(116128, 849, 575, 33, 1, 0, dt | hd, 1056.18), 230 | s(2477065, 1627, 1023, 21, 0, 0, dt | hd, 1037.52), 231 | s(2615748, 964, 695, 5, 0, 2, dt | hd, 1012.3), 232 | s(1632673, 927, 586, 13, 0, 2, dt | hr | hd, 1011.85), 233 | s(106033, 870, 647, 20, 0, 0, dt | hr | hd, 986.523), 234 | s(1030374, 669, 508, 26, 0, 0, dt | hd, 968.932), 235 | s(199593, 975, 645, 47, 0, 0, dt | hr | hd, 896.057), 236 | s(2365928, 882, 697, 22, 0, 0, dt | hd, 885.361), 237 | s(1702172, 540, 530, 30, 0, 5, dt | hr | hd, 881.386), 238 | s(1942218, 675, 461, 33, 0, 0, dt | hr | hd, 880.43), 239 | s(1209189, 2023, 1625, 43, 1, 1, dt, 878.343), 240 | s(161787, 505, 407, 12, 0, 0, dt | hr | hd, 867.164), 241 | s(1754777, 1246, 1611, 58, 9, 14, dt, 854.65), 242 | s(2316176, 2351, 1537, 23, 0, 0, dt | hd, 847.769), 243 | s(126859, 716, 536, 22, 0, 0, dt | hr | hd, 843.576), 244 | s(675734, 1821, 2036, 102, 4, 6, dt, 837.741), 245 | s(1650477, 588, 464, 4, 0, 0, dt | hd, 836.435), 246 | s(74684, 1974, 1372, 51, 1, 0, dt, 829.431), 247 | s(847314, 1290, 848, 44, 0, 0, dt | hr | hd, 825.423), 248 | s(1032126, 623, 485, 13, 0, 0, dt | hd, 824.893), 249 | s(196839, 832, 519, 6, 0, 0, dt | hd, 816.835), 250 | s(241578, 880, 551, 14, 0, 0, dt | hr | hd, 814.071), 251 | s(195305, 946, 560, 18, 0, 0, dt, 809.014), 252 | s(2653571, 727, 643, 3, 0, 1, dt | hd, 800.708), 253 | s(244224, 582, 399, 6, 0, 0, dt | hr | hd, 797.096), 254 | s(83338, 916, 613, 13, 0, 0, dt | hr | hd, 788.305), 255 | s(776749, 1134, 839, 10, 0, 1, dt | hd, 783.565), 256 | s(1640135, 789, 573, 17, 1, 0, dt | hd, 777.388), 257 | s(1578009, 1365, 906, 14, 0, 0, dt | hd, 766.161), 258 | s(86757, 747, 549, 18, 0, 0, dt | hd, 764.69), 259 | s(2156842, 2012, 1215, 21, 1, 0, dt | hd, 748.575), 260 | s(533775, 1041, 704, 14, 0, 0, dt | hd, 742.939), 261 | s(742975, 881, 585, 10, 0, 2, dt | hd, 742.112), 262 | s(75359, 625, 467, 13, 0, 0, dt | hd, 732.827), 263 | s(2308904, 1298, 897, 11, 1, 0, dt | hd, 727.874), 264 | s(335628, 1828, 2019, 48, 2, 2, dt, 721.373), 265 | s(341891, 895, 624, 18, 0, 0, dt | hd, 719.32), 266 | s(2116001, 1175, 1056, 21, 0, 0, dt | hd, 717.88), 267 | s(1706834, 533, 477, 20, 1, 4, dt, 714.501), 268 | s(662064, 974, 688, 25, 0, 0, dt | hd, 709.645), 269 | s(1982762, 778, 559, 28, 1, 3, dt | hd, 705.491), 270 | s(2109842, 1543, 1300, 23, 1, 1, dt | hd, 691.438), 271 | s(767774, 491, 486, 17, 0, 2, dt | hd, 688.856), 272 | s(764293, 785, 620, 15, 0, 2, dt | hd, 685.832), 273 | s(119238, 1041, 981, 6, 0, 1, dt | hd, 680.149), 274 | s(2410945, 406, 330, 9, 0, 2, dt | hd, 677.782), 275 | s(356968, 730, 529, 16, 0, 0, dt | hd, 665.118), 276 | s(1631290, 626, 647, 12, 1, 2, dt | hd, 665.091), 277 | s(1929567, 606, 381, 2, 0, 0, dt | hd, 663.437), 278 | s(435350, 1449, 1040, 11, 0, 1, dt | hd, 657.785), 279 | s(50892, 779, 699, 30, 0, 0, dt, 653.457), 280 | s(386759, 990, 660, 6, 0, 0, dt | hd, 646.294), 281 | s(261705, 562, 381, 0, 0, 0, dt | hd, 644.886), 282 | s(827803, 1145, 1180, 33, 2, 8, dt, 642.817), 283 | s(1962756, 550, 389, 7, 0, 0, dt | hd, 642.734), 284 | s(103132, 592, 535, 24, 0, 1, dt | hr | hd, 635.552), 285 | s(1577751, 446, 501, 9, 0, 3, dt | hd, 633.504), 286 | s(231917, 457, 347, 14, 0, 0, dt | hd, 629.4), 287 | s(180104, 932, 781, 36, 3, 1, dt | hd, 625.244), 288 | s(1172819, 1638, 1852, 51, 0, 2, nomod, 623.785), 289 | s(43388, 539, 470, 1, 0, 0, dt | nc | hd, 617.854), 290 | s(209276, 733, 603, 16, 0, 2, dt | hd, 615.857), 291 | s(136749, 536, 539, 3, 0, 1, dt | hd, 613.096), 292 | s(1642274, 465, 350, 4, 0, 4, dt | hd, 612.106), 293 | s(1863272, 663, 456, 10, 0, 0, dt | hd, 611.476), 294 | s(258634, 679, 681, 6, 1, 2, dt | hd, 610.795), 295 | s(795627, 776, 980, 15, 0, 3, dt | hd, 610.729), 296 | s(2192267, 949, 1057, 24, 0, 7, dt | hd, 608.532), 297 | s(245284, 831, 624, 79, 2, 1, dt, 602.225), 298 | s(2102287, 213, 155, 3, 0, 0, dt | hd, 601.102), 299 | s(297668, 567, 639, 10, 0, 3, dt | hd, 600.141), 300 | s(52398, 437, 361, 35, 0, 0, dt | hr | hd, 598.898), 301 | s(2330482, 521, 612, 34, 10, 10, dt | hd, 598.718), 302 | s(58915, 1382, 1169, 29, 0, 0, dt | hd, 598.059), 303 | s(2058788, 515, 334, 19, 0, 3, dt | hd, 591.712), 304 | s(722596, 1164, 1421, 63, 0, 2, dt | hd, 590.328), 305 | s(676172, 670, 985, 15, 2, 8, dt | hd, 589.997), 306 | s(1783947, 453, 428, 12, 0, 3, dt | hd, 588.632), 307 | s(861164, 541, 560, 18, 0, 7, dt | hd, 587.726), 308 | s(117641, 637, 523, 5, 0, 1, dt, 586.279), 309 | s(80991, 903, 508, 29, 0, 0, dt | hd, 584.31), 310 | s(261729, 668, 488, 8, 0, 0, dt | hd, 583.719), 311 | s(718679, 1477, 1721, 37, 0, 13, dt, 583.574), 312 | s(137303, 722, 589, 6, 1, 0, dt, 582.738), 313 | s(75831, 739, 588, 45, 0, 0, dt | hr | hd, 580.593), 314 | s(2109844, 1115, 1276, 24, 1, 5, dt | hd, 580.242), 315 | s(76717, 889, 517, 30, 1, 0, dt | hr | hd, 580.083), 316 | s(98566, 754, 518, 6, 0, 0, dt | hd, 575.413), 317 | s(1156927, 368, 432, 11, 1, 1, dt, 575.238), 318 | s(962500, 814, 1220, 10, 0, 3, dt | hd, 572), 319 | s(293134, 1064, 725, 20, 1, 2, dt, 570.517), 320 | s(93836, 657, 423, 0, 0, 1, dt, 569.818), 321 | s(1807735, 424, 376, 10, 0, 7, dt | hd, 567.712), 322 | s(45658, 515, 338, 6, 0, 0, dt | hd, 563.061), 323 | s(1655981, 323, 349, 22, 0, 5, dt | hd, 562.83), 324 | s(148009, 735, 532, 20, 0, 0, dt | hd, 562.625), 325 | s(484890, 810, 575, 3, 0, 1, dt | hd, 562.451), 326 | s(2069841, 974, 674, 7, 0, 0, dt | hd, 1067.86), 327 | s(1919312, 1812, 1219, 8, 0, 0, dt | hd, 1049.58), 328 | s(1872396, 1682, 1245, 11, 0, 0, dt | hd, 1044.61), 329 | s(1816113, 4078, 2977, 38, 0, 0, nomod, 968.724), 330 | s(2058788, 537, 348, 8, 0, 0, dt | hr | hd, 966.545), 331 | s(2049467, 7540, 5650, 12, 0, 0, nomod, 966.49), 332 | s(1636104, 1260, 1149, 23, 0, 2, dt | hd, 954.234), 333 | s(1258642, 1720, 1231, 8, 0, 0, dt | hd, 934.609), 334 | s(652234, 1261, 889, 18, 0, 2, dt | hd, 934.305), 335 | s(795627, 1328, 981, 17, 0, 0, dt | hd, 926.57), 336 | s(2469345, 464, 336, 1, 0, 0, dt | hd, 923.355), 337 | s(2502643, 3088, 2540, 23, 0, 0, nomod, 918.575), 338 | s(1351114, 2085, 1377, 42, 0, 2, dt | hd, 893.909), 339 | s(1943046, 3016, 2440, 15, 0, 0, nomod, 888.887), 340 | s(1860433, 510, 329, 20, 0, 3, dt | hr | hd, 877.889), 341 | s(1892257, 506, 372, 7, 0, 0, dt | hd, 874.242), 342 | s(1860169, 3635, 2806, 26, 0, 5, hr, 844.016), 343 | s(2097898, 428, 309, 7, 0, 0, dt | hd, 836.648), 344 | s(438456, 1195, 1018, 16, 0, 2, dt | hd, 824.541), 345 | s(774965, 1774, 1164, 9, 0, 0, dt | hd, 823.89), 346 | s(1969946, 396, 278, 7, 0, 1, dt | hr | hd, 819.596), 347 | s(1484776, 575, 418, 5, 0, 0, dt | hd, 816.914), 348 | s(1537817, 1637, 1160, 21, 0, 0, dt | hd, 816.061), 349 | s(734339, 2048, 1247, 20, 1, 0, dt | hd, 812.53), 350 | s(2164997, 619, 451, 9, 0, 0, so | dt | nf | hd, 812.33), 351 | s(865344, 1541, 1037, 15, 0, 0, dt | hd, 807.59), 352 | s(1451282, 1366, 1059, 7, 0, 0, hr | hd, 806.048), 353 | s(853625, 1099, 976, 11, 0, 2, dt | hd, 805.959), 354 | s(1857519, 402, 259, 3, 0, 1, dt | hd, 803.557), 355 | s(1955170, 509, 353, 10, 0, 0, dt | hd, 796.508), 356 | s(1619555, 472, 382, 3, 0, 0, dt | hd, 795.229), 357 | s(2530755, 562, 384, 5, 0, 0, dt | hd, 791.566), 358 | s(2237466, 523, 379, 2, 0, 0, dt | hd, 787.636), 359 | s(2083422, 896, 771, 7, 0, 3, dt | hd, 782.354), 360 | s(999944, 943, 728, 13, 1, 1, dt | hd, 779.882), 361 | s(1570203, 428, 348, 7, 0, 1, dt | hd, 770.964), 362 | s(1046339, 724, 526, 2, 0, 0, dt | hd, 758.014), 363 | s(1412805, 2191, 1669, 4, 0, 0, hr, 757.235), 364 | s(790415, 1524, 1155, 10, 0, 0, dt | hd, 755.606), 365 | s(1385397, 453, 372, 7, 0, 2, dt | hd, 754.285), 366 | s(2146365, 3070, 2824, 28, 0, 4, nomod, 747.85), 367 | s(827488, 1229, 828, 3, 0, 0, dt | hd, 747.278), 368 | s(1528015, 3375, 3089, 19, 0, 1, nomod, 745.323), 369 | s(1977528, 481, 318, 8, 0, 0, dt | hd, 741.442), 370 | s(907850, 1483, 1097, 11, 0, 0, dt | hd, 738.175), 371 | s(1221602, 1788, 1243, 12, 0, 1, dt | hd, 736.041), 372 | s(1115737, 1593, 1130, 3, 0, 0, hr | hd, 734.75), 373 | s(804164, 982, 782, 20, 0, 0, dt | hd, 734.308), 374 | s(1600415, 1924, 1956, 18, 3, 4, hr, 733.761), 375 | s(1690355, 493, 414, 7, 0, 1, dt | hd, 727.954), 376 | s(2609731, 366, 268, 3, 0, 0, dt | hd, 727.365), 377 | s(718156, 2798, 2190, 16, 0, 1, hr, 722.781), 378 | s(1649528, 959, 741, 2, 0, 0, hr, 722.566), 379 | s(2188031, 517, 350, 25, 0, 0, so | dt | nf | hd, 722.527), 380 | s(1911308, 465, 290, 11, 0, 2, dt | hr | hd, 720.644), 381 | s(1501472, 611, 387, 10, 0, 0, dt | hd, 720.312), 382 | s(1630299, 879, 593, 13, 0, 0, dt | hd, 720.295), 383 | s(980431, 506, 387, 7, 0, 0, dt | hd, 720.149), 384 | s(1046536, 870, 737, 25, 1, 1, dt | hd, 718.519), 385 | s(939618, 1757, 1314, 9, 0, 1, hr | hd, 716.634), 386 | s(1031991, 3018, 3094, 41, 3, 3, nomod, 715.212), 387 | s(1187393, 599, 409, 2, 0, 0, dt | hd, 714.038), 388 | s(2596015, 333, 248, 1, 0, 0, dt | hd, 712.391), 389 | s(1763013, 589, 390, 3, 0, 2, dt | hd, 711.911), 390 | s(2061192, 1350, 1059, 8, 0, 1, nomod, 710.693), 391 | s(924337, 1860, 1251, 14, 0, 3, hr, 710.618), 392 | s(1377957, 1397, 1384, 15, 1, 4, hr, 709.49), 393 | s(1270000, 1447, 1156, 11, 0, 2, hr | hd, 709.305), 394 | s(2245774, 192, 142, 5, 0, 0, dt | hd, 709.251), 395 | s(735272, 474, 333, 5, 0, 1, dt | hd, 706.429), 396 | s(696225, 936, 676, 7, 0, 0, dt | hd, 705.98), 397 | s(953586, 752, 524, 7, 0, 0, dt | hd, 705.08), 398 | s(1063410, 530, 360, 7, 0, 0, dt | hd, 702.348), 399 | s(1642274, 474, 351, 6, 0, 1, dt | hd, 702.11), 400 | s(1187302, 2011, 1479, 15, 0, 1, hr | hd, 701.716), 401 | s(1241370, 2451, 2167, 46, 3, 7, hr | hd, 700.672), 402 | s(1003944, 553, 426, 1, 0, 1, dt | hd, 693.879), 403 | s(1546425, 1513, 1197, 6, 0, 0, hr | hd, 693.826), 404 | s(791274, 2911, 2200, 13, 0, 0, hr, 693.448), 405 | s(821548, 925, 670, 14, 0, 0, dt | nc | hd, 692.669), 406 | s(1201636, 1670, 2041, 27, 5, 1, hr, 692.623), 407 | s(655794, 1147, 750, 11, 0, 0, dt, 692.413), 408 | s(2699196, 1179, 1104, 19, 4, 4, hr, 690.113), 409 | s(2128030, 1812, 1570, 36, 0, 8, nomod, 689.997), 410 | s(1586059, 488, 358, 4, 0, 1, dt | hd, 689.934), 411 | s(965243, 2170, 1413, 20, 0, 0, hr, 687.787), 412 | s(1571309, 763, 548, 20, 0, 1, dt | hd, 687.751), 413 | s(1861487, 1858, 2331, 31, 0, 9, hr, 687.301), 414 | s(816600, 496, 406, 9, 0, 1, dt | hd, 686.086), 415 | s(2084629, 1422, 1034, 3, 0, 0, hr | hd, 684.188), 416 | s(848994, 675, 487, 8, 0, 0, dt | hd, 683.278), 417 | s(1419243, 1325, 1079, 10, 0, 3, hr, 682.426), 418 | s(1665829, 1350, 962, 10, 0, 0, nomod, 680.946), 419 | s(1097543, 1265, 1086, 8, 0, 3, dt | hd, 680.787), 420 | s(954692, 570, 395, 12, 0, 0, dt | hd, 679.202), 421 | s(1366190, 880, 726, 26, 0, 0, nomod, 678.345), 422 | s(1071242, 467, 336, 5, 0, 0, dt | hd, 676.102), 423 | s(1572198, 616, 434, 4, 0, 0, dt | hd, 675.253), 424 | s(785097, 1329, 958, 17, 0, 1, dt | hd, 672.08), 425 | s(894189, 1832, 1267, 10, 0, 0, dt | hd, 670.024), 426 | s(2628691, 1317, 947, 13, 0, 0, dt, 971.998), 427 | s(2469345, 432, 323, 13, 0, 1, dt | nc | hr | hd, 940.674), 428 | s(2185824, 1475, 964, 22, 0, 0, dt | nc | hd, 925.722), 429 | s(2315558, 1352, 994, 33, 2, 0, dt, 915.621), 430 | s(2486881, 1628, 1132, 20, 1, 3, dt | hd, 908.91), 431 | s(2231831, 561, 405, 6, 0, 0, dt | hd, 896.312), 432 | s(1969946, 409, 279, 7, 0, 0, dt | hr | hd, 885.68), 433 | s(2058788, 535, 333, 22, 1, 0, dt | hr | hd, 882.391), 434 | s(1554502, 1703, 1202, 14, 0, 0, dt | nc | hd, 879.175), 435 | s(1778560, 1250, 915, 9, 0, 0, dt, 878.905), 436 | s(2596015, 332, 247, 2, 0, 0, dt | hr | hd, 867.446), 437 | s(2616989, 1438, 1065, 29, 2, 2, dt | hd, 867.075), 438 | s(1955170, 451, 337, 25, 0, 1, dt | nc | hr | hd, 855.313), 439 | s(2097898, 426, 311, 5, 0, 0, dt | nc | hd, 851.721), 440 | s(2444148, 495, 324, 9, 0, 0, dt | hd, 851.499), 441 | s(2428358, 729, 548, 5, 0, 0, dt | hd, 846.978), 442 | s(2559126, 588, 404, 9, 0, 0, dt | hd, 843.835), 443 | s(2149819, 504, 339, 27, 0, 0, dt | hr | hd, 841.948), 444 | s(1911308, 519, 287, 16, 0, 0, dt | nc | hr | hd, 828.715), 445 | s(1872396, 1523, 1203, 50, 2, 1, dt | hd, 826.486), 446 | s(2404778, 536, 375, 9, 0, 0, dt | hd, 826.1), 447 | s(2530755, 556, 352, 37, 0, 0, dt | hr | hd, 820.231), 448 | s(1867520, 1212, 895, 15, 0, 0, dt | hd, 820.191), 449 | s(2164997, 619, 451, 9, 0, 0, dt | hd, 816.784), 450 | s(1892257, 506, 363, 15, 1, 0, dt | hd, 813.977), 451 | s(2237466, 523, 381, 0, 0, 0, dt | hd, 810.194), 452 | s(2468866, 505, 347, 6, 0, 0, dt | hd, 810.161), 453 | s(1978372, 1223, 812, 16, 1, 0, dt | hd, 808.644), 454 | s(1983406, 787, 512, 21, 0, 0, dt | hd, 807.251), 455 | s(2518847, 221, 163, 5, 0, 0, dt | hr | hd, 804.1), 456 | s(1860433, 542, 342, 8, 0, 2, dt | nc | hd, 799.017), 457 | s(2628166, 544, 383, 5, 0, 1, dt, 796.078), 458 | s(2461085, 535, 402, 6, 0, 1, dt | hr | hd, 791.381), 459 | s(1473301, 808, 538, 5, 0, 0, dt | hd, 789.36), 460 | s(1619555, 472, 381, 3, 1, 0, dt | hd, 783.307), 461 | s(2026320, 464, 303, 3, 0, 0, dt | nc | hd, 783.128), 462 | s(1857519, 405, 253, 9, 1, 0, dt | hd, 782.576), 463 | s(1571309, 802, 557, 12, 0, 0, dt | hd, 782.16), 464 | s(1736329, 530, 365, 18, 0, 0, dt | hd, 779.96), 465 | s(2164274, 618, 432, 1, 1, 0, dt | hd, 777.167), 466 | s(1804393, 1166, 776, 19, 1, 0, dt | hd, 763.163), 467 | s(1919312, 1578, 1159, 64, 1, 3, dt | hd, 752.868), 468 | s(1570203, 446, 349, 4, 0, 3, dt | hd, 751.236), 469 | s(2684033, 1053, 960, 31, 1, 4, dt, 748.533), 470 | s(1640330, 783, 559, 3, 1, 1, dt | nc | hd, 747.88), 471 | s(1843653, 415, 228, 12, 0, 2, dt | nc | hr | hd, 746.174), 472 | s(2201460, 550, 344, 17, 0, 0, dt | hd, 740.544), 473 | s(2149821, 501, 349, 9, 0, 0, dt | hd, 739.7), 474 | s(1992711, 172, 131, 7, 0, 0, dt | hr | hd, 737.86), 475 | s(2044362, 525, 367, 12, 1, 0, dt | hd, 735.834), 476 | s(2428357, 764, 535, 7, 0, 0, dt | hd, 735.748), 477 | s(2245774, 192, 144, 3, 0, 0, dt | nc | hd, 735.54), 478 | s(2188031, 515, 352, 23, 0, 0, dt | hd, 731.292), 479 | s(2246465, 174, 130, 5, 0, 0, dt | hr | hd, 725.735), 480 | s(2044366, 506, 354, 7, 0, 0, dt | hd, 724.102), 481 | s(1580814, 518, 382, 13, 0, 0, dt | nc | hd, 723.596), 482 | s(1763013, 597, 387, 7, 0, 1, dt | nc | hd, 715.304), 483 | s(1630299, 879, 592, 14, 0, 0, dt | hd, 715.092), 484 | s(1742483, 530, 408, 12, 0, 0, dt | hd, 714.511), 485 | s(2474574, 537, 364, 10, 0, 0, dt | hd, 713.255), 486 | s(1621068, 471, 372, 5, 0, 0, dt | nc | hd, 711.782), 487 | s(1610745, 252, 190, 4, 0, 0, dt | nc | hd, 707.446), 488 | s(1640362, 799, 559, 21, 4, 1, dt | hd, 706.965), 489 | s(1968445, 418, 302, 1, 0, 0, dt | hd, 706.343), 490 | s(2197401, 1364, 884, 8, 0, 2, dt | hd, 703.523), 491 | s(2010006, 629, 476, 10, 0, 0, dt | hd, 701.126), 492 | s(2120669, 559, 388, 9, 0, 0, dt | hd, 696.789), 493 | s(1988911, 511, 380, 8, 2, 0, dt | nc | hd, 696.304), 494 | s(1749322, 427, 318, 6, 0, 0, dt | hd, 696.265), 495 | s(2034983, 499, 341, 20, 3, 0, dt | hd, 695.108), 496 | s(1971822, 542, 396, 7, 0, 0, dt | hd, 695.09), 497 | s(1893461, 147, 99, 0, 0, 0, dt | nc | hr | hd, 695.036), 498 | s(1893432, 503, 351, 12, 0, 1, dt | hd, 693.943), 499 | s(1649064, 916, 665, 35, 2, 0, dt | hd, 693.94), 500 | s(1977528, 479, 310, 16, 0, 0, dt | nc | hd, 692.75), 501 | s(994495, 244, 195, 5, 0, 0, dt | hd, 692.701), 502 | s(2078961, 520, 356, 9, 0, 0, dt | hd, 691.279), 503 | s(2360153, 503, 357, 3, 0, 0, dt | hd, 689.932), 504 | s(1659421, 516, 362, 13, 0, 0, dt | nc | hd, 689.261), 505 | s(2112902, 485, 350, 6, 0, 1, dt | hd, 689.133), 506 | s(2397136, 574, 379, 10, 1, 0, dt | hd, 688.583), 507 | s(2149822, 552, 366, 20, 0, 0, dt | hd, 684.741), 508 | s(1102144, 517, 374, 2, 0, 0, dt | hd, 682.741), 509 | s(2351580, 582, 406, 20, 1, 0, dt | hd, 681.576), 510 | s(2118444, 501, 336, 4, 0, 2, dt | hd, 681.462), 511 | s(1623285, 248, 204, 2, 0, 0, dt | hd, 680.538), 512 | s(2118440, 469, 342, 6, 0, 0, dt | hd, 677.282), 513 | s(2530810, 235, 161, 9, 0, 0, dt | hr | hd, 676.159), 514 | s(2563055, 466, 345, 8, 0, 0, dt | hd, 673.661), 515 | s(2112908, 490, 302, 20, 0, 0, dt | nc | hd, 673.537), 516 | s(1579673, 858, 584, 20, 2, 0, dt | nc | hd, 672.542), 517 | s(2546306, 404, 347, 8, 0, 1, dt | hd, 671.528), 518 | s(2126265, 537, 356, 2, 0, 0, dt | hd, 671.516), 519 | s(2245768, 194, 136, 0, 0, 0, dt | hd, 671.326), 520 | s(1642274, 468, 347, 10, 0, 1, dt | nc | hd, 670.608), 521 | s(1993380, 169, 123, 2, 0, 0, dt | nc | hr | hd, 668.971), 522 | s(2245766, 193, 144, 1, 0, 0, dt | hd, 668.942), 523 | s(1852572, 518, 406, 20, 0, 3, dt | hd, 668.082), 524 | s(2237465, 520, 354, 5, 0, 0, dt | hd, 665.779), 525 | s(1919312, 1741, 1209, 16, 0, 2, dt | hd, 940.402), 526 | s(2616989, 1439, 1075, 22, 0, 1, dt | hd, 909.931), 527 | s(1522329, 1283, 939, 12, 0, 0, dt | hd, 894.404), 528 | s(2486881, 1544, 1136, 17, 0, 3, dt | hd, 890.879), 529 | s(860022, 2010, 1344, 32, 0, 0, dt | hd, 877.699), 530 | s(2034983, 506, 364, 0, 0, 0, dt | hd, 866.185), 531 | s(2083422, 1030, 760, 21, 0, 0, dt | hd, 864.287), 532 | s(1258642, 1720, 1211, 28, 0, 0, dt | hd, 863.625), 533 | s(1537817, 1637, 1172, 9, 0, 0, dt | hd, 853.91), 534 | s(795627, 1195, 978, 20, 0, 0, dt | hd, 851.706), 535 | s(1872396, 1351, 1239, 15, 0, 2, dt | hd, 850.969), 536 | s(1882794, 1546, 1105, 31, 1, 0, dt | hd, 847.863), 537 | s(1919344, 1083, 761, 10, 0, 0, dt | hd, 847.778), 538 | s(2368880, 1562, 1124, 11, 0, 1, dt | hd, 847.348), 539 | s(2164997, 619, 455, 5, 0, 0, dt | hd, 846.062), 540 | s(2129143, 604, 453, 8, 0, 1, dt | hd, 844.756), 541 | s(1994564, 430, 339, 6, 0, 0, dt | hd, 835.628), 542 | s(903667, 1746, 1504, 28, 1, 1, dt | hd, 834.706), 543 | s(1867520, 1212, 898, 12, 0, 0, dt | hd, 833.379), 544 | s(2022718, 617, 415, 1, 0, 0, dt | hd, 822.818), 545 | s(876685, 2237, 1560, 24, 0, 0, dt | hd, 821.643), 546 | s(2239445, 2607, 1985, 10, 0, 0, nomod, 817.335), 547 | s(2469345, 441, 332, 4, 0, 1, dt | hd, 814.102), 548 | s(2173528, 3259, 2503, 44, 0, 0, hd, 810.37), 549 | s(880206, 1976, 1918, 33, 1, 4, dt | hd, 809.759), 550 | s(770576, 2238, 1496, 28, 0, 0, dt | hd, 809.361), 551 | s(1936713, 1352, 1130, 25, 0, 4, dt | hd, 809.183), 552 | s(2459413, 1752, 1171, 25, 0, 0, dt | hd, 804.524), 553 | s(1808741, 967, 716, 17, 0, 3, dt | hd, 804.274), 554 | s(436723, 1858, 1205, 13, 1, 0, dt | hd, 798.649), 555 | s(499343, 1431, 1059, 7, 0, 0, dt | hd, 798.057), 556 | s(1911308, 482, 290, 13, 0, 0, dt | hr | hd, 797.763), 557 | s(688886, 1215, 826, 5, 0, 0, dt | hd, 796.906), 558 | s(1636104, 1169, 1125, 41, 3, 5, dt | hd, 796.731), 559 | s(2069999, 3233, 2872, 12, 0, 0, nomod, 792.6), 560 | s(2044362, 528, 374, 6, 0, 0, dt | nc | hd, 791.11), 561 | s(1988911, 511, 390, 0, 0, 0, dt | hd, 789.913), 562 | s(2231774, 1455, 1071, 30, 1, 0, dt | hd, 789.539), 563 | s(1649064, 967, 685, 17, 0, 0, dt | hd, 788.217), 564 | s(2025393, 1342, 1095, 13, 0, 1, dt | hd, 787.711), 565 | s(1857519, 405, 253, 10, 0, 0, dt | hd, 785.942), 566 | s(1630299, 879, 603, 3, 0, 0, dt | nc | hd, 785.741), 567 | s(1937747, 1170, 829, 9, 0, 0, dt | hd, 785.147), 568 | s(962500, 1479, 1200, 29, 2, 2, dt | hd, 780.679), 569 | s(2245774, 172, 144, 2, 0, 1, dt | hr | hd, 778.471), 570 | s(1977528, 481, 322, 4, 0, 0, dt | hd, 777.054), 571 | s(2058788, 441, 347, 7, 0, 2, dt | hr | hd, 774.197), 572 | s(1220498, 1170, 898, 7, 1, 0, dt | hd, 773.896), 573 | s(865344, 1456, 1043, 8, 0, 1, dt | hd, 771.752), 574 | s(2315560, 1345, 986, 18, 0, 1, dt | hd, 770.696), 575 | s(1849580, 2452, 2109, 19, 0, 5, hd, 768.22), 576 | s(679903, 1993, 1324, 7, 0, 0, dt | hd, 767.336), 577 | s(2112908, 500, 317, 5, 0, 0, dt | hd, 766.881), 578 | s(1129535, 2062, 1400, 27, 1, 0, dt | hd, 765.914), 579 | s(1537566, 2424, 1806, 6, 0, 0, hr, 765.331), 580 | s(2461075, 1923, 1253, 3, 0, 0, dt | hd, 762.582), 581 | s(949029, 1080, 901, 10, 0, 2, dt | nc | hd, 761.954), 582 | s(816600, 550, 405, 11, 0, 0, dt | hd, 761.301), 583 | s(1124630, 1008, 772, 10, 0, 0, dt | hd, 760.941), 584 | s(1640330, 784, 556, 8, 0, 0, dt | nc | hd, 757.826), 585 | s(784411, 1038, 749, 12, 1, 0, dt | hd, 757.003), 586 | s(2178147, 1506, 1516, 29, 1, 1, dt | hd, 755.409), 587 | s(1600415, 2644, 1946, 34, 1, 0, nomod, 753.811), 588 | s(1818428, 542, 395, 1, 0, 3, dt | hd, 751.616), 589 | s(1594774, 1256, 864, 6, 0, 1, dt | hd, 751.594), 590 | s(1473301, 809, 532, 11, 0, 0, dt | hd, 751.43), 591 | s(1843653, 431, 240, 2, 0, 0, dt | hd, 751.152), 592 | s(1776628, 1268, 977, 3, 0, 2, dt | hd, 750.223), 593 | s(1000684, 1751, 1217, 17, 0, 0, dt | hd, 747.075), 594 | s(2315558, 884, 1003, 26, 0, 0, dt | hd, 744.004), 595 | s(898195, 1454, 1349, 17, 1, 2, dt | hd, 743.113), 596 | s(1951973, 483, 390, 2, 0, 0, dt | hd, 739.02), 597 | s(2287401, 538, 391, 4, 0, 0, dt | hd, 738.746), 598 | s(2231706, 825, 687, 10, 0, 2, dt | hd, 737.43), 599 | s(1074470, 1723, 1066, 6, 0, 0, dt | hd, 736.877), 600 | s(2428358, 693, 539, 13, 0, 1, dt | hd, 736.798), 601 | s(1633675, 1448, 1032, 20, 0, 0, dt | hd, 736.549), 602 | s(1762429, 460, 340, 8, 1, 0, dt | hr | hd, 736.534), 603 | s(255700, 1815, 1216, 7, 0, 0, dt | hd, 736.111), 604 | s(1485947, 1502, 1222, 29, 1, 1, dt | hd, 736.057), 605 | s(725908, 638, 501, 6, 0, 0, dt | hd, 735.894), 606 | s(2444148, 417, 326, 6, 0, 1, dt | hd, 734.278), 607 | s(2321904, 1504, 901, 10, 0, 0, dt | hd, 731.862), 608 | s(2654458, 1686, 1334, 9, 0, 0, nomod, 730.825), 609 | s(1571309, 772, 556, 12, 0, 1, dt | hd, 729.625), 610 | s(1997892, 3206, 2733, 13, 0, 0, nomod, 728.112), 611 | s(2237466, 522, 376, 4, 0, 1, dt | hd, 727.988), 612 | s(1178526, 722, 546, 6, 0, 0, dt | hd, 727.267), 613 | s(889327, 542, 405, 7, 0, 0, dt | hd, 726.93), 614 | s(1246395, 1734, 1186, 19, 0, 0, dt | hd, 726.661), 615 | s(2120669, 559, 392, 5, 0, 0, dt | hd, 725.962), 616 | s(2360153, 504, 360, 0, 0, 0, dt | hd, 725.207), 617 | s(2034429, 975, 850, 20, 0, 1, dt | hd, 724.555), 618 | s(544850, 1601, 1135, 3, 0, 0, dt | hd, 724.479), 619 | s(1734135, 1311, 990, 5, 0, 0, dt | hd, 724.239), 620 | s(827488, 1229, 823, 8, 0, 0, dt | hd, 723.836), 621 | s(1271007, 544, 379, 9, 0, 0, dt | hd, 723.589), 622 | s(1689076, 1373, 1004, 17, 0, 0, dt | hd, 722.905), 623 | s(1081936, 1848, 1284, 37, 0, 0, dt | hd, 722.011), 624 | s(2486881, 1592, 1142, 13, 0, 1, dt | hd, 956.732), 625 | s(2330357, 1426, 946, 7, 0, 0, dt | hd, 946.132), 626 | s(1911308, 500, 301, 2, 0, 0, dt | nc | hr | hd, 927.946), 627 | s(1919312, 1690, 1192, 34, 1, 0, dt | hd, 904.643), 628 | s(1554502, 1703, 1206, 10, 0, 0, dt | hd, 894.819), 629 | s(1969946, 410, 279, 7, 0, 0, dt | nc | hr | hd, 887.211), 630 | s(2461085, 563, 405, 4, 0, 0, dt | hr | hd, 881.513), 631 | s(1983406, 788, 525, 8, 0, 0, dt | hd, 877.645), 632 | s(1955170, 509, 361, 2, 0, 0, dt | hd, 866.114), 633 | s(1892257, 504, 371, 8, 0, 0, dt | hd, 863.936), 634 | s(2593619, 2522, 1745, 35, 0, 0, dt | hd, 861.184), 635 | s(2404778, 536, 379, 5, 0, 0, dt | hd, 858.068), 636 | s(1867520, 1207, 901, 9, 0, 0, dt | hd, 845.349), 637 | s(2469345, 440, 335, 1, 0, 1, dt | hd, 843.107), 638 | s(2129143, 624, 442, 20, 0, 0, dt | hd, 836.989), 639 | s(2097898, 427, 306, 10, 0, 0, dt | hd, 812.633), 640 | s(2468866, 506, 346, 7, 0, 0, dt | hd, 802.873), 641 | s(1804393, 1170, 784, 12, 0, 0, dt | hd, 799.545), 642 | s(1977528, 481, 324, 2, 0, 0, dt | hd, 799.315), 643 | s(2245777, 197, 151, 1, 0, 0, dt | hr | hd, 799.148), 644 | s(2047629, 672, 417, 11, 0, 0, dt | hd, 796.718), 645 | s(1968836, 487, 334, 10, 0, 0, dt | hr | hd, 796.663), 646 | s(2518847, 232, 159, 9, 0, 0, dt | hr | hd, 792.403), 647 | s(2149819, 520, 361, 3, 2, 0, dt | hd, 791.746), 648 | s(2485438, 527, 386, 8, 0, 0, dt | hd, 790.354), 649 | s(1097543, 1536, 1086, 10, 0, 1, dt | hd, 789.432), 650 | s(2044362, 528, 374, 5, 1, 0, dt | nc | hd, 788.146), 651 | s(2360153, 504, 348, 12, 0, 0, dt | hr | hd, 782.29), 652 | s(1763013, 612, 390, 4, 1, 0, dt | hd, 779.048), 653 | s(1630299, 879, 602, 4, 0, 0, dt | hd, 777.934), 654 | s(2336704, 599, 401, 4, 0, 0, dt | hd, 776.575), 655 | s(2246465, 176, 132, 3, 0, 0, dt | hr | hd, 762.712), 656 | s(2164274, 618, 430, 4, 0, 0, dt | hd, 760.807), 657 | s(2237466, 523, 376, 5, 0, 0, dt | hd, 758.523), 658 | s(2444149, 492, 311, 8, 0, 0, dt | hr | hd, 758.208), 659 | s(1055147, 453, 309, 2, 2, 0, dt | hd, 757.153), 660 | s(2081844, 1064, 798, 5, 0, 0, dt | hd, 748.645), 661 | s(2185824, 1139, 966, 18, 0, 2, dt | hd, 746.555), 662 | s(2025393, 1302, 1088, 20, 0, 1, dt | hd, 745.66), 663 | s(2428357, 764, 536, 6, 0, 0, dt | hd, 742.449), 664 | s(1988911, 510, 385, 5, 0, 0, dt | hd, 739.347), 665 | s(1917082, 400, 300, 3, 0, 0, dt | hd, 738.831), 666 | s(2245774, 192, 144, 3, 0, 0, dt | hd, 735.54), 667 | s(2397136, 574, 385, 5, 0, 0, dt | hd, 734.44), 668 | s(2010006, 632, 481, 5, 0, 0, dt | hd, 734.373), 669 | s(2225036, 919, 623, 5, 0, 0, dt | hd, 734.187), 670 | s(2530810, 235, 166, 4, 0, 0, dt | hr | hd, 730.36), 671 | s(2428358, 683, 538, 14, 0, 1, dt | hd, 724.523), 672 | s(2245768, 193, 132, 4, 0, 0, dt | hr | hd, 723.825), 673 | s(2112902, 485, 354, 2, 0, 1, dt | hd, 722.178), 674 | s(1952177, 460, 363, 2, 0, 0, dt | hd, 721.239), 675 | s(1579673, 861, 594, 12, 0, 0, dt | nc | hd, 721.102), 676 | s(1989857, 175, 145, 0, 0, 0, dt | hd, 719.096), 677 | s(2474574, 536, 365, 9, 0, 0, dt | hd, 719.071), 678 | s(2197401, 1388, 878, 16, 0, 0, dt | hd, 717.599), 679 | s(2596018, 314, 229, 4, 0, 0, dt | hr | hd, 717.384), 680 | s(2245772, 187, 125, 1, 0, 0, dt | hd, 716.081), 681 | s(1843589, 592, 403, 4, 0, 0, dt | hd, 716.003), 682 | s(1619555, 471, 371, 14, 0, 0, dt | hd, 715.823), 683 | s(2044366, 519, 351, 10, 0, 0, dt | hd, 714.129), 684 | s(2112908, 489, 316, 5, 0, 1, dt | hd, 713.714), 685 | s(1784189, 687, 508, 10, 0, 2, dt | hd, 713.134), 686 | s(2225736, 531, 402, 4, 1, 0, dt | hd, 712.393), 687 | s(2102287, 213, 154, 4, 0, 0, dt | hr | hd, 710.438), 688 | s(953586, 751, 525, 6, 0, 0, dt | hd, 710.184), 689 | s(1935389, 583, 414, 10, 1, 0, dt | hd, 700.836), 690 | s(2410945, 470, 314, 25, 0, 2, dt | hd, 700.374), 691 | s(2287401, 538, 387, 6, 2, 0, dt | hd, 698.496), 692 | s(794670, 649, 451, 2, 0, 0, dt | hd, 698.08), 693 | s(1003944, 559, 422, 6, 0, 0, dt | hd, 697.55), 694 | s(1992711, 173, 138, 0, 0, 0, dt | hd, 696.183), 695 | s(1571309, 768, 556, 10, 0, 3, dt | hd, 695.795), 696 | s(2609731, 366, 265, 6, 0, 0, dt | hd, 695.792), 697 | s(1537817, 1445, 1148, 30, 2, 1, dt | hd, 695.707), 698 | s(1933561, 480, 331, 0, 0, 0, dt | hd, 694.55), 699 | s(1102144, 517, 375, 1, 0, 0, dt | hd, 692.717), 700 | s(1993380, 169, 124, 1, 0, 0, dt | hr | hd, 692.25), 701 | s(1675830, 244, 191, 1, 0, 0, dt | hr | hd, 691.345), 702 | s(1816320, 595, 427, 8, 1, 0, dt | hd, 690.531), 703 | s(1944926, 588, 540, 7, 0, 4, dt | hd, 689.582), 704 | s(1932923, 639, 420, 6, 0, 0, dt | hd, 688.495), 705 | s(2025942, 265, 182, 10, 0, 0, dt | hr | hd, 687.874), 706 | s(1534523, 252, 188, 2, 0, 0, dt | nc | hr | hd, 687.748), 707 | s(2596015, 333, 246, 3, 0, 0, dt | hd, 686.816), 708 | s(1968445, 418, 300, 3, 0, 0, dt | hd, 684.597), 709 | s(2120669, 558, 386, 11, 0, 0, dt | hd, 683.829), 710 | s(2050536, 471, 342, 2, 0, 0, dt | hd, 682.107), 711 | s(1752315, 421, 323, 2, 0, 0, dt | hd, 681.104), 712 | s(1885374, 609, 449, 4, 0, 0, dt | hd, 680.96), 713 | s(2078961, 520, 354, 11, 0, 0, dt | hd, 677.98), 714 | s(1970829, 413, 258, 2, 0, 0, dt | hr | hd, 677.035), 715 | s(2444148, 413, 321, 10, 0, 2, dt | hd, 676.864), 716 | s(2014389, 1532, 1069, 9, 0, 0, nomod, 673.914), 717 | s(1843653, 417, 240, 0, 0, 2, dt | hd, 672.901), 718 | s(2485259, 438, 327, 3, 0, 0, dt | hd, 672.573), 719 | s(2131079, 417, 296, 5, 1, 0, dt | hd, 670.963), 720 | s(2034983, 400, 360, 2, 1, 1, dt | hd, 670.962), 721 | s(2058788, 475, 340, 11, 0, 5, dt | hr | hd, 670.554), 722 | s(827488, 1221, 816, 14, 0, 1, dt | hd, 670.2), 723 | s(2185824, 1478, 977, 9, 0, 0, dt | hd, 985.195), 724 | s(1241370, 2724, 2215, 8, 0, 0, hr | hd, 905.305), 725 | s(2368880, 1596, 1126, 10, 0, 0, dt | hd, 894.454), 726 | s(1502678, 2004, 1460, 23, 0, 0, dt | hd, 882.585), 727 | s(2164997, 619, 458, 2, 0, 0, dt | hd, 872.014), 728 | s(658127, 2402, 1957, 16, 0, 0, hr | hd, 859.046), 729 | s(1955170, 509, 359, 4, 0, 0, dt | hd, 845.075), 730 | s(2469345, 441, 335, 1, 0, 1, dt | hd, 844.401), 731 | s(795627, 1328, 948, 50, 0, 0, dt | hd, 836.658), 732 | s(1754777, 2261, 1690, 2, 0, 0, hr | hd, 834.961), 733 | s(1849580, 2650, 2108, 24, 0, 1, hd, 834.407), 734 | s(2486881, 1405, 1128, 26, 1, 1, dt | hd, 829.143), 735 | s(1882794, 1544, 1097, 40, 0, 0, dt | hd, 827.265), 736 | s(2077119, 2838, 2254, 5, 0, 0, hr, 822.858), 737 | s(1201636, 2530, 2063, 11, 0, 0, hd, 819.705), 738 | s(2164274, 618, 434, 0, 0, 0, dt | hd, 801.781), 739 | s(1983406, 768, 520, 12, 0, 1, dt | hd, 799.72), 740 | s(1872396, 1502, 1203, 45, 4, 4, dt | hd, 778.771), 741 | s(1816113, 2785, 2970, 43, 0, 2, hd, 778.708), 742 | s(2069841, 821, 657, 17, 3, 4, dt | hd, 777.238), 743 | s(1893432, 505, 358, 6, 0, 0, dt | hd, 773.311), 744 | s(965243, 2173, 1423, 10, 0, 0, hr | hd, 773.15), 745 | s(638551, 1556, 1178, 9, 0, 0, dt | hd, 765.1), 746 | s(320098, 1389, 1018, 10, 0, 0, dt | hd, 764.77), 747 | s(1246395, 1734, 1196, 9, 0, 0, dt | hd, 762.585), 748 | s(1969946, 411, 283, 3, 0, 0, dt | hd, 761.02), 749 | s(1919312, 1517, 1191, 31, 0, 5, dt | hd, 760.957), 750 | s(2360394, 1811, 1991, 3, 0, 3, hr | hd, 760.19), 751 | s(1270000, 1451, 1162, 7, 0, 0, hr | hd, 758.698), 752 | s(1949106, 8266, 6849, 98, 0, 6, nomod, 757.884), 753 | s(1630299, 878, 599, 7, 0, 0, dt | hd, 755.778), 754 | s(1402392, 1709, 1389, 3, 2, 0, hr | hd, 753.421), 755 | s(1011658, 1597, 1197, 5, 0, 0, dt | hd, 753.326), 756 | s(2097898, 346, 315, 0, 0, 1, dt | hd, 749.295), 757 | s(1412813, 833, 555, 8, 1, 0, dt | hd, 742.235), 758 | s(1911308, 549, 298, 5, 0, 0, dt | nc | hd, 742.058), 759 | s(2225036, 920, 624, 4, 0, 0, dt | hd, 741.69), 760 | s(807074, 2636, 2112, 22, 0, 0, hd, 737.452), 761 | s(1619555, 465, 379, 5, 0, 1, dt | hd, 732.955), 762 | s(2118444, 503, 339, 2, 0, 1, dt | hd, 732.883), 763 | s(1081936, 1844, 1297, 23, 0, 1, dt | hd, 731.74), 764 | s(1640330, 784, 551, 13, 0, 0, dt | hd, 730.138), 765 | s(1537566, 2053, 1798, 14, 0, 0, hr | hd, 727.177), 766 | s(974662, 1969, 1418, 13, 0, 0, hr | hd, 726.847), 767 | s(1822108, 1375, 1429, 22, 0, 6, hr | hd, 726.232), 768 | s(2014389, 1531, 1068, 9, 1, 0, hd, 722.796), 769 | s(1412805, 2122, 1655, 13, 0, 5, hr | hd, 712.182), 770 | s(1438946, 3479, 2403, 13, 0, 0, nomod, 708.929), 771 | s(785097, 1341, 961, 15, 0, 0, dt | hd, 708.417), 772 | s(1028733, 620, 595, 27, 0, 2, hr | hd, 707.232), 773 | s(1917082, 400, 296, 7, 0, 0, dt | nc | hd, 704.154), 774 | s(1952177, 460, 361, 4, 0, 0, dt | hd, 702.864), 775 | s(1537817, 1453, 1150, 28, 2, 1, dt | hd, 702.849), 776 | s(1763938, 1029, 828, 4, 0, 0, hr | hd, 701.019), 777 | s(1749322, 428, 318, 6, 0, 0, dt | hd, 697.374), 778 | s(1610745, 253, 189, 5, 0, 0, dt | hd, 697.357), 779 | s(1981688, 1770, 1231, 19, 0, 0, dt | hd, 697.287), 780 | s(2239445, 2244, 1952, 38, 0, 5, hd, 697.039), 781 | s(1642274, 469, 351, 6, 0, 1, dt | hd, 696.766), 782 | s(341891, 850, 631, 10, 0, 1, dt | hd, 695.673), 783 | s(1351956, 2023, 1965, 28, 6, 5, hr | hd, 694.516), 784 | s(1554502, 1577, 1170, 42, 0, 4, dt | hd, 693.779), 785 | s(2654458, 1550, 1319, 23, 0, 1, hd, 690.887), 786 | s(890467, 968, 687, 19, 0, 0, dt | hd, 689.486), 787 | s(953586, 752, 521, 10, 0, 0, dt | hd, 689.194), 788 | s(774965, 1529, 1165, 7, 0, 1, dt, 688.343), 789 | s(1473299, 716, 458, 5, 0, 0, dt | hd, 686.961), 790 | s(1083052, 1531, 1187, 17, 0, 3, hr | hd, 686.123), 791 | s(306018, 1381, 1039, 9, 0, 1, dt | nc | hd, 686.06), 792 | s(861889, 2961, 2219, 25, 0, 1, hr | hd, 685.629), 793 | s(1720423, 437, 331, 3, 0, 0, dt | hd, 685.269), 794 | s(949029, 937, 896, 16, 0, 1, dt | nc | hd, 684.377), 795 | s(2249059, 2064, 1993, 14, 0, 2, nomod, 682.823), 796 | s(794670, 649, 449, 4, 0, 0, dt | hd, 681.289), 797 | s(1892257, 417, 366, 11, 0, 2, dt | hd, 678.769), 798 | s(1843589, 592, 398, 9, 0, 0, dt | hd, 674.93), 799 | s(2168768, 498, 346, 2, 0, 1, dt | hd, 673.335), 800 | s(1474956, 455, 315, 1, 0, 0, dt | hd, 673.24), 801 | s(696225, 934, 668, 15, 0, 0, dt | hd, 673.012), 802 | s(1621068, 465, 371, 5, 0, 1, dt | hd, 671.076), 803 | s(1289927, 710, 583, 10, 0, 0, hr, 670.904), 804 | s(985141, 2383, 1743, 19, 0, 0, hr | hd, 670.294), 805 | s(1988911, 511, 374, 16, 0, 0, dt | hd, 669.51), 806 | s(816600, 506, 401, 14, 0, 1, dt | hd, 669.453), 807 | s(1623093, 486, 386, 1, 0, 0, dt | hd, 667.865), 808 | s(1432041, 2592, 2014, 54, 0, 1, hr | hd, 666.873), 809 | s(1635388, 1263, 1139, 5, 0, 1, hr | hd, 666.405), 810 | s(92051, 3461, 2522, 63, 0, 0, hr | hd, 665.481), 811 | s(1763013, 594, 386, 6, 0, 3, dt | hd, 664.673), 812 | s(2079597, 1352, 2290, 21, 0, 5, hr | hd, 661.595), 813 | s(894189, 1835, 1264, 13, 0, 0, dt | hd, 660.665), 814 | s(1054525, 1023, 712, 10, 0, 0, dt | hd, 660.037), 815 | s(592113, 2324, 1977, 38, 6, 3, hr | hd, 658.597), 816 | s(1474795, 1356, 1008, 8, 0, 0, hr | hd, 656.523), 817 | s(851255, 1204, 818, 1, 0, 0, hr | hd, 655.769), 818 | s(1869933, 377, 244, 15, 0, 2, dt | nc | hd, 655.028), 819 | s(779179, 2332, 1859, 25, 1, 1, hr | hd, 654.345), 820 | s(1621319, 1001, 804, 6, 0, 0, hr | hd, 652.554), 821 | s(1849580, 2677, 2110, 23, 0, 0, hr, 1047.39), 822 | s(1822108, 1828, 1448, 9, 0, 0, hr, 947.936), 823 | s(2079597, 2445, 2300, 14, 0, 2, hr, 922.468), 824 | s(890190, 3469, 2565, 27, 0, 0, hr, 910.899), 825 | s(962194, 3969, 3407, 41, 2, 2, hr, 892.228), 826 | s(869222, 2384, 1996, 24, 2, 4, hr, 855.905), 827 | s(1377957, 1639, 1389, 15, 0, 0, hr, 851.809), 828 | s(1949106, 8692, 6902, 51, 0, 0, nomod, 846.948), 829 | s(1241370, 2724, 2213, 10, 0, 0, hr, 834.508), 830 | s(2077121, 2762, 2143, 13, 0, 3, hr, 831.615), 831 | s(1889232, 1974, 1578, 14, 0, 0, hr, 813.72), 832 | s(1860169, 3628, 2783, 49, 0, 5, hr, 812.404), 833 | s(1097543, 1628, 1090, 7, 0, 0, dt | nc, 810.466), 834 | s(2076663, 3279, 2758, 32, 0, 0, hr, 809.061), 835 | s(1028733, 716, 608, 16, 0, 0, hr, 807.159), 836 | s(1007522, 1992, 1970, 47, 0, 0, hr, 806.64), 837 | s(1179007, 2467, 1849, 23, 1, 0, hr, 805.279), 838 | s(2077119, 2839, 2243, 16, 0, 0, hr, 800.914), 839 | s(658127, 2402, 1959, 14, 0, 0, hr, 799.383), 840 | s(2111505, 1860, 1594, 13, 0, 1, hr, 779.947), 841 | s(1815397, 2762, 2350, 23, 0, 1, hr, 778.46), 842 | s(1419243, 1379, 1087, 5, 0, 0, hr, 766.115), 843 | s(1968674, 1791, 1409, 15, 4, 1, hr, 762.969), 844 | s(1745634, 4193, 3070, 43, 0, 0, hr, 761.754), 845 | s(369349, 7939, 6046, 7, 0, 0, nomod, 759.688), 846 | s(718156, 2811, 2196, 11, 0, 0, hr, 755.945), 847 | s(1537566, 2423, 1801, 11, 0, 0, hr, 752.322), 848 | s(1412805, 2193, 1667, 6, 0, 0, hr, 752.306), 849 | s(1816113, 3498, 2896, 111, 3, 5, nomod, 746.405), 850 | s(966920, 8158, 6278, 18, 0, 0, nomod, 746.08), 851 | s(670881, 3216, 2577, 73, 1, 0, hr, 745.562), 852 | s(774965, 1758, 1151, 19, 2, 1, dt | nc | hd, 744.475), 853 | s(1083052, 1721, 1200, 6, 0, 1, hr, 743.719), 854 | s(1886274, 2115, 2063, 43, 1, 7, hr, 742.015), 855 | s(1569904, 2311, 1200, 36, 0, 2, hr, 739.986), 856 | s(1351956, 2143, 1971, 33, 0, 0, hr, 734.531), 857 | s(787307, 5051, 4124, 45, 3, 0, nomod, 731.999), 858 | s(1201636, 2527, 2050, 24, 0, 0, nomod, 730.149), 859 | s(1451282, 1366, 1054, 12, 0, 0, hr, 730.027), 860 | s(1861781, 1457, 1433, 20, 5, 0, hr, 729.947), 861 | s(1649528, 959, 742, 1, 0, 0, hr, 727.32), 862 | s(1544643, 2089, 1483, 23, 0, 0, hr, 723.958), 863 | s(1432943, 2635, 1647, 37, 0, 0, hr, 723.365), 864 | s(2109961, 2482, 1837, 10, 1, 0, hd, 719.625), 865 | s(1537817, 1636, 1135, 46, 0, 0, dt, 714.534), 866 | s(970048, 1466, 1143, 6, 0, 0, hr, 711.096), 867 | s(965243, 2175, 1421, 12, 0, 0, hr, 710.441), 868 | s(1861487, 2559, 2325, 35, 5, 6, nomod, 709.414), 869 | s(1270000, 1452, 1164, 5, 0, 0, hr, 709.247), 870 | s(680394, 2985, 2001, 42, 1, 0, hr, 707.178), 871 | s(1402392, 1709, 1391, 3, 0, 0, hr, 704.707), 872 | s(724015, 1384, 1004, 10, 0, 1, hr, 704.449), 873 | s(1289927, 711, 590, 3, 0, 0, hr, 704.362), 874 | s(807074, 2637, 2122, 12, 0, 0, nomod, 698.47), 875 | s(1362265, 2471, 2326, 20, 0, 1, hr, 693.039), 876 | s(974662, 1970, 1424, 7, 0, 0, hr, 690.172), 877 | s(592113, 2510, 1982, 42, 0, 0, hr, 687.023), 878 | s(804164, 982, 782, 19, 1, 0, dt, 686.448), 879 | s(306683, 1025, 866, 10, 0, 0, hr, 680.917), 880 | s(880321, 1766, 1131, 10, 0, 0, hr, 679.344), 881 | s(791274, 2910, 2192, 20, 1, 0, hr, 676.807), 882 | s(959426, 1168, 810, 10, 0, 3, hr, 676.687), 883 | s(1294935, 3887, 2884, 10, 1, 0, nomod, 673.98), 884 | s(1115737, 1591, 1126, 7, 0, 0, hr, 665.49), 885 | s(1187302, 2071, 1472, 23, 0, 0, hr, 664.398), 886 | s(129891, 2245, 1917, 61, 1, 4, hr, 664.129), 887 | s(1218337, 3691, 2317, 24, 4, 0, hr, 662.618), 888 | s(1645523, 2199, 1573, 7, 0, 0, hr, 661.41), 889 | s(1567645, 1898, 1376, 19, 0, 0, hr, 660.956), 890 | s(915210, 1415, 1164, 9, 0, 3, hr, 660.678), 891 | s(172662, 1114, 868, 9, 0, 0, hr, 660.325), 892 | s(779179, 2470, 1866, 20, 0, 0, hr, 659.654), 893 | s(555797, 2042, 1502, 88, 0, 0, nomod, 655.971), 894 | s(133938, 999, 663, 7, 0, 0, hr, 655.14), 895 | s(112922, 5833, 3972, 55, 0, 0, hr, 649.661), 896 | s(1170505, 3682, 2641, 8, 0, 0, hr, 646.414), 897 | s(92051, 3461, 2546, 39, 0, 0, hr, 645.513), 898 | s(669496, 2001, 1535, 13, 0, 0, hr, 645.298), 899 | s(922172, 3587, 2256, 27, 0, 0, nomod, 645.25), 900 | s(736215, 1337, 1003, 5, 0, 0, hr, 640.613), 901 | s(1534949, 1187, 860, 7, 0, 1, hr, 637.377), 902 | s(1763938, 1029, 825, 7, 0, 0, hr, 636.886), 903 | s(955737, 2938, 2323, 48, 0, 0, hr, 631.09), 904 | s(657917, 2320, 1801, 19, 0, 0, hr, 630.791), 905 | s(816327, 2090, 1468, 56, 0, 0, nomod, 630.167), 906 | s(1621319, 1011, 809, 1, 0, 0, hr, 629.495), 907 | s(861889, 2965, 2206, 39, 0, 0, hr, 629.462), 908 | s(1955867, 1837, 1160, 23, 0, 0, hr, 629.427), 909 | s(1856597, 2813, 2730, 42, 0, 10, hr, 629.081), 910 | s(1031991, 2743, 3093, 34, 5, 9, nomod, 628.51), 911 | s(1546425, 1511, 1198, 4, 0, 1, hr, 627.228), 912 | s(1583228, 1815, 1190, 7, 0, 0, hr, 626.706), 913 | s(1414762, 2748, 2059, 87, 0, 0, hr, 625.726), 914 | s(1206528, 2207, 1568, 11, 0, 0, hr, 615.747), 915 | s(1474795, 1356, 1010, 6, 0, 0, hr, 615.193), 916 | s(1524103, 1075, 1086, 13, 0, 2, hr, 614.541), 917 | s(790048, 1426, 1053, 5, 0, 0, hr, 614.454), 918 | s(1665829, 1260, 948, 24, 0, 0, nomod, 614.183), 919 | s(939618, 1589, 1304, 20, 0, 0, hr, 613.976), 920 | s(1321823, 2087, 1346, 35, 0, 0, nomod, 610.109), 921 | ] 922 | 923 | del fl 924 | del hd 925 | del td 926 | del nf 927 | del nc 928 | del ht 929 | del nomod 930 | del hr 931 | del ez 932 | del dt 933 | del so 934 | del s 935 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | test suite for pyttanko. 5 | 6 | this is free and unencumbered software released into the public 7 | domain. check the attached UNLICENSE or http://unlicense.org/ 8 | ''' 9 | 10 | import traceback 11 | import sys 12 | import pyttanko 13 | from . import suite 14 | 15 | try: 16 | FileNotFoundError 17 | except NameError: 18 | FileNotFoundError = IOError 19 | 20 | def print_score(s): 21 | pyttanko.info( 22 | "%d +%s %dx %dx300 %dx100 %dx50 %dxmiss %gpp\n" % ( 23 | s.id, pyttanko.mods_str(s.mods), s.max_combo, s.n300, 24 | s.n100, s.n50, s.nmiss, s.pp 25 | ) 26 | ) 27 | 28 | 29 | def run(): 30 | ERROR_MARGIN = 0.02 31 | '''pp can be off by +- 2% 32 | margin is actually 3x for < 100pp, 2x for 100-200, 33 | 1.5x for 200-300''' 34 | 35 | p = pyttanko.parser() 36 | bmap = pyttanko.beatmap() 37 | stars = pyttanko.diff_calc() 38 | 39 | try: 40 | for s in suite.suite: 41 | print_score(s) 42 | 43 | with open("test/test_suite/%d.osu" % (s.id), "r") as f: 44 | p.map(f, bmap=bmap) 45 | 46 | stars.calc(bmap, s.mods) 47 | pp, _, _, _, _ = pyttanko.ppv2( 48 | stars.aim, stars.speed, bmap=bmap, mods=s.mods, 49 | n300=s.n300, n100=s.n100, n50=s.n50, nmiss=s.nmiss, 50 | combo=s.max_combo 51 | ) 52 | 53 | margin = s.pp * ERROR_MARGIN 54 | 55 | if s.pp < 100: 56 | margin *= 3 57 | elif s.pp < 200: 58 | margin *= 2 59 | elif s.pp < 300: 60 | margin *= 1.5 61 | 62 | if abs(pp - s.pp) >= margin: 63 | pyttanko.info( 64 | "failed test: got %gpp, expected %g\n" % ( 65 | pp, s.pp 66 | ) 67 | ) 68 | 69 | exit(1) 70 | 71 | except KeyboardInterrupt: 72 | pass 73 | 74 | except FileNotFoundError: 75 | pyttanko.info( 76 | "please download the test suite by running " + 77 | "./download_suite\n" 78 | ) 79 | sys.exit(1) 80 | 81 | except Exception as e: 82 | if p.done: 83 | raise 84 | else: 85 | pyttanko.info( 86 | "%s\n%s\n" % (traceback.format_exc(), str(p)) 87 | ) 88 | 89 | sys.exit(1) 90 | 91 | --------------------------------------------------------------------------------