# HG changeset patch # User John "Elwin" Edwards # Date 1388686188 18000 # Node ID 25843238434aa2b8324a7b696117b243d8f7dad1 # Parent 05a4afbe6299c768bab7a35058fc5ade645144bf Change the Python module's name back to rlgall. It is no longer an experimental variant. Using a database as a backend is a settled feature. diff -r 05a4afbe6299 -r 25843238434a README.txt --- a/README.txt Thu Jan 02 11:48:15 2014 -0500 +++ b/README.txt Thu Jan 02 13:09:48 2014 -0500 @@ -12,7 +12,7 @@ py/recorder.py processes the log files and stores the data in a PostgreSQL database. It should be run periodically by cron. -py/rlgalldb.py is a module which recorder.py requires. It should be installed +py/rlgall.py is a module which recorder.py requires. It should be installed in /lib/python/site-packages or the equivalent location. web/ contains the static parts of the rlgallery.org website. Note that when diff -r 05a4afbe6299 -r 25843238434a py/cleandb.py --- a/py/cleandb.py Thu Jan 02 11:48:15 2014 -0500 +++ b/py/cleandb.py Thu Jan 02 13:09:48 2014 -0500 @@ -1,7 +1,7 @@ #!/usr/bin/python3 # cleandb.py: empty the database in an orderly fashion -import rlgalldb as rlgall +import rlgall import psycopg2 dbconn = psycopg2.connect("dbname=rlg") diff -r 05a4afbe6299 -r 25843238434a py/recorder.py --- a/py/recorder.py Thu Jan 02 11:48:15 2014 -0500 +++ b/py/recorder.py Thu Jan 02 13:09:48 2014 -0500 @@ -2,7 +2,7 @@ import os import psycopg2 -import rlgalldb as rlgall +import rlgall # Contains a dir for everyone who registered everydir = "/var/dgl/dgldir/ttyrec/" diff -r 05a4afbe6299 -r 25843238434a py/rlgall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/rlgall.py Thu Jan 02 13:09:48 2014 -0500 @@ -0,0 +1,516 @@ +# rlgall.py +# Module for the Roguelike Gallery, using a postgres database +# Requires Python 3.3 + +import os +import psycopg2 +from datetime import datetime +import pytz + +# Configuration +logdir = "/var/dgl/var/games/roguelike/" +webdir = "/var/www/lighttpd/scoring/" +ppagename = webdir + "players/{0}.html" +hpagename = webdir + "highscores.html" + +# HTML fragments for templating +phead = """ + +{0} + + +""" + +ptop = """ +

Yendor Guild

