This commit is contained in:
Yannik Schmidt
2021-12-20 19:07:55 +01:00
parent 12c001ac71
commit 6e677c663b
7 changed files with 68 additions and 448 deletions

View File

@@ -1,63 +0,0 @@
import json
import datetime
import player
import datetime
class MapSummary:
def __init__(self, rounds):
'''Create a map MapSummary from a Round-Array'''
self.securityWins = 0
self.insurgentWins = 0
self.times = []
self.predictions = []
self.totalGames = 0
self.confidence = []
self.mapName = None
for r in rounds:
self.mapName = r.mapName
self.totalGames += 1
if r.winnerSideString == "Insurgent":
self.insurgentWins += 1
else:
self.securityWins += 1
self.predictions += [r.numericPrediction]
self.confidence += [r.confidence]
self.times += [r.duration]
self.insurgentWinPercent = ""
self.securityWinPercent = ""
self.ratingSystemDeviation = "-"
self.averageTime = ""
try:
self.insurgentWinPercent = self.insurgentWins / self.totalGames*100
self.securityWinPercent = self.securityWins / self.totalGames*100
averageSeconds = sum([t.total_seconds() for t in self.times]) / len(self.times)
self.averageTime = datetime.timedelta(seconds=int(averageSeconds))
mapper = [ 1 if x == 0 else -1 for x in self.predictions ]
reverseMapper = [ 1 if x == 0 else 0 for x in self.predictions ]
self.ratingSystemDeviation = 0
confidenceCutoff = 60
confidenceTupels = list(filter(lambda x: x[1] > confidenceCutoff,
zip(reverseMapper, self.confidence)))
mapperTupels = list(filter(lambda x: x[1] > confidenceCutoff,
zip(mapper, self.confidence)))
for i in range(0, len(mapperTupels)):
self.ratingSystemDeviation += mapperTupels[i][0] * max(100, 50+mapperTupels[i][1])
self.ratingSystemDeviation /= len(mapperTupels)
self.predictionCorrectPercentage = sum([x[0] for x in confidenceTupels])
self.predictionCorrectPercentage /= len(confidenceTupels)
self.predictionCorrectPercentage *= 100
self.predictionCorrectPercentage = round(self.predictionCorrectPercentage)
except ZeroDivisionError:
pass

View File

@@ -1,60 +0,0 @@
import json
import datetime
import player
import json
import os
class Round:
def __init__(self, dbRow):
'''Create Round Object from cursor database row'''
timestamp, winners, losers, winnerSide, mapName, duration, prediction, confidence = dbRow
startTime = datetime.datetime.fromtimestamp(int(float(timestamp)))
winnersParsed = json.loads(winners)
losersParsed = json.loads(losers)
self.startTime = startTime
self.id = int(float(timestamp))
self.winners = [ player.playerFromDict(wp, int(duration)) for wp in winnersParsed ]
self.losers = [ player.playerFromDict(lp, int(duration)) for lp in losersParsed ]
self.winnerSide = winnerSide
self.duration = datetime.timedelta(seconds=int(duration))
self.blacklist = False
blacklistNames = []
blacklistFile = "blacklist.json"
if os.path.isfile(blacklistFile):
with open(blacklistFile) as f:
blacklistNames = json.load(f)["blacklist"]
for name in blacklistNames:
for p in self.winners:
if p.name == name:
self.blacklist = True
for p in self.losers:
if p.name == name:
self.blacklist = True
if winnerSide == 1:
self.winnerSideString = "Red"
self.loserSideString = "Blue"
else:
self.winnerSideString = "Blue"
self.loserSideString = "Red"
if mapName:
self.mapName = mapName
else:
self.mapName = "unavailiable"
self.numericPrediction = prediction
self.confidence = (int(confidence * 100) - 50)*2
self.quality = int(150 - self.confidence)
if self.confidence < 50:
self.prediction = "-"
elif prediction == 0:
self.prediction = self.winnerSideString
elif prediction == 1:
self.prediction = self.loserSideString
else:
self.prediction = "Error"

View File

@@ -1 +0,0 @@
DB_PATH="/home/sheppy-gaming/insurgency-skillbird/python/"

View File

@@ -1,2 +0,0 @@
# rename to config.py and remove this line for this file to have effect
DB_PATH="players.sqlite"

View File

