mirror of
https://github.com/FAUSheppy/open-web-leaderboard.git
synced 2025-12-06 07:01:36 +01:00
Compare commits
32 Commits
master
...
ese-custom
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d98016040 | ||
|
|
6a2ff6a72f | ||
|
|
ee61d1bb63 | ||
|
|
5c8191f4cf | ||
|
|
5dc1d16447 | ||
|
|
eb967900d0 | ||
|
|
bcb460bc24 | ||
|
|
02be6031a2 | ||
| a8da2b71bc | |||
| 7f8af681dd | |||
| 25ea0e03b5 | |||
| 7f845cc17c | |||
| 6a5b150cb8 | |||
| 82680cc0db | |||
| bdd24d68fa | |||
| cab9dceab7 | |||
| ead1a05c2e | |||
| 77e58305da | |||
| 9cdee90a7f | |||
| 0a1675c637 | |||
| f6ce5a9533 | |||
| 8f8eba81fa | |||
| 3a028607b4 | |||
| ca8dfb5208 | |||
|
|
3df3ade848 | ||
| 2ce48a7147 | |||
| 590763c613 | |||
| 5b8ab6fa78 | |||
| 1641332661 | |||
| bf31cb59e8 | |||
| a52bd66aed | |||
| 54f02c978a |
10
Round.py
10
Round.py
@@ -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
113
api.py
Normal 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
|
||||
19
database.py
19
database.py
@@ -31,7 +31,7 @@ class DatabaseConnection:
|
||||
'''Get the total number of players in the database'''
|
||||
|
||||
cursor = self.connPlayers.cursor()
|
||||
cursor.execute("SELECT Count(*) FROM players 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 ''',
|
||||
|
||||
BIN
rounds.sqlite
BIN
rounds.sqlite
Binary file not shown.
50
rounds/2021-04-26-round-01.json
Normal file
50
rounds/2021-04-26-round-01.json
Normal 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"
|
||||
}
|
||||
50
rounds/2021-04-26-round-02.json
Normal file
50
rounds/2021-04-26-round-02.json
Normal 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"
|
||||
}
|
||||
50
rounds/2021-04-26-round-03.json
Normal file
50
rounds/2021-04-26-round-03.json
Normal 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"
|
||||
}
|
||||
71
rounds/2021-05-24-round-01.json
Normal file
71
rounds/2021-05-24-round-01.json
Normal 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"
|
||||
}
|
||||
72
rounds/2021-05-31-round-01.json
Normal file
72
rounds/2021-05-31-round-01.json
Normal 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"
|
||||
}
|
||||
72
rounds/2021-05-31-round-02.json
Normal file
72
rounds/2021-05-31-round-02.json
Normal 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"
|
||||
}
|
||||
71
rounds/2021-05-31-round-03.json
Normal file
71
rounds/2021-05-31-round-03.json
Normal 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"
|
||||
}
|
||||
50
rounds/2021-06-07-round-01.json
Normal file
50
rounds/2021-06-07-round-01.json
Normal 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"
|
||||
}
|
||||
50
rounds/2021-07-05-round-01.json
Normal file
50
rounds/2021-07-05-round-01.json
Normal 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"
|
||||
}
|
||||
50
rounds/2021-07-05-round-02.json
Normal file
50
rounds/2021-07-05-round-02.json
Normal 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"
|
||||
}
|
||||
50
rounds/2021-07-05-round-03.json
Normal file
50
rounds/2021-07-05-round-03.json
Normal 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
7
rounds/all.sh
Normal 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
13
rounds/data
Normal 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
44
rounds/gen.py
Executable 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
252
server.py
@@ -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
5
sqlite.init
Normal file
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE players(
|
||||
playerName TEXT PRIMARY KEY,
|
||||
rating INTEGER,
|
||||
lastupdated TEXT
|
||||
);
|
||||
300
static/balance.js
Normal file
300
static/balance.js
Normal 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()
|
||||
}
|
||||
@@ -8,6 +8,12 @@ body{
|
||||
|
||||
}
|
||||
|
||||
.border-special{
|
||||
border-style: outset;
|
||||
border-width: 0.2px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.top-bar{
|
||||
background-color: orange;
|
||||
padding-bottom: 10px;
|
||||
|
||||
41
templates/balance_response_partial.html
Normal file
41
templates/balance_response_partial.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
150
templates/json_builder.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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> -->
|
||||
|
||||
@@ -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">
|
||||
|
||||
70
templates/role_submission.html
Normal file
70
templates/role_submission.html
Normal 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>
|
||||
@@ -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;">
|
||||
|
||||
Reference in New Issue
Block a user