+""" + +navtop = '\n' +navscore = '\n' +navplayer = '' + +pti = '

{0}

\n' + +secthead = '

{0}

\n' +tblhead = '
\n' +rowstart = '
\n' +rowend = '
\n' +cell = ' {0}\n' +rcell = ' {0}\n' +hcell = ' {0}\n' +tblend = '
\n' +pend = "\n" + +# This would be more useful if we had to do translation +headerbook = {"endt":"End time", "score":"Score", "name":"Name", "xl":"XL", + "fate":"Fate", "rank":"Rank", "game":"Game", "class": "Class"} +# Queries for the games table +offselstr = "SELECT offbytes FROM games WHERE gname = %s;" +newoffstr = "UPDATE games SET offbytes = %s WHERE gname = %s;" + +def getconn(): + "Returns a database connection, or None if the connection fails." + try: + conn = psycopg2.connect("dbname=rlg") + except psycopg2.OperationalError: + return None + return conn + +def recnameToTS(filename): + pattern = "%Y-%m-%d.%H:%M:%S.ttyrec" + try: + dt = datetime.strptime(filename, pattern).replace(tzinfo=pytz.utc) + return dt + except ValueError: + return None + +def ttyreclink(text, name, game, gtime): + "Returns a link to the ttyrec archivist" + lstr = '{3}' + return lstr.format(name, game, gtime, text) + +def playerlink(name): + "Returns a link to a player's page" + lstr = '' + name + '' + return lstr + +def linktoArchive(entry): + "Takes an entry dict and returns a link to the ttyrec archivist." + lstr = '{3}' + linktext = entry["endt"].strftime("%Y/%m/%d %H:%M:%S") + stamp = int(entry["endt"].timestamp()) + return lstr.format(entry["name"], entry["game"].uname, stamp, linktext) + +def maketablerow(cells, isheader=None): + "Takes a list of strings and returns a HTML table row with each string \ + in its own cell. isheader will make them header cells, obviously." + if isheader: + celler = hcell + else: + celler = cell + rowstr = rowstart + for entry in cells: + rowstr = rowstr + celler.format(entry) + rowstr = rowstr + rowend + return rowstr + +def printTable(entries, fields, of): + "Takes a list of entry dicts and a list of field strings and writes a \ + HTML table to of." + of.write(tblhead) + clist = [] + for field in fields: + if field in headerbook: + clist.append(headerbook[field]) + else: + clist.append("{0}".format(field)) + of.write(maketablerow(clist, True)) + rnum = 0 + for i, entry in enumerate(entries): + clist = [] + for field in fields: + if field == "rank": + clist.append(("{0}".format(i + 1), rcell)) + elif field in entry: + thing = entry[field] + if field == "game": + clist.append((thing.name, cell)) + elif field == "xl" or field == "score": # Numerics + clist.append((str(thing), rcell)) + elif field == "name": + clist.append((playerlink(thing), cell)) + elif field == "fate": + clist.append((thing, cell)) + elif field == "endt": + clist.append((linktoArchive(entry), cell)) + else: + clist.append(("{0}".format(thing), cell)) + else: + clist.append(("N/A", cell)) + rowstr = rowstart + "".join([ t.format(s) for (s, t) in clist ]) + rowend + of.write(rowstr) + of.write(tblend) + return + +class Game: + def __init__(self, name, uname): + pass + def logtoDict(self, entry): + "Processes a log entry string, returning a dict." + ndict = {"game": self} + entrylist = entry.strip().split(self.logdelim, len(self.logspec) - 1) + for item, value in zip(self.logspec, entrylist): + if self.sqltypes[item] == "int": + ndict[item] = int(value) + if self.sqltypes[item] == "bool": + ndict[item] = bool(int(value)) + elif self.sqltypes[item] == "timestamptz": + ndict[item] = datetime.fromtimestamp(int(value), pytz.utc) + else: + ndict[item] = value + return ndict + def getEntryDicts(self, entfile, entlist): + "Reads logfile entries from entfile, interprets them according to the \ + instructions in self.logspec/logdelim, and adds them as dicts to entlist." + while True: + nextentry = entfile.readline() + if not nextentry: + break + if nextentry[-1] != '\n': + break + entlist.append(self.logtoDict(nextentry)) + return + def loadnew(self): + conn = getconn() + if conn == None: + return [] + cur = conn.cursor() + # Get the previous offset + cur.execute(offselstr, [self.uname]) + offset = cur.fetchone()[0] + newlist = [] + try: + scr = open(self.scores, encoding="utf-8") + scr.seek(offset) + self.getEntryDicts(scr, newlist) + except IOError: + noffset = offset # Can't read anything, assume no new games + else: + noffset = scr.tell() + scr.close() + cur.execute(newoffstr, [noffset, self.uname]) + # The new players must already be added to the players table. + updatenames = set([ e["name"] for e in newlist ]) + self.postprocess(newlist) + self.putIntoDB(newlist, conn) + cur.close() + conn.close() + return updatenames + def postprocess(self, gamelist): + "Default postprocessing function: adds start time and ttyrec list" + names = set([ e["name"] for e in gamelist ]) + conn = getconn() + if conn == None: + return [] + cur = conn.cursor() + for nameF in names: + # Get all this player's games ordered by time + itsEntries = [ entry for entry in gamelist if entry["name"] == nameF ] + itsEntries.sort(key=lambda e: e["endt"]) + # Find the end time of the latest game already in the db + tquery = "SELECT endt FROM {0} WHERE name = %s ORDER BY endt DESC LIMIT 1;".format(self.uname) + cur.execute(tquery, [nameF]) + result = cur.fetchone() + if result: + prev = result[0] + else: + prev = datetime.fromtimestamp(0, pytz.utc); + ttyrecdir = "/var/dgl/dgldir/ttyrec/{0}/{1}/".format(nameF, self.uname) + allfilekeys = [ (recnameToTS(f), f) for f in os.listdir(ttyrecdir) ] + vfilekeys = [ e for e in allfilekeys if e[0] > prev ] + vfilekeys.sort(key=lambda e: e[0]) + # Now determine startt and ttyrecs for each game + for i in range(0, len(itsEntries)): + if i == 0: + lowlim = prev + else: + lowlim = itsEntries[i-1]["endt"] + hilim = itsEntries[i]["endt"] + recs = [ k[1] for k in vfilekeys if lowlim <= k[0] < hilim ] + itsEntries[i]["startt"] = recnameToTS(recs[0]) + itsEntries[i]["ttyrecs"] = recs + cur.close() + conn.close() + def putIntoDB(self, dictlist, conn): + cur = conn.cursor() + cur.executemany(self.insertq, [ d for d in dictlist if d["game"] == self ]) + conn.commit() + cur.close() + return + def tablerecent(self, of): + "Prints the most recent games from the logfile, NOT the database." + newest = [] + try: + scr = open(self.scores, encoding="utf-8") + except FileNotFoundError: + pass + else: + # Text streams don't support random seeking. + try: + scr.buffer.seek(self.lookback, 2) + except OSError: + scr.buffer.seek(0) # The file wasn't that long, start at the beginning + if scr.buffer.tell() != 0: + scr.buffer.readline() # Throw away the incomplete line + self.getEntryDicts(scr, newest) + newest.reverse() + scr.close() + of.write(secthead.format(self.name)) + if not newest: + of.write("
No one has braved this dungeon yet.
\n") + else: + printTable(newest, self.fields, of) + return +# End Game class definition + +class RogueGame(Game): + def __init__(self, name, uname, suffix): + self.name = name + self.uname = uname + self.scores = logdir + uname + ".log" + self.logspec = ["endt", "score", "name", "xl", "fate"] + self.sqltypes = {"endt": "timestamptz", "score": "int", + "name": "varchar(20)", "xl": "int", "fate": "text", + "ttyrecs": "text ARRAY", "startt": "timestamptz"} + self.logdelim = " " + self.lookback = -1500 + # Construct the insert query + fields = self.sqltypes.keys() + colspec = ", ".join(fields) + valspec = ", ".join([ "%({})s".format(c) for c in fields ]) + self.insertq = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, + colspec, valspec) + # Class variables, used by some methods + fields = ["name", "score", "xl", "fate", "endt"] + rankfields = ["rank", "score", "name", "xl", "fate", "endt"] + pfields = ["score", "xl", "fate", "endt"] + def getRecent(self, n=20): + "Gets the n most recent games out of the database, returning a list \ + of dicts." + try: + n = int(n) + except (ValueError, TypeError): + return [] + if n <= 0: + return [] + dictlist = [] + conn = psycopg2.connect("dbname=rlg") + cur = conn.cursor() + qstr = "SELECT endt, score, name, xl, fate, startt FROM {0} ORDER BY endt DESC \ + LIMIT %s;".format(self.uname) + cur.execute(qstr, [n]) + for record in cur: + # This should be less hardcodish + ndict = {"game": self} + ndict["endt"] = record[0] + ndict["score"] = record[1] + ndict["name"] = record[2] + ndict["xl"] = record[3] + ndict["fate"] = record[4] + ndict["startt"] = record[5] + dictlist.append(ndict) + cur.close() + conn.close() + return dictlist + def getHigh(self, n=10, offset=0): + "Gets the n highest scores (starting at offset) from the database, \ + returning a list of dicts." + qstr = "SELECT endt, score, name, xl, fate, startt FROM {0} ORDER BY score DESC ".format(self.uname) + qvals = [] + try: + n = int(n) + except (ValueError, TypeError): + return [] + if n <= 0: + return [] + qstr += " LIMIT %s" + qvals.append(n) + try: + offset = int(offset) + except (ValueError, TypeError): + return [] + if n > 0: + qstr += " OFFSET %s" + qvals.append(offset) + qstr += ";" + conn = psycopg2.connect("dbname=rlg") + cur = conn.cursor() + cur.execute(qstr, qvals) + dictlist = [] + for record in cur: + ndict = {"game": self} + ndict["endt"] = record[0] + ndict["score"] = record[1] + ndict["name"] = record[2] + ndict["xl"] = record[3] + ndict["fate"] = record[4] + ndict["startt"] = record[5] + dictlist.append(ndict) + cur.close() + conn.close() + return dictlist + def getPlayer(self, player): + "Gets all player's games from the database." + qstr = "SELECT endt, score, name, xl, fate, startt FROM " + self.uname + " WHERE name = %s;" + conn = getconn() + if conn == None: + return [] + cur = conn.cursor() + entries = [] + cur.execute(qstr, [player]) + for record in cur: + ndict = {"game": self} + ndict["endt"] = record[0] + ndict["score"] = record[1] + ndict["name"] = record[2] + ndict["xl"] = record[3] + ndict["fate"] = record[4] + ndict["startt"] = record[5] + entries.append(ndict) + cur.close() + conn.close() + return entries + def putIntoDB(self, dictlist, conn): + "Add the entries in dictlist to the database through connection conn, \ + which needs the INSERT privilege." + fields = self.sqltypes.keys() + fstr = ", ".join(fields) + vstr = ", ".join([ "%({})s".format(c) for c in fields ]) + qstr = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, fstr, vstr); + cur = conn.cursor() + cur.executemany(self.insertq, [ d for d in dictlist if d["game"] == self ]) + conn.commit() + cur.close() + return +# End RogueGame class definition + +class ARogueGame(Game): + def __init__(self, name, uname, suffix): + self.name = name + self.uname = uname + self.scores = logdir + uname + ".log" + self.logspec = ["endt", "score", "name", "xl", "class", "depth", + "maxdepth", "quest", "hadquest", "fate"] + self.sqltypes = {"endt": "timestamptz", "score": "int", + "name": "varchar(20)", "xl": "int", "class": "text", "depth": "int", + "maxdepth": "int", "quest": "int", "hadquest": "bool", + "fate": "text", "ttyrecs": "text ARRAY", "startt": "timestamptz"} + self.logdelim = " " + self.lookback = -1800 + # Construct the insert query + fields = self.sqltypes.keys() + colspec = ", ".join(fields) + valspec = ", ".join([ "%({})s".format(c) for c in fields ]) + self.insertq = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, + colspec, valspec) + # Class variables + fields = ["name", "score", "class", "xl", "fate", "endt"] + rankfields = ["rank", "score", "name", "class", "xl", "fate", "endt"] + pfields = ["score", "class", "xl", "fate", "endt"] + def getHigh(self, n=10, offset=0): + "Gets the n highest scores (starting at offset) from the database, \ + returning a list of dicts." + qstr = "SELECT endt, score, name, xl, class, fate FROM {0} ORDER BY score DESC ".format(self.uname) + qvals = [] + try: + n = int(n) + except (ValueError, TypeError): + return [] + if n <= 0: + return [] + qstr += " LIMIT %s" + qvals.append(n) + try: + offset = int(offset) + except (ValueError, TypeError): + return [] + if n > 0: + qstr += " OFFSET %s" + qvals.append(offset) + qstr += ";" + conn = psycopg2.connect("dbname=rlg") + cur = conn.cursor() + cur.execute(qstr, qvals) + dictlist = [] + for record in cur: + ndict = {"game": self} + ndict["endt"] = record[0] + ndict["score"] = record[1] + ndict["name"] = record[2] + ndict["xl"] = record[3] + ndict["class"] = record[4] + ndict["fate"] = record[5] + dictlist.append(ndict) + cur.close() + conn.close() + return dictlist + def getRecent(self, n=20): + return [] + def getPlayer(self, player): + "Gets all player's games from the database." + qstr = "SELECT endt, score, name, xl, class, fate FROM " + self.uname + " WHERE name = %s;" + conn = getconn() + if conn == None: + return [] + cur = conn.cursor() + entries = [] + cur.execute(qstr, [player]) + for record in cur: + ndict = {"game": self} + ndict["endt"] = record[0] + ndict["score"] = record[1] + ndict["name"] = record[2] + ndict["xl"] = record[3] + ndict["class"] = record[4] + ndict["fate"] = record[5] + entries.append(ndict) + cur.close() + conn.close() + return entries + +rogue3 = RogueGame("Rogue V3", "rogue3", "r3") +rogue4 = RogueGame("Rogue V4", "rogue4", "r4") +rogue5 = RogueGame("Rogue V5", "rogue5", "r5") +srogue = RogueGame("Super-Rogue", "srogue", "sr") +arogue5 = ARogueGame("Advanced Rogue 5", "arogue5", "ar5") + +gamelist = [rogue3, rogue4, rogue5, srogue, arogue5] + +def playerpage(pname): + "Generate a player's HTML page" + # Write the beginning of the page + ppagefi = open(ppagename.format(pname), "w", encoding="utf-8") + ppagefi.write(phead.format(pname)) + ppagefi.write(ptop) + ppagefi.write(navplayer.format(pname)) + ppagefi.write(pti.format("Results for " + pname)) + for game in gamelist: + ppagefi.write(secthead.format(game.name)) + entries = game.getPlayer(pname) + if not entries: + ppagefi.write("
" + pname + " has not yet completed an expedition\ + in this dungeon.
\n") + else: + entries.sort(key=lambda e: e["endt"]) + printTable(entries, game.pfields, ppagefi) + scoresum = 0 + for entry in entries: + scoresum += int(entry["score"]) + avgscr = scoresum // len(entries) + ppagefi.write("

Average score: {0}

\n".format(avgscr)) + # That should settle it. Print the end; then stop. + ppagefi.write(pend) + ppagefi.close() + return + +def highpage(): + # open the page and print the beginning + highpfi = open(hpagename, "w", encoding="utf-8") + highpfi.write(phead.format("High Scores")) + highpfi.write(ptop) + highpfi.write(navscore.format("High Scores")) + highpfi.write(pti.format("List of High Scores")) + for game in gamelist: + highpfi.write(secthead.format(game.name)) + entries = game.getHigh(40) + if not entries: + highpfi.write("
No one has braved this dungeon yet.
\n") + else: + printTable(entries, game.rankfields, highpfi) + # That should finish it. + highpfi.write(pend) + highpfi.close() + return diff -r 05a4afbe6299 -r 25843238434a py/rlgalldb.py --- a/py/rlgalldb.py Thu Jan 02 11:48:15 2014 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,516 +0,0 @@ -# rlgalldb.py -# Module for the Roguelike Gallery, using a postgres database -# Requires Python 3.3 - -import os -import psycopg2 -from datetime import datetime -import pytz - -# Configuration -logdir = "/var/dgl/var/games/roguelike/" -webdir = "/var/www/lighttpd/scoring/" -ppagename = webdir + "players/{0}.html" -hpagename = webdir + "highscores.html" - -# HTML fragments for templating -phead = """ - -{0} - - -""" - -ptop = """ -

