Compare commits

32 Commits

Author SHA1 Message Date
Yannik Schmidt
5d98016040 fix various problem reported by users 2021-09-06 10:57:01 +02:00
Yannik Schmidt
6a2ff6a72f reset color on bad request 2021-07-27 13:56:40 +02:00
Yannik Schmidt
ee61d1bb63 update 2021-07-26 23:25:06 +02:00
Yannik Schmidt
5c8191f4cf new rounds 2021-07-26 21:19:43 +02:00
Yannik Schmidt
5dc1d16447 remove leaderboard from navbar 2021-07-26 21:18:27 +02:00
Yannik Schmidt
eb967900d0 more api fixes 2021-07-26 21:07:57 +02:00
Yannik Schmidt
bcb460bc24 implement basic queries from api 2021-07-25 03:04:24 +02:00
Yannik Schmidt
02be6031a2 prepare api query 2021-07-24 13:22:25 +02:00
a8da2b71bc add rounds 2021-06-07 21:06:27 +02:00
7f8af681dd fix some types 2021-06-07 18:25:00 +02:00
25ea0e03b5 implement simple balance by rating 2021-06-07 17:50:28 +02:00
7f845cc17c add new line 2021-06-06 15:03:03 +02:00
6a5b150cb8 add help 2021-06-06 15:01:54 +02:00
82680cc0db fix url 2021-06-04 20:22:54 +02:00
bdd24d68fa redirect to id url if none given 2021-06-04 20:21:50 +02:00
cab9dceab7 some minor improvements 2021-06-04 19:08:11 +02:00
ead1a05c2e implement autoform clear 2021-06-04 18:38:49 +02:00
77e58305da tabs -> spaces 2021-06-04 18:32:41 +02:00
9cdee90a7f implement role submissioN 2021-06-04 18:32:10 +02:00
0a1675c637 allow lol names with length 3 2021-06-03 21:29:02 +02:00
f6ce5a9533 support non-full teams 2021-06-03 21:11:26 +02:00
8f8eba81fa implement spinner to show loading status 2021-06-03 21:06:30 +02:00
3a028607b4 finish better algo serverside 2021-06-03 21:02:18 +02:00
ca8dfb5208 remove old code 2021-06-03 21:02:04 +02:00
Yannik Schmidt
3df3ade848 change algorithm 2021-06-03 20:38:46 +02:00
2ce48a7147 allow * internally 2021-06-03 20:22:11 +02:00
590763c613 implement multiline input copy 2021-06-02 22:32:57 +02:00
5b8ab6fa78 remove console.logs 2021-06-02 01:12:54 +02:00
1641332661 implement balance tool basic func done 2021-06-02 01:00:23 +02:00
bf31cb59e8 more features 2021-06-01 23:41:13 +02:00
a52bd66aed basic balance input 2021-06-01 19:41:34 +02:00
54f02c978a ESE custom modifications 2021-06-01 15:53:38 +02:00
31 changed files with 1650 additions and 87 deletions

View File

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

113
api.py Normal file
View File

