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

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 datetime
import flask_caching as fcache
import itertools
import json
import os
import MapSummary
import random
import secrets
import riotwatcher
import time
import statistics
from database import DatabaseConnection
import api
app = flask.Flask("open-leaderboard")
WATCHER = None
KEY = None
if os.path.isfile("config.py"):
app.config.from_object("config")
@@ -115,7 +126,7 @@ def rounds():
end = flask.request.args.get("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()
else:
start = datetime.datetime.fromtimestamp(start)
@@ -133,6 +144,227 @@ def rounds():
# display outcome
# 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")
def player():
'''Show Info about Player'''
@@ -189,8 +421,7 @@ def player():
Y_MIN=yMin, Y_MAX=yMax)
@app.route('/leaderboard')
@app.route('/')
@cache.cached(timeout=600, query_string=True)
@cache.cached(timeout=10, query_string=True)
def leaderboard():
'''Show main leaderboard page with range dependant on parameters'''
@@ -245,6 +476,7 @@ def leaderboard():
if end > maxEntry:
start = maxEntry - ( maxEntry % SEGMENT ) - 1
end = maxEntry - 1
print(maxEntry)
reachedEnd = True
playerList = db.getRankRange(start, end)
@@ -274,12 +506,11 @@ def send_js(path):
@app.before_first_request
def init():
SERVERS_FILE = "servers.json"
if os.path.isfile(SERVERS_FILE):
import valve.source.a2s
from valve.source import NoResponseError
with open(SERVERS_FILE) as f:
SERVERS = json.load(f)
global WATCHER
with open("key.txt","r") as f:
key = f.read().strip()
WATCHER = riotwatcher.LolWatcher(key)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Start open-leaderboard', \
@@ -289,9 +520,10 @@ if __name__ == "__main__":
parser.add_argument('--port', default="5002", \
help='Port on which flask (this server) will take requests on')
parser.add_argument('--skillbird-db', required=True, help='skillbird database (overrides web connection if set)')
parser.add_argument('--skillbird-db', required=False, help='skillbird database (overrides web connection if set)')
args = parser.parse_args()
app.config["DB_PATH"] = args.skillbird_db
app.config["TEMPLATES_AUTO_RELOAD"] = True
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{
background-color: orange;
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' %}
<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"
cellspacing="0">
<thead>

View File

@@ -1,9 +1,9 @@
<div class="footer-copyright text-center py-3 bg-dark" role="contentinfo">
<a style="color: rgba(255,255,255,.5);"
class="footerLink" href="https://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);"
class="footerLink mid" href="steam://connect/athq.de/:27026">
Join the Server!</a>
class="footerLink mid" href="https://esports-erlangen.de">
ESE Website</a>
<a style="color: rgba(255,255,255,.5);"
class="footerLink" href="https://github.com/FAUSheppy/skillbird">Star on GitHub</a>
</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 -->
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/">Leaderboard</a>
<a class="nav-link" href="/">New Balance</a>
</li>
<!--
<li class="nav-item">
<a class="nav-link" href="/rounds">Rounds</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/maps">Maps</a>
</li>
</ul>
<ul class="navbar-nav right">
<li class="nav-item">
<a class="nav-link" href="steam://connect/athq.de/:27026">Server 1</a>
</li>
<li class="nav-item mr-4">
<a class="nav-link" href="steam://connect/athq.de/:27015">Server 2</a>
</li>
<!--
<li class="nav-item">
<a class="navbar-brand hover-to-75 patreon" style="position: unset !important;" href="https://www.patreon.com/erlangen_sheppy">Support me
</a>
</li>
-->
-->
</ul>
</div>
</nav>

View File

@@ -18,10 +18,8 @@
<li class="nav-item">
<a id="button-forward" class="nav-link" href="/?page={{ nextPageNumber }}">Forward</a>
</li>
<li class="nav-item" {% if not gameInProgress %} style="opacity: 0.5" {% endif %}>
<a id="button-forward" class="nav-link" href="/livegames">Livegames
<div style="font-size: small; color: red; opacity: 1; float: right; margin-left: 10px;">(experimental)</div>
</a>
<li class="nav-item">
<a id="button-forward" class="nav-link" href="/balance-tool">Balance Tool</a>
</li>
</ul>
<ul class="navbar-nav">
@@ -33,15 +31,12 @@
<li class="nav-item right mr-2">
<a class="nav-link" href="/rounds">Rounds</a>
</li>
<li class="nav-item right mr-2">
<a class="nav-link" href="/maps">Maps</a>
</li>
<li class="nav-item right">
<input id="getPlayer" type="text" aria-label="search for player" class="mt-1"
placeholder="search player...">
</li>
{{ findPlayer }}
<!--- <li class="nav-item right">
<!-- <li class="nav-item right">
<input id="gotoRank" type="number" aria-label="goto rank"
placeholder="goto rank...">
</li> -->

View File

@@ -14,13 +14,7 @@
</h1>
<h3>
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 }}
{%endif%}
Rank: {{ player.rank }}
</h3>
</div>
<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;">
<a href="/player?id={{ p.playerId }}">{{ p.name }}</a>
</div>
<div class="col-sm">
{{ p.participation }}%
</div>
<div class="col-sm">
{% if not r.invalid %}
<small style="color: green;">
@@ -60,9 +57,6 @@
<div class="col-sm" style="overflow: hidden;">
<a href="/player?id={{ p.playerId }}">{{ p.name }}</a>
</div>
<div class="col-sm">
{{ p.participation }}%
</div>
<div class="col-sm">
{% if not r.invalid %}
<small style="color: red;">