from bunch import Bunch import re, datetime, string from feature import summary_for_dem splitter = re.compile('(?: [ \t]*)|(?:\t[ \t]*)') catmap = {'Easy runs':'ER', 'Nightmare runs': 'NR', 'Easy 100%': 'EH', 'Nightmare 100%': 'NH', 'cheated easy run':'CER', 'cheated easy 100%':'CEH', 'cheated nightmare run':'CNR', 'cheated nightmare 100%':'CNH', 'naked easy 100%': 'NEH', 'naked nightmare 100%': 'NNH', 'careful': 'CAREFUL', } class Config(object): def __init__(self): self.levels = {} self.players = {} self.player_pages = {} self.aliases = {} self.lc_aliases = {} self.vars = {} self.levelseq = [] def all_demos(self): for level in self.levels.itervalues(): for cat in level.demos.itervalues(): for demo in cat: yield demo class Demo(object): def __init__(self, **kw): self.__dict__.update(kw) self.parseddate = parseDate(self.date) self.parsedtime = parseTime(self.time) def computeLocation(self): if self.filename is None: self.location = None elif self.cat == 'CAREFUL': self.location = "otherdemos/demos/careful/%s.dz"%(self.filename,) elif self.cat.startswith('NE') or self.cat.startswith('NN'): self.location = "otherdemos/demos/naked/%s/%s.dz"%(self.cat.lower()[1:], self.filename) elif self.cat.startswith('C'): self.location = "otherdemos/demos/cheated/%s/%s.dz"%(self.cat.lower()[1:], self.filename) else: self.location = "demos/%s/%s.dz"%(self.cat, self.filename) def __repr__(self): fs = [] ks = self.__class__.__dict__.keys() ks.sort() for field in ks: if field in self.__dict__: v = self.__dict__[field] if v != self.__class__.__dict__[field]: m = getattr(self, 'fmt_' + field, self.fmt_default) r = m(self.__dict__[field]) if r != None: fs.append('%s=%s'%(field, r)) return '%s(%s)'%(self.__class__.__name__, ', '.join(fs)) def fmt_default(self, thing): return repr(thing) def fmt_level(self, level): return level.name def fmt_players(self, players): return [p.name for p in players] def fmt_location(self, location): return None fmt_lineno = fmt_location filename = None location = None players = [] time = '' date = '' level = None cat = '' optimal = False kills = 0 record = False lineno = -1 class Player(Bunch): def __init__(self, **kw): super(Player, self).__init__(**kw) self.runs = [] name = '' page = '' seqno = -1 def first_record(self): if self.runs: self.runs.sort(lambda x,y:cmp(x.parseddate, y.parseddate)) return self.runs[0] else: return None class Level(Bunch): def __init__(self, **kw): super(Level, self).__init__(**kw) self.demos = {} self.records = {} name = '' title = '' file = '' author = '' url = '' marathon = False variantOf = None series = None seqno = -1 comment = '' no100 = False def parseConfig(config, c): c.__init__() # <- XXX # config vars... for line in config: if '[players]' in line: break line = line.strip() if line.startswith('#') or not line: continue var, e, value = line.split() assert e == '=' c.vars[var] = value # players ... for line in config: if '[levels]' in line: break line = line.strip() if line.startswith('#') or not line: continue name, aliases, page = line.split(':') name = unicode(name, 'latin-1') name = name.replace('ä', u'\N{latin small letter a with diaeresis}') name = name.replace('å', u'\N{latin small letter a with ring above}') if page == '': page = str(len(c.players) + 1) p = Player(name=name, page=page, seqno=len(c.players)) c.aliases[name] = p c.lc_aliases[name] = c.lc_aliases[name.lower()] = p for alias in aliases.split(','): if not alias: continue c.aliases[alias] = p c.lc_aliases[alias.lower()] = p c.lc_aliases[alias] = p c.players[name] = p c.player_pages[page] = p # levels serieses = [('id', 'id1'), ('hip', 'hip'), ('rogue', 'doe'), ('ep', c.vars['lastcustep']), ('lvl', None)] series, last = serieses.pop(0) for line in config: if '[tables]' in line: break line = line.strip() if line.startswith('#') or not line: continue cmpts = line.split(':', 4) if len(cmpts) == 4: cmpts.append('') name, title, file, author, url = cmpts if url.startswith('#'): variantOf = url[1:] url = '' title = '%s (%s)'%(c.levels[variantOf].title, title) else: variantOf = None level = Level(name=name, title=title, file=file, author=author, url=url, variantOf=variantOf, seqno=len(c.levels)) c.levelseq.append(level) if name.startswith('%'): level.name = level.name[1:] level.marathon = True if not level.file: level.file = level.name level.series = series c.levels[level.name] = level if level.name == last: series, last = serieses.pop(0) for l in "e1m7", "r2m8", "shafted", "hipend": c.levels[l].no100 = True # tables for line in config: if '[comments]' in line: break # comments for line in config: line = line.strip() if line.startswith('#') or not line: continue if '::' in line: level, comment = line.split('::') level = c.levels[level] else: comment = " " + line level.comment += comment return c def computeFilename(demo): return demo.level.file + '_' + demo.time.replace(':', '') def parseExtras(extras, demo): filename = None include = True nolink = False for extra in extras: if not extra: continue if extra[0] == '[': demo.kills = int(extra[1:-1]) elif '/' in extra: demo.kills = int(extra.split('/')[0]) elif extra[0] == '{': filename = extra[1:-1] elif extra == '++': demo.optimal = True elif extra == '*': nolink = True elif extra == '**': return False assert not (filename and nolink) if filename is not None: demo.filename = filename elif not nolink: demo.filename = computeFilename(demo) demo.computeLocation() return True def parsePlayers(players, config): r = [] for name in players.split('/'): if not name: continue name = unicode(name, 'latin-1') if name not in config.aliases: # print "warning!", repr(name) page = str(len(config.players) + 1) p = Player(name=name, page=page, seqno=len(config.players)) config.players[name] = config.aliases[name] = p config.lc_aliases[name] = p config.lc_aliases[name.lower()] = config.player_pages[page] = p r.append(config.aliases[name]) return r def splitit(line): return map(str.strip, splitter.split(line)) def parseTime(t): try: mins, secs = map(int, t.split(':')) except ValueError: mins, secs = map(int, t.split('.')) return secs + 60*mins def parseDate(d): day, month, year = map(int, d.split('.')) if year > 90: year += 1900 else: year += 2000 return datetime.date(year, month, day) def parseRegularSection(config, cat, section): for lineno, line in section: splitted = splitit(line) if splitted[0] == '': splitted[0] = level level, date, players, time = splitted[:4] players = parsePlayers(players, config) if level not in config.levels: continue levelo = config.levels[level] demo = Demo(level=levelo, date=date, players=players, time=time, cat=cat, lineno=lineno) if not parseExtras(splitted[4:], demo): continue levelo.demos.setdefault(cat, []).append(demo) def parseMarathonSection(config, section): for lineno, line in section: splitted = splitit(line) if splitted[0] == '': splitted[0] = level else: bits = splitted[0].split() level = bits[1] if bits[0] == 'Easy': cat1 = 'E' elif bits[0] in ['Night', 'Nightmare']: cat1 = 'N' else: # print bits cat1 = '?' if len(bits) == 2: cat2 = 'R' elif len(bits) == 3: cat2 = 'H' else: # print bits cat2 = '?' cat = cat1 + cat2 splitted[0] = level level, date, players, time = splitted[:4] players = parsePlayers(players, config) if level not in config.levels: continue levelo = config.levels[level] demo = Demo(level=levelo, date=date, players=players, time=time, cat=cat, lineno=lineno) if not parseExtras(splitted[4:], demo): continue levelo.demos.setdefault(cat, []).append(demo) def parseCoopSection(config, section): for lineno, line in section: splitted = splitit(line) if splitted[0] == '': splitted[0] = level level, date, players, time = splitted[:4] players = parsePlayers(players, config) if level not in config.levels: continue levelo = config.levels[level] if len(splitted) > 4: stuff = splitted[4].split() for s in stuff: if s and s[0] in 'CEN': cat = s demo = Demo(level=levelo, date=date, players=players, time=time, cat=cat, lineno=lineno) if not parseExtras(' '.join(splitted[4:]).split(), demo): continue levelo.demos.setdefault(cat, []).append(demo) def normalizeHistory(config): for level in config.levels: cats = config.levels[level].demos.keys() cats.sort() lowest = {} for cat in cats: record = None mintime = 10000000 maxkills = 0 for demo in config.levels[level].demos[cat]: for p in demo.players: if demo not in p.runs: p.runs.append(demo) secs = demo.parsedtime if demo.kills > maxkills \ or (secs < mintime and demo.kills == maxkills): record = demo mintime = secs maxkills = demo.kills if record is not None: config.levels[level].records[cat] = record basecat = cat.strip('0123456789') baserec = lowest.get(basecat) if baserec is None \ or mintime < baserec.parsedtime \ or (mintime == baserec.parsedtime and record.parseddate < baserec.parseddate): lowest[basecat] = record record.record = True # elif (not (baserec is None # or mintime < baserec.parsedtime)) \ # and (mintime == baserec.parsedtime # and record.parseddate >= baserec.parseddate): # print baserec.level.name, baserec.cat, record.cat, # print record.date, record.parseddate, # print baserec.date, baserec.parseddate if record is not config.levels[level].demos[cat][-1]: print config.levels[level].name, cat, 'all fucked up!' for level in config.levels.itervalues(): for cat in level.demos: if cat[1] != 'R': continue cat100 = cat[0] + 'H' + cat[2:] if cat100 not in level.records: continue rec100 = level.records[cat100] recrun = level.records[cat] if rec100.parsedtime < recrun.parsedtime: recrun.record = False for d in config.all_demos(): d.summary = summary_for_dem(d) def parseHistory(history, config): level = None sections = [('', [])] it = enumerate(history.readlines()) for lineno, line in it: if line.startswith('#'): continue if line.strip('\n').endswith('/'): lineno, nextline = it.next() line = line.strip('\n') + nextline.lstrip() if not line.strip(): continue if line.startswith('**'): sectname = line.strip().strip('*').strip() sections.append((sectname, [])) else: sections[-1][1].append((lineno+1, line.strip('\n'))) for sectname, section in sections: if sectname in catmap: parseRegularSection(config, catmap[sectname], section) elif sectname == 'Marathon': parseMarathonSection(config, section) elif sectname == 'Coop' or sectname == 'coop': parseCoopSection(config, section) elif sectname: print sectname normalizeHistory(config) def doit(): config = Config() parseConfig(open('config', 'U'), config) parseHistory(open('history.txt', 'U'), config) parseHistory(open('c-history.txt', 'U'), config) return config if __name__=='__main__': config = doit() print config.levels['p_se_xxx'].records['ER']