-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUEFA_Champions_League.py
447 lines (385 loc) · 21 KB
/
UEFA_Champions_League.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# !/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'NereWARin'
from Team import Team
from Cups import Cup
import DataStoring as db
import util
from Leagues import League, TeamResult
import Match as M
from values import Coefficients as C, TournamentSchemas as schemas, UEFA_CL_TYPE_ID, \
UEFA_EL_TYPE_ID, VALUES_VERSION, UEFA_TOURNAMENTS_ID, UEFA_CL_SCHEMA, UEFA_EL_SCHEMA, RESERVED_EL_TEAMS
import values as v
from operator import attrgetter, itemgetter
import random
import time
import os
import sys
import warnings
import copy
class UEFA_Champions_League(Cup):
def __init__(self,
name = None, # id from Tournaments_played
season = None,
year = None,
members = None,
delta_coefs = C(VALUES_VERSION).getRatingUpdateCoefs("list"),
pair_mode = 1,
seeding = UEFA_CL_SCHEMA,
state_params = ("final_stage", ),
save_to_db = True,
prefix = "UEFA",
type_id = UEFA_CL_TYPE_ID, # id from tournaments_types_names
country_id = None
):
"""
UEFA_Champ_L is a tournament, implemented as three tournaments:
Qualification Cup, Group League, Play-Off Cup.
Num of rounds and pairs in every tournament defined by schema
:param name:
:param season:
:param members:
:param delta_coefs:
:param schema:
:param pair_mode:
:param state_params:
:return:
"""
if country_id:
warnings.warn("UEFA not uses country as parameter - its international!")
super(UEFA_Champions_League, self).__init__(**{par:val for par,val in locals().iteritems()
if par != "self" })
# if par not in ("self", "ntp_teams", "nations")})
print "\nWELCOME TO UEFA LEAGUE *** id = %s season_id = %s ***" % (name, season.getID())
def setMembers(self):
"""
defines members for every round
convert indexes of countries, from what championships members are getting, to indexes of self.members for every team
:return:
"""
ntp_teams = self.season.get_ntp()
nations = self.season.getNations()
def shift_tourn(ntp_teams, nations, tourn_id):
"""
shift seeding source, if current team is already seeded in this or higher UEFA tournament
"""
# get next league - stay in the same half
# ifcup, reminder = divmod(tourn_id, self.nations)
# tourn_id = ifcup * self.nations + (reminder + 1) % self.nations
# get from next-ranked LEAGUE (even if CUP was empty) # shift
tourn_id = (tourn_id + 1) % nations
# get again
ntt = ntp_teams[tourn_id]
return tourn_id, ntt
# shift_seed is used to shift indexes of seeded members in cases - if some country has'n got so many counties
# to pass them to UEFA cup, so shift nation
shift_nation = 0
# or if member seeded to UEFA_EL by Cup was already seeded to UEFA_CL - so get team from next pos of national
# league
shift_team = 0
# self.members = []
# list of seeded members ordered by sub-tournament from qual to play-off
self.sub_schems = []
# parsing stored in schema values
stages = []
# winners of every sub-tournament will be added to next sub-tournament
winners = []
# UEFA tournament consists of sub_tournaments - Qualification, Group, Pla-off which are played in order
for sub_tourn in copy.deepcopy(self.seeding): # schema - list of dicts
# print stage
sub_tourn_members = []
classname, round_num, parts, seeding, round_num, sub_tourn_name, pair_mode = \
None, None, None, None, None, None, None
rounds_info = {}
# parts = None
# pair_mode = None
# tourn_class = None
# classname = None
assert len(sub_tourn.keys()) == 1, "unexpected sub_tourn keys (%s) > 1 " % len(sub_tourn.keys())
# v1 the same as
# sub_tourn_name = sub_tourn.keys()[0]
# sub_tourn_info = sub_tourn[sub_tourn_name]
# v2
for sub_tourn_name, sub_tourn_info in sub_tourn.iteritems():
# print "sub_tourn_name %s" % sub_tourn_name
classname = sub_tourn_info["classname"]
# tourn_class = getattr(sys.modules[__name__], classname)
parts = sub_tourn_info["parts"] # for Groups
pair_mode = sub_tourn_info["pair_mode"]
rounds_info = sub_tourn_info["tindx_in_round"]
# borders = {}
for round_num, seeded_sources in rounds_info.items():
# print "round_num = %s" % round_num#, members_schema
rounds_info[round_num] = {} # replace dict ntp_index:pos by
round_members = []
for source, pos in seeded_sources.iteritems():
# print "sourcse, pos = ", source, pos
if isinstance(source, tuple):
# getting teams from source
if isinstance(pos, int):
# print "seed from national League"
# shift if cup is references to orders Leagues and Cups are stored in ntp, see Teams
shift_ifcup = 0
position = pos
elif pos == "cupwinner":
# print "seed from national Cup"
shift_ifcup = nations
position = 1
else:
raise Exception, "unknown pos %s type %s" % (pos, type(pos))
# tourn_pos is a index of self.ntp, it is stored in schema (see values.py) and points to ntp
for tourn_pos in source:
# -1 cause torn_id start from 1 in db, but index of ntp_teams starts from 0
tourn_id = tourn_pos + shift_ifcup + shift_nation - 1 # index of list ntp_teams
# national tournament teams list
ntt = ntp_teams[tourn_id] # list of teams for league or cup winner for cup
# index of ntt list
# -1 cause position start from 1 in db, but index of teams in ntp_teams starts from 0
team_index = (position - 1) + shift_team
# check national tournament has this position (ntp exists)
while team_index > (len(ntt) - 1): # league is too small!
# if tournament has not got more unseeded teams, tourn will be shifted to next
warnings.warn("national tournament_id %s has not position %s to qualify it to UEFA!"
% (tourn_id, team_index))
# TODO there may be a problem
tourn_id, ntt = shift_tourn(ntp_teams, nations, tourn_id)
seeded_team = ntt[team_index]
# seeded_team = ntt.pop(0)
def check_already_seeded_in_UEFA(type, season, seeded_team):
# look at current lists
if (seeded_team in round_members) or (seeded_team in sub_tourn_members) or \
(seeded_team in [other_sub[2] for other_sub in self.sub_schems]):
return True
if type == UEFA_EL_TYPE_ID:
# additionally see in members CL stored in Season
return season.check_seed_in_CL(seeded_team)
# not found - ok, team is ready to be seeded
return False
# # check team was already seeded in UEFA by another source
while check_already_seeded_in_UEFA(self.type_id, self.season, seeded_team):
# get team of lower position of the same league
# if League or Cup member out of range, shift tournament to next
team_index += 1
while team_index > (len(ntt) - 1):
# if tournament hasn't got more unseeded teams, shift source by rating to get
# next national tournament
tourn_id, ntt = shift_tourn(ntp_teams, nations, tourn_id)
# TODO we check one tournament but not others! we can make "reserve" tag in schema for additional list of teams and pop from it
seeded_team = ntt[team_index]#.pop()
round_members.append(seeded_team)
elif source == "toss":
rounds_info[round_num]["toss"] = pos
elif source == "CL":
round_members += self.season.get_CL_EL_seeding(pos)
sub_tourn_members += reversed(round_members)
rounds_info[round_num]["count"] = len(round_members) # TODO round_members = 8 but group_winners = 24! count should be 0!
if sub_tourn_name == "Play-Off":
rounds_info[round_num]["count"] = 0
self.sub_schems.append((classname, sub_tourn_name, sub_tourn_members[::-1], rounds_info, parts, pair_mode))
# TODO delete [::-1] iabove and below
# common list of members used for quick search by another tournament (UEFA_EL) - maybe it will be unused
self.members = []
for sub_schema in reversed(self.sub_schems): # from qualification to play-off
# get sub_tourn_members from sub_schems and add them to the common list
self.members += sub_schema[2]
# reverse back - now its from favorite to outsider
self.members = self.members[::-1]
print "ok UEFA setMembers"
return self.members
def run(self, print_matches = False):
"""
run sub-tournaments
:param print_matches:
:return:
"""
# register ID of tournament if unregistered yet
self.name_id = self.saveTounramentPlayed()
# fot Matches (not use cause id_tournament column references to tourn_type_name through tournaments_played)
# tourn_type_name = db.select(what="name", table_names=db.TOURNAMENTS_TYPES_TABLE, where=" WHERE ", columns="id",
# sign=" = ", values=self.type_id)
# print "tourn_type_name: %s" % tourn_type_name
# dict of CL loosers that will be transferred to EL seeding
self.CL_EL_seeding = {"Qualification 3" : [], "Qualification 4" : [], "Group 3th places" : []}
# parse schema
pre_winners = []
for sub_schema in self.sub_schems:
# tourn_class, sub_tourn_name, members, rounds_info, parts, pair_mode = sub_schema
classname, sub_tourn_name, members, rounds_info, parts, pair_mode = sub_schema
tourn_class = getattr(sys.modules[__name__], classname)
print "classname %s, sub_tourn_name %s, seeded_members %s, rounds_info %s, parts %s, pair_mode %s" % (
classname, sub_tourn_name, members, rounds_info, parts, pair_mode)
if not parts:
warnings.warn("empty round!")
# go to next round
continue
if not sub_tourn_name:
warnings.warn("no name sub-tournament!")
continue
if not pair_mode:
warnings.warn("no pair mode!")
if not rounds_info:
warnings.warn("no rounds_info collected!")
# sourcse, pos = = sub_tourn_name
# seeding = {round_num : {"count": len(sub_tourn_members)} } # - already defined
# members = sub_tourn_members
# pair_mode = pair_mode
# for multiple groups
sub_winners = []
# add winners from previous sub-tournament
members = pre_winners + members
# members = pre_winners + self.members_by_sub
# split members by parts
# if sub-tournament has classname = "League"
if classname == "League": # or prefix == "Group" (or starts from group)
# sort teams by rating
members = sort_by_ratings(members)
pass
# split by 4 baskets
baskets_count = 4
if len(members) % baskets_count != 0: # TODO deleteme
pass
assert len(members) % baskets_count == 0, "teams in baskets cannot be equal!"
basket_len = len(members) / baskets_count
baskets = []
for basket_num in xrange(baskets_count):
basket = members[basket_len*basket_num : basket_len*(basket_num+1)]
baskets.append(basket) # TODO APPEND
# define group members Group
group_members = [[] for part in xrange(parts)]
attempts = 1000
while members and attempts:
for part in xrange(parts):
for basket in baskets:
unchecked_candidates = list(basket)
if not unchecked_candidates:
print "groups_seeded_successfully"
break
candidate = random.choice(unchecked_candidates)
checked = False
# check no same country in the group
# there can be case that only thow teams with same nation are the rest of baskets
# and check will never be True - so we constraint this case with a fixed num of attempts
check_attempts = 100
while not checked and check_attempts:
checked = True
# если не встретилась команда с той же страной -то Тру закрепляется, иначе выст.флаг Фолс
for member in group_members[part]:
if member.getCountry() == candidate.getCountry():
checked = False
# unchecked_candidates.remove(candidate)
new_candidate = random.choice(unchecked_candidates)
while candidate == new_candidate and check_attempts:
check_attempts -= 1
new_candidate = random.choice(unchecked_candidates)
candidate = new_candidate
break
# if not checked:
# if not unchecked_candidates:
# # unlucky! but no way
# print "cannot found candidate with the different country for %s" % candidate
# break
# candidate = random.choice(unchecked_candidates)
group_members[part].append(candidate)
basket.remove(candidate)
members.remove(candidate)
print "TOSS group_members is ok!\n" % group_members
self.set_group_members(group_members)
# RUN SUB_TOURNAMENTS
for part in xrange(parts):
part_num = part + 1
# if parts > 1, so its groups
# print "run UEFA part_num=%s" % part_num, "sub_tourn_name = %s" % sub_tourn_name
if classname == "League":
# members = group_members[baskets_count*part : baskets_count*(part+1)]
members = group_members[part]
# prefix = tourn_type_name + " " + sub_tourn_name + " %s" % part_num
prefix = sub_tourn_name + " %s " % part_num
else:
# prefix = tourn_type_name + " " + sub_tourn_name
prefix = sub_tourn_name + " "
if classname == "Cup" and "qualification" in prefix.lower():
pass # catching problem (debug breakpoint)
sub_tournament = tourn_class(name = self.name_id,
season = self.season,
year = self.year,
members = members,
pair_mode = pair_mode,
seeding = rounds_info,
# save_to_db = True, # by default
prefix = prefix,
type_id = self.type_id) # TODO !!!
if classname == "League":
# filter two first teams
group_results = sub_tournament.run()
first_place = group_results[0]["Team"]
second_place = group_results[1]["Team"]
# if its CL, if will be used used for EL
third_place = group_results[2]["Team"]
# collect all groups winners
sub_winners = [first_place] + sub_winners + [second_place]
# save 3rd places to seed them to Europe League play-off started soon
self.CL_EL_seeding["Group 3th places"].append(third_place)
else:
# classname == "Cup"
sub_results = sub_tournament.run()
sub_winners += sub_results
if "qualification" in prefix.lower():
net = sub_tournament.getNet()
self.CL_EL_seeding["Qualification 3"] = [pair[-1] for pair in net[3]] # -1 points to loosers
self.CL_EL_seeding["Qualification 4"] = [pair[-1] for pair in net[4]] # -1 points to loosers
pre_winners = sub_winners
return
def get_CL_to_EL_seeding(self):
"""
:return: dict {"Qualification 3" : [Team...Team], "Qualification 4" : [], "Group 3th places" : []}
"""
return self.CL_EL_seeding
def set_group_members(self, members):
"""
:param members: list of lists_of_teams, combinated by played in Group
:return:
"""
self.group_members = members
def test(self, print_matches = False, print_ratings = False):
print "\nTEST CUP CLASS\n"
print "pair_mode = %s\nseeding = %s\n" % (self.pair_mode, self.seeding)
print "initial Net:"
print self#.printTable()
if print_matches:
print "\nMatches:"
self.run(print_matches)
print "\nWinner:\n%s" % self.getWinner()
# print "\nresults:\n%s" % [(k, [team.getName() for team in self.results[k]] ) for k in self.results.keys()]
print "\nresults:"
for k in self.results.keys():
# TODO sort results ny round (maybe store them in list)
print k, [team.getName() for team in self.results[k]]
print "\nFinal Net:\n", str(self), "\n"
# ratings after league
# print "print_ratings = %s" % print_ratings
# print "self.getMember() %s" % self.getMember()
if print_ratings:
for team in self.getMember():
print team.getName(), team.getRating()
def sort_by_ratings(teams):
"""
sort list of teams by ratings and return in descending order [favorite ... outsider]
:param teams:
:return:
"""
# return teams.sort(key=lambda x: x.getUefaPos(), reverse=False)
return sorted(teams, key=lambda x: x.getUefaPos())
@util.timer
def Test(*args, **kwargs):
# test sort_by_ratings
teams_unsorted = [Team(1), Team(15), Team(20), Team(2)]
teams_sorted = sort_by_ratings(teams_unsorted)
assert (teams_sorted == [teams_unsorted[ind] for ind in (0,3,1,2)]), "test sort_by_ratings fails!"
# TEST CUP CLASS
if "ids" in kwargs:
for id in kwargs["ids"]:
tstcp = UEFA_Champions_League(name=id)
# TEST
if __name__ == "__main__":
Test(ids = UEFA_TOURNAMENTS_ID)
# print v.get_schema(UEFA_CL_TYPE_ID)