Compare commits

10 Commits

Author SHA1 Message Date
5f7713daaf docu: info about python-valve integration 2024-07-21 23:57:16 +02:00
ee14c3fd7e feat: add build & update to latest python 2024-07-21 23:49:12 +02:00
Yannik Schmidt
c32155fd40 fix typo in variable name 2022-03-07 01:32:57 +01:00
Yannik Schmidt
defcf5671d implement simple medals 2021-07-24 14:17:35 +02:00
2133249947 Merge branch 'master' of github.com:FAUSheppy/open-web-leaderboard 2021-07-24 13:25:42 +02:00
db3d2bb57e remove footer in player display 2021-07-24 13:25:29 +02:00
c5b7963fff improve livegame display 2021-07-24 13:25:20 +02:00
23fa7f9862 fade & whitespace 2021-07-24 13:24:31 +02:00
8f7b1b47ac add cache header to static 2021-07-24 13:23:05 +02:00
bdd7d9bf01 fix server player query 2021-03-25 17:52:32 +01:00
36 changed files with 298 additions and 1679 deletions

32
.github/workflows/main.yaml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Container Build for open-web-leaderboard
on:
push:
branches:
- "master"
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
View 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"]

View File

@@ -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 # 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: Source-Servers can be added via the *servers.json*-file:
[ [

View File

@@ -36,12 +36,12 @@ class Round:
self.blacklist = True self.blacklist = True
if winnerSide == 1: if winnerSide == 2:
self.winnerSideString = "Red" self.winnerSideString = "Security"
self.loserSideString = "Blue" self.loserSideString = "Insurgent"
else: else:
self.winnerSideString = "Blue" self.winnerSideString = "Insurgent"
self.loserSideString = "Red" self.loserSideString = "Security"
if mapName: if mapName:
self.mapName = mapName self.mapName = mapName
else: else:

113
api.py
View File

@@ -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
View File

@@ -1,3 +1,5 @@
import server import server
def createApp(envivorment=None, start_response=None): def createApp(envivorment=None, start_response=None):
with server.app.app_context():
server.create_app()
return server.app return server.app

View File

@@ -31,7 +31,7 @@ class DatabaseConnection:
'''Get the total number of players in the database''' '''Get the total number of players in the database'''
cursor = self.connPlayers.cursor() 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] count = cursor.fetchone()[0]
return count return count
@@ -79,8 +79,8 @@ class DatabaseConnection:
cursor = self.connPlayers.cursor() cursor = self.connPlayers.cursor()
limit = end - start limit = end - start
print(limit, start) sqlQuery = '''Select * FROM players where games >= 10
sqlQuery = '''Select * FROM players where games >= 1 and not lastgame is null
ORDER BY (mu - 2*sigma) DESC LIMIT ? OFFSET ?''' ORDER BY (mu - 2*sigma) DESC LIMIT ? OFFSET ?'''
cursor.execute(sqlQuery, (limit, start)) cursor.execute(sqlQuery, (limit, start))
rows = cursor.fetchall() rows = cursor.fetchall()
@@ -121,8 +121,11 @@ class DatabaseConnection:
can't and shouldn't be used to identify a player''' can't and shouldn't be used to identify a player'''
cursor = self.connPlayers.cursor() cursor = self.connPlayers.cursor()
cursor.execute('''SELECT COUNT(*) from players if(player.games < 10):
where (mu-2*sigma) > (?-2*?);''', 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)) (player.mu, player.sigma))
rank = cursor.fetchone()[0] rank = cursor.fetchone()[0]
return rank return rank
@@ -178,9 +181,9 @@ class DatabaseConnection:
WHERE timestamp < ? AND id = ?''', WHERE timestamp < ? AND id = ?''',
(roundObj.startTime.timestamp(), p.playerId)) (roundObj.startTime.timestamp(), p.playerId))
#if(cursorHist.fetchone()[0] < 10): if(cursorHist.fetchone()[0] < 10):
# p.ratingChangeString = "Placements" p.ratingChangeString = "Placements"
# continue continue
cursorHist.execute('''SELECT mu,sima FROM playerHistoricalData cursorHist.execute('''SELECT mu,sima FROM playerHistoricalData
WHERE timestamp < ? AND id = ? order by timestamp DESC LIMIT 1 ''', WHERE timestamp < ? AND id = ? order by timestamp DESC LIMIT 1 ''',

93
medals.py Normal file
View File

@@ -0,0 +1,93 @@
import flask
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" },
"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-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" : "Played {} games above 3000 rating",
"color" : "red",
"text-color" : "black" },
}
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 flask.Markup(tmp)
def getMedals(ratingList, gamesPlayed, currentRating):
'''Get Medals this player should have'''
medals = []
if 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 > 100:
medals += [medalGen(medalDict["rating-2k-2"], games2k)]
elif games2k > 0:
medals += [medalGen(medalDict["rating-2k-1"], games2k)]
if games3k > 200:
medals += [medalGen(medalDict["rating-3k-3"], games3k)]
if games3k > 50:
medals += [medalGen(medalDict["rating-3k-2"], games3k)]
elif games3k > 5:
medals += [medalGen(medalDict["rating-3k-1"], games3k)]
if currentRating > 1500:
medals += [medalGen(medalDict["rating-cur-1"])]
return medals