Yendor Guild

-""" - -navtop = '\n' -navscore = '\n' -navplayer = '' - -pti = '

{0}

\n' - -secthead = '

{0}

\n' -tblhead = '
\n' -rowstart = '
\n' -rowend = '
\n' -cell = ' {0}\n' -rcell = ' {0}\n' -hcell = ' {0}\n' -tblend = '
\n' -pend = "\n" - -# This would be more useful if we had to do translation -headerbook = {"endt":"End time", "score":"Score", "name":"Name", "xl":"XL", - "fate":"Fate", "rank":"Rank", "game":"Game", "class": "Class"} -# Queries for the games table -offselstr = "SELECT offbytes FROM games WHERE gname = %s;" -newoffstr = "UPDATE games SET offbytes = %s WHERE gname = %s;" - -def getconn(): - "Returns a database connection, or None if the connection fails." - try: - conn = psycopg2.connect("dbname=rlg") - except psycopg2.OperationalError: - return None - return conn - -def recnameToTS(filename): - pattern = "%Y-%m-%d.%H:%M:%S.ttyrec" - try: - dt = datetime.strptime(filename, pattern).replace(tzinfo=pytz.utc) - return dt - except ValueError: - return None - -def ttyreclink(text, name, game, gtime): - "Returns a link to the ttyrec archivist" - lstr = '{3}' - return lstr.format(name, game, gtime, text) - -def playerlink(name): - "Returns a link to a player's page" - lstr = '' + name + '' - return lstr - -def linktoArchive(entry): - "Takes an entry dict and returns a link to the ttyrec archivist." - lstr = '{3}' - linktext = entry["endt"].strftime("%Y/%m/%d %H:%M:%S") - stamp = int(entry["endt"].timestamp()) - return lstr.format(entry["name"], entry["game"].uname, stamp, linktext) - -def maketablerow(cells, isheader=None): - "Takes a list of strings and returns a HTML table row with each string \ - in its own cell. isheader will make them header cells, obviously." - if isheader: - celler = hcell - else: - celler = cell - rowstr = rowstart - for entry in cells: - rowstr = rowstr + celler.format(entry) - rowstr = rowstr + rowend - return rowstr - -def printTable(entries, fields, of): - "Takes a list of entry dicts and a list of field strings and writes a \ - HTML table to of." - of.write(tblhead) - clist = [] - for field in fields: - if field in headerbook: - clist.append(headerbook[field]) - else: - clist.append("{0}".format(field)) - of.write(maketablerow(clist, True)) - rnum = 0 - for i, entry in enumerate(entries): - clist = [] - for field in fields: - if field == "rank": - clist.append(("{0}".format(i + 1), rcell)) - elif field in entry: - thing = entry[field] - if field == "game": - clist.append((thing.name, cell)) - elif field == "xl" or field == "score": # Numerics - clist.append((str(thing), rcell)) - elif field == "name": - clist.append((playerlink(thing), cell)) - elif field == "fate": - clist.append((thing, cell)) - elif field == "endt": - clist.append((linktoArchive(entry), cell)) - else: - clist.append(("{0}".format(thing), cell)) - else: - clist.append(("N/A", cell)) - rowstr = rowstart + "".join([ t.format(s) for (s, t) in clist ]) + rowend - of.write(rowstr) - of.write(tblend) - return - -class Game: - def __init__(self, name, uname): - pass - def logtoDict(self, entry): - "Processes a log entry string, returning a dict." - ndict = {"game": self} - entrylist = entry.strip().split(self.logdelim, len(self.logspec) - 1) - for item, value in zip(self.logspec, entrylist): - if self.sqltypes[item] == "int": - ndict[item] = int(value) - if self.sqltypes[item] == "bool": - ndict[item] = bool(int(value)) - elif self.sqltypes[item] == "timestamptz": - ndict[item] = datetime.fromtimestamp(int(value), pytz.utc) - else: - ndict[item] = value - return ndict - def getEntryDicts(self, entfile, entlist): - "Reads logfile entries from entfile, interprets them according to the \ - instructions in self.logspec/logdelim, and adds them as dicts to entlist." - while True: - nextentry = entfile.readline() - if not nextentry: - break - if nextentry[-1] != '\n': - break - entlist.append(self.logtoDict(nextentry)) - return - def loadnew(self): - conn = getconn() - if conn == None: - return [] - cur = conn.cursor() - # Get the previous offset - cur.execute(offselstr, [self.uname]) - offset = cur.fetchone()[0] - newlist = [] - try: - scr = open(self.scores, encoding="utf-8") - scr.seek(offset) - self.getEntryDicts(scr, newlist) - except IOError: - noffset = offset # Can't read anything, assume no new games - else: - noffset = scr.tell() - scr.close() - cur.execute(newoffstr, [noffset, self.uname]) - # The new players must already be added to the players table. - updatenames = set([ e["name"] for e in newlist ]) - self.postprocess(newlist) - self.putIntoDB(newlist, conn) - cur.close() - conn.close() - return updatenames - def postprocess(self, gamelist): - "Default postprocessing function: adds start time and ttyrec list" - names = set([ e["name"] for e in gamelist ]) - conn = getconn() - if conn == None: - return [] - cur = conn.cursor() - for nameF in names: - # Get all this player's games ordered by time - itsEntries = [ entry for entry in gamelist if entry["name"] == nameF ] - itsEntries.sort(key=lambda e: e["endt"]) - # Find the end time of the latest game already in the db - tquery = "SELECT endt FROM {0} WHERE name = %s ORDER BY endt DESC LIMIT 1;".format(self.uname) - cur.execute(tquery, [nameF]) - result = cur.fetchone() - if result: - prev = result[0] - else: - prev = datetime.fromtimestamp(0, pytz.utc); - ttyrecdir = "/var/dgl/dgldir/ttyrec/{0}/{1}/".format(nameF, self.uname) - allfilekeys = [ (recnameToTS(f), f) for f in os.listdir(ttyrecdir) ] - vfilekeys = [ e for e in allfilekeys if e[0] > prev ] - vfilekeys.sort(key=lambda e: e[0]) - # Now determine startt and ttyrecs for each game - for i in range(0, len(itsEntries)): - if i == 0: - lowlim = prev - else: - lowlim = itsEntries[i-1]["endt"] - hilim = itsEntries[i]["endt"] - recs = [ k[1] for k in vfilekeys if lowlim <= k[0] < hilim ] - itsEntries[i]["startt"] = recnameToTS(recs[0]) - itsEntries[i]["ttyrecs"] = recs - cur.close() - conn.close() - def putIntoDB(self, dictlist, conn): - cur = conn.cursor() - cur.executemany(self.insertq, [ d for d in dictlist if d["game"] == self ]) - conn.commit() - cur.close() - return - def tablerecent(self, of): - "Prints the most recent games from the logfile, NOT the database." - newest = [] - try: - scr = open(self.scores, encoding="utf-8") - except FileNotFoundError: - pass - else: - # Text streams don't support random seeking. - try: - scr.buffer.seek(self.lookback, 2) - except OSError: - scr.buffer.seek(0) # The file wasn't that long, start at the beginning - if scr.buffer.tell() != 0: - scr.buffer.readline() # Throw away the incomplete line - self.getEntryDicts(scr, newest) - newest.reverse() - scr.close() - of.write(secthead.format(self.name)) - if not newest: - of.write("
No one has braved this dungeon yet.
\n") - else: - printTable(newest, self.fields, of) - return -# End Game class definition - -class RogueGame(Game): - def __init__(self, name, uname, suffix): - self.name = name - self.uname = uname - self.scores = logdir + uname + ".log" - self.logspec = ["endt", "score", "name", "xl", "fate"] - self.sqltypes = {"endt": "timestamptz", "score": "int", - "name": "varchar(20)", "xl": "int", "fate": "text", - "ttyrecs": "text ARRAY", "startt": "timestamptz"} - self.logdelim = " " - self.lookback = -1500 - # Construct the insert query - fields = self.sqltypes.keys() - colspec = ", ".join(fields) - valspec = ", ".join([ "%({})s".format(c) for c in fields ]) - self.insertq = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, - colspec, valspec) - # Class variables, used by some methods - fields = ["name", "score", "xl", "fate", "endt"] - rankfields = ["rank", "score", "name", "xl", "fate", "endt"] - pfields = ["score", "xl", "fate", "endt"] - def getRecent(self, n=20): - "Gets the n most recent games out of the database, returning a list \ - of dicts." - try: - n = int(n) - except (ValueError, TypeError): - return [] - if n <= 0: - return [] - dictlist = [] - conn = psycopg2.connect("dbname=rlg") - cur = conn.cursor() - qstr = "SELECT endt, score, name, xl, fate, startt FROM {0} ORDER BY endt DESC \ - LIMIT %s;".format(self.uname) - cur.execute(qstr, [n]) - for record in cur: - # This should be less hardcodish - ndict = {"game": self} - ndict["endt"] = record[0] - ndict["score"] = record[1] - ndict["name"] = record[2] - ndict["xl"] = record[3] - ndict["fate"] = record[4] - ndict["startt"] = record[5] - dictlist.append(ndict) - cur.close() - conn.close() - return dictlist - def getHigh(self, n=10, offset=0): - "Gets the n highest scores (starting at offset) from the database, \ - returning a list of dicts." - qstr = "SELECT endt, score, name, xl, fate, startt FROM {0} ORDER BY score DESC ".format(self.uname) - qvals = [] - try: - n = int(n) - except (ValueError, TypeError): - return [] - if n <= 0: - return [] - qstr += " LIMIT %s" - qvals.append(n) - try: - offset = int(offset) - except (ValueError, TypeError): - return [] - if n > 0: - qstr += " OFFSET %s" - qvals.append(offset) - qstr += ";" - conn = psycopg2.connect("dbname=rlg") - cur = conn.cursor() - cur.execute(qstr, qvals) - dictlist = [] - for record in cur: - ndict = {"game": self} - ndict["endt"] = record[0] - ndict["score"] = record[1] - ndict["name"] = record[2] - ndict["xl"] = record[3] - ndict["fate"] = record[4] - ndict["startt"] = record[5] - dictlist.append(ndict) - cur.close() - conn.close() - return dictlist - def getPlayer(self, player): - "Gets all player's games from the database." - qstr = "SELECT endt, score, name, xl, fate, startt FROM " + self.uname + " WHERE name = %s;" - conn = getconn() - if conn == None: - return [] - cur = conn.cursor() - entries = [] - cur.execute(qstr, [player]) - for record in cur: - ndict = {"game": self} - ndict["endt"] = record[0] - ndict["score"] = record[1] - ndict["name"] = record[2] - ndict["xl"] = record[3] - ndict["fate"] = record[4] - ndict["startt"] = record[5] - entries.append(ndict) - cur.close() - conn.close() - return entries - def putIntoDB(self, dictlist, conn): - "Add the entries in dictlist to the database through connection conn, \ - which needs the INSERT privilege." - fields = self.sqltypes.keys() - fstr = ", ".join(fields) - vstr = ", ".join([ "%({})s".format(c) for c in fields ]) - qstr = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, fstr, vstr); - cur = conn.cursor() - cur.executemany(self.insertq, [ d for d in dictlist if d["game"] == self ]) - conn.commit() - cur.close() - return -# End RogueGame class definition - -class ARogueGame(Game): - def __init__(self, name, uname, suffix): - self.name = name - self.uname = uname - self.scores = logdir + uname + ".log" - self.logspec = ["endt", "score", "name", "xl", "class", "depth", - "maxdepth", "quest", "hadquest", "fate"] - self.sqltypes = {"endt": "timestamptz", "score": "int", - "name": "varchar(20)", "xl": "int", "class": "text", "depth": "int", - "maxdepth": "int", "quest": "int", "hadquest": "bool", - "fate": "text", "ttyrecs": "text ARRAY", "startt": "timestamptz"} - self.logdelim = " " - self.lookback = -1800 - # Construct the insert query - fields = self.sqltypes.keys() - colspec = ", ".join(fields) - valspec = ", ".join([ "%({})s".format(c) for c in fields ]) - self.insertq = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, - colspec, valspec) - # Class variables - fields = ["name", "score", "class", "xl", "fate", "endt"] - rankfields = ["rank", "score", "name", "class", "xl", "fate", "endt"] - pfields = ["score", "class", "xl", "fate", "endt"] - def getHigh(self, n=10, offset=0): - "Gets the n highest scores (starting at offset) from the database, \ - returning a list of dicts." - qstr = "SELECT endt, score, name, xl, class, fate FROM {0} ORDER BY score DESC ".format(self.uname) - qvals = [] - try: - n = int(n) - except (ValueError, TypeError): - return [] - if n <= 0: - return [] - qstr += " LIMIT %s" - qvals.append(n) - try: - offset = int(offset) - except (ValueError, TypeError): - return [] - if n > 0: - qstr += " OFFSET %s" - qvals.append(offset) - qstr += ";" - conn = psycopg2.connect("dbname=rlg") - cur = conn.cursor() - cur.execute(qstr, qvals) - dictlist = [] - for record in cur: - ndict = {"game": self} - ndict["endt"] = record[0] - ndict["score"] = record[1] - ndict["name"] = record[2] - ndict["xl"] = record[3] - ndict["class"] = record[4] - ndict["fate"] = record[5] - dictlist.append(ndict) - cur.close() - conn.close() - return dictlist - def getRecent(self, n=20): - return [] - def getPlayer(self, player): - "Gets all player's games from the database." - qstr = "SELECT endt, score, name, xl, class, fate FROM " + self.uname + " WHERE name = %s;" - conn = getconn() - if conn == None: - return [] - cur = conn.cursor() - entries = [] - cur.execute(qstr, [player]) - for record in cur: - ndict = {"game": self} - ndict["endt"] = record[0] - ndict["score"] = record[1] - ndict["name"] = record[2] - ndict["xl"] = record[3] - ndict["class"] = record[4] - ndict["fate"] = record[5] - entries.append(ndict) - cur.close() - conn.close() - return entries - -rogue3 = RogueGame("Rogue V3", "rogue3", "r3") -rogue4 = RogueGame("Rogue V4", "rogue4", "r4") -rogue5 = RogueGame("Rogue V5", "rogue5", "r5") -srogue = RogueGame("Super-Rogue", "srogue", "sr") -arogue5 = ARogueGame("Advanced Rogue 5", "arogue5", "ar5") - -gamelist = [rogue3, rogue4, rogue5, srogue, arogue5] - -def playerpage(pname): - "Generate a player's HTML page" - # Write the beginning of the page - ppagefi = open(ppagename.format(pname), "w", encoding="utf-8") - ppagefi.write(phead.format(pname)) - ppagefi.write(ptop) - ppagefi.write(navplayer.format(pname)) - ppagefi.write(pti.format("Results for " + pname)) - for game in gamelist: - ppagefi.write(secthead.format(game.name)) - entries = game.getPlayer(pname) - if not entries: - ppagefi.write("
" + pname + " has not yet completed an expedition\ - in this dungeon.
\n") - else: - entries.sort(key=lambda e: e["endt"]) - printTable(entries, game.pfields, ppagefi) - scoresum = 0 - for entry in entries: - scoresum += int(entry["score"]) - avgscr = scoresum // len(entries) - ppagefi.write("

Average score: {0}

\n".format(avgscr)) - # That should settle it. Print the end; then stop. - ppagefi.write(pend) - ppagefi.close() - return - -def highpage(): - # open the page and print the beginning - highpfi = open(hpagename, "w", encoding="utf-8") - highpfi.write(phead.format("High Scores")) - highpfi.write(ptop) - highpfi.write(navscore.format("High Scores")) - highpfi.write(pti.format("List of High Scores")) - for game in gamelist: - highpfi.write(secthead.format(game.name)) - entries = game.getHigh(40) - if not entries: - highpfi.write("
No one has braved this dungeon yet.
\n") - else: - printTable(entries, game.rankfields, highpfi) - # That should finish it. - highpfi.write(pend) - highpfi.close() - return diff -r 05a4afbe6299 -r 25843238434a py/setupdb.py --- a/py/setupdb.py Thu Jan 02 11:48:15 2014 -0500 +++ b/py/setupdb.py Thu Jan 02 13:09:48 2014 -0500 @@ -1,7 +1,7 @@ #!/usr/bin/python3 # setupdb.py: initializes the database tables used by the rlg system. -import rlgalldb as rlgall +import rlgall import psycopg2 webuser = "webserver" diff -r 05a4afbe6299 -r 25843238434a py/stats.py --- a/py/stats.py Thu Jan 02 11:48:15 2014 -0500 +++ b/py/stats.py Thu Jan 02 13:09:48 2014 -0500 @@ -1,6 +1,8 @@ +#!/usr/bin/python3 + import math import psycopg2 -import rlgalldb as rlgall +import rlgall def makeExpPDF(lbd): def lpdf(x): @@ -22,13 +24,13 @@ total = sum(scores) lbd = float(count) / total cdf = makeExpCDF(lbd) - print "{0}: {1} games, average {2}".format(game.name, count, int(1/lbd)) + print("{0}: {1} games, average {2}".format(game.name, count, int(1/lbd))) for i in range(0, 10000, 1000): actual = len([ s for s in scores if i <= s < i + 1000 ]) predicted = (cdf(i + 1000) - cdf(i)) * count - print "{0:5}: {1:4} {2}".format(i, actual, predicted) + print("{0:5}: {1:4} {2}".format(i, actual, predicted)) high = max(scores) - print "Max: {0} {1}\n".format(high, 1 - cdf(high)) + print("Max: {0} {1}\n".format(high, 1 - cdf(high))) cur.close() conn.close() diff -r 05a4afbe6299 -r 25843238434a web/archive.cgi --- a/web/archive.cgi Thu Jan 02 11:48:15 2014 -0500 +++ b/web/archive.cgi Thu Jan 02 13:09:48 2014 -0500 @@ -7,7 +7,7 @@ import calendar from datetime import datetime import pytz -import rlgalldb as rlgall +import rlgall #import cgitb #cgitb.enable() diff -r 05a4afbe6299 -r 25843238434a web/recent.cgi --- a/web/recent.cgi Thu Jan 02 11:48:15 2014 -0500 +++ b/web/recent.cgi Thu Jan 02 13:09:48 2014 -0500 @@ -3,7 +3,7 @@ import sys import time -import rlgalldb as rlgall +import rlgall # It is assumed that sys.stdout uses UTF-8 encoding. If this is not the case, # configure the Web server to set the LC_CTYPE environment variable to a UTF-8 diff -r 05a4afbe6299 -r 25843238434a web/scoring/players/index.cgi --- a/web/scoring/players/index.cgi Thu Jan 02 11:48:15 2014 -0500 +++ b/web/scoring/players/index.cgi Thu Jan 02 13:09:48 2014 -0500 @@ -2,7 +2,7 @@ import os import sys -import rlgalldb as rlgall +import rlgall playerdir = "/var/www/lighttpd/scoring/players/" linkstr = '
  • {0}
  • \n'