mirror of
https://github.com/FAUSheppy/open-web-leaderboard.git
synced 2025-12-06 15:11:35 +01:00
Compare commits
13 Commits
ese-custom
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f964ddb55 | |||
| 6491afc272 | |||
| 756b24a447 | |||
| 5f7713daaf | |||
| ee14c3fd7e | |||
|
|
c32155fd40 | ||
|
|
defcf5671d | ||
| 2133249947 | |||
| db3d2bb57e | |||
| c5b7963fff | |||
| 23fa7f9862 | |||
| 8f7b1b47ac | |||
| bdd7d9bf01 |
34
.github/workflows/main.yaml
vendored
Normal file
34
.github/workflows/main.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Container Build for open-web-leaderboard
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
schedule:
|
||||
- cron: "0 2 * * 0"
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: prod
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to Docker Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ secrets.REGISTRY }}
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASS }}
|
||||
-
|
||||
name: open-web-leaderboard
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: "${{ secrets.REGISTRY }}/atlantishq/open-web-leaderboard:latest"
|
||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM alpine
|
||||
|
||||
RUN apk add --no-cache py3-pip
|
||||
RUN python3 -m pip install --no-cache-dir --break-system-packages waitress
|
||||
COPY req.txt .
|
||||
RUN python3 -m pip install --no-cache-dir --break-system-packages -r req.txt
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
COPY ./ .
|
||||
|
||||
ENTRYPOINT ["waitress-serve"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "5000", "--call", "app:createApp"]
|
||||
63
MapSummary.py
Normal file
63
MapSummary.py
Normal file
@@ -0,0 +1,63 @@
|
||||
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
|
||||
|
||||
@@ -33,6 +33,7 @@ Players can be blacklisted by name via a *blacklist.json* file in the project ro
|
||||
}
|
||||
|
||||
# Adding servers for player count live info
|
||||
**THIS FEATURE IS DISABLED BECAUSE py-valve DOES NOT SUPPORT PYTHON>3.9**
|
||||
Source-Servers can be added via the *servers.json*-file:
|
||||
|
||||
[
|
||||
|
||||
60
Round.py
Normal file
60
Round.py
Normal file
@@ -0,0 +1,60 @@
|
||||
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 == 2:
|
||||
self.winnerSideString = "Security"
|
||||
self.loserSideString = "Insurgent"
|
||||
else:
|
||||
self.winnerSideString = "Insurgent"
|
||||
self.loserSideString = "Security"
|
||||
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"
|
||||
113
api.py
113
api.py
@@ -1,113 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import riotwatcher
|
||||
import json
|
||||
import argparse
|
||||
import os
|
||||
import time
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import requests
|
||||
import datetime as dt
|
||||
import sqlite3
|
||||
|
||||
REGION = "euw1"
|
||||
DEFAULT_RATING = 1200
|
||||
|
||||
def tierToNumber(tier):
|
||||
ratingmap = { 'CHALLENGER' : 4500,
|
||||
'GRANDMASTER': 4000,
|
||||
'MASTER' : 3500,
|
||||
'DIAMOND' : 3000,
|
||||
'PLATINUM' : 2500,
|
||||
'GOLD' : 1500,
|
||||
'SILVER' : 1000,
|
||||
'BRONZE' : 500,
|
||||
'IRON' : 0 }
|
||||
return ratingmap[tier]
|
||||
|
||||
def divisionToNumber(division):
|
||||
divisionmap = { "I" : 300,
|
||||
"II" : 200,
|
||||
"III" : 100,
|
||||
"IV" : 0 }
|
||||
return divisionmap[division]
|
||||
|
||||
DATABASE = "rating_cache.sqlite"
|
||||
def checkPlayerKnown(playerName):
|
||||
conn = sqlite3.connect(DATABASE)
|
||||
cursor = conn.cursor()
|
||||
backlog = dt.datetime.now() - dt.timedelta(days=7)
|
||||
query = '''SELECT * from players where playerName = ? LIMIT 1;'''
|
||||
cursor.execute(query, (playerName,))
|
||||
try:
|
||||
playerName, rating, lastUpdated = cursor.fetchone()
|
||||
except TypeError:
|
||||
print("sqlite cache '{}' not found".format(playerName))
|
||||
return None
|
||||
conn.close()
|
||||
return (playerName, rating, lastUpdated)
|
||||
|
||||
def addToDB(playerName, rating):
|
||||
|
||||
conn = sqlite3.connect(DATABASE)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("INSERT INTO players VALUES(?,?,?);",(
|
||||
playerName,
|
||||
rating,
|
||||
dt.datetime.now().timestamp()))
|
||||
conn.commit()
|
||||
print("Added {}".format(playerName))
|
||||
conn.close()
|
||||
|
||||
|
||||
def getPlayerRatingFromApi(playerName, WATCHER):
|
||||
|
||||
if not playerName:
|
||||
return DEFAULT_RATING
|
||||
|
||||
tupel = checkPlayerKnown(playerName)
|
||||
if tupel:
|
||||
return tupel[1]
|
||||
|
||||
while(True):
|
||||
try:
|
||||
pTmp = WATCHER.summoner.by_name(REGION, playerName)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
# not found #
|
||||
if e.response.status_code == 404:
|
||||
addToDB(playerName, DEFAULT_RATING)
|
||||
return DEFAULT_RATING
|
||||
# rate limit
|
||||
elif e.response.status_code == 429:
|
||||
print("Ratelimit reached")
|
||||
#time.sleep(120)
|
||||
#continue
|
||||
return DEFAULT_RATING
|
||||
else:
|
||||
raise e
|
||||
if not pTmp:
|
||||
addToDB(playerName, 0)
|
||||
return DEFAULT_RATING
|
||||
|
||||
computed = DEFAULT_RATING
|
||||
|
||||
try:
|
||||
pInfo = WATCHER.league.by_summoner(REGION, pTmp["id"])
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 429:
|
||||
print("Ratelimit reached")
|
||||
return DEFAULT_RATING
|
||||
#time.sleep(120)
|
||||
#continue
|
||||
else:
|
||||
raise e
|
||||
|
||||
for queue in pInfo:
|
||||
if queue["queueType"] != "RANKED_SOLO_5x5":
|
||||
continue
|
||||
computed = tierToNumber(queue["tier"]) + divisionToNumber(queue["rank"]) + \
|
||||
int(queue["leaguePoints"])
|
||||
print(computed)
|
||||
|
||||
addToDB(playerName, computed)
|
||||
return computed
|
||||
2
app.py
2
app.py
@@ -1,3 +1,5 @@
|
||||
import server
|
||||
def createApp(envivorment=None, start_response=None):
|
||||
with server.app.app_context():
|
||||
server.create_app()
|
||||
return server.app
|
||||
|
||||
68
balance.py
68
balance.py
@@ -1,68 +0,0 @@
|
||||
from constants import *
|
||||
|
||||
NO_CHANGE = -1
|
||||
|
||||
def shouldSwapBasedOnPrio(teams, curIndex, compareIndex, curTeamIndex):
|
||||
'''Return a team ID in which to switch with the compare index'''
|
||||
|
||||
otherTeam = (curTeamIndex + 1) % 2
|
||||
|
||||
curPrio = teams[curTeamIndex].affinityFor(curIndex)
|
||||
compPrioOtherTeam = teams[otherTeam].affinityFor(compareIndex)
|
||||
compPrioSameTeam = team[otherTeam].affinityFor(compareIndex)
|
||||
|
||||
if curPrio > compPrioSameTeam and compPrioSameTeam > compPrioOtherTeam:
|
||||
return compPrioSameTeam
|
||||
elif curPrio > compPrioOtherTeam:
|
||||
return compPrioOtherTeam
|
||||
else:
|
||||
return NO_CHANGE
|
||||
|
||||
def swap(teams, teamIndex1, teamIndex2, pos1, pos2):
|
||||
'''Swap two positions in the same or different teams'''
|
||||
|
||||
tmp = teams[teamIndex1][pos1]
|
||||
teams[teamIndex1][pos1] = teams[teamIndex2][pos2]
|
||||
teams[teamIndex2][pos2] = tmp
|
||||
|
||||
def ratingDiff(team1, team2):
|
||||
'''Positive if first > seconds, negative if second > first, 0 if equal'''
|
||||
return sum([p.rating for p in team1]) - sum([p.rating for p in team2])
|
||||
|
||||
def balance(players):
|
||||
|
||||
# initial teams #
|
||||
playersByRating = sorted(players, key=lambda p: p.rating)
|
||||
|
||||
teams = dict( { 0 : [] }, { 1 : [] } )
|
||||
for i in range(0, len(playersByRating)):
|
||||
teams[i%2] = playersByRating[i]
|
||||
|
||||
# optimize positions worst case ~8n^2 * 2log(n) #
|
||||
for teamIndex in teams.keys():
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for curIndex in range(0, 5)
|
||||
for compareIndex in range(curIndex, 5)
|
||||
if curIndex == compareIndex:
|
||||
continue
|
||||
|
||||
# shouldSwap return -1 for no swap or the team to swap with #
|
||||
swapTeam = shouldSwapBasedOnPrio(teams, curIndex, compareIndex, teamIndex)
|
||||
elif VALID_INDEX(swapTeam):
|
||||
changed = True
|
||||
swap(teams, teamIndex, swapTeam, curIndex, compareIndex)
|
||||
|
||||
# optimize team rating #
|
||||
changedRating = True
|
||||
while changedRating:
|
||||
|
||||
diff = ratingDiff(teams[0], teams[1])
|
||||
diffByPos = [ teams[0][i] - teams[1][i] for i in range(0, 5) ]
|
||||
|
||||
for i in range(0, diffByPos):
|
||||
diffHelper = abs(diffByPos[i]-diff)
|
||||
if diffHelper <
|
||||
for curIndex in range(0, len(teams[0])):
|
||||
if rating diff
|
||||
1
config.py
Normal file
1
config.py
Normal file
@@ -0,0 +1 @@
|
||||
DB_PATH="/home/sheppy-gaming/insurgency-skillbird/python/"
|
||||
2
config.py.example
Normal file
2
config.py.example
Normal file
@@ -0,0 +1,2 @@
|
||||
# rename to config.py and remove this line for this file to have effect
|
||||
DB_PATH="players.sqlite"
|
||||
@@ -1,8 +0,0 @@
|
||||
POSITIONS_NAMES = ["Top", "Jungle", "Mid", "Bottom", "Support" ]
|
||||
DATABASE_PRIO_NAMES = ["prioTop", "prioJungle", "prioMid", "prioBot", "prioSupport" ]
|
||||
TYPE_JSON = 'application/json'
|
||||
|
||||
HTTP_OK = 200
|
||||
|
||||
def VALID_INDEX(index):
|
||||
return index == 0 or index == 1
|
||||
19
database.py
19
database.py
@@ -31,7 +31,7 @@ class DatabaseConnection:
|
||||
'''Get the total number of players in the database'''
|
||||
|
||||
cursor = self.connPlayers.cursor()
|
||||
cursor.execute("SELECT Count(*) FROM players")
|
||||
cursor.execute("SELECT Count(*) FROM players where games >= 10 and not lastgame is null")
|
||||
count = cursor.fetchone()[0]
|
||||
return count
|
||||
|
||||
@@ -79,8 +79,8 @@ class DatabaseConnection:
|
||||
|
||||
cursor = self.connPlayers.cursor()
|
||||
limit = end - start
|
||||
print(limit, start)
|
||||
sqlQuery = '''Select * FROM players where games >= 1
|
||||
sqlQuery = '''Select * FROM players where games >= 10
|
||||
and not lastgame is null
|
||||
ORDER BY (mu - 2*sigma) DESC LIMIT ? OFFSET ?'''
|
||||
cursor.execute(sqlQuery, (limit, start))
|
||||
rows = cursor.fetchall()
|
||||
@@ -121,8 +121,11 @@ class DatabaseConnection:
|
||||
can't and shouldn't be used to identify a player'''
|
||||
|
||||
cursor = self.connPlayers.cursor()
|
||||
cursor.execute('''SELECT COUNT(*) from players
|
||||
where (mu-2*sigma) > (?-2*?);''',
|
||||
if(player.games < 10):
|
||||
return -1
|
||||
cursor.execute('''SELECT COUNT(*) from players where games >= 10
|
||||
and not lastgame is null
|
||||
and (mu-2*sigma) > (?-2*?);''',
|
||||
(player.mu, player.sigma))
|
||||
rank = cursor.fetchone()[0]
|
||||
return rank
|
||||
@@ -178,9 +181,9 @@ class DatabaseConnection:
|
||||
WHERE timestamp < ? AND id = ?''',
|
||||
(roundObj.startTime.timestamp(), p.playerId))
|
||||
|
||||
#if(cursorHist.fetchone()[0] < 10):
|
||||
# p.ratingChangeString = "Placements"
|
||||
# continue
|
||||
if(cursorHist.fetchone()[0] < 10):
|
||||
p.ratingChangeString = "Placements"
|
||||
continue
|
||||
|
||||
cursorHist.execute('''SELECT mu,sima FROM playerHistoricalData
|
||||
WHERE timestamp < ? AND id = ? order by timestamp DESC LIMIT 1 ''',
|
||||
|
||||
110
medals.py
Normal file
110
medals.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import flask
|
||||
import markupsafe
|
||||
|
||||
medalDict = { "games-played-1" : { "name" : "Tourist",
|
||||
"text" : "Played {} games on this server",
|
||||
"color" : "white",
|
||||
"text-color" : "black" },
|
||||
|
||||
"games-played-2" : { "name" : "Enlisted",
|
||||
"text" : "Played {} games on this server",
|
||||
"color": "green",
|
||||
"text-color" : "white" },
|
||||
|
||||
"games-played-3" : { "name" : "Veteran",
|
||||
"text" : "Played {} games on this server",
|
||||
"color" : "yellow",
|
||||
"text-color" : "black" },
|
||||
|
||||
"games-played-4" : { "name" : "Alpha Chad",
|
||||
"text" : "Played {} games on this server",
|
||||
"color" : "black",
|
||||
"text-color" : "red" },
|
||||
|
||||
"rating-cur-1" : { "name" : "Slightly skilled",
|
||||
"text" : "Rated above 1500",
|
||||
"color" : "beige",
|
||||
"text-color" : "black" },
|
||||
|
||||
"rating-2k-1" : { "name" : "Contender",
|
||||
"text" : "Played {} games above 2000 rating",
|
||||
"color" : "coral",
|
||||
"text-color" : "black" },
|
||||
"rating-2k-2" : { "name" : "Known Contender",
|
||||
"text" : "Played {} games above 2000 rating",
|
||||
"color" : "lightgreen",
|
||||
"text-color" : "black" },
|
||||
"rating-2k-3" : { "name" : "Epic Backup",
|
||||
"text" : "Played {} games above 2000 rating",
|
||||
"color" : "darkgreen",
|
||||
"text-color" : "white" },
|
||||
|
||||
"rating-3k-1" : { "name" : "Epic",
|
||||
"text" : "Played {} games above 3000 rating",
|
||||
"color" : "lightblue",
|
||||
"text-color" : "black" },
|
||||
"rating-3k-2" : { "name" : "Legend",
|
||||
"text" : "Played {} games above 3000 rating",
|
||||
"color" : "orange",
|
||||
"text-color" : "black" },
|
||||
"rating-3k-3" : { "name" : "All Along The Watchtower",
|
||||
"text" : "???",
|
||||
"color" : "red",
|
||||
"text-color" : "black" },
|
||||
"rating-3k-4" : { "name" : "Highlander",
|
||||
"text" : "There can only be one.",
|
||||
"color" : "darkred",
|
||||
"text-color" : "white" }
|
||||
}
|
||||
|
||||
def medalGen(medal, formatInsert=None):
|
||||
'''Gen HTML for metal'''
|
||||
html = '\
|
||||
<div style="background-color: {bg} !important; color: {color} !important;"\
|
||||
class="btn btn-secondary"\
|
||||
data-toggle="tooltip" data-placement="top" title="{tooltip}">\
|
||||
{text}\
|
||||
</div>\
|
||||
'
|
||||
tmp = html.format(bg=medal["color"], tooltip=medal["text"], text=medal["name"],
|
||||
color=medal["text-color"])
|
||||
if formatInsert:
|
||||
tmp = tmp.format(formatInsert)
|
||||
return markupsafe.Markup(tmp)
|
||||
|
||||
def getMedals(ratingList, gamesPlayed, currentRating):
|
||||
'''Get Medals this player should have'''
|
||||
medals = []
|
||||
|
||||
if gamesPlayed > 2000:
|
||||
medals += [medalGen(medalDict["games-played-4"], gamesPlayed)]
|
||||
elif gamesPlayed > 500:
|
||||
medals += [medalGen(medalDict["games-played-3"], gamesPlayed)]
|
||||
elif gamesPlayed > 100:
|
||||
medals += [medalGen(medalDict["games-played-2"], gamesPlayed)]
|
||||
elif gamesPlayed > 50:
|
||||
medals += [medalGen(medalDict["games-played-1"], gamesPlayed)]
|
||||
|
||||
games2k = len(list(filter(lambda x: x > 2000, ratingList)))
|
||||
games3k = len(list(filter(lambda x: x > 3000, ratingList)))
|
||||
|
||||
if games2k > 1000:
|
||||
medals += [medalGen(medalDict["rating-2k-3"], games2k)]
|
||||
elif games2k > 100:
|
||||
medals += [medalGen(medalDict["rating-2k-2"], games2k)]
|
||||
elif games2k > 0:
|
||||
medals += [medalGen(medalDict["rating-2k-1"], games2k)]
|
||||
|
||||
if games3k > 350:
|
||||
medals += [medalGen(medalDict["rating-3k-4"], games3k)]
|
||||
if games3k > 200:
|
||||
medals += [medalGen(medalDict["rating-3k-3"], games3k)]
|
||||
if games3k > 50:
|
||||
medals += [medalGen(medalDict["rating-3k-2"], games3k)]
|
||||
if games3k > 5:
|
||||
medals += [medalGen(medalDict["rating-3k-1"], games3k)]
|
||||
|
||||
if currentRating > 1500:
|
||||
medals += [medalGen(medalDict["rating-cur-1"])]
|
||||
|
||||
return medals
|
||||
49
player.py
Normal file
49
player.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/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)
|
||||
BIN
rounds.sqlite
Normal file
BIN
rounds.sqlite
Normal file
Binary file not shown.
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "1",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "xXxMarethyuxXx",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "iTrash",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "JuckF Bierhuhn",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "TryhardYordle",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ChessGM lbfxd",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "TheSlapstick",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Vyne",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE Felix",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 1456,
|
||||
"startTime": "2021-04-25T22:00+02:00"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "0",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "ESE Felix",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Vyne",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE Kniveless",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "JuckF Bierhuhn",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "TryhardYordle",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "TheSlapstick",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "xXxMarethyuxXx",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ChessGM lbfxd",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 1932,
|
||||
"startTime": "2021-04-25T21:00+02:00"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "1",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Vyne",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE Felix",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ChessGM lbfxd",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "TheSlapstick",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "xXxMarethyuxXx",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE Kniveless",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "JuckF Bierhuhn",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "TryhardYordle",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 2017,
|
||||
"startTime": "2021-04-25T20:00+02:00"
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"map" : "SR",
|
||||
"winner-side" : 1,
|
||||
"winners" : [
|
||||
{
|
||||
"playerId" : "TryhardYordle",
|
||||
"playerName" : "TryhardYordle",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Schmohi",
|
||||
"playerName" : "ESE Schmohi",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "Area 522",
|
||||
"playerName" : "Area 522",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "iTrash",
|
||||
"playerName" : "iTrash",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "Téa Jay",
|
||||
"playerName" : "Téa Jay",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
}
|
||||
|
||||
],
|
||||
"losers" : [
|
||||
{
|
||||
"playerId" : "Phirop",
|
||||
"playerName" : "Phirop",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "ENTAC",
|
||||
"playerName" : "ENTAC",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "BoringBookCover",
|
||||
"playerName" : "BoringBookCover",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Litzuck",
|
||||
"playerName" : "ESE Litzuck",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE MarsUltor",
|
||||
"playerName" : "ESE MarsUltor",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1971
|
||||
}
|
||||
],
|
||||
"duration" : 1971,
|
||||
"startTime" : "2021-05-24T20:00+02:00"
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"map" : "SR",
|
||||
"winner-side" : 0,
|
||||
"winners" : [
|
||||
{
|
||||
"playerId" : "Vyne",
|
||||
"playerName" : "Vyne",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Kniveless",
|
||||
"playerName" : "ESE Kniveless",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Vinnie",
|
||||
"playerName" : "ESE Vinnie",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Litzuck",
|
||||
"playerName" : "ESE Litzuck",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE MarsUltor",
|
||||
"playerName" : "ESE MarsUltor",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
}
|
||||
|
||||
],
|
||||
"losers" : [
|
||||
{
|
||||
"playerId" : "ESE Schmohi",
|
||||
"playerName" : "ESE Schmohi",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Nemesis",
|
||||
"playerName" : "ESE Nemesis",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "ENTAC",
|
||||
"playerName" : "ENTAC",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "Area 522",
|
||||
"playerName" : "Area 552",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
},
|
||||
{
|
||||
"playerId" : "Subsidiary",
|
||||
"playerName" : "Subsidiary",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1927
|
||||
}
|
||||
|
||||
],
|
||||
"duration" : 1927,
|
||||
"startTime" : "2021-05-31T20:00+02:00"
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"map" : "SR",
|
||||
"winner-side" : 1,
|
||||
"winners" : [
|
||||
{
|
||||
"playerId" : "Vyne",
|
||||
"playerName" : "Vyne",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Kniveless",
|
||||
"playerName" : "ESE Kniveless",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Vinnie",
|
||||
"playerName" : "ESE Vinnie",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Litzuck",
|
||||
"playerName" : "ESE Litzuck",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE MarsUltor",
|
||||
"playerName" : "ESE MarsUltor",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
}
|
||||
|
||||
],
|
||||
"losers" : [
|
||||
{
|
||||
"playerId" : "Muchlove the One",
|
||||
"playerName" : "Muchlove the One",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Nemesis",
|
||||
"playerName" : "ESE Nemesis",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "ENTAC",
|
||||
"playerName" : "ENTAC",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "Area 522",
|
||||
"playerName" : "Area 552",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
},
|
||||
{
|
||||
"playerId" : "Subsidiary",
|
||||
"playerName" : "Subsidiary",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1864
|
||||
}
|
||||
|
||||
],
|
||||
"duration" : 1864,
|
||||
"startTime" : "2021-05-31T21:00+02:00"
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"map" : "SR",
|
||||
"winner-side" : 0,
|
||||
"winners" : [
|
||||
{
|
||||
"playerId" : "Vyne",
|
||||
"playerName" : "Vyne",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Kniveless",
|
||||
"playerName" : "ESE Kniveless",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "Muchlove the One",
|
||||
"playerName" : "Muchlove the One",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Litzuck",
|
||||
"playerName" : "ESE Litzuck",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "ENTAC",
|
||||
"playerName" : "ENTAC",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
}
|
||||
],
|
||||
"losers" : [
|
||||
{
|
||||
"playerId" : "ESE MarsUltor",
|
||||
"playerName" : "ESE MarsUltor",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Nemesis",
|
||||
"playerName" : "ESE Nemesis",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "Area 522",
|
||||
"playerName" : "Area 552",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "Takki",
|
||||
"playerName" : "Takki",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
},
|
||||
{
|
||||
"playerId" : "ESE Vinnie",
|
||||
"playerName" : "ESE Vinnie",
|
||||
"isFake" : false,
|
||||
"activeTime" : 1450
|
||||
}
|
||||
|
||||
],
|
||||
"duration" : 1450,
|
||||
"startTime" : "2021-05-31T23:00+02:00"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "0",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Victor Lustig",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "TSo",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE MarsUltor",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "Téa Jay",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Heinz sama",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Area 522",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Byndeskanzler",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE Schmohi",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 2019,
|
||||
"startTime": "2021-06-07T20:00+02:00"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "1",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "ESE TomTom",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Byndeskanzler",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Area 522",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Heinz sama",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Téa Jay",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "lbfxd",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Victor Lustig",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE MarsUltor",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 1978,
|
||||
"startTime": "2021-07-05T20:00+02:00"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "1",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "ESE TomTom",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Area 522",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Heinz sama",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Téa Jay",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "lbfxd",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Victor Lustig",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE MarsUltor",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Byndeskanzler",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 1978,
|
||||
"startTime": "2021-07-05T21:00+02:00"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"map": "SR",
|
||||
"winner-side": "0",
|
||||
"winners": [
|
||||
{
|
||||
"playerId": "Phirop",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ENTAC",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Heinz sama",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Victor Lustig",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE MarsUltor",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"losers": [
|
||||
{
|
||||
"playerId": "Area 522",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "lbfxd",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Téa Jay",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "Byndeskanzler",
|
||||
"isFake": false
|
||||
},
|
||||
{
|
||||
"playerId": "ESE TomTom",
|
||||
"isFake": false
|
||||
}
|
||||
],
|
||||
"duration": 1804,
|
||||
"startTime": "2021-07-05T22:00+02:00"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-04-26-round-01.json http://localhost:10010/submitt-round
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-04-26-round-02.json http://localhost:10010/submitt-round
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-04-26-round-03.json http://localhost:10010/submitt-round
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-05-24-round-01.json http://localhost:10010/submitt-round
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-05-31-round-01.json http://localhost:10010/submitt-round
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-05-31-round-02.json http://localhost:10010/submitt-round
|
||||
curl -X POST -H "Content-Type: application/json" -d @2021-05-31-round-03.json http://localhost:10010/submitt-round
|
||||
13
rounds/data
13
rounds/data
@@ -1,13 +0,0 @@
|
||||
1
|
||||
24:16
|
||||
TryhardYordle
|
||||
ChessGM lbfxd
|
||||
TheSlapstick
|
||||
Vyne
|
||||
ESE Felix
|
||||
Phirop
|
||||
xXxMarethyuxXx
|
||||
iTrash
|
||||
JuckF Bierhuhn
|
||||
ENTAC
|
||||
2021-04-25T22:00+02:00
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
winnerside = input("Winnerseide (0/1 , 0=bue, 1=red): ")
|
||||
duration = input("Duration: ")
|
||||
duration = int(duration.split(":")[0])*60+int(duration.split(":")[1])
|
||||
|
||||
print("Blue Team")
|
||||
blueTeam = []
|
||||
for i in range(5):
|
||||
pname = input("Name: ")
|
||||
p = dict()
|
||||
p.update({"playerId" : pname})
|
||||
p.update({"isFake" : False})
|
||||
blueTeam += [p]
|
||||
|
||||
print("Red Team")
|
||||
redTeam = []
|
||||
for i in range(5):
|
||||
pname = input("Name: ")
|
||||
p = dict()
|
||||
p.update({"playerId" : pname})
|
||||
p.update({"isFake" : False})
|
||||
redTeam += [p]
|
||||
|
||||
if winnerside == 0:
|
||||
winners = blueTeam
|
||||
losers = redTeam
|
||||
else:
|
||||
winners = redTeam
|
||||
losers = blueTeam
|
||||
|
||||
startTime = input("Start Time: ")
|
||||
|
||||
retDict = {}
|
||||
retDict.update({ "map" : "SR" })
|
||||
retDict.update({ "winner-side" : winnerside })
|
||||
retDict.update({ "winners" : winners})
|
||||
retDict.update({ "losers" : losers})
|
||||
retDict.update({ "duration" : duration })
|
||||
retDict.update({ "startTime" : startTime})
|
||||
|
||||
import json
|
||||
print()
|
||||
print(json.dumps(retDict, indent=4, sort_keys=False))
|
||||
536
server.py
536
server.py
@@ -1,307 +1,323 @@
|
||||
#!/usr/bin/python3
|
||||
import medals
|
||||
import flask
|
||||
import requests
|
||||
import argparse
|
||||
import datetime
|
||||
import itertools
|
||||
import flask_caching as fcache
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
import riotwatcher
|
||||
import time
|
||||
import statistics
|
||||
import api
|
||||
import MapSummary
|
||||
|
||||
from constants import *
|
||||
from database import DatabaseConnection
|
||||
#import valve.source.a2s
|
||||
#from valve.source import NoResponseError
|
||||
|
||||
|
||||
app = flask.Flask("open-leaderboard")
|
||||
|
||||
WATCHER = None
|
||||
KEY = None
|
||||
|
||||
if os.path.isfile("config.py"):
|
||||
app.config.from_object("config")
|
||||
|
||||
cache = fcache.Cache(app, config={'CACHE_TYPE': 'simple'})
|
||||
cache.init_app(app)
|
||||
|
||||
SEGMENT=100
|
||||
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)
|
||||
|
||||
class PlayerInDatabase(db.Model):
|
||||
__tablename__ = "players"
|
||||
player = Column(String, primary_key=True)
|
||||
rating = Column(Integer)
|
||||
ratingFix = Column(Integer)
|
||||
lastUpdated = Column(Integer)
|
||||
|
||||
class Submission(db.Model):
|
||||
__tablename__ = "submissions"
|
||||
ident = Column(String, primary_key=True)
|
||||
submissionId = Column(String)
|
||||
player = Column(String)
|
||||
prioTop = Column(Integer)
|
||||
prioJungle = Column(Integer)
|
||||
prioMid = Column(Integer)
|
||||
prioBot = Column(Integer)
|
||||
prioSupport = Column(Integer)
|
||||
|
||||
def toDict():
|
||||
pass
|
||||
|
||||
def affinityFor(self, posIndex):
|
||||
prio = getattr(self, DATABASE_PRIO_NAMES[posIndex])
|
||||
return prio
|
||||
|
||||
class Player:
|
||||
def __init__(self, name, prio):
|
||||
self.name = name
|
||||
self.prio = prio
|
||||
|
||||
@app.route("/role-submission", methods=['GET', 'POST'])
|
||||
def roleSubmissionTool():
|
||||
|
||||
submissionId = flask.request.args.get("id")
|
||||
player = flask.request.args.get("player")
|
||||
|
||||
if flask.request.method == 'POST':
|
||||
|
||||
submissionQuery = db.session.query(PlayerInDatabase)
|
||||
identKey = "{}-{}".format(submissionId, player)
|
||||
submission = submissionQuery.filter(PlayerInDatabase.ident == identKey).first()
|
||||
|
||||
if not submission:
|
||||
submission = Submission(identKey, submissionId, player, -1, -1, -1, -1, -1)
|
||||
|
||||
for i in range(0, 5):
|
||||
formKey = "prio_" + POSITIONS_NAMES[i]
|
||||
setattr(submission, DATABASE_PRIO_NAMES[i], flask.request.form[formKey])
|
||||
|
||||
db.session.merge(submission)
|
||||
db.session.commit()
|
||||
|
||||
return flask.redirect("/balance-tool?id=" + ident)
|
||||
def prettifyMinMaxY(computedMin, computedMax):
|
||||
if computedMax > 0 and computedMin > 0:
|
||||
return (0, 4000)
|
||||
else:
|
||||
return flask.render_template("role_submission.html", positions=positions, ident=ident)
|
||||
return (computedMin - 100, 4000)
|
||||
|
||||
@app.route("/balance-tool-data")
|
||||
def balanceToolData():
|
||||
@app.route("/players-online")
|
||||
def playersOnline():
|
||||
'''Calc and return the online players'''
|
||||
|
||||
submissionId = flask.request.args.get("id")
|
||||
submissionsQuery = db.session.query(PlayerInDatabase)
|
||||
submissions = submissionsQuery.filter(PlayerInDatabase.submissionId == submissionId).all()
|
||||
playerTotal = 0
|
||||
error = ""
|
||||
|
||||
if not submissions:
|
||||
return flask.Response(json.dumps({ "no-data" : False }), HTTP_OK, mimetype=TYPE_JSON)
|
||||
for s in SERVERS:
|
||||
try:
|
||||
pass
|
||||
#with valve.source.a2s.ServerQuerier((s["host"], s["port"])) as server:
|
||||
# playerTotal += int(server.info()["player_count"])
|
||||
except NoResponseError:
|
||||
error = "Server Unreachable"
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
|
||||
retDict.update()
|
||||
retDict = { "player_total" : playerTotal, "error" : error }
|
||||
return flask.Response(json.dumps(retDict), 200, mimetype='application/json')
|
||||
|
||||
|
||||
|
||||
dicts = [ s.toDict() for s in submissions ]
|
||||
return flask.Response(json.dumps({ "submissions" : dicts }), HTTP_OK, mimetype=TYPE_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("/maps")
|
||||
def maps():
|
||||
'''Show an overview of maps'''
|
||||
|
||||
db = DatabaseConnection(app.config["DB_PATH"])
|
||||
start = datetime.datetime.now() - datetime.timedelta(days=4000)
|
||||
end = datetime.datetime.now()
|
||||
rounds = db.roundsBetweenDates(start, end)
|
||||
distinctMaps = db.distinctMaps()
|
||||
maps = []
|
||||
for mapName in [ tupel[0] for tupel in distinctMaps]:
|
||||
roundsWithMap = list(filter(lambda r: r.mapName == mapName , rounds))
|
||||
maps += [MapSummary.MapSummary(roundsWithMap)]
|
||||
|
||||
allMaps = MapSummary.MapSummary(rounds)
|
||||
allMaps.mapName = "All Maps*"
|
||||
maps += [allMaps]
|
||||
|
||||
|
||||
mapsFiltered = filter(lambda x: x.mapName, maps)
|
||||
return flask.render_template("maps.html", maps=mapsFiltered)
|
||||
|
||||
@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=7)
|
||||
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
|
||||
|
||||
@app.route("/player")
|
||||
def player():
|
||||
'''Show Info about Player'''
|
||||
|
||||
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
|
||||
|
||||
# data for medals #
|
||||
medalsRatingList = []
|
||||
|
||||
if histData:
|
||||
datapoints = histData[playerId]
|
||||
if datapoints:
|
||||
|
||||
tickCounter = 10
|
||||
for dpk in datapoints.keys():
|
||||
|
||||
# timestamp #
|
||||
t = datetime.datetime.fromtimestamp(int(float(dpk)))
|
||||
tsMs = str(int(t.timestamp() * 1000))
|
||||
|
||||
computedRating = int(datapoints[dpk]["mu"]) - 2*int(datapoints[dpk]["sigma"])
|
||||
|
||||
# for medals #
|
||||
medalsRatingList += [computedRating]
|
||||
|
||||
# for moment js #
|
||||
ratingString = str(computedRating)
|
||||
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)
|
||||
|
||||
medalsList = medals.getMedals(medalsRatingList, player.games, player.rating)
|
||||
|
||||
# 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, medals=medalsList)
|
||||
|
||||
@app.route('/leaderboard')
|
||||
@app.route('/')
|
||||
@app.route("/balance-tool", methods=['GET', 'POST'])
|
||||
def balanceTool():
|
||||
@cache.cached(timeout=600, query_string=True)
|
||||
def leaderboard():
|
||||
'''Show main leaderboard page with range dependant on parameters'''
|
||||
|
||||
if flask.request.method == 'POST':
|
||||
# parse parameters #
|
||||
page = flask.request.args.get("page")
|
||||
playerName = flask.request.args.get("string")
|
||||
db = DatabaseConnection(app.config["DB_PATH"])
|
||||
|
||||
players = []
|
||||
threshold = 0.7
|
||||
for k,v in flask.request.json.items():
|
||||
if k == "acceptable-solution-threshold":
|
||||
threshold = v
|
||||
continue
|
||||
for i in range(5):
|
||||
if v[i] in positions:
|
||||
v[i] = 5
|
||||
else:
|
||||
v[i] = int(v[i])
|
||||
p = Player(k, v)
|
||||
players += [p]
|
||||
|
||||
# theoretical minnimum #
|
||||
theoMin = sum([ min(p.prio) for p in players ])
|
||||
|
||||
permutations = itertools.permutations(players)
|
||||
|
||||
best = 100
|
||||
bestOption = None
|
||||
alternateOptions = []
|
||||
alternateOptionsAboveThreshold = []
|
||||
for option in permutations:
|
||||
|
||||
cur = 0
|
||||
|
||||
for i in range(len(option)):
|
||||
cur += option[i].prio[i%5]
|
||||
|
||||
if theoMin/cur > threshold:
|
||||
alternateOptionsAboveThreshold.append(list(option))
|
||||
|
||||
qualityCur = int(theoMin/cur*100)
|
||||
if cur < best:
|
||||
best = cur
|
||||
bestOption = list(option)
|
||||
alternateOptions = []
|
||||
alternateOptions.append(list(option))
|
||||
print("Option Found Quality: {}%".format(str(qualityCur)))
|
||||
elif cur == best or qualityCur > threshold*100:
|
||||
alternateOptions.append(list(option))
|
||||
|
||||
alternateOptions += alternateOptionsAboveThreshold
|
||||
retDict = { "left" : {}, "right" : {} }
|
||||
bestOption = list(bestOption)
|
||||
if len(bestOption) < 10:
|
||||
for x in range(10-len(bestOption)):
|
||||
bestOption += [Player("", [0,0,0,0,0])]
|
||||
for o in alternateOptions:
|
||||
for x in range(10-len(o)):
|
||||
o += [Player("", [0,0,0,0,0])]
|
||||
|
||||
# fix options with rating #
|
||||
bestOptionWithRating = None
|
||||
bestOptionRatings = None
|
||||
|
||||
# alternate options rundown positional diff #
|
||||
posCurrDiff = 100000
|
||||
for o in alternateOptions:
|
||||
firstHalf = o[:5]
|
||||
secondHalf = o[5:]
|
||||
|
||||
firstHalfVal = [0, 0, 0, 0, 0]
|
||||
secondHalfVal = [0, 0, 0, 0, 0]
|
||||
|
||||
countFirstHalf = 0
|
||||
for pil in firstHalf:
|
||||
if pil:
|
||||
firstHalfVal[countFirstHalf] = api.getPlayerRatingFromApi(pil.name, WATCHER)
|
||||
#print(pil.name, firstHalfVal[countFirstHalf])
|
||||
countFirstHalf += 1
|
||||
|
||||
countSecondHalf = 0
|
||||
for pil in secondHalf:
|
||||
if pil:
|
||||
secondHalfVal[countSecondHalf] = api.getPlayerRatingFromApi(pil.name, WATCHER)
|
||||
#print(pil.name, secondHalfVal[countSecondHalf])
|
||||
countSecondHalf += 1
|
||||
|
||||
posDiff = abs(statistics.median(firstHalfVal) - statistics.median(secondHalfVal))
|
||||
|
||||
# check if posdiff is better #
|
||||
if posDiff < posCurrDiff:
|
||||
bestOptionWithRating = o
|
||||
bestOptionRatings = firstHalfVal + secondHalfVal
|
||||
qualityRatings = -1
|
||||
|
||||
# find the best permutation of this solution #
|
||||
for i in range(0,5):
|
||||
teamDiff = abs(sum(firstHalfVal) - sum(secondHalfVal))
|
||||
|
||||
# first flip
|
||||
tmp = firstHalfVal[i]
|
||||
firstHalfVal[i] = secondHalfVal[i]
|
||||
secondHalfVal[i] = tmp
|
||||
teamDiffNew = abs(sum(firstHalfVal) - sum(secondHalfVal))
|
||||
|
||||
# if new is not better #
|
||||
if not (teamDiffNew < teamDiff):
|
||||
# flip it back #
|
||||
tmp = firstHalfVal[i]
|
||||
firstHalfVal[i] = secondHalfVal[i]
|
||||
secondHalfVal[i] = tmp
|
||||
# else flip the names too #
|
||||
else:
|
||||
tmp = firstHalf[i]
|
||||
firstHalf[i] = secondHalf[i]
|
||||
secondHalf[i] = tmp
|
||||
# and reset the option #
|
||||
bestOptionWithRating = firstHalf + secondHalf
|
||||
bestOptionRatings = firstHalfVal + secondHalfVal
|
||||
qualityRatings = min(sum(firstHalfVal)/sum(secondHalfVal),
|
||||
sum(secondHalfVal)/sum(firstHalfVal))
|
||||
|
||||
|
||||
|
||||
for i in range(5):
|
||||
retDict["left"].update( { positions[i] : bestOptionWithRating[i].name })
|
||||
retDict["right"].update({ positions[i] : bestOptionWithRating[i+5].name })
|
||||
|
||||
import sys
|
||||
print(flask.request.json, file=sys.stderr)
|
||||
print(retDict, file=sys.stderr)
|
||||
renderContent = flask.render_template("balance_response_partial.html", d=retDict,
|
||||
reqJson=flask.request.json,
|
||||
positions=positions,
|
||||
ratings=bestOptionRatings,
|
||||
qualityPositions=int(theoMin/best*100),
|
||||
qualityRatings=int(qualityRatings*100))
|
||||
return flask.Response(
|
||||
json.dumps({ "content": renderContent }), 200, mimetype='application/json')
|
||||
if page:
|
||||
pageInt = int(page)
|
||||
if pageInt < 0:
|
||||
pageInt = 0
|
||||
start = SEGMENT * int(page)
|
||||
else:
|
||||
givenIdent = flask.request.args.get("id")
|
||||
if givenIdent:
|
||||
ident = givenIdent
|
||||
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:
|
||||
ident = secrets.token_urlsafe(16)
|
||||
return flask.redirect("/balance-tool?id={}".format(ident))
|
||||
return flask.render_template("json_builder.html",
|
||||
positions=positions,
|
||||
sides=["left", "right"],
|
||||
ident=ident)
|
||||
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
|
||||
|
||||
@app.route("/player-api")
|
||||
def playerApi():
|
||||
result = api.getPlayerRatingFromApi(flask.request.args.get("id"), WATCHER)
|
||||
if result:
|
||||
return ("OK", 200)
|
||||
else:
|
||||
return ("Nope", 404)
|
||||
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
|
||||
reachedEnd = True
|
||||
|
||||
@app.route("/player-api-cache")
|
||||
def playerApiCache():
|
||||
result = api.checkPlayerKnown(flask.request.args.get("id"))
|
||||
if result and result[1] != 0:
|
||||
return ("OK", 200)
|
||||
else:
|
||||
return ("Nope", 404)
|
||||
|
||||
@app.route("/get-cache")
|
||||
def getCacheLoc():
|
||||
return (json.dumps(api.getCache()), 200)
|
||||
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>')
|
||||
def send_js(path):
|
||||
return send_from_directory('static', path)
|
||||
|
||||
@app.before_first_request
|
||||
def init():
|
||||
response = send_from_directory('static', path)
|
||||
response.headers['Cache-Control'] = "max-age=2592000"
|
||||
return response
|
||||
|
||||
global WATCHER
|
||||
|
||||
app.config["DB"] = db
|
||||
db.create_all()
|
||||
def create_app():
|
||||
|
||||
with open("key.txt","r") as f:
|
||||
key = f.read().strip()
|
||||
WATCHER = riotwatcher.LolWatcher(key)
|
||||
global SERVERS
|
||||
|
||||
SERVERS_FILE = "servers.json"
|
||||
if os.path.isfile(SERVERS_FILE):
|
||||
with open(SERVERS_FILE) as f:
|
||||
SERVERS = json.load(f)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='Start open-leaderboard', \
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--interface', default="localhost")
|
||||
parser.add_argument('--port', default="5002")
|
||||
|
||||
args = parser.parse_args()
|
||||
parser = argparse.ArgumentParser(description='Start open-leaderboard',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--interface', default="localhost",
|
||||
help='Interface on which flask (this server) will take requests on')
|
||||
parser.add_argument('--port', default="5002",
|
||||
help='Port on which flask (this server) will take requests on')
|
||||
parser.add_argument('--skillbird-db', required=True,
|
||||
help='skillbird database (overrides web connection if set)')
|
||||
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
with app.app_context():
|
||||
create_app()
|
||||
|
||||
args = parser.parse_args()
|
||||
app.config["DB_PATH"] = args.skillbird_db
|
||||
app.run(host=args.interface, port=args.port)
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
positions = [ "Top", "Jungle", "Mid", "Bottom", "Support" ]
|
||||
acceptedParser = [ "top", "jungle", "mid", "sup" , "bot", "adc", "support", "bottom", "*" ]
|
||||
sides = [ "left", "right"]
|
||||
|
||||
var checkPlayerFunc = function checkPlayer() {
|
||||
if(this.value == ""){
|
||||
return
|
||||
}
|
||||
url = "/player-api-cache?id=" + this.value
|
||||
fetch(url).then(r => {
|
||||
if(r.status == 200){
|
||||
this.style.background = "#74bb74"
|
||||
}else{
|
||||
this.style.background = "#d25252"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var checkPlayerApiFunc = function checkPlayer() {
|
||||
if(this.value == ""){
|
||||
return
|
||||
}
|
||||
url = "/player-api?id=" + this.value
|
||||
fetch(url).then(r => {
|
||||
if(r.status == 200){
|
||||
this.style.background = "#74bb74"
|
||||
}else{
|
||||
//this.style.background = "#d25252"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var fastPosChangedFunc = function fastPosChanged() {
|
||||
|
||||
accepted = [ "top", "jungle", "mid", "sup" , "bot" ]
|
||||
uniqArr = []
|
||||
prioArray = [5, 5, 5, 5, 5]
|
||||
|
||||
/* commence cleanup */
|
||||
clean = this.value.replaceAll(" ", "").toLocaleLowerCase()
|
||||
clean = clean.replace("support", "sup")
|
||||
clean = clean.replace("adc", "bot")
|
||||
clean = clean.replace("bottom", "bot")
|
||||
|
||||
retVal = true
|
||||
if(clean.includes("<")){
|
||||
console.log("Not accepted (includes <)")
|
||||
retVal = false
|
||||
}
|
||||
|
||||
list = clean.split(">")
|
||||
cur = 1
|
||||
list.forEach(el => {
|
||||
if(el.includes("=")){
|
||||
listEq = el.split("=")
|
||||
listEq.forEach(sub => {
|
||||
if(accepted.includes(sub) && !uniqArr.includes(sub)){
|
||||
prioArray[accepted.indexOf(sub)] = cur
|
||||
uniqArr += [sub]
|
||||
}else{
|
||||
console.log("Not accepted (=): " + sub)
|
||||
retVal = false
|
||||
}
|
||||
})
|
||||
cur++
|
||||
}else{
|
||||
if(accepted.includes(el) && !uniqArr.includes(el)){
|
||||
prioArray[accepted.indexOf(el)] = cur
|
||||
uniqArr += [el]
|
||||
cur++
|
||||
}else{
|
||||
console.log("Not accepted (>): " + el)
|
||||
retVal = false
|
||||
}
|
||||
}
|
||||
})
|
||||
for(i = 0; i<5; i++){
|
||||
arr = this.id.split("-")
|
||||
pNr = arr[arr.length-1]
|
||||
side = this.id.includes("left") ? "left" : "right"
|
||||
|
||||
roleSubmission = document.getElementById("fastpos-submission")
|
||||
if(roleSubmission){
|
||||
string = "prio_" + accepted[i]
|
||||
}else {
|
||||
string = "prio-" + side + "-" + accepted[i] + "-" + pNr
|
||||
}
|
||||
|
||||
string = string.replace("top","Top")
|
||||
string = string.replace("jungle","Jungle")
|
||||
string = string.replace("mid","Mid")
|
||||
string = string.replace("sup","Support")
|
||||
string = string.replace("bot","Bottom")
|
||||
|
||||
selector = document.getElementById(string)
|
||||
selector.value = prioArray[i]
|
||||
selector.style.background = "lightblue"
|
||||
}
|
||||
|
||||
/* allow some basic shit */
|
||||
if(clean == "*" || clean == ""){
|
||||
retVal = true
|
||||
}
|
||||
|
||||
if(retVal){
|
||||
this.style.background = "#74bb74"
|
||||
}else{
|
||||
this.style.background = "#d25252"
|
||||
}
|
||||
}
|
||||
|
||||
function balance(){
|
||||
|
||||
cont = document.getElementById("response-container")
|
||||
cont.innerHTML = ""
|
||||
cont.style.color = "black";
|
||||
sides = ["left", "right"]
|
||||
|
||||
blue = [ "", "", "", "", ""]
|
||||
red = [ "", "", "", "", ""]
|
||||
|
||||
dictToBeSorted = {
|
||||
0 : [],
|
||||
1 : [],
|
||||
2 : [],
|
||||
3 : [],
|
||||
4 : []
|
||||
}
|
||||
filler = []
|
||||
impossible = []
|
||||
|
||||
dictAll = {}
|
||||
for(sid = 0; sid < 2; sid++){
|
||||
for(id = 0; id < 5; id++){
|
||||
|
||||
var pname = "undef"
|
||||
var prioList = [5, 5, 5, 5, 5]
|
||||
|
||||
stringPid = `playername-${sides[sid]}-${id}`
|
||||
pnameObj = document.getElementById(stringPid)
|
||||
pname = pnameObj.value
|
||||
for(acc = 0; acc < 5; acc++){
|
||||
stringSelectorId = `prio-${sides[sid]}-${positions[acc]}-${id}`
|
||||
selObj = document.getElementById(stringSelectorId)
|
||||
prioList[acc] = selObj.value
|
||||
}
|
||||
|
||||
dictAll[pname] = prioList
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
prioBalanceCheckbox = document.getElementById("prio-balance")
|
||||
if(prioBalanceCheckbox.checked){
|
||||
dictAll["acceptable-solution-threshold"] = 0.5
|
||||
}else{
|
||||
dictAll["acceptable-solution-threshold"] = 0.7
|
||||
}
|
||||
jsonData = JSON.stringify(dictAll, null, 4);
|
||||
|
||||
/* transmitt */
|
||||
spinner = document.getElementById("loading")
|
||||
spinner.style.display = "block";
|
||||
fetch(window.location.href, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json' },
|
||||
body: jsonData
|
||||
}).then(r => r.json()).then(j => {
|
||||
spinner.style.display = "none";
|
||||
cont.innerHTML = j["content"]
|
||||
}).catch(err => {
|
||||
spinner.style.display = "none";
|
||||
cont.style.color = "red";
|
||||
cont.innerHTML = "Error - request failed."
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function parseMultiline(){
|
||||
|
||||
var names = []
|
||||
var prioStrings = []
|
||||
|
||||
field = document.getElementById("multi-line-copy")
|
||||
lines = field.value.split("\n")
|
||||
lines.forEach(l => {
|
||||
|
||||
lowestIndex = 100
|
||||
|
||||
acceptedParser.forEach( p => {
|
||||
i = l.indexOf(" " + p)
|
||||
if(i >= 3 && i < lowestIndex){
|
||||
lowestIndex = i
|
||||
}
|
||||
})
|
||||
|
||||
if(lowestIndex != 100){
|
||||
name = l.substring(0, lowestIndex)
|
||||
prioStr = l.substring(lowestIndex)
|
||||
names.push(name)
|
||||
prioStrings.push(prioStr)
|
||||
}
|
||||
})
|
||||
|
||||
count = 0
|
||||
sides.forEach(s => {
|
||||
for(i = 0; i<5; i++){
|
||||
|
||||
pObjField = document.getElementById("playername-" + s + "-" + i)
|
||||
prObjField = document.getElementById("check-" + s + "-fastpos-" + i)
|
||||
|
||||
if(count >= names.length){
|
||||
pObjField.value = ""
|
||||
prObjField.value = ""
|
||||
}else{
|
||||
pObjField.value = names[count]
|
||||
prObjField.value = prioStrings[count]
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
})
|
||||
|
||||
const inputEvent = new Event("input")
|
||||
fastPosFields.forEach(el => el.dispatchEvent(inputEvent))
|
||||
balance()
|
||||
}
|
||||
|
||||
function queryForPlayerData(){
|
||||
ident = document.getElementById("ident-field").innerHTML
|
||||
fetch("/balance-tool-data?id=" + ident).then(r => r.json()).then(j => {
|
||||
if("no-data" in j){
|
||||
return
|
||||
}
|
||||
j["submissions"].forEach(el => {
|
||||
console.log(el)
|
||||
breakToSubmissions = false
|
||||
for(sid = 0; sid < 2; sid++){
|
||||
if(breakToSubmissions){
|
||||
break;
|
||||
}
|
||||
for(id = 0; id < 5; id++){
|
||||
stringPid = `playername-${sides[sid]}-${id}`
|
||||
pnameObj = document.getElementById(stringPid)
|
||||
if(pnameObj.value == "" || pnameObj.value == el["name"]){
|
||||
pnameObj.value = el["name"]
|
||||
for(acc = 0; acc < 5; acc++){
|
||||
stringSelectorId = `prio-${sides[sid]}-${positions[acc]}-${id}`
|
||||
selObj = document.getElementById(stringSelectorId)
|
||||
selObj.value = el[positions[acc]]
|
||||
}
|
||||
breakToSubmissions = true
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function copy() {
|
||||
|
||||
ident = document.getElementById("ident-field").innerHTML
|
||||
path = "/role-submission?id="
|
||||
copyText = window.location.protocol + "//" + window.location.hostname + path + ident
|
||||
|
||||
navigator.clipboard.writeText(copyText)
|
||||
document.getElementById("copyLink").innerHTML = "Copied!"
|
||||
setTimeout(() => {
|
||||
document.getElementById("copyLink").innerHTML = "Copy Submission Link" }, 500);
|
||||
|
||||
}
|
||||
|
||||
fastPosFields = document.getElementsByClassName("fastpos")
|
||||
playerNameFields = document.getElementsByClassName("pname")
|
||||
|
||||
playerNameFields.forEach(el => el.addEventListener('input', checkPlayerFunc));
|
||||
playerNameFields.forEach(el => el.addEventListener('focus', checkPlayerFunc));
|
||||
|
||||
fastPosFields.forEach(el => el.addEventListener('input', fastPosChangedFunc));
|
||||
//fastPosFields.forEach(el => el.addEventListener('focus', fastPosChangedFunc));
|
||||
|
||||
fastposSubmission = document.getElementById("fastpos-submission")
|
||||
if(fastposSubmission){
|
||||
fastposSubmission.addEventListener("input", fastPosChangedFunc)
|
||||
fastposSubmission.addEventListener("focus", fastPosChangedFunc)
|
||||
}
|
||||
setInterval(queryForPlayerData(), 3000)
|
||||
|
||||
formContainer = document.getElementById("form-container")
|
||||
if(formContainer){
|
||||
//formContainer.reset()
|
||||
}
|
||||
|
||||
function resetAll(){
|
||||
formContainer = document.getElementById("form-container")
|
||||
formContainer.reset()
|
||||
}
|
||||
@@ -8,12 +8,6 @@ body{
|
||||
|
||||
}
|
||||
|
||||
.border-special{
|
||||
border-style: outset;
|
||||
border-width: 0.2px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.top-bar{
|
||||
background-color: orange;
|
||||
padding-bottom: 10px;
|
||||
@@ -70,14 +64,14 @@ body{
|
||||
font-size: 5vw;
|
||||
margin-right: 2.5%;
|
||||
}
|
||||
|
||||
|
||||
.input-field{
|
||||
margin-top: 2vw;
|
||||
float: left;
|
||||
font-size: 6vw;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
.input-field-number{
|
||||
display: none;
|
||||
}
|
||||
@@ -87,7 +81,7 @@ body{
|
||||
}
|
||||
}
|
||||
|
||||
/* ############# PLAYER INFORMATION IN LINES ############# */
|
||||
/* ############# PLAYER INFORMATION IN LINES ############# */
|
||||
.playerRank{
|
||||
margin-left:1%;
|
||||
float: left;
|
||||
@@ -149,7 +143,7 @@ body{
|
||||
}
|
||||
|
||||
.line-odd{
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
color: black;
|
||||
background-color: lightgrey !important;
|
||||
overflow: hidden;
|
||||
@@ -173,7 +167,7 @@ body{
|
||||
font-size: 4.5vw;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.colum-names{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -205,14 +199,14 @@ body{
|
||||
width: 15%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.playerWinratio{
|
||||
/* 19% is just enought to cut the last letter */
|
||||
width: 19%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ######################## FOOTER ####################### */
|
||||
/* ######################## FOOTER ####################### */
|
||||
.footer{
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
@@ -223,7 +217,6 @@ body{
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.footerLink{
|
||||
@@ -247,7 +240,6 @@ body{
|
||||
.footerLink{
|
||||
font-size: 4vw;
|
||||
}
|
||||
|
||||
.mid{
|
||||
margin-left: 2.5%;
|
||||
margin-right: 2.5%;
|
||||
@@ -264,3 +256,11 @@ body{
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0.3; }
|
||||
}
|
||||
|
||||
.animate-flicker {
|
||||
animation: fadeIn 2s alternate;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<div class="my-3">
|
||||
<h3> Suggestion </h3>
|
||||
<p>Postion Wishes: {{ qualityPositions }}% fullfilled /
|
||||
Teambalance: {{ qualityRatings }}% </p>
|
||||
</div>
|
||||
{% for x in range(5) %}
|
||||
<div class="row">
|
||||
{% set leftP = d["left"][positions[x]] %}
|
||||
{% set rightP = d["right"][positions[x]] %}
|
||||
<div class="col-sm">
|
||||
{{ positions[x] }}
|
||||
</div>
|
||||
<div class="col-sm"
|
||||
{% if reqJson.get(leftP) and reqJson.get(leftP)[x] | int >= 4 %}
|
||||
style="background: red;"
|
||||
{% endif %}>
|
||||
{{ leftP }} ({{ ratings[x] }})
|
||||
</div>
|
||||
<div class="col-sm"
|
||||
{% if reqJson.get(rightP) and reqJson.get(rightP)[x] | int >= 4 %}
|
||||
style="background: red;"
|
||||
{% endif %}>
|
||||
{{ rightP }} ({{ ratings[x+5] }})
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-3">
|
||||
<h3>For copying into Lobby:</h3>
|
||||
<div class="mt-2">
|
||||
<div class="row">
|
||||
<textarea style="padding-left: 10px !important; padding-top: 10px !important" rows="16" cols= 100>
|
||||
Left Side Team
|
||||
{% for x in range(5) %}
|
||||
{% set leftP = d["left"][positions[x]] %}{{ leftP }} left team {{ positions[x] }}{% endfor %}
|
||||
|
||||
Right Side Team
|
||||
{% for x in range(5) %}
|
||||
{% set rightP = d["right"][positions[x]] %}{{ rightP }} right team {{ positions[x] }}{% endfor %}
|
||||
</textarea>
|
||||
</div>
|
||||
@@ -10,6 +10,29 @@
|
||||
{% include 'navbar_leaderboard.html' %}
|
||||
|
||||
<div class="container mt-3 mb-3" role="main">
|
||||
<div id="playerDisplay" class="playerDisplay mb-3 mt-2">
|
||||
<script>
|
||||
function players(){
|
||||
//document.getElementById("playerDisplay").classList.remove("animate-flicker")
|
||||
fetch("/players-online").then(
|
||||
response => response.json()
|
||||
).then(
|
||||
data => {
|
||||
if(data["error"] == ""){
|
||||
document.getElementById("playerDisplay").innerHTML = "Players Online: " + data["player_total"]
|
||||
if(parseInt(data["player_total"]) == 0){
|
||||
//document.getElementById("playerDisplay").classList.add("animate-flicker")
|
||||
}
|
||||
}else{
|
||||
document.getElementById("playerDisplay").innerHTML = "Players Online: (error)" + data["error"]
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
players()
|
||||
setInterval(players, 10000)
|
||||
</script>
|
||||
</div>
|
||||
<table id="tableMain" class="table table-striped table-bordered table-sm"
|
||||
cellspacing="0">
|
||||
<thead>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="footer-copyright text-center py-3 bg-dark" role="contentinfo">
|
||||
<a style="color: rgba(255,255,255,.5);"
|
||||
class="footerLink" href="https://atlantishq.de/impressum">Impressum/Legal</a>
|
||||
class="footerLink" href="https://blog.atlantishq.de/about">Impressum/Legal</a>
|
||||
<a style="color: rgba(255,255,255,.5);"
|
||||
class="footerLink mid" href="https://esports-erlangen.de">
|
||||
ESE Website</a>
|
||||
class="footerLink mid" href="steam://connect/athq.de/:27026">
|
||||
Join the Server!</a>
|
||||
<a style="color: rgba(255,255,255,.5);"
|
||||
class="footerLink" href="https://github.com/FAUSheppy/skillbird">Star on GitHub</a>
|
||||
</div>
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Balance and Submission Tool</title>
|
||||
<meta name="Description" content="Sheppy is awesome?">
|
||||
<script defer src="/static/balance.js"></script>
|
||||
{% include 'default_head_content.html' %}
|
||||
</head>
|
||||
<body class="bg-special">
|
||||
{% include 'navbar.html' %}
|
||||
<div id="ident-field" style="display: none;">{{ ident }}</div>
|
||||
<div class="container mt-3 mb-3" role="main">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<h1>Balance Tool</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button type="button" class="mb-3 btn btn-secondary" onclick="balance()">
|
||||
Find Teams
|
||||
</button>
|
||||
</br>
|
||||
<button id="copyLink" type="button"
|
||||
class="mb-3 btn btn-secondary" onclick="copy()">
|
||||
Copy Submission Link
|
||||
</button>
|
||||
<button type="button"
|
||||
class="mb-3 btn btn-secondary" onclick="queryForPlayerData()">
|
||||
Check Submissions
|
||||
</button>
|
||||
</br>
|
||||
{% if True %}
|
||||
<button class="btn btn-primary" type="button" data-toggle="collapse"
|
||||
data-target="#hilfe" aria-expanded="false"
|
||||
aria-controls="hilfe">
|
||||
Hilfe Anzeigen
|
||||
</button>
|
||||
<button type="button"
|
||||
class="mb-3 btn btn-secondary" onclick="resetAll()">
|
||||
Reset all
|
||||
</button></br>
|
||||
|
||||
<div style="display: none;" class="form-check mt-3">
|
||||
<input class="form-check-input" type="checkbox" value=""
|
||||
id="prio-balance">
|
||||
<label class="form-check-label" for="flexCheckChecked">
|
||||
Prioritize balance over positional preferences
|
||||
<p style="color: red;">(experimental, slow af)</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="hilfe" class="collapse mt-5 border-special">
|
||||
<h4>Priorität</h4>
|
||||
<p>
|
||||
1 = höchste Priorität
|
||||
5 = niedrigste Priorität
|
||||
</p>
|
||||
<hr class="my-3">
|
||||
<h4>Fast Position</h4>
|
||||
<p>
|
||||
<b>Positionen können in den Kurzschreibweisen: top, mid, bot, jungle, support mit "=" und ">" angegeben werden. Zum Beispiel:</b></br></br>
|
||||
top > mid > top = adc</br>
|
||||
mid = top > bot</br>
|
||||
support > jungle</br>
|
||||
etc..</br>
|
||||
</p>
|
||||
<hr class="my-3">
|
||||
<h4>Submission Link</h4>
|
||||
<p>
|
||||
Mit diesem Link können Spieler eine Positionspräferenz selbst übermitteln, einfach diesen Link in den Chat pasten.
|
||||
</p>
|
||||
<hr class="my-3">
|
||||
<h4>Find Teams</h4>
|
||||
<p>
|
||||
Sobald alle eingetragen sind einmal auf "Find Teams" klicken und auf den Vorschlag warten.
|
||||
</p>
|
||||
<hr class="my-3">
|
||||
<h4>Multiline Input</h4>
|
||||
<p>
|
||||
Jeder Kann seine Rollen im Schnellformat in den Chat schreiben, in das Feld unten kopieren und Button drücken (überschreibt existierende Einträge).
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spinner-border" id="loading" style="display: none;" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<div id="response-container" class="mt-3 mb-3">
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<form id="form-container" class="row">
|
||||
{% for side in sides %}
|
||||
<div class="col-sm">
|
||||
{% for field in range(5) %}
|
||||
<div id="{{ side }}-{{ field }}" class="row mt-2 mb-2">
|
||||
<div class="col-sm">
|
||||
|
||||
<!-- player name field -->
|
||||
<input class="form-control pname" type="text" placeholder="Player"
|
||||
id="playername-{{ side }}-{{ field }}">
|
||||
|
||||
<!-- fast postition slection field -->
|
||||
<input class="form-control fastpos" type="text"
|
||||
placeholder="top > mid = bot"
|
||||
id="check-{{ side }}-fastpos-{{ field }}">
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<!-- checkboxes for pos -->
|
||||
{% for pos in positions %}
|
||||
<select id="prio-{{ side }}-{{ pos }}-{{ field }}"
|
||||
class="form-select">
|
||||
<option selected>{{ pos }}</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
</select>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</form>
|
||||
<hr>
|
||||
|
||||
<textarea type="text" id="multi-line-copy" rows="10"
|
||||
placeholder="Have everybody write their roles in the form of 'top > mid = sup = jungle >adc' in the chat and then copy it in here and hit 'Use Multiline Input' :)"
|
||||
class="form-control md-textarea"></textarea>
|
||||
<button type="button" class="mb-3 btn btn-secondary" onclick="parseMultiline()">
|
||||
Use Multiline Input..
|
||||
</button>
|
||||
|
||||
<hr>
|
||||
</div>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,13 +10,28 @@
|
||||
<!-- left side -->
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">New Balance</a>
|
||||
<a class="nav-link" href="/">Leaderboard</a>
|
||||
</li>
|
||||
<!--
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/rounds">Rounds</a>
|
||||
</li>
|
||||
-->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/maps">Maps</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav right">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="steam://connect/athq.de/:27026">Server 1</a>
|
||||
</li>
|
||||
<li class="nav-item mr-4">
|
||||
<a class="nav-link" href="steam://connect/athq.de/:27015">Server 2</a>
|
||||
</li>
|
||||
<!--
|
||||
<li class="nav-item">
|
||||
<a class="navbar-brand hover-to-75 patreon" style="position: unset !important;" href="https://www.patreon.com/erlangen_sheppy">Support me
|
||||
</a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
<li class="nav-item">
|
||||
<a id="button-forward" class="nav-link" href="/?page={{ nextPageNumber }}">Forward</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a id="button-forward" class="nav-link" href="/balance-tool">Balance Tool</a>
|
||||
<li class="nav-item" {% if not gameInProgress %} style="opacity: 0.5" {% endif %}>
|
||||
<a id="button-forward" class="nav-link" href="/livegames">Livegames
|
||||
<div style="font-size: small; color: red; opacity: 1; float: right; margin-left: 10px;">(experimental)</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
@@ -31,12 +33,15 @@
|
||||
<li class="nav-item right mr-2">
|
||||
<a class="nav-link" href="/rounds">Rounds</a>
|
||||
</li>
|
||||
<li class="nav-item right mr-2">
|
||||
<a class="nav-link" href="/maps">Maps</a>
|
||||
</li>
|
||||
<li class="nav-item right">
|
||||
<input id="getPlayer" type="text" aria-label="search for player" class="mt-1"
|
||||
placeholder="search player...">
|
||||
</li>
|
||||
{{ findPlayer }}
|
||||
<!-- <li class="nav-item right">
|
||||
<!--- <li class="nav-item right">
|
||||
<input id="gotoRank" type="number" aria-label="goto rank"
|
||||
placeholder="goto rank...">
|
||||
</li> -->
|
||||
|
||||
@@ -14,17 +14,28 @@
|
||||
</h1>
|
||||
<h3>
|
||||
Rating: <i>{{ player.rating }}</i> <br>
|
||||
Rank: {{ player.rank }}
|
||||
{% if player.rank == 0 %}
|
||||
<i><small>Missing {{ 10 - player.games }} placement games!</small></i>
|
||||
{% elif not player.lastGame %}
|
||||
<i><small>Must play a game again before being assigned a rank!</small></i>
|
||||
{% else %}
|
||||
Rank: {{ player.rank }}
|
||||
{%endif%}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="plot-container">
|
||||
<canvas id="lineChart">
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="mt-3 medal-container">
|
||||
{% for m in medals %}
|
||||
{{ m }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="mt-5 mb-3">
|
||||
</p>
|
||||
</div>
|
||||
{% include 'footer.html' %}
|
||||
<!-- {% include 'footer.html' %}-->
|
||||
<script defer>
|
||||
var canvas = document.getElementById("lineChart")
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Balance and Submission Tool</title>
|
||||
<meta name="Description" content="Sheppy is awesome?">
|
||||
<script defer src="/static/balance.js"></script>
|
||||
{% include 'default_head_content.html' %}
|
||||
</head>
|
||||
<body class="bg-special">
|
||||
{% include 'navbar.html' %}
|
||||
<div class="container mt-3 mb-3" role="main">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<h1>Role Preference Submission Tool</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- player name field -->
|
||||
<form action="/role-submission?id={{ ident }}" method="POST">
|
||||
<p style="color: red; font-weight: bold"> PLEASE USE YOUR _CORRECTLY SPELLED_ LoL-NAME!!</p>
|
||||
<input class="form-control pname m-3" type="text" placeholder="Player"
|
||||
name="playername" id="playername">
|
||||
|
||||
<!-- fast postition slection field -->
|
||||
<input class="form-control fastpos m-3" type="text"
|
||||
placeholder="Fast Position Input"
|
||||
id="fastpos-submission">
|
||||
|
||||
<div class="col-sm m-3" style="min-width: 300px;">
|
||||
<!-- checkboxes for pos -->
|
||||
{% for pos in positions %}
|
||||
<div>
|
||||
<label style="min-width: 100px;"
|
||||
for="prio_{{ pos }}">{{ pos }}</label>
|
||||
<select name="prio_{{ pos }}" id="prio_{{ pos }}"
|
||||
class="form-select ml-3 mr-3">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
</select>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input type="submit" value="Submit">
|
||||
|
||||
<div class="mt-5">
|
||||
<h4>Priorität</h4>
|
||||
<p>
|
||||
1 = höchste </br>
|
||||
5 = niedrigste </br>
|
||||
</p>
|
||||
<hr class="my-3">
|
||||
<h4>Fast-Position</h4>
|
||||
<p>
|
||||
<b>Positionen können in den Kurzschreibweisen: top, mid, bot, jungle, support mit "=" und ">" angegeben werden. Zum Beispiel:</b></br></br>
|
||||
top > mid > top = adc</br>
|
||||
mid = top > bot</br>
|
||||
support > jungle</br>
|
||||
etc..</br>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -41,6 +41,9 @@
|
||||
<div class="col-sm" style="overflow: hidden;">
|
||||
<a href="/player?id={{ p.playerId }}">{{ p.name }}</a>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
{{ p.participation }}%
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
{% if not r.invalid %}
|
||||
<small style="color: green;">
|
||||
@@ -57,6 +60,9 @@
|
||||
<div class="col-sm" style="overflow: hidden;">
|
||||
<a href="/player?id={{ p.playerId }}">{{ p.name }}</a>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
{{ p.participation }}%
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
{% if not r.invalid %}
|
||||
<small style="color: red;">
|
||||
|
||||
Reference in New Issue
Block a user