BIN
rounds.sqlite Normal file

Binary file not shown.

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -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))

308
server.py
View File

@@ -1,29 +1,21 @@
#!/usr/bin/python3 #!/usr/bin/python3
import medals
import flask import flask
import requests import requests
import argparse import argparse
import datetime import datetime
import flask_caching as fcache import flask_caching as fcache
import itertools
import json import json
import os import os
import MapSummary import MapSummary
import random
import secrets
import riotwatcher
import time
import statistics
from database import DatabaseConnection from database import DatabaseConnection
import api #import valve.source.a2s
#from valve.source import NoResponseError
app = flask.Flask("open-leaderboard") app = flask.Flask("open-leaderboard")
WATCHER = 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")
@@ -50,8 +42,9 @@ def playersOnline():
for s in SERVERS: for s in SERVERS:
try: try:
with valve.source.a2s.ServerQuerier((args.host, args.port)) as server: pass
playerTotal += int(server.info()["player_count"]) #with valve.source.a2s.ServerQuerier((s["host"], s["port"])) as server:
# playerTotal += int(server.info()["player_count"])
except NoResponseError: except NoResponseError:
error = "Server Unreachable" error = "Server Unreachable"
except Exception as e: except Exception as e:
@@ -126,7 +119,7 @@ def rounds():
end = flask.request.args.get("end") end = flask.request.args.get("end")
if not start or not end: if not start or not end:
start = datetime.datetime.now() - datetime.timedelta(days=365) start = datetime.datetime.now() - datetime.timedelta(days=7)
end = datetime.datetime.now() end = datetime.datetime.now()
else: else:
start = datetime.datetime.fromtimestamp(start) start = datetime.datetime.fromtimestamp(start)
@@ -144,227 +137,6 @@ def rounds():
# display outcome # display outcome
# display map # display map
class Player:
def __init__(self, name, prio):
self.name = name
self.prio = prio
# TODO
submission = dict()
@app.route("/role-submission", methods=['GET', 'POST'])
def roleSubmissionTool():
positions=["Top", "Jungle", "Mid", "Bottom", "Support" ]
ident = flask.request.args.get("id")
if flask.request.method == 'POST':
if not ident in submission:
submission.update({ ident : [] })
tmp = dict()
tmp.update({ "name" : flask.request.form["playername"] })
for p in positions:
tmp.update({ p : flask.request.form["prio_{}".format(p)] })
existed = False
for pl in submission[ident]:
if pl["name"] == tmp["name"]:
for p in positions:
pl.update({ p : flask.request.form["prio_{}".format(p)] })
existed = True
break;
if not existed:
submission[ident] += [tmp]
return flask.redirect("/balance-tool?id={}".format(ident))
else:
return flask.render_template("role_submission.html",
positions=positions,
ident=ident)
@app.route("/balance-tool-data")
def balanceToolData():
ident = flask.request.args.get("id")
retDict = dict()
if not ident in submission:
return flask.Response(json.dumps({ "no-data" : False }), 200, mimetype='application/json')
retDict.update({ "submissions" : submission[ident] })
return flask.Response(json.dumps(retDict), 200, mimetype='application/json')
@app.route('/')
@app.route("/balance-tool", methods=['GET', 'POST'])
def balanceTool():
positions=["Top", "Jungle", "Mid", "Bottom", "Support"]
#db = DatabaseConnection(app.config["DB_PATH"])
if flask.request.method == 'POST':
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')
else:
givenIdent = flask.request.args.get("id")
if givenIdent:
ident = givenIdent
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)
@app.route("/get-cache")
def getCacheLoc():
return (json.dumps(api.getCache()), 200)
@app.route("/player-api")
def playerApi():
result = api.getPlayerRatingFromApi(flask.request.args.get("id"), WATCHER)
if result:
return ("OK", 200)
else:
return ("Nope", 404)
@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("/player") @app.route("/player")
def player(): def player():
'''Show Info about Player''' '''Show Info about Player'''
@@ -389,16 +161,29 @@ def player():
minRating = 3000 minRating = 3000
maxRating = 0 maxRating = 0
# data for medals #
medalsRatingList = []
if histData: if histData:
datapoints = histData[playerId] datapoints = histData[playerId]
if datapoints: if datapoints:
tickCounter = 10 tickCounter = 10
for dpk in datapoints.keys(): for dpk in datapoints.keys():
# timestamp #
t = datetime.datetime.fromtimestamp(int(float(dpk))) t = datetime.datetime.fromtimestamp(int(float(dpk)))
tsMs = str(int(t.timestamp() * 1000)) tsMs = str(int(t.timestamp() * 1000))
ratingString = str(int(datapoints[dpk]["mu"]) - 2*int(datapoints[dpk]["sigma"]))
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 + '}' ratingAmored = '{ x : ' + tsMs + ', y : ' + ratingString + '}'
csv_timestamps += [str(tsMs)] csv_timestamps += [str(tsMs)]
csv_ratings += [ratingAmored] csv_ratings += [ratingAmored]
@@ -412,16 +197,19 @@ def player():
yMin, yMax = prettifyMinMaxY(minRating, maxRating) yMin, yMax = prettifyMinMaxY(minRating, maxRating)
medalsList = medals.getMedals(medalsRatingList, player.games, player.rating)
# change displayed rank to start from 1 :) # change displayed rank to start from 1 :)
player.rank += 1 player.rank += 1
return flask.render_template("player.html", player=player, CSV_RATINGS=",".join(csv_ratings), return flask.render_template("player.html", player=player, CSV_RATINGS=",".join(csv_ratings),
CSV_MONTH_YEAR_OF_RATINGS=",".join(csv_month_year), CSV_MONTH_YEAR_OF_RATINGS=",".join(csv_month_year),
CSV_TIMESTAMPS=csv_timestamps, CSV_TIMESTAMPS=csv_timestamps,
Y_MIN=yMin, Y_MAX=yMax) Y_MIN=yMin, Y_MAX=yMax, medals=medalsList)
@app.route('/leaderboard') @app.route('/leaderboard')
@cache.cached(timeout=10, query_string=True) @app.route('/')
@cache.cached(timeout=600, query_string=True)
def leaderboard(): def leaderboard():
'''Show main leaderboard page with range dependant on parameters''' '''Show main leaderboard page with range dependant on parameters'''
@@ -476,7 +264,6 @@ def leaderboard():
if end > maxEntry: if end > maxEntry:
start = maxEntry - ( maxEntry % SEGMENT ) - 1 start = maxEntry - ( maxEntry % SEGMENT ) - 1
end = maxEntry - 1 end = maxEntry - 1
print(maxEntry)
reachedEnd = True reachedEnd = True
playerList = db.getRankRange(start, end) playerList = db.getRankRange(start, end)
@@ -490,11 +277,11 @@ def leaderboard():
if maxEntry <= 100: if maxEntry <= 100:
start = max(start, 0) start = max(start, 0)
finalResponse = flask.render_template("base.html", playerList=playerList, \ finalResponse = flask.render_template("base.html", playerList=playerList,
doNotComputeRank=doNotComputeRank, \ doNotComputeRank=doNotComputeRank,
start=start, \ start=start,
endOfBoardIndicator=endOfBoardIndicator, \ endOfBoardIndicator=endOfBoardIndicator,
findPlayer=cannotFindPlayer, \ findPlayer=cannotFindPlayer,
searchName=searchName, searchName=searchName,
nextPageNumber=int(pageInt)+1, nextPageNumber=int(pageInt)+1,
prevPageNumber=int(pageInt)-1) prevPageNumber=int(pageInt)-1)
@@ -502,28 +289,35 @@ def leaderboard():
@app.route('/static/<path:path>') @app.route('/static/<path:path>')
def send_js(path): def send_js(path):
return send_from_directory('static', path)
@app.before_first_request response = send_from_directory('static', path)
def init(): response.headers['Cache-Control'] = "max-age=2592000"
return response
global WATCHER
with open("key.txt","r") as f: def create_app():
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__": 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') 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') 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)')
parser.add_argument('--skillbird-db', required=False, help='skillbird database (overrides web connection if set)') with app.app_context():
create_app()
args = parser.parse_args() args = parser.parse_args()
app.config["DB_PATH"] = args.skillbird_db app.config["DB_PATH"] = args.skillbird_db
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
);

