Last active
March 1, 2018 05:11
-
-
Save hannahherbig/d67c2bfefcca207640c001e0ddd5e000 to your computer and use it in GitHub Desktop.
run this in a directory with rescaled music_db.xml, it reads song titles and ratings from there (don't use the english patched one)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import xml.etree.ElementTree as ET | |
from collections import defaultdict | |
import csv | |
from functools import lru_cache | |
@lru_cache(1024) | |
def lev(s1, s2): | |
"""Returns the levenshtein distance between strings s1 and s2.""" | |
if not len(s1): return len(s2) | |
if not len(s2): return len(s1) | |
return min( | |
lev(s1[:-1], s2) + 1, | |
lev(s1, s2[:-1]) + 1, | |
lev(s1[:-1], s2[:-1]) + (0 if s1[-1] == s2[-1] else 1) | |
) | |
def fix(name): | |
# a bunch of chars get mapped oddly - bemani specific fuckery | |
# MISSING: © | |
replacements = [ | |
['\u203E', '~'], | |
['\u301C', '~'], | |
['\u49FA', 'ê'], | |
['\u5F5C', 'ū'], | |
['\u66E6', 'à'], | |
['\u66E9', 'è'], | |
['\u8E94', '🐾'], | |
['\u9A2B', 'á'], | |
['\u9A69', 'Ø'], | |
['\u9A6B', 'ā'], | |
['\u9A6A', 'ō'], | |
['\u9AAD', 'ü'], | |
['\u9B2F', 'ī'], | |
['\u9EF7', 'ē'], | |
['\u9F63', 'Ú'], | |
['\u9F67', 'Ä'], | |
['\u973B', '♠'], | |
['\u9F6A', '♣'], | |
['\u9448', '♦'], | |
['\u9F72', '♥'], | |
['\u9F76', '♡'], | |
['\u9F77', 'é'], | |
] | |
for rep in replacements: | |
name = name.replace(rep[0], rep[1]) | |
return name | |
with open('music_db.xml', 'r') as f: | |
mdb = ET.fromstring(f.read()) | |
INF_VER = {2: 'INF', 3: 'GRV', 4: 'HVN'} | |
mapping = {} | |
titles = set() | |
for music in mdb.iter('music'): | |
info = music.find('info') | |
title = info.find('title_name').text | |
assert title not in titles | |
titles.add(title) | |
inf_ver = int(info.find('inf_ver').text) | |
diff = music.find('difficulty') | |
mapping[title, 'NOV'] = fix(title), 'NOV', int(diff.find('novice').find('difnum').text) | |
mapping[title, 'ADV'] = fix(title), 'ADV', int(diff.find('advanced').find('difnum').text) | |
mapping[title, 'EXH'] = fix(title), 'EXH', int(diff.find('exhaust').find('difnum').text) | |
if inf_ver: | |
mapping[title, 'INF'] = fix(title), INF_VER[inf_ver], int(diff.find('infinite').find('difnum').text) | |
titles = sorted(titles) | |
def find(title, diff): | |
key = title, diff | |
if key in mapping: | |
return mapping[key] | |
for title2 in titles: | |
if lev(title, title2) < 3: | |
ret = mapping[key] = mapping[title2, diff] | |
return ret |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
import sys | |
from collections import defaultdict | |
from math import floor | |
import argparse | |
from music_db import find, fix | |
ARCANA_TOKEN = '' | |
def walk(d): | |
if type(d) == dict: | |
if '_links' in d and '_self' in d['_links']: | |
yield d | |
for item in d.values(): | |
yield from walk(item) | |
elif type(d) == list: | |
for item in d: | |
yield from walk(item) | |
class Arcana: | |
def __init__(self, key): | |
self.s = requests.Session() | |
self.s.headers.update({'Authorization': 'Bearer %s' % key}) | |
self.cache = {} | |
def get(self, url, **params): | |
if url in self.cache: | |
return self.cache[url] | |
res = self.s.get(url, params=params) | |
print(res.url) | |
res.raise_for_status() | |
data = res.json() | |
for item in walk(data): | |
self.cache[item['_links']['_self']] = item | |
return data | |
def get_list(self, url, **params): | |
while url: | |
data = self.get(url, **params) | |
yield from data['_items'] | |
url = data['_links']['_next'] | |
params = {} | |
def grade(score): | |
x = score / 10_000_000 | |
if x >= .99: return 'S' | |
if x >= .98: return 'AAA+' | |
if x >= .97: return 'AAA' | |
if x >= .95: return 'AA+' | |
if x >= .93: return 'AA' | |
if x >= .90: return 'A+' | |
if x >= .87: return 'A' | |
if x >= .75: return 'B' | |
if x >= .65: return 'C' | |
return 'D' | |
def calc(score, rating): | |
x = score / 10_000_000 | |
if x >= .99: n = 1 | |
elif x >= .98: n = 0.99 | |
elif x >= .97: n = 0.98 | |
elif x >= .95: n = 0.97 | |
elif x >= .93: n = 0.96 | |
elif x >= .90: n = 0.95 | |
elif x >= .87: n = 0.94 | |
elif x >= .75: n = 0.93 | |
elif x >= .65: n = 0.92 | |
else: n = 0.91 | |
return 25 * n * (rating + 1) * x | |
a = Arcana(ARCANA_TOKEN | |
) | |
scores = [] | |
parser = argparse.ArgumentParser() | |
parser.add_argument('name') | |
args = parser.parse_args() | |
name = args.name | |
if len(name) <= 9: | |
params = {} | |
if len(name) <= 8: | |
params['name'] = name | |
else: | |
params['sdvx_id'] = name | |
profiles = list(a.get_list('https://arcana.nu/api/v1/sdvx/3/profiles/', **params)) | |
if len(profiles) == 0: | |
print('No matching profiles') | |
sys.exit(1) | |
elif len(profiles) > 1: | |
for profile in profiles: | |
print('{_id} {name} {sdvx_id} -- {register_time} {access_time}'.format_map(profile)) | |
print('Be more specific.') | |
sys.exit(1) | |
else: | |
id = profiles[0]['_id'] | |
else: | |
id = name | |
for best in a.get_list('https://arcana.nu/api/v1/sdvx/3/player_bests/', profile_id=id): | |
chart = a.get(best['_links']['chart']) | |
music = a.get(best['_links']['music']) | |
title, difficulty, rating = find(music['title'], chart['difficulty']) | |
score = best['score'] | |
force = calc(score, rating) | |
scores.append({ | |
'force': force, | |
'score': score, | |
'rating': rating, | |
'title': title, | |
'difficulty': difficulty, | |
'grade': grade(score), | |
'status': best['status'] | |
}) | |
scores.sort(key=lambda o: (o['force'], o['rating'], o['score'], o['title']), reverse=True) | |
for o in scores: | |
o['force'] = floor(o['force']) | |
force = sum(o['force'] for o in scores[:20]) | |
print(f'{force:,} VF') | |
for o in scores[:20]: | |
print('{force:,} -- {score:,} ({grade}) {title} [{difficulty} {rating}] {status}'.format(**o)) | |
import json | |
print(json.dumps([{"name": '{title} [{difficulty}]'.format(**o), 'level': o['rating'], 'score': o['score']} for o in scores])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment