diff --git a/leagues.py b/leagues.py index 7d3eedc..d194bed 100644 --- a/leagues.py +++ b/leagues.py @@ -1,4 +1,5 @@ import time, asyncio, jsonpickle, random, math +from itertools import chain from games import team, game from discord import Embed, Color import database as db @@ -7,13 +8,165 @@ import database as db -class league(object): - def __init__(self, name, subleagues_dic): - self.subleagues = {} #key: name, value: [divisions] - self.max_days +class league_structure(object): + def __init__(self, name, league_dic, division_games = 1, inter_division_games = 1, inter_league_games = 1, games_per_hour = 2): + self.league = league_dic #key: subleague, value: {division, teams} + self.constraints = { + "division_games" : division_games, + "inter_div_games" : inter_division_games, + "inter_league_games" : inter_league_games + } + self.season_length = 0 self.day = 1 self.name = name - self.subleagues = subleagues_dic + self.schedule = {} + self.series_length = 3 #can be changed + self.game_length = None + self.active = False + self.games_per_hour = games_per_hour + + def last_series_check(self): + return day + 1 in self.schedule.keys() + + def find_team(self, team_name): + for subleague in iter(self.league.keys()): + for division in iter(self.league[subleague].keys()): + if team_name in self.league[subleague][division]: + return (subleague, division) + + def teams_in_league(self): + teams = [] + for division in self.league.values(): + for teams_list in division.values(): + teams += teams_list + return teams + + def teams_in_subleague(self, subleague_name): + teams = [] + if subleague_name in self.league.keys(): + for division_list in self.league[subleague_name].values(): + teams += division_list + return teams + else: + print("League not found.") + return None + + def teams_in_division(self, subleague_name, division_name): + if subleague_name in self.league.keys() and division_name in self.league[subleague_name].keys(): + return self.league[subleague_name][division_name] + else: + print("Division in that league not found.") + return None + + def make_matchups(self): + matchups = [] + batch_subleagues = [] #each sub-array is all teams in each subleague + subleague_max = 1 + for subleague in self.league.keys(): + teams = self.teams_in_subleague(subleague) + if subleague_max < len(teams): + subleague_max = len(teams) + batch_subleagues.append(teams) + + for subleague in batch_subleagues: + while len(subleague) < subleague_max: + subleague.append("OFF") + + for i in range(0, self.constraints["inter_league_games"]): #generates inter-league matchups + unmatched_indices = [i for i in range(0, len(batch_subleagues))] + for subleague_index in range(0, len(batch_subleagues)): + if subleague_index in unmatched_indices: + unmatched_indices.pop(unmatched_indices.index(subleague_index)) + match_with_index = random.choice(unmatched_indices) + unmatched_indices.pop(unmatched_indices.index(match_with_index)) + league_a = batch_subleagues[subleague_index].copy() + league_b = batch_subleagues[match_with_index].copy() + random.shuffle(league_a) + random.shuffle(league_b) + a_home = True + for team_a, team_b in zip(league_a, league_b): + if a_home: + matchups.append([team_b, team_a]) + else: + matchups.append([team_a, team_b]) + a_home != a_home + + for i in range(0, self.constraints["inter_div_games"]): #inter-division matchups + for subleague in self.league.keys(): + division_max = 1 + divisions = [] + for div in self.league[subleague].keys(): + if division_max < len(self.league[subleague][div]): + divison_max = len(self.league[subleague][div]) + divisions.append(self.league[subleague][div]) + + last_div = None + if len(divisions) % 2 != 0: + if division_max % 2 != 0: + divisions.append(["OFF" for i in range(0, division_max)]) + else: + last_div = divisions.pop + + divs_a = list(chain(divisions[int(len(divisions)/2):]))[0] + if last_div is not None: + divs_a.extend(last_div[int(len(last_div)/2):]) + random.shuffle(divs_a) + + divs_b = list(chain(divisions[:int(len(divisions)/2)]))[0] + if last_div is not None: + divs_a.extend(last_div[:int(len(last_div)/2)]) + random.shuffle(divs_b) + + a_home = True + for team_a, team_b in zip(divs_a, divs_b): + if a_home: + matchups.append([team_b, team_a]) + else: + matchups.append([team_a, team_b]) + a_home != a_home + + self.season_length = self.constraints["division_games"]*(division_max) + self.constraints["inter_div_games"] + self.constraints["inter_league_games"] + + for subleague in self.league.keys(): + for division in self.league[subleague].values(): #generate round-robin matchups + if len(division) % 2 != 0: + division.append("OFF") + + for i in range(0, len(division)-1): + teams_a = division[int(len(division)/2):] + teams_b = division[:int(len(division)/2)] + teams_b.reverse() + + for team_a, team_b in zip(teams_a, teams_b): + for j in range(0, self.constraints["division_games"]): + if i % 2 == 0: + matchups.append([team_b, team_a]) + else: + matchups.append([team_a, team_b]) + + division.insert(1, division.pop()) + return matchups + + def generate_schedule(self): + matchups = self.make_matchups() + random.shuffle(matchups) + for game in matchups: + scheduled = False + day = 1 + while not scheduled: + found = False + if day in self.schedule.keys(): + for game_on_day in self.schedule[day]: + for team in game: + if team in game_on_day: + found = True + if not found: + self.schedule[day].append(game) + scheduled = True + else: + self.schedule[day] = [game] + scheduled = True + day += 1 class division(object): def __init__(self): @@ -40,7 +193,7 @@ class tournament(object): self.id = id - def build_bracket(self, random_sort = False, by_wins = False): + def build_bracket(self, random_sort = False, by_wins = False, manual = False): teams_list = list(self.teams.keys()).copy() if random_sort: @@ -54,8 +207,9 @@ class tournament(object): else: #sort by average stars def sorter(team_in_list): return team_in_list.average_stars() - - teams_list.sort(key=sorter, reverse=True) + + if not manual: + teams_list.sort(key=sorter, reverse=True) bracket_layers = int(math.ceil(math.log(len(teams_list), 2))) diff --git a/the_prestige.py b/the_prestige.py index 9df36cf..c4c8804 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1,7 +1,6 @@ -import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues +import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues, datetime import database as db import onomancer as ono -import random from the_draft import Draft, DRAFT_ROUNDS from flask import Flask from uuid import uuid4 @@ -757,6 +756,7 @@ commands = [ client = discord.Client() gamesarray = [] active_tournaments = [] +active_leagues = [] setupmessages = {} thread1 = threading.Thread(target=main_controller.update_loop) @@ -1382,4 +1382,161 @@ def get_team_fuzzy_search(team_name): team = teams[0] return team +async def start_league_day(channel, league): + current_games = [] + if league.schedule is {}: + league.generate_schedule() + + games_to_start = league.schedule[math.ceil(league.day/league.series_length)] + if league.game_length is None: + game_length = games.config()["default_length"] + else: + game_length = league.game_length + + for pair in games_to_start: + if pair[0] is not None and pair[1] is not None: + this_game = games.game(pair[0].prepare_for_save().finalize(), pair[1].prepare_for_save().finalize(), length = game_length) + this_game, state_init = prepare_game(this_game) + + state_init["is_league"] = True + series_string = f"Series score:" + state_init["title"] = f"{series_string} 0 - 0" + discrim_string = league.name + + id = str(uuid4()) + current_games.append((this_game, id)) + main_controller.master_games_dic[id] = (this_game, state_init, discrim_string) + + ext = "?league=" + urllib.parse.quote_plus(league.name) + + if league.last_series_check(): #if finals + await channel.send(f"The final series of the {league.name} is starting now, at {config()['simmadome_url']+ext}") + last = True + + else: + await channel.send(f"The next series of the {league.name} is starting now, at {config()['simmadome_url']+ext}") + last = False + + await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, last) + + +async def league_day_watcher(channel, league, games_list, filter_url, last = False): + league.active = True + active_leagues.append(league) + wins_in_series = {} + losses_in_series = {} + + while league.active: + queued_games = [] + while len(games_list) > 0: + try: + for i in range(0, len(games_list)): + game, key = games_list[i] + if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8: + if game.teams['home'].name not in wins_in_series.keys(): + wins_in_series[game.teams["home"].name] = 0 + if game.teams['home'].name not in losses_in_series.keys(): + losses_in_series[game.teams["home"].name] = 0 + if game.teams['away'].name not in wins_in_series.keys(): + wins_in_series[game.teams["away"].name] = 0 + if game.teams['away'].name not in losses_in_series.keys(): + losses_in_series[game.teams["away"].name] = 0 + + winner_name = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name + loser_name = game.teams['away'].name if game.teams['home'].score > game.teams['away'].score else game.teams['home'].name + + wins_in_series[winner_name] += 1 + losses_in_series[loser_name] += 1 + + final_embed = game_over_embed(game) + await channel.send(f"A {league.name} game just ended!") + await channel.send(embed=final_embed) + if wins_in_series[winner_name] + losses_in_series[winner_name] < league.series_length: + queued_games.append(game) + games_list.pop(i) + break + except: + print("something went wrong in league_day_watcher") + await asyncio.sleep(4) + + + if len(queued_games) > 0: + + now = datetime.datetime.now() + + validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)] + for i in range(0, len(validminutes)): + if now.minute > validminutes[i]: + if i < len(validminutes)-1: + delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute)) + else: + delta = datetime.timedelta(minutes= (60 - now.minute)) + + next_start = (now + delta).replace(seconds=0, microsecond=0) + wait_seconds = (next_start - now).seconds + + + await channel.send(f"The next batch of games for the {league.name} will start in {int(wait_seconds/60)} minutes.") + await asyncio.sleep(wait_seconds) + await channel.send(f"A {league.name} series is continuing now at {filter_url}") + games_list = await continue_league_series(league, queued_games, games_list, wins_in_series) + else: + league.active = False + + if last: #if this series was the last + #needs some kind of notification that it's over here + active_leagues.pop(active_leagues.index(league)) + return + + now = datetime.datetime.now() + + validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)] + for i in range(0, len(validminutes)): + if now.minute > validminutes[i]: + if i < len(validminutes)-1: + delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute)) + else: + delta = datetime.timedelta(minutes= (60 - now.minute)) + + next_start = (now + delta).replace(seconds=0, microsecond=0) + wait_seconds = (next_start - now).seconds + + await channel.send(f"""This {league.name} series is now complete! The next series will be starting in {int(wait_seconds/60)} minutes.""") + await asyncio.sleep(wait_seconds) + league.day += 1 + await start_league_day(channel, league) + +async def continue_league_series(tourney, queue, games_list, wins_in_series): + for oldgame in queue: + away_team = games.get_team(oldgame.teams["away"].name) + home_team = games.get_team(oldgame.teams["home"].name) + this_game = games.game(away_team.finalize(), home_team.finalize(), length = tourney.game_length) + this_game, state_init = prepare_game(this_game) + + state_init["is_league"] = True + series_string = f"Series score:" + state_init["title"] = f"{series_string} {wins_in_series[away_team.name]} - {wins_in_series[home_team.name]}" + discrim_string = league.name + + id = str(uuid4()) + games_list.append((this_game, id)) + main_controller.master_games_dic[id] = (this_game, state_init, discrim_string) + + return games_list + + +league = leagues.league_structure("test", { + "nL" : { + "nL west" : ["a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"], + "nL east" : ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"] + }, + "aL" : { + "aL west" : ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + "aL east" : ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"] + } +}, division_games=6, inter_division_games=3, inter_league_games=3) +league.generate_schedule() +#print(league.schedule[2]) + + client.run(config()["token"])