View File

@@ -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()
}

View File

@@ -8,12 +8,6 @@ body{
} }
.border-special{
border-style: outset;
border-width: 0.2px;
padding: 20px;
}
.top-bar{ .top-bar{
background-color: orange; background-color: orange;
padding-bottom: 10px; padding-bottom: 10px;
@@ -223,7 +217,6 @@ body{
bottom: 0; bottom: 0;
left: 0; left: 0;
height: 30px; height: 30px;
} }
.footerLink{ .footerLink{
@@ -247,7 +240,6 @@ body{
.footerLink{ .footerLink{
font-size: 4vw; font-size: 4vw;
} }
.mid{ .mid{
margin-left: 2.5%; margin-left: 2.5%;
margin-right: 2.5%; margin-right: 2.5%;
@@ -264,3 +256,11 @@ body{
font-style: italic; font-style: italic;
font-weight: bold; font-weight: bold;
} }
@keyframes fadeIn {
from { opacity: 0.3; }
}
.animate-flicker {
animation: fadeIn 2s alternate;
}

View File

@@ -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>

View File

@@ -10,6 +10,29 @@
{% include 'navbar_leaderboard.html' %} {% include 'navbar_leaderboard.html' %}
<div class="container mt-3 mb-3" role="main"> <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" <table id="tableMain" class="table table-striped table-bordered table-sm"
cellspacing="0"> cellspacing="0">
<thead> <thead>

View File

@@ -1,9 +1,9 @@
<div class="footer-copyright text-center py-3 bg-dark" role="contentinfo"> <div class="footer-copyright text-center py-3 bg-dark" role="contentinfo">
<a style="color: rgba(255,255,255,.5);" <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);" <a style="color: rgba(255,255,255,.5);"
class="footerLink mid" href="https://esports-erlangen.de"> class="footerLink mid" href="steam://connect/athq.de/:27026">
ESE Website</a> Join the Server!</a>
<a style="color: rgba(255,255,255,.5);" <a style="color: rgba(255,255,255,.5);"
class="footerLink" href="https://github.com/FAUSheppy/skillbird">Star on GitHub</a> class="footerLink" href="https://github.com/FAUSheppy/skillbird">Star on GitHub</a>
</div> </div>

View File

@@ -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>

View File

@@ -10,13 +10,28 @@
<!-- left side --> <!-- left side -->
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/">New Balance</a> <a class="nav-link" href="/">Leaderboard</a>
</li> </li>
<!--
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/rounds">Rounds</a> <a class="nav-link" href="/rounds">Rounds</a>
</li> </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> </ul>
</div> </div>
</nav> </nav>

View File

@@ -18,8 +18,10 @@
<li class="nav-item"> <li class="nav-item">
<a id="button-forward" class="nav-link" href="/?page={{ nextPageNumber }}">Forward</a> <a id="button-forward" class="nav-link" href="/?page={{ nextPageNumber }}">Forward</a>
</li> </li>
<li class="nav-item"> <li class="nav-item" {% if not gameInProgress %} style="opacity: 0.5" {% endif %}>
<a id="button-forward" class="nav-link" href="/balance-tool">Balance Tool</a> <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> </li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
@@ -31,12 +33,15 @@
<li class="nav-item right mr-2"> <li class="nav-item right mr-2">
<a class="nav-link" href="/rounds">Rounds</a> <a class="nav-link" href="/rounds">Rounds</a>
</li> </li>
<li class="nav-item right mr-2">
<a class="nav-link" href="/maps">Maps</a>
</li>
<li class="nav-item right"> <li class="nav-item right">
<input id="getPlayer" type="text" aria-label="search for player" class="mt-1" <input id="getPlayer" type="text" aria-label="search for player" class="mt-1"
placeholder="search player..."> placeholder="search player...">
</li> </li>
{{ findPlayer }} {{ findPlayer }}
<!-- <li class="nav-item right"> <!--- <li class="nav-item right">
<input id="gotoRank" type="number" aria-label="goto rank" <input id="gotoRank" type="number" aria-label="goto rank"
placeholder="goto rank..."> placeholder="goto rank...">
</li> --> </li> -->

View File

@@ -14,17 +14,28 @@
</h1> </h1>
<h3> <h3>
Rating: <i>{{ player.rating }}</i> <br> Rating: <i>{{ player.rating }}</i> <br>
{% 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 }} Rank: {{ player.rank }}
{%endif%}
</h3> </h3>
</div> </div>
<div class="plot-container"> <div class="plot-container">
<canvas id="lineChart"> <canvas id="lineChart">
</canvas> </canvas>
</div> </div>
<div class="mt-3 medal-container">
{% for m in medals %}
{{ m }}
{% endfor %}
</div>
<p class="mt-5 mb-3"> <p class="mt-5 mb-3">
</p> </p>
</div> </div>
{% include 'footer.html' %} <!-- {% include 'footer.html' %}-->
<script defer> <script defer>
var canvas = document.getElementById("lineChart") var canvas = document.getElementById("lineChart")
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');

View File

@@ -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>

View File

@@ -41,6 +41,9 @@
<div class="col-sm" style="overflow: hidden;"> <div class="col-sm" style="overflow: hidden;">
<a href="/player?id={{ p.playerId }}">{{ p.name }}</a> <a href="/player?id={{ p.playerId }}">{{ p.name }}</a>
</div> </div>
<div class="col-sm">
{{ p.participation }}%
</div>
<div class="col-sm"> <div class="col-sm">
{% if not r.invalid %} {% if not r.invalid %}
<small style="color: green;"> <small style="color: green;">
@@ -57,6 +60,9 @@
<div class="col-sm" style="overflow: hidden;"> <div class="col-sm" style="overflow: hidden;">
<a href="/player?id={{ p.playerId }}">{{ p.name }}</a> <a href="/player?id={{ p.playerId }}">{{ p.name }}</a>
</div> </div>
<div class="col-sm">
{{ p.participation }}%
</div>
<div class="col-sm"> <div class="col-sm">
{% if not r.invalid %} {% if not r.invalid %}
<small style="color: red;"> <small style="color: red;">