Skip to content

Instantly share code, notes, and snippets.

@hannahherbig
Last active March 1, 2018 05:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hannahherbig/d67c2bfefcca207640c001e0ddd5e000 to your computer and use it in GitHub Desktop.
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)
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
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