mirror of
https://github.com/FAUSheppy/open-web-leaderboard.git
synced 2025-12-06 15:11:35 +01:00
implement basic db support for player stats
This commit is contained in:
138
database.py
138
database.py
@@ -2,56 +2,112 @@
|
|||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import player
|
import player
|
||||||
|
import os
|
||||||
|
|
||||||
DB_BASE = "file:{}?mode=ro"
|
DATABASE_PLAYERS = "players.sqlite"
|
||||||
|
DATABASE_ROUNDS = "rounds.sqlite"
|
||||||
|
DATABASE_HISTORICAL = "players.sqlite"
|
||||||
|
|
||||||
def getTotalPlayers(database):
|
class DatabaseConnection:
|
||||||
'''Get the total number of players in the database'''
|
def __init__(self, basepath):
|
||||||
|
self.dbFormatString = "file:{}?mode=ro"
|
||||||
|
self.databasePlayers = self.dbFormatString.format(os.path.join(basepath, DATABASE_PLAYERS))
|
||||||
|
self.databaseRounds = self.dbFormatString.format(os.path.join(basepath, DATABASE_ROUNDS))
|
||||||
|
self.databaseHistorical = self.dbFormatString.format(
|
||||||
|
os.path.join(basepath, DATABASE_HISTORICAL))
|
||||||
|
|
||||||
conn = sqlite3.connect(DB_BASE.format(database), uri=True)
|
self.connPlayers = sqlite3.connect(self.databasePlayers, uri=True)
|
||||||
cursor = conn.cursor()
|
self.connRounds = sqlite3.connect(self.databaseRounds, uri=True)
|
||||||
cursor.execute("SELECT Count(*) FROM players")
|
self.connHistorical = sqlite3.connect(self.databaseHistorical, uri=True)
|
||||||
count = cursor.fetchone()[0]
|
|
||||||
conn.close()
|
|
||||||
return count
|
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.connPlayers.close();
|
||||||
|
self.connRounds.close();
|
||||||
|
self.connHistorical.close();
|
||||||
|
|
||||||
def getRankRange(database, start, end):
|
def getTotalPlayers(self):
|
||||||
'''Get a range of players by rank'''
|
'''Get the total number of players in the database'''
|
||||||
|
|
||||||
conn = sqlite3.connect(DB_BASE.format(database), uri=True)
|
cursor = self.connPlayers.cursor()
|
||||||
cursor = conn.cursor()
|
cursor.execute("SELECT Count(*) FROM players")
|
||||||
limit = end - start
|
count = cursor.fetchone()[0]
|
||||||
cursor.execute("Select * FROM players ORDER BY (mu - 2*sigma) DESC LIMIT ? OFFSET ?", (limit, start))
|
return count
|
||||||
rows = cursor.fetchall()
|
|
||||||
conn.close()
|
|
||||||
playerList = []
|
|
||||||
for row in rows:
|
|
||||||
playerList += [player.PlayerInLeaderboard(row)]
|
|
||||||
|
|
||||||
return playerList
|
def getHistoricalForPlayerId(self, playerId):
|
||||||
|
'''Get historical data for a player'''
|
||||||
|
|
||||||
def findPlayerByName(database, playerName):
|
cursor = self.connHistorical.cursor()
|
||||||
'''Find a player by his name (prefer fullmatch)'''
|
cursor.execute("SELECT * FROM playerHistoricalData where id = ?", (playerId,))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
conn = sqlite3.connect(DB_BASE.format(database), uri=True)
|
PLAYER_ID = 0
|
||||||
cursor = conn.cursor()
|
TIMESTAMP = 1
|
||||||
playerNamePrepared = "%{}%".format(playerName.replace("%", "%%"))
|
MU = 2
|
||||||
cursor.execute("SELECT * FROM players WHERE name == ?", (playerName,))
|
SIGMA = 3
|
||||||
row = cursor.fetchone()
|
|
||||||
|
|
||||||
playerRow = None
|
playerIdDict = dict()
|
||||||
if not playerRow:
|
for r in rows:
|
||||||
cursor.execute("SELECT * FROM players WHERE name LIKE ?", (playerNamePrepared,))
|
timestampDict = dict()
|
||||||
playerRow = cursor.fetchone()
|
timestampDict.update({ "mu" : r[MU] })
|
||||||
|
timestampDict.update({ "sigma" : r[SIGMA]})
|
||||||
|
playerIdDict.update({ r[TIMESTAMP] : timestampDict })
|
||||||
|
|
||||||
|
retDict = { rows[0][PLAYER_ID] : playerIdDict }
|
||||||
|
return retDict
|
||||||
|
|
||||||
|
def getPlayerById(self, playerId):
|
||||||
|
'''Get a player by his id'''
|
||||||
|
|
||||||
|
cursor = self.connPlayers.cursor()
|
||||||
|
cursor.execute("SELECT * FROM players where playerId = ?", (playerId,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
if(row):
|
||||||
|
playerInLeaderboard = player.PlayerInLeaderboard(playerRow)
|
||||||
|
else:
|
||||||
|
playerInLeaderboard = None
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
def getRankRange(self, start, end):
|
||||||
|
'''Get a range of players by rank'''
|
||||||
|
|
||||||
|
cursor = self.connPlayers.cursor()
|
||||||
|
limit = end - start
|
||||||
|
sqlQuery = "Select * FROM players ORDER BY (mu - 2*sigma) DESC LIMIT ? OFFSET ?"
|
||||||
|
cursor.execute(sqlQuery, (limit, start))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
playerList = []
|
||||||
|
for row in rows:
|
||||||
|
playerList += [player.PlayerInLeaderboard(row)]
|
||||||
|
return playerList
|
||||||
|
|
||||||
|
def findPlayerByName(self, playerName):
|
||||||
|
'''Find a player by his name (prefer fullmatch)'''
|
||||||
|
|
||||||
|
cursor = self.connPlayers.cursor()
|
||||||
|
playerNamePrepared = "%{}%".format(playerName.replace("%", "%%"))
|
||||||
|
cursor.execute("SELECT * FROM players WHERE name == ?", (playerName,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
playerRow = None
|
||||||
if not playerRow:
|
if not playerRow:
|
||||||
conn.close()
|
cursor.execute("SELECT * FROM players WHERE name LIKE ?", (playerNamePrepared,))
|
||||||
return (None, None)
|
playerRow = cursor.fetchone()
|
||||||
|
if not playerRow:
|
||||||
|
conn.close()
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
playerInLeaderboard = player.PlayerInLeaderboard(playerRow)
|
||||||
|
player.rank = getPlayerRank(playerInLeaderboard)
|
||||||
|
return playerInLeaderboard
|
||||||
|
|
||||||
playerInLeaderboard = player.PlayerInLeaderboard(playerRow)
|
def getPlayerRank(self, player):
|
||||||
# compte rank
|
'''Calculate player rank - a player rank may change rapidly and
|
||||||
cursor.execute("SELECT COUNT(*) from players where (mu-2*sigma) > (?-2*?);",
|
can't and shouldn't be used to identify a player'''
|
||||||
(playerInLeaderboard.mu, playerInLeaderboard.sigma))
|
|
||||||
rank = cursor.fetchone()[0]
|
cursor = connPlayers.cursor()
|
||||||
conn.close()
|
cursor.execute("SELECT COUNT(*) from players where (mu-2*sigma) > (?-2*?);",
|
||||||
return (playerInLeaderboard, rank)
|
(playerInLeaderboard.mu, playerInLeaderboard.sigma))
|
||||||
|
rank = cursor.fetchone()[0]
|
||||||
|
return rank
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class PlayerInLeaderboard:
|
|||||||
self.games = int(games)
|
self.games = int(games)
|
||||||
self.wins = int(wins)
|
self.wins = int(wins)
|
||||||
self.loses = self.games - self.wins
|
self.loses = self.games - self.wins
|
||||||
|
self.rank = None
|
||||||
|
|
||||||
# determine winratio #
|
# determine winratio #
|
||||||
if self.games == 0:
|
if self.games == 0:
|
||||||
|
|||||||
32
server.py
32
server.py
@@ -4,9 +4,10 @@ import requests
|
|||||||
import argparse
|
import argparse
|
||||||
import flask_caching as fcache
|
import flask_caching as fcache
|
||||||
import json
|
import json
|
||||||
import database as db
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from database import DatabaseConnection
|
||||||
|
|
||||||
|
|
||||||
app = flask.Flask("open-leaderboard")
|
app = flask.Flask("open-leaderboard")
|
||||||
|
|
||||||
@@ -18,6 +19,27 @@ cache.init_app(app)
|
|||||||
|
|
||||||
SEGMENT=100
|
SEGMENT=100
|
||||||
|
|
||||||
|
@app.route('/playerdata')
|
||||||
|
def playerInfo():
|
||||||
|
'''API-Endpoint for Canvas Query'''
|
||||||
|
|
||||||
|
playerId = flask.request.args.get("id")
|
||||||
|
|
||||||
|
db = DatabaseConnection(app.config["DB_PATH"])
|
||||||
|
data = db.getHistoricalForPlayerId(playerId)
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
@app.route("/player")
|
||||||
|
def player():
|
||||||
|
'''Show Info about Player'''
|
||||||
|
|
||||||
|
db = DatabaseConnection(app.config["DB_PATH"])
|
||||||
|
player = db.getPlayerById(playerId)
|
||||||
|
player.rank = db.getPlayerRank(player)
|
||||||
|
|
||||||
|
return flask.render_template("player.html", player=player)
|
||||||
|
|
||||||
@app.route('/leaderboard')
|
@app.route('/leaderboard')
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@cache.cached(timeout=600, query_string=True)
|
@cache.cached(timeout=600, query_string=True)
|
||||||
@@ -27,7 +49,7 @@ def leaderboard():
|
|||||||
# parse parameters #
|
# parse parameters #
|
||||||
page = flask.request.args.get("page")
|
page = flask.request.args.get("page")
|
||||||
playerName = flask.request.args.get("string")
|
playerName = flask.request.args.get("string")
|
||||||
|
db = DatabaseConnection(app.config["DB_PATH"])
|
||||||
|
|
||||||
if page:
|
if page:
|
||||||
start = SEGMENT * int(page)
|
start = SEGMENT * int(page)
|
||||||
@@ -39,7 +61,7 @@ def leaderboard():
|
|||||||
searchName = ""
|
searchName = ""
|
||||||
|
|
||||||
if playerName:
|
if playerName:
|
||||||
playerInLeaderboard, rank = db.findPlayerByName(app.config["DB_PATH"], playerName)
|
playerInLeaderboard, rank = db.findPlayerByName(playerName)
|
||||||
if not playerInLeaderboard:
|
if not playerInLeaderboard:
|
||||||
cannotFindPlayer = flask.Markup("<div class=noPlayerFound>No player of that name</div>")
|
cannotFindPlayer = flask.Markup("<div class=noPlayerFound>No player of that name</div>")
|
||||||
start = 0
|
start = 0
|
||||||
@@ -51,14 +73,14 @@ def leaderboard():
|
|||||||
|
|
||||||
|
|
||||||
# compute range #
|
# compute range #
|
||||||
maxEntry = db.getTotalPlayers(app.config["DB_PATH"])
|
maxEntry = db.getTotalPlayers()
|
||||||
reachedEnd = False
|
reachedEnd = False
|
||||||
if end > maxEntry:
|
if end > maxEntry:
|
||||||
start = maxEntry - ( maxEntry % SEGMENT ) - 1
|
start = maxEntry - ( maxEntry % SEGMENT ) - 1
|
||||||
end = maxEntry - 1
|
end = maxEntry - 1
|
||||||
reachedEnd = True
|
reachedEnd = True
|
||||||
|
|
||||||
playerList = db.getRankRange(app.config["DB_PATH"], start, end)
|
playerList = db.getRankRange(start, end)
|
||||||
|
|
||||||
columContent = flask.Markup(flask.render_template("playerLine.html", \
|
columContent = flask.Markup(flask.render_template("playerLine.html", \
|
||||||
playerRank="Rank", \
|
playerRank="Rank", \
|
||||||
|
|||||||
Reference in New Issue
Block a user