@@ -0,0 +1,113 @@
#!/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

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 where games >= 10 and not lastgame is null") cursor.execute("SELECT Count(*) FROM players")
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
sqlQuery = '''Select * FROM players where games >= 10 print(limit, start)
and not lastgame is null sqlQuery = '''Select * FROM players where games >= 1
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,11 +121,8 @@ 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()
if(player.games < 10): cursor.execute('''SELECT COUNT(*) from players
return -1 where (mu-2*sigma) > (?-2*?);''',
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
@@ -181,9 +178,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 ''',

Binary file not shown.

View File

@@ -0,0 +1,50 @@
{
"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

@@ -0,0 +1,50 @@
{
"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

@@ -0,0 +1,50 @@
{
"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

@@ -0,0 +1,71 @@
{
"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

@@ -0,0 +1,72 @@
{
"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

@@ -0,0 +1,72 @@
{
"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

@@ -0,0 +1,71 @@
{
"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

@@ -0,0 +1,50 @@
{
"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

@@ -0,0 +1,50 @@
{
"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

@@ -0,0 +1,50 @@
{
"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

@@ -0,0 +1,50 @@
{
"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"
}

7
rounds/all.sh Normal file
View File

@@ -0,0 +1,7 @@
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 Normal file
View File

@@ -0,0 +1,13 @@
1
24:16
TryhardYordle
ChessGM lbfxd
TheSlapstick
Vyne
ESE Felix
Phirop
xXxMarethyuxXx
iTrash
JuckF Bierhuhn
ENTAC
2021-04-25T22:00+02:00

44
rounds/gen.py Executable file
View File

@@ -0,0 +1,44 @@
#!/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))

252
server.py
View File

@@ -4,15 +4,26 @@ 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
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")
@@ -115,7 +126,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=7) start = datetime.datetime.now() - datetime.timedelta(days=365)
end = datetime.datetime.now() end = datetime.datetime.now()
else: else:
start = datetime.datetime.fromtimestamp(start) start = datetime.datetime.fromtimestamp(start)
@@ -133,6 +144,227 @@ 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'''
@@ -189,8 +421,7 @@ def player():
Y_MIN=yMin, Y_MAX=yMax) Y_MIN=yMin, Y_MAX=yMax)
@app.route('/leaderboard') @app.route('/leaderboard')
@app.route('/') @cache.cached(timeout=10, query_string=True)
@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'''
@@ -245,6 +476,7 @@ 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)
@@ -274,12 +506,11 @@ def send_js(path):
@app.before_first_request @app.before_first_request
def init(): def init():
SERVERS_FILE = "servers.json"
if os.path.isfile(SERVERS_FILE): global WATCHER
import valve.source.a2s with open("key.txt","r") as f:
from valve.source import NoResponseError key = f.read().strip()
with open(SERVERS_FILE) as f: WATCHER = riotwatcher.LolWatcher(key)
SERVERS = json.load(f)
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Start open-leaderboard', \ parser = argparse.ArgumentParser(description='Start open-leaderboard', \
@@ -289,9 +520,10 @@ if __name__ == "__main__":
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)')
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)

5
sqlite.init Normal file
View File

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

300
static/balance.js Normal file
View File

@@ -0,0 +1,300 @@
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,6 +8,12 @@ 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;

View File

@@ -0,0 +1,41 @@
<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,25 +10,6 @@
{% 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(){
fetch("/players-online").then(
response => response.json()
).then(
data => {
if(data["error"] == ""){
document.getElementById("playerDisplay").innerHTML = "Players Online: " + data["player_total"]
}else{
document.getElementById("playerDisplay").innerHTML = "Players Online: (error)" + data["error"]
}
}
)
}
players()
setInterval(players, 2000)
</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://blog.atlantishq.de/about">Impressum/Legal</a> class="footerLink" href="https://atlantishq.de/impressum">Impressum/Legal</a>
<a style="color: rgba(255,255,255,.5);" <a style="color: rgba(255,255,255,.5);"
class="footerLink mid" href="steam://connect/athq.de/:27026"> class="footerLink mid" href="https://esports-erlangen.de">
Join the Server!</a> ESE Website</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>

150
templates/json_builder.html Normal file
View File

@@ -0,0 +1,150 @@
<!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,28 +10,13 @@
<!-- 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="/">Leaderboard</a> <a class="nav-link" href="/">New Balance</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,10 +18,8 @@
<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" {% if not gameInProgress %} style="opacity: 0.5" {% endif %}> <li class="nav-item">
<a id="button-forward" class="nav-link" href="/livegames">Livegames <a id="button-forward" class="nav-link" href="/balance-tool">Balance Tool</a>
<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">
@@ -33,15 +31,12 @@
<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,13 +14,7 @@
</h1> </h1>
<h3> <h3>
Rating: <i>{{ player.rating }}</i> <br> Rating: <i>{{ player.rating }}</i> <br>
{% if player.rank == 0 %} Rank: {{ player.rank }}
<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> </h3>
</div> </div>
<div class="plot-container"> <div class="plot-container">

View File

@@ -0,0 +1,70 @@
<!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,9 +41,6 @@
<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;">
@@ -60,9 +57,6 @@
<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;">