@@ -1,49 +0,0 @@
#!/usr/bin/python3
import flask
def playerFromDict(d, duration):
p = PlayerInLeaderboard([d["id"], d["name"], None, 0, 0, 0, 0])
p.participation = min(int(d["active_time"]/duration*100), 100)
return p
class PlayerInLeaderboard:
def __init__(self, dbRow):
'''Initialize a player object later to be serialized to HTML'''
playerId, name, lastGame, wins, mu, sigma, games = dbRow
# set relevant values #
self.name = name
self.playerId = playerId
self.mu = mu
self.sigma = sigma
self.rating = int(self.mu) - 2*int(self.sigma)
self.ratingStr = str(self.rating)
self.games = int(games)
self.wins = int(wins)
self.loses = self.games - self.wins
self.rank = None
self.lastGame = lastGame
self.participation = -1
self.muChange = None
self.sigmaChange = None
self.ratingChangeString = "N/A"
# determine winratio #
if self.games == 0:
self.winratio = "N/A"
else:
self.winratio = str(int(self.wins/self.games * 100))
def getLineHTML(self, rank):
'''Build a single line for a specific player in the leaderboard'''
string = flask.render_template("playerLine.html", \
playerRank = rank, \
playerName = self.name, \
playerRating = self.rating, \
playerGames = self.games, \
playerWinratio = self.winratio)
return flask.Markup(string)

330
server.py
View File

