forked from schemes-ohyeah/tcs-overwatch-elo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
269 lines (226 loc) · 8.61 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
from flask import Flask, render_template, request, jsonify
from TCS_Functions import TCS_Functions as TCS
from TCS_Objects import Team, Match
from typing import List
import json
from operator import itemgetter
app = Flask(__name__)
GLOBAL_teams = TCS.read_teams_from_json(reset=False)
GLOBAL_matches = TCS.read_matches_from_json()
GLOBAL_future_matches = TCS.read_matches_from_json(future=True)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/matches")
def matches_json():
"""
Returns a json for all matches. Currently not publicly findable.
:return:
"""
global GLOBAL_matches
matches = [GLOBAL_matches[key] for key in GLOBAL_matches]
return jsonify([match.__dict__() for match in matches])
@app.route("/rankings/<region>")
@app.route("/rankings")
def rankings_page(region=None):
"""
Lists all teams in order by rank, optionally by region
:param region: west north south east
:return:
"""
global GLOBAL_teams
teams = [GLOBAL_teams[key] for key in GLOBAL_teams]
# If a region is specified, filter filter teams only by the region
if region:
teams = [team for team in teams if team.region == region]
# Sort by elo
teams.sort(key=lambda x: x.elo, reverse=True)
# If `?json={anything}` return json page rather than html
if request.args.get("json"):
# return jsonify([team.__dict__() for team in teams])
return json.dumps([team.__dict__() for team in teams])
# Set json link to be displayed in html
if region is None:
json_link = "/rankings?json=true"
region = "nationwide"
else:
json_link = "/rankings/" + region + "?json=true"
return render_template("rankings.html",
teams=teams,
region=region,
json_link=json_link)
@app.route("/search")
def search_page():
"""
Searches by team name, university, or player within team
:return:
"""
global GLOBAL_teams
query = request.args.get("query").lower()
teams = [GLOBAL_teams[key] for key in GLOBAL_teams]
results = []
for team in teams:
name = team.name.lower()
university = team.university.lower()
abbreviation = get_abbreviation(team.university).lower()
search = " ".join([name, university, abbreviation])
if query in search:
# Search for name, university, abbreviation
results.append(team)
else:
# Search for battle tag in team
for player in team.players:
if query in player.battle_tag.lower():
results.append(team)
return render_template("search.html",
query=query,
results=results)
@app.route("/team/<team_id>")
def team_page(team_id):
"""
Displays known team data such as players, match history, and future
matches. Pending feature to show summary of map choices (maps
chosen when team loses) and best maps (just based on wins)
:param team_id:
:return:
"""
global GLOBAL_teams, GLOBAL_matches, GLOBAL_future_matches
team = GLOBAL_teams[int(team_id)]
matches = [
GLOBAL_matches[match_id] for match_id in team.matches
]
my_matches = []
# Init elo trend as team.average_sr as first item, will add more later
elo_trend = [team.average_sr]
# Refer to find_match_data doc to see what is returned in data
for match in matches:
data = find_match_data(team, match, elo_trend)
my_matches.append(data)
map_results = []
for data in my_matches:
map_results.extend(data["results"])
# Makes a map summary of best to worst maps
map_data = []
for s in sorted(
find_map_data(map_results).items(),
key=lambda k_v: k_v[1]["win"],
reverse=True):
map_data.append(s)
map_data = dict(map_data)
future_matches = [
GLOBAL_future_matches[match_id] for match_id in team.future_matches
]
my_future_matches = []
# Refer to find_future_match_data to see what is returned in data
for future_match in future_matches:
data = find_future_match_data(team, future_match)
my_future_matches.append(data)
return render_template("team.html",
team=team,
elo_trend=elo_trend,
map_data=map_data,
matches=my_matches,
future_matches=my_future_matches)
def find_match_data(team: Team, match: Match, elo_trend: List[int]):
"""
Match data is stored in the perspective of "team 1" as defined by Tespa.
Since the match page is to show in perspective of the requested team,
data is rearranged if necessary to be in that team's perspective.
:param team:
:param match:
:param elo_trend:
:return: Dict ->
results: list of str map and int -1 / 0 / 1 indicating loss, tie, win
elos: list of float pre-calculated Elo values from each map
opponent_elo: float opponent team starting Elo
opponent: Team
opponent_id: int
url: str tespa url of the match
win_chance: float calculated win chance from starting Elos
"""
global GLOBAL_teams
data = {}
if match.team_1id == team.id:
data["results"] = match.results
data["elos"] = match.team_1elos
data["opponent_elo"] = match.team_2elos[0]
data["opponent"] = GLOBAL_teams[match.team_2id].name
data["opponent_id"] = match.team_2id
elif match.team_2id == team.id:
# Matches store win result relative to team 1. Flip if this is team 2
data["results"] = [[-result[0], result[1]] for result in match.results]
data["elos"] = match.team_2elos
data["opponent_elo"] = match.team_1elos[0]
data["opponent"] = GLOBAL_teams[match.team_1id].name
data["opponent_id"] = match.team_1id
else:
print("something is wrong with team match finding")
data["url"] = match.url
elo_trend.extend(data["elos"][1:])
# Probably to win based on math from here
data["win_chance"] = Match.calculate_win_chance(
data["elos"][0], data["opponent_elo"]
)
return data
def find_future_match_data(team: Team, future_match: Match):
"""
Refer to find_match_data doc for details about perspective
:param team:
:param future_match:
:return: Dict ->
elo: float current team's elo
opponent_elo: float
opponent: Team
opponent_id: int
win_chance: float calculated win chance from most recent Elos
"""
global GLOBAL_teams
data = {}
if future_match.team_1id == team.id:
data["elo"] = future_match.team_1elos[0]
data["opponent_elo"] = future_match.team_2elos[0]
data["opponent"] = GLOBAL_teams[future_match.team_2id].name
data["opponent_id"] = future_match.team_2id
elif future_match.team_2id == team.id:
data["elo"] = future_match.team_2elos[0]
data["opponent_elo"] = future_match.team_1elos[0]
data["opponent"] = GLOBAL_teams[future_match.team_1id].name
data["opponent_id"] = future_match.team_1id
else:
print("something is wrong with the future match finding")
data["url"] = future_match.url
data["win_chance"] = Match.calculate_win_chance(
data["elo"], data["opponent_elo"]
)
return data
def find_map_data(map_results):
NAME = 1
RESULT = 0
data = {}
for result in map_results:
if result[NAME] not in data:
data[result[NAME]] = {
"win" : 0,
"lose" : 0
}
if result[RESULT] == 1:
data[result[NAME]]["win"] += 1
elif result[RESULT] == -1:
data[result[NAME]]["lose"] += 1
else:
# Draws do not appear to be recorded by Tespa, they will
# play a tie breaker and winner of that map will be recorded
# as the winner for the original map, tie breaker map is
# not recorded
pass
return data
def get_abbreviation(text: str) -> str:
"""
Takes in title cased university name and returns an abbreviation using
only the upper case letters
:param text:
:return: upper case abbreviation
"""
return "".join(c for c in text if c.isupper())
if __name__ == "__main__":
app.run()