@@ -3,18 +3,14 @@ import flask
import requests import requests
import argparse import argparse
import datetime import datetime
import flask_caching as fcache
import itertools import itertools
import json import json
import os import os
import MapSummary
import random import random
import secrets import secrets
import riotwatcher import riotwatcher
import time import time
import statistics import statistics
from database import DatabaseConnection
import api import api
@@ -23,152 +19,90 @@ app = flask.Flask("open-leaderboard")
WATCHER = None WATCHER = None
KEY = None KEY = None
if os.path.isfile("config.py"): if os.path.isfile("config.py"):
app.config.from_object("config") app.config.from_object("config")
cache = fcache.Cache(app, config={'CACHE_TYPE': 'simple'})
cache.init_app(app)
SEGMENT=100 SEGMENT=100
SERVERS=list() SERVERS=list()
from sqlalchemy import Column, Integer, String, Boolean, or_, and_
from sqlalchemy.orm import sessionmaker
from sqlalchemy.exc import IntegrityError
from sqlalchemy.sql import func
import sqlalchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
def prettifyMinMaxY(computedMin, computedMax): POSITIONS_NAMES = ["Top", "Jungle", "Mid", "Bottom", "Support" ]
if computedMax > 0 and computedMin > 0: DATABASE_PRIO_NAMES = ["prioTop", "prioJungle", "prioMid", "prioBot", "prioSupport" ]
return (0, 4000) TYPE_JSON = 'application/json'
else:
return (computedMin - 100, 4000)
@app.route("/players-online") HTTP_OK = 200
def playersOnline():
'''Calc and return the online players'''
playerTotal = 0 class PlayerInDatabase(db.Model):
error = "" __tablename__ = "players"
player = Column(String, primary_key=True)
rating = Column(Integer)
ratingFix = Column(Integer)
lastUpdated = Column(Integer)
for s in SERVERS: class Submission(db.Model):
try: __tablename__ = "submissions"
with valve.source.a2s.ServerQuerier((args.host, args.port)) as server: ident = Column(String, primary_key=True)
playerTotal += int(server.info()["player_count"]) submissionId = Column(String)
except NoResponseError: player = Column(String)
error = "Server Unreachable" prioTop = Column(Integer)
except Exception as e: prioJungle = Column(Integer)
error = str(e) prioMid = Column(Integer)
prioBot = Column(Integer)
retDict = { "player_total" : playerTotal, "error" : error } prioSupport = Column(Integer)
return flask.Response(json.dumps(retDict), 200, mimetype='application/json')
@app.route("/round-info")
def singleRound():
'''Display info about a single round itdentified by it's timestamp'''
timestamp = flask.request.args.get("id")
if not timestamp:
return ("ID Missing", 404)
if not timestamp.endswith(".0"):
timestamp = timestamp + ".0"
db = DatabaseConnection(app.config["DB_PATH"])
r = db.getRoundByTimestamp(timestamp)
if not r:
return ("Round not found", 404)
elif r.blacklist:
return ("Unavailable due to pending GDPR deletion request", 451)
r = db.calcRatingChanges(r)
if not r:
return ("", 404)
r.winners = sorted(r.winners, key=lambda p: p.participation, reverse=True)
r.losers = sorted(r.losers, key=lambda p: p.participation, reverse=True)
return flask.render_template("single_round.html", r=r)
@app.route("/livegames")
def liveGames():
'''Display info about a single round itdentified by it's timestamp'''
db = DatabaseConnection(app.config["DB_PATH"])
rounds = db.getLiveGames()
return flask.render_template("livegames.html", liveGameRounds=rounds, noRounds=not bool(rounds))
@app.route("/rounds-by-timestamp")
@app.route("/rounds")
def rounds():
'''Show rounds played on the server'''
start = flask.request.args.get("start")
end = flask.request.args.get("end")
if not start or not end:
start = datetime.datetime.now() - datetime.timedelta(days=365)
end = datetime.datetime.now()
else:
start = datetime.datetime.fromtimestamp(start)
end = datetime.datetime.fromtimestamp(end)
db = DatabaseConnection(app.config["DB_PATH"])
rounds = db.roundsBetweenDates(start, end)
return flask.render_template("rounds.html", rounds=rounds)
# get timestamp
# display players
# display rating change
# display outcome
# display map
class Player: class Player:
def __init__(self, name, prio): def __init__(self, name, prio):
self.name = name self.name = name
self.prio = prio self.prio = prio
# TODO
submission = dict()
@app.route("/role-submission", methods=['GET', 'POST']) @app.route("/role-submission", methods=['GET', 'POST'])
def roleSubmissionTool(): def roleSubmissionTool():
positions=["Top", "Jungle", "Mid", "Bottom", "Support" ]
ident = flask.request.args.get("id") submissionId = flask.request.args.get("id")
player = flask.request.args.get("player")
if flask.request.method == 'POST': if flask.request.method == 'POST':
if not ident in submission: submissionQuery = db.session.query(PlayerInDatabase)
submission.update({ ident : [] }) identKey = "{}-{}".format(submissionId, player)
submission = submissionQuery.filter(PlayerInDatabase.ident == identKey).first()
tmp = dict() if not submission:
tmp.update({ "name" : flask.request.form["playername"] }) submission = Submission(identKey, submissionId, player, -1, -1, -1, -1, -1)
for p in positions:
tmp.update({ p : flask.request.form["prio_{}".format(p)] })
existed = False for i in range(0, 5):
for pl in submission[ident]: formKey = "prio_" + POSITIONS_NAMES[i]
if pl["name"] == tmp["name"]: setattr(submission, DATABASE_PRIO_NAMES[i], flask.request.form[formKey])
for p in positions:
pl.update({ p : flask.request.form["prio_{}".format(p)] })
existed = True
break;
if not existed: db.session.merge(submission)
submission[ident] += [tmp] db.session.commit()
return flask.redirect("/balance-tool?id={}".format(ident)) return flask.redirect("/balance-tool?id=" + ident)
else: else:
return flask.render_template("role_submission.html", return flask.render_template("role_submission.html", positions=positions, ident=ident)
positions=positions,
ident=ident)
@app.route("/balance-tool-data") @app.route("/balance-tool-data")
def balanceToolData(): def balanceToolData():
ident = flask.request.args.get("id")
retDict = dict() submissionId = flask.request.args.get("id")
if not ident in submission: submissionsQuery = db.session.query(PlayerInDatabase)
return flask.Response(json.dumps({ "no-data" : False }), 200, mimetype='application/json') submissions = submissionsQuery.filter(PlayerInDatabase.submissionId == submissionId).all()
retDict.update({ "submissions" : submission[ident] })
return flask.Response(json.dumps(retDict), 200, mimetype='application/json') if not submissions:
return flask.Response(json.dumps({ "no-data" : False }), HTTP_OK, mimetype=TYPE_JSON)
retDict.update()
dicts = [ s.toDict() for s in submissions ]
return flask.Response(json.dumps({ "submissions" : dicts }), HTTP_OK, mimetype=TYPE_JSON)
@app.route('/') @app.route('/')
@@ -323,9 +257,6 @@ def balanceTool():
positions=positions, positions=positions,
sides=["left", "right"], sides=["left", "right"],
ident=ident) ident=ident)
@app.route("/get-cache")
def getCacheLoc():
return (json.dumps(api.getCache()), 200)
@app.route("/player-api") @app.route("/player-api")
def playerApi(): def playerApi():
@@ -343,140 +274,9 @@ def playerApiCache():
else: else:
return ("Nope", 404) return ("Nope", 404)
@app.route("/player") @app.route("/get-cache")
def player(): def getCacheLoc():
'''Show Info about Player''' return (json.dumps(api.getCache()), 200)
playerId = flask.request.args.get("id")
if(not playerId):
return ("", 404)
db = DatabaseConnection(app.config["DB_PATH"])
player = db.getPlayerById(playerId)
if(not player):
return ("", 404)
player.rank = db.getPlayerRank(player)
histData = db.getHistoricalForPlayerId(playerId)
csv_month_year = []
csv_ratings = []
csv_timestamps = []
minRating = 3000
maxRating = 0
if histData:
datapoints = histData[playerId]
if datapoints:
tickCounter = 10
for dpk in datapoints.keys():
t = datetime.datetime.fromtimestamp(int(float(dpk)))
tsMs = str(int(t.timestamp() * 1000))
ratingString = str(int(datapoints[dpk]["mu"]) - 2*int(datapoints[dpk]["sigma"]))
ratingAmored = '{ x : ' + tsMs + ', y : ' + ratingString + '}'
csv_timestamps += [str(tsMs)]
csv_ratings += [ratingAmored]
tickCounter -= 1
if tickCounter <= 0:
tickCounter = 10
csv_month_year += ['new Date({})'.format(tsMs)]
minRating = min(minRating, int(ratingString))
maxRating = max(maxRating, int(ratingString))
yMin, yMax = prettifyMinMaxY(minRating, maxRating)
# change displayed rank to start from 1 :)
player.rank += 1
return flask.render_template("player.html", player=player, CSV_RATINGS=",".join(csv_ratings),
CSV_MONTH_YEAR_OF_RATINGS=",".join(csv_month_year),
CSV_TIMESTAMPS=csv_timestamps,
Y_MIN=yMin, Y_MAX=yMax)
@app.route('/leaderboard')
@cache.cached(timeout=10, query_string=True)
def leaderboard():
'''Show main leaderboard page with range dependant on parameters'''
# parse parameters #
page = flask.request.args.get("page")
playerName = flask.request.args.get("string")
db = DatabaseConnection(app.config["DB_PATH"])
if page:
pageInt = int(page)
if pageInt < 0:
pageInt = 0
start = SEGMENT * int(page)
else:
pageInt = 0
start = 0
# handle find player request #
cannotFindPlayer = ""
searchName = ""
playerList = None
doNotComputeRank = True
if playerName:
playersInLeaderboard = db.findPlayerByName(playerName)
if not playersInLeaderboard:
cannotFindPlayer = flask.Markup("<div class=noPlayerFound>No player of that name</div>")
start = 0
else:
if len(playersInLeaderboard) == 1:
rank = playersInLeaderboard[0].rank
if(playersInLeaderboard[0].games < 10):
return flask.redirect("/player?id={}".format(playersInLeaderboard[0].playerId))
searchName = playersInLeaderboard[0].name
start = rank - (rank % SEGMENT)
else:
playerList = playersInLeaderboard
for p in playerList:
if p.rank == -1:
p.rankStr = "N/A"
else:
p.rankStr = str(p.rank)
doNotComputeRank = False
reachedEnd = False
maxEntry = 0
if not playerList:
# compute range #
end = start + SEGMENT
maxEntry = db.getTotalPlayers()
reachedEnd = False
if end > maxEntry:
start = maxEntry - ( maxEntry % SEGMENT ) - 1
end = maxEntry - 1
print(maxEntry)
reachedEnd = True
playerList = db.getRankRange(start, end)
endOfBoardIndicator = ""
if reachedEnd:
endOfBoardHtml = "<div id='eof' class=endOfBoardIndicator> - - - End of Board - - - </div>"
endOfBoardIndicator = flask.Markup(endOfBoardHtml)
# fix <100 player start at 0 #
if maxEntry <= 100:
start = max(start, 0)
finalResponse = flask.render_template("base.html", playerList=playerList, \
doNotComputeRank=doNotComputeRank, \
start=start, \
endOfBoardIndicator=endOfBoardIndicator, \
findPlayer=cannotFindPlayer, \
searchName=searchName,
nextPageNumber=int(pageInt)+1,
prevPageNumber=int(pageInt)-1)
return finalResponse
@app.route('/static/<path:path>') @app.route('/static/<path:path>')
def send_js(path): def send_js(path):
@@ -486,22 +286,22 @@ def send_js(path):
def init(): def init():
global WATCHER global WATCHER
app.config["DB"] = db
db.create_all()
with open("key.txt","r") as f: with open("key.txt","r") as f:
key = f.read().strip() key = f.read().strip()
WATCHER = riotwatcher.LolWatcher(key) WATCHER = riotwatcher.LolWatcher(key)
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Start open-leaderboard', \ parser = argparse.ArgumentParser(description='Start open-leaderboard', \
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--interface', default="localhost", \ parser.add_argument('--interface', default="localhost")
help='Interface on which flask (this server) will take requests on') parser.add_argument('--port', default="5002")
parser.add_argument('--port', default="5002", \
help='Port on which flask (this server) will take requests on')
parser.add_argument('--skillbird-db', required=False, help='skillbird database (overrides web connection if set)')
args = parser.parse_args() args = parser.parse_args()
app.config["DB_PATH"] = args.skillbird_db
app.config["TEMPLATES_AUTO_RELOAD"] = True app.config["TEMPLATES_AUTO_RELOAD"] = True
app.run(host=args.interface, port=args.port) app.run(host=args.interface, port=args.port)

View File

@@ -1,5 +0,0 @@
CREATE TABLE players(
playerName TEXT PRIMARY KEY,
rating INTEGER,
lastupdated TEXT
);