From 12255e3eef5fdd62ccc3713b4f33436ad9149ef4 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 06:06:51 -0500 Subject: [PATCH 1/8] m;starttournament now starts a debug tournament --- games.py | 3 +- leagues.py | 53 ++++++++--- the-prestige.pyproj | 4 - the_prestige.py | 214 +++++++++++++++++++++++++------------------- 4 files changed, 164 insertions(+), 110 deletions(-) diff --git a/games.py b/games.py index 8a375a4..3f38d7a 100644 --- a/games.py +++ b/games.py @@ -200,8 +200,7 @@ class team(object): class game(object): - def __init__(self, name, team1, team2, length=None): - self.name = name + def __init__(self, team1, team2, length=None): self.over = False self.teams = {"away" : team1, "home" : team2} self.inning = 1 diff --git a/leagues.py b/leagues.py index ad18681..33e9de2 100644 --- a/leagues.py +++ b/leagues.py @@ -16,18 +16,22 @@ class league(object): class division(object): def __init__(self): - self.teams = {} #key: team object, value: {wins; losses; run diff} + self.teams = {} #key: team object, value: {wins; rd (run diff)} class tournament(object): - def __init__(self, team_dic): - self.teams = {} #same format as division, wins/losses will be used for seeding later - self.bracket = {} - self.bracket_layers = 0 + def __init__(self, name, team_dic, series_length = 5, maxinnings = 9): + self.name = name + self.teams = team_dic #same format as division, wins/losses will be used for seeding later + self.bracket = None + self.results = None + self.series_length = series_length + self.game_length = maxinnings + self.active = False - def build_bracket(self, random = False, by_wins = False): - teams_list = self.teams.keys().copy() + def build_bracket(self, random_sort = False, by_wins = False): + teams_list = list(self.teams.keys()).copy() - if random: + if random_sort: def sorter(team_in_list): return random.random() @@ -35,14 +39,43 @@ class tournament(object): def sorter(team_in_list): return self.teams[team_in_list][0] #sorts by wins - if not random and not by_wins: #sort by average stars + else: #sort by average stars def sorter(team_in_list): return team_in_list.average_stars() teams_list.sort(key=sorter, reverse=True) - self.bracket_layers = int(math.ceil(math.log(len(teams_list), 2))) + bracket_layers = int(math.ceil(math.log(len(teams_list), 2))) empty_slots = int(math.pow(2, bracket_layers) - len(teams_list)) for i in range(0, empty_slots): teams_list.append(None) + + previous_bracket_layer = teams_list.copy() + for i in range(0, bracket_layers-1): + this_layer = [] + for pair in range(0, int(len(previous_bracket_layer)/2)): + if pair % 2 == 0: #if even number + this_layer.insert(0+int(pair/2), [previous_bracket_layer.pop(0), previous_bracket_layer.pop(-1)]) #every other pair goes at front of list, moving forward + else: + this_layer.insert(0-int((1+pair)/2), [previous_bracket_layer.pop(0), previous_bracket_layer.pop(-1)]) #every other pair goes at end of list, moving backward + previous_bracket_layer = this_layer + self.bracket = bracket(previous_bracket_layer, bracket_layers) + self.bracket.get_bottom_row() + +class bracket(object): + def __init__(self, bracket_list, depth): + self.this_bracket = bracket_list + self.depth = depth + self.bottom_row = [] + + def get_bottom_row(self): + self.bottom_row = [] + self.dive(self.this_bracket) + return self.bottom_row + + def dive(self, branch): + if not isinstance(branch[0], list): #if it's a pair of games + self.bottom_row.append(branch) + else: + return self.dive(branch[0]), self.dive(branch[1]) \ No newline at end of file diff --git a/the-prestige.pyproj b/the-prestige.pyproj index 351afa6..dc816c7 100644 --- a/the-prestige.pyproj +++ b/the-prestige.pyproj @@ -61,10 +61,6 @@ - - - - diff --git a/the_prestige.py b/the_prestige.py index badfa81..85c99e2 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -169,7 +169,7 @@ class StartGameCommand(Command): return if team1 is not None and team2 is not None: - game = games.game(msg.author.name, team1.finalize(), team2.finalize(), length=innings) + game = games.game(team1.finalize(), team2.finalize(), length=innings) channel = msg.channel await msg.delete() @@ -179,30 +179,6 @@ class StartGameCommand(Command): await msg.channel.send("We can't find one or both of those teams. Check your staging, chief.") return -class SetupGameCommand(Command): - name = "setupgame" - template = "m;setupgame" - description = "Begins setting up a 3-inning pickup game. Pitchers, lineups, and team names are given during the setup process by anyone able to type in that channel. Idols are easily signed up via emoji during the process. The game will start automatically after setup." - - async def execute(self, msg, command): - if len(gamesarray) > 45: - await msg.channel.send("We're running 45 games and we doubt Discord will be happy with any more. These edit requests don't come cheap.") - return - elif config()["game_freeze"]: - await msg.channel.send("Patch incoming. We're not allowing new games right now.") - return - - for game in gamesarray: - if game.name == msg.author.name: - await msg.channel.send("You've already got a game in progress! Wait a tick, boss.") - return - try: - inningmax = int(command) - except: - inningmax = 3 - game_task = asyncio.create_task(setup_game(msg.channel, msg.author, games.game(msg.author.name, games.team(), games.team(), length=inningmax))) - await game_task - class SaveTeamCommand(Command): name = "saveteam" template = """m;saveteam @@ -445,7 +421,6 @@ class AssignOwnerCommand(Command): return user.id in config()["owners"] async def execute(self, msg, command): - #try: new_owner = msg.mentions[0] team_name = command.strip().split(new_owner.mention+" ")[1] print(team_name) @@ -453,8 +428,32 @@ class AssignOwnerCommand(Command): await msg.channel.send(f"{team_name} is now owned by {new_owner.display_name}. Don't break it.") else: await msg.channel.send("We couldn't find that team. Typo?") - #except: - #await msg.channel.send("We hit a snag. Tell xvi.") + +class StartTournamentCommand(Command): + name = "starttournament" + template = "m;starttournament" + description = "We'll DM you and get your own tournament set up. Just follow our instructions and we'll be right as rain." + + async def execute(self, msg, command): + test_bracket = { + games.get_team("Milwaukee Lockpicks") : {"wins": 0, "rd": 0}, + games.get_team("Madagascar Penguins") : {"wins": 0, "rd": 0}, + games.get_team("Twin Cities Evening") : {"wins": 0, "rd": 0}, + games.get_team("Washington State Houses") : {"wins": 0, "rd": 0}, + games.get_team("Appalachian Underground") : {"wins": 0, "rd": 0}, + games.get_team("Pacific2 Rams") : {"wins": 0, "rd": 0}, + games.get_team("New Jersey Radio") : {"wins": 0, "rd": 0}, + games.get_team("Moline Jolenes") : {"wins": 0, "rd": 0}, + games.get_team("California Commissioners") : {"wins": 0, "rd": 0}, + games.get_team("Pigeon’s Reckoning") : {"wins": 0, "rd": 0}, + games.get_team("Kernow Technologists") : {"wins": 0, "rd": 0} + } + tourney = leagues.tournament("Test Tourney", test_bracket) + tourney.build_bracket(random_sort=True) + + tourney_task = asyncio.create_task(start_tournament_round(msg.channel, tourney)) + await tourney_task + commands = [ @@ -476,6 +475,7 @@ commands = [ ShowAllTeamsCommand(), SearchTeamsCommand(), StartGameCommand(), + StartTournamentCommand(), CreditCommand(), RomanCommand(), HelpCommand(), @@ -558,19 +558,6 @@ async def on_message(msg): except CommandError as ce: await msg.channel.send(str(ce)) -async def start_game(channel): - msg = await channel.send("Play ball!") - await asyncio.sleep(4) - newgame = games.debug_game() - gamesarray.append(newgame) - while not newgame.over: - state = newgame.gamestate_update_full() - if not state.startswith("Game over"): - await msg.edit(content=state) - await asyncio.sleep(3) - await channel.send(state) - gamesarray.pop() - async def setup_game(channel, owner, newgame): newgame.owner = owner @@ -697,32 +684,7 @@ Creator, type `{newgame.name} done` to finalize lineups.""") await game_task async def watch_game(channel, newgame, user = None, league = None): - blank_emoji = discord.utils.get(client.emojis, id = 790899850295509053) - empty_base = discord.utils.get(client.emojis, id = 790899850395779074) - occupied_base = discord.utils.get(client.emojis, id = 790899850320543745) - out_emoji = discord.utils.get(client.emojis, id = 791578957241778226) - in_emoji = discord.utils.get(client.emojis, id = 791578957244792832) - - - - await asyncio.sleep(1) - weathers = games.all_weathers() - newgame.weather = weathers[random.choice(list(weathers.keys()))] - state_init = { - "away_name" : newgame.teams['away'].name, - "home_name" : newgame.teams['home'].name, - "max_innings" : newgame.max_innings, - "update_pause" : 0, - "top_of_inning" : True, - "victory_lap" : False, - "weather_emoji" : newgame.weather.emoji, - "weather_text" : newgame.weather.name, - "start_delay" : 5, - "end_delay" : 10 - } - if newgame.weather.name == "Heavy Snow": - newgame.weather.counter_away = random.randint(0,len(newgame.teams['away'].lineup)-1) - newgame.weather.counter_home = random.randint(0,len(newgame.teams['home'].lineup)-1) + newgame, state_init = prepare_game(newgame) if league is not None: discrim_string = league @@ -742,15 +704,79 @@ async def watch_game(channel, newgame, user = None, league = None): await channel.send(f"{newgame.teams['away'].name} vs. {newgame.teams['home'].name}, starting at {config()['simmadome_url']+ext}") gamesarray.append((newgame, channel, user, timestamp)) - - main_controller.master_games_dic[timestamp] = (newgame, state_init, discrim_string) -async def play_from_queue(channel, game, user_mention): - await channel.send(f"{user_mention}, your game's ready.") - game_task = asyncio.create_task(watch_game(channel, game)) - await game_task +def prepare_game(newgame, league = None, weather_name = None): + if weather_name is None: + weathers = games.all_weathers() + newgame.weather = weathers[random.choice(list(weathers.keys()))] + + state_init = { + "away_name" : newgame.teams['away'].name, + "home_name" : newgame.teams['home'].name, + "max_innings" : newgame.max_innings, + "update_pause" : 0, + "top_of_inning" : True, + "victory_lap" : False, + "weather_emoji" : newgame.weather.emoji, + "weather_text" : newgame.weather.name, + "start_delay" : 5, + "end_delay" : 10 + } + + if league is None: + state_init["is_league"] = False + else: + state_init["is_league"] = True + + if newgame.weather.name == "Heavy Snow": + newgame.weather.counter_away = random.randint(0,len(newgame.teams['away'].lineup)-1) + newgame.weather.counter_home = random.randint(0,len(newgame.teams['home'].lineup)-1) + return newgame, state_init + +async def start_tournament_round(channel, tourney, seeding = None): + current_games = [] + if tourney.bracket is None: + if seeding is None: + tourney.build_bracket(random_sort=True) + + games_to_start = tourney.bracket.get_bottom_row() + + for pair in games_to_start: + if pair[0] is not None and pair[1] is not None: + this_game = games.game(pair[0].finalize(), pair[1].finalize(), length = tourney.game_length) + this_game, state_init = prepare_game(this_game) + + state_init["is_league"] = True + discrim_string = tourney.name + print(discrim_string) + + timestamp = str(time.time() * 1000.0 + random.randint(0,3000)) + current_games.append((this_game, timestamp)) + main_controller.master_games_dic[timestamp] = (this_game, state_init, discrim_string) + + ext = "?league=" + urllib.parse.quote_plus(tourney.name) + + await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") + tourney_task = asyncio.create_task(tourney_watcher(channel, tourney, current_games)) + await tourney_task + + +async def tourney_watcher(channel, tourney, games_list): + tourney.active = True + while len(games_list) > 0: + 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"] <= 9: + final_embed = game_over_embed(game) + await channel.send(f"A {tourney.name} game just ended!") + await channel.send(embed=final_embed) + gamesarray.pop(i) + break + tourney.active = False + await channel.send(f"This round of games for {tourney.name} is now complete!") + async def team_delete_confirm(channel, team, owner): team_msg = await channel.send(embed=build_team_embed(team)) @@ -933,37 +959,37 @@ async def game_watcher(): this_array = gamesarray.copy() for i in range(0,len(this_array)): game, channel, user, key = this_array[i] - if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 9: - title_string = f"{game.teams['away'].name} at {game.teams['home'].name} ended after {game.inning-1} innings" - if (game.inning - 1) > game.max_innings: #if extra innings - title_string += f" with {game.inning - (game.max_innings+1)} extra innings." - else: - title_string += "." - - winning_team = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name - winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n" - if game.victory_lap and winning_team == game.teams['home'].name: - winstring += f"{winning_team} wins with a victory lap!" - elif winning_team == game.teams['home'].name: - winstring += f"{winning_team} wins, shaming {game.teams['away'].name}!" - else: - winstring += f"{winning_team} wins!" - + if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 9: + final_embed = game_over_embed(game) if user is not None: await channel.send(f"{user.mention}'s game just ended.") else: - await channel.send("A game started from this channel just ended.") - - final_embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) - final_embed.add_field(name="Final score:", value=winstring) + await channel.send("A game started from this channel just ended.") await channel.send(embed=final_embed) gamesarray.pop(i) break except: print("something broke in game_watcher") + await asyncio.sleep(4) - await asyncio.sleep(6) +def game_over_embed(game): + title_string = f"{game.teams['away'].name} at {game.teams['home'].name} ended after {game.inning-1} innings" + if (game.inning - 1) > game.max_innings: #if extra innings + title_string += f" with {game.inning - (game.max_innings+1)} extra innings." + else: + title_string += "." + winning_team = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name + winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n" + if game.victory_lap and winning_team == game.teams['home'].name: + winstring += f"{winning_team} wins with a victory lap!" + elif winning_team == game.teams['home'].name: + winstring += f"{winning_team} wins, shaming {game.teams['away'].name}!" + else: + winstring += f"{winning_team} wins!" + embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) + embed.add_field(name="Final score:", value=winstring) + return embed client.run(config()["token"]) From d5f2ceeac9ee4fd76481cc78cbd53cf74c3fd510 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 06:09:27 -0500 Subject: [PATCH 2/8] set max innings of debug tournament round to 3 for faster testing --- leagues.py | 4 ++-- the_prestige.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/leagues.py b/leagues.py index 33e9de2..6c59a42 100644 --- a/leagues.py +++ b/leagues.py @@ -19,13 +19,13 @@ class division(object): self.teams = {} #key: team object, value: {wins; rd (run diff)} class tournament(object): - def __init__(self, name, team_dic, series_length = 5, maxinnings = 9): + def __init__(self, name, team_dic, series_length = 5, max_innings = 9): self.name = name self.teams = team_dic #same format as division, wins/losses will be used for seeding later self.bracket = None self.results = None self.series_length = series_length - self.game_length = maxinnings + self.game_length = max_innings self.active = False def build_bracket(self, random_sort = False, by_wins = False): diff --git a/the_prestige.py b/the_prestige.py index 85c99e2..3fa722f 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -448,7 +448,7 @@ class StartTournamentCommand(Command): games.get_team("Pigeon’s Reckoning") : {"wins": 0, "rd": 0}, games.get_team("Kernow Technologists") : {"wins": 0, "rd": 0} } - tourney = leagues.tournament("Test Tourney", test_bracket) + tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) tourney.build_bracket(random_sort=True) tourney_task = asyncio.create_task(start_tournament_round(msg.channel, tourney)) From 527170a81e11f44debf335c1970ed9461bbdc1ed Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 06:30:07 -0500 Subject: [PATCH 3/8] fixed tournament blocking everything else --- the_prestige.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index 3fa722f..f426b90 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -451,9 +451,7 @@ class StartTournamentCommand(Command): tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) tourney.build_bracket(random_sort=True) - tourney_task = asyncio.create_task(start_tournament_round(msg.channel, tourney)) - await tourney_task - + await start_tournament_round(msg.channel, tourney) commands = [ @@ -759,21 +757,25 @@ async def start_tournament_round(channel, tourney, seeding = None): ext = "?league=" + urllib.parse.quote_plus(tourney.name) await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") - tourney_task = asyncio.create_task(tourney_watcher(channel, tourney, current_games)) - await tourney_task + await tourney_watcher(channel, tourney, current_games)) async def tourney_watcher(channel, tourney, games_list): tourney.active = True while len(games_list) > 0: - 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"] <= 9: - final_embed = game_over_embed(game) - await channel.send(f"A {tourney.name} game just ended!") - await channel.send(embed=final_embed) - gamesarray.pop(i) - break + 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"] <= 9: + final_embed = game_over_embed(game) + await channel.send(f"A {tourney.name} game just ended!") + await channel.send(embed=final_embed) + games_list.pop(i) + break + except: + print("something went wrong in tourney_watcher") + + await asyncio.sleep(4) tourney.active = False await channel.send(f"This round of games for {tourney.name} is now complete!") From 2c14d7cf7382f0c0865c2c0b495d7a13550622ac Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 06:56:40 -0500 Subject: [PATCH 4/8] added win values to debug tourney to test win seeding --- leagues.py | 7 +++++-- the_prestige.py | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/leagues.py b/leagues.py index 6c59a42..8291c20 100644 --- a/leagues.py +++ b/leagues.py @@ -1,5 +1,6 @@ import time, asyncio, jsonpickle, random, math from games import team, game +from discord import Embed, Color import database as db @@ -37,7 +38,7 @@ class tournament(object): elif by_wins: def sorter(team_in_list): - return self.teams[team_in_list][0] #sorts by wins + return self.teams[team_in_list]["wins"] #sorts by wins else: #sort by average stars def sorter(team_in_list): @@ -50,9 +51,10 @@ class tournament(object): for i in range(0, empty_slots): teams_list.append(None) + print(teams_list) previous_bracket_layer = teams_list.copy() - for i in range(0, bracket_layers-1): + for i in range(0, bracket_layers - 1): this_layer = [] for pair in range(0, int(len(previous_bracket_layer)/2)): if pair % 2 == 0: #if even number @@ -60,6 +62,7 @@ class tournament(object): else: this_layer.insert(0-int((1+pair)/2), [previous_bracket_layer.pop(0), previous_bracket_layer.pop(-1)]) #every other pair goes at end of list, moving backward previous_bracket_layer = this_layer + print(previous_bracket_layer) self.bracket = bracket(previous_bracket_layer, bracket_layers) self.bracket.get_bottom_row() diff --git a/the_prestige.py b/the_prestige.py index f426b90..8ab698d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -436,20 +436,20 @@ class StartTournamentCommand(Command): async def execute(self, msg, command): test_bracket = { - games.get_team("Milwaukee Lockpicks") : {"wins": 0, "rd": 0}, - games.get_team("Madagascar Penguins") : {"wins": 0, "rd": 0}, - games.get_team("Twin Cities Evening") : {"wins": 0, "rd": 0}, - games.get_team("Washington State Houses") : {"wins": 0, "rd": 0}, - games.get_team("Appalachian Underground") : {"wins": 0, "rd": 0}, - games.get_team("Pacific2 Rams") : {"wins": 0, "rd": 0}, - games.get_team("New Jersey Radio") : {"wins": 0, "rd": 0}, - games.get_team("Moline Jolenes") : {"wins": 0, "rd": 0}, - games.get_team("California Commissioners") : {"wins": 0, "rd": 0}, - games.get_team("Pigeon’s Reckoning") : {"wins": 0, "rd": 0}, - games.get_team("Kernow Technologists") : {"wins": 0, "rd": 0} + games.get_team("Milwaukee Lockpicks") : {"wins": 10, "rd": 0}, + games.get_team("Madagascar Penguins") : {"wins": 2, "rd": 0}, + games.get_team("Twin Cities Evening") : {"wins": 1, "rd": 0}, + games.get_team("Washington State Houses") : {"wins": 9, "rd": 0}, + games.get_team("Appalachian Underground") : {"wins": 8, "rd": 0}, + games.get_team("Pacific2 Rams") : {"wins": 3, "rd": 0}, + games.get_team("New Jersey Radio") : {"wins": 45, "rd": 0}, + games.get_team("Moline Jolenes") : {"wins": 44, "rd": 0}, + games.get_team("California Commissioners") : {"wins": 41, "rd": 0}, + games.get_team("Pigeon’s Reckoning") : {"wins": 45, "rd": 0}, + games.get_team("Kernow Technologists") : {"wins": 42, "rd": 0} } tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) - tourney.build_bracket(random_sort=True) + tourney.build_bracket(by_wins=True) await start_tournament_round(msg.channel, tourney) @@ -757,7 +757,7 @@ async def start_tournament_round(channel, tourney, seeding = None): ext = "?league=" + urllib.parse.quote_plus(tourney.name) await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") - await tourney_watcher(channel, tourney, current_games)) + await tourney_watcher(channel, tourney, current_games) async def tourney_watcher(channel, tourney, games_list): From 5e00d0d27137fdc2bdd5d5974a7816548a68055d Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 16:48:51 -0500 Subject: [PATCH 5/8] moved the team fuzzy searching to a get_team_fuzzy_search for ease of coding --- main_controller.py | 2 +- the_prestige.py | 60 ++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/main_controller.py b/main_controller.py index d6ae7ff..65892a4 100644 --- a/main_controller.py +++ b/main_controller.py @@ -1,4 +1,4 @@ -import asyncio, time, datetime, games, json, threading, jinja2 +import asyncio, time, datetime, games, json, threading, jinja2, leagues from flask import Flask, url_for, Response, render_template, request, jsonify from flask_socketio import SocketIO, emit diff --git a/the_prestige.py b/the_prestige.py index 8bd079a..a3bb61d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1,4 +1,4 @@ -import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib +import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues import database as db import onomancer as ono from flask import Flask @@ -127,32 +127,19 @@ class StartGameCommand(Command): innings = None try: team_name1 = command.split("\n")[1].strip() - team1 = games.get_team(team_name1) - if team1 is None: - teams = games.search_team(team_name1.lower()) - if len(teams) == 1: - team1 = teams[0] + team1 = get_team_fuzzy_search(team_name1) + team_name2 = command.split("\n")[2].strip() - team2 = games.get_team(team_name2) - if team2 is None: - teams = games.search_team(team_name2.lower()) - if len(teams) == 1: - team2 = teams[0] + team2 = get_team_fuzzy_search(team_name2) + innings = int(command.split("\n")[3]) except IndexError: try: team_name1 = command.split("\n")[1].strip() - team1 = games.get_team(team_name1) - if team1 is None: - teams = games.search_team(team_name1.lower()) - if len(teams) == 1: - team1 = teams[0] + team1 = get_team_fuzzy_search(team_name1) + team_name2 = command.split("\n")[2].strip() - team2 = games.get_team(team_name2) - if team2 is None: - teams = games.search_team(team_name2.lower()) - if len(teams) == 1: - team2 = teams[0] + team2 = get_team_fuzzy_search(team_name2) except IndexError: await msg.channel.send("We need at least three lines: startgame, away team, and home team are required. Optionally, the number of innings can go at the end, if you want a change of pace.") return @@ -164,8 +151,8 @@ class StartGameCommand(Command): await msg.channel.send("Anything less than 2 innings isn't even an outing. Try again.") return - elif innings is not None and innings > 30 and msg.author.id not in config()["owners"]: - await msg.channel.send("Y'all can't behave, so we've limited games to 30 innings. Ask xvi to start it with more if you really want to.") + elif innings is not None and innings > 200 and msg.author.id not in config()["owners"]: + await msg.channel.send("Y'all can behave, so we've upped the limit on game length to 200 innings.") return if team1 is not None and team2 is not None: @@ -230,15 +217,11 @@ class ShowTeamCommand(Command): async def execute(self, msg, command): team_name = command.strip() - team = games.get_team(team_name) + team = get_team_fuzzy_search(team_name) if team is not None: await msg.channel.send(embed=build_team_embed(team)) - else: - teams = games.search_team(team_name.lower()) - if len(teams) == 1: - await msg.channel.send(embed=build_team_embed(teams[0])) - else: - await msg.channel.send("Can't find that team, boss. Typo?") + return + await msg.channel.send("Can't find that team, boss. Typo?") class ShowAllTeamsCommand(Command): name = "showallteams" @@ -275,7 +258,7 @@ class SwapPlayerCommand(Command): template = """m;swapsection [team name] [player name]""" - description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership." + description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -304,7 +287,7 @@ class MovePlayerCommand(Command): [team name] [player name] [new lineup/rotation position number] (indexed with 1 being the top)""" - description = "Moves a player in your lineup or rotation. Requires team ownership." + description = "Moves a player in your lineup or rotation. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -334,7 +317,7 @@ class AddPlayerCommand(Command): template = """m;addplayer pitcher (or m;addplayer batter) [team name] [player name]""" - description = "Recruits a new player to your team, as either a pitcher or a batter. Requires team ownership." + description = "Recruits a new player to your team, as either a pitcher or a batter. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -367,6 +350,7 @@ class RemovePlayerCommand(Command): template = """m;removeplayer [team name] [player name]""" + description = "Removes a player from your team. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -412,7 +396,7 @@ class HelpCommand(Command): class DeleteTeamCommand(Command): name = "deleteteam" template = "m;deleteteam [name]" - description = "Allows you to delete the team with the provided name if you are the owner of it, Gives a confirmation first to prevent accidental deletions. If it isn't letting you delete your team, you probably created it before teams having owners was a thing, contact xvi and xie can assign you as the owner." + description = "Allows you to delete the team with the provided name. Requires team ownership. If you are the owner and the bot is telling you it's not yours, contact xvi and xie can assist." async def execute(self, msg, command): team_name = command.strip() @@ -1005,5 +989,13 @@ def game_over_embed(game): embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) embed.add_field(name="Final score:", value=winstring) return embed + +def get_team_fuzzy_search(team_name): + team = games.get_team(team_name) + if team is None: + teams = games.search_team(team_name.lower()) + if len(teams) == 1: + team = teams[0] + return team client.run(config()["token"]) From 1838e9198aa994a3f14337c9b672126bbfaa494e Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 16:57:41 -0500 Subject: [PATCH 6/8] added weather to discord embed at end of game --- the_prestige.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index a3bb61d..46adffb 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -743,6 +743,7 @@ async def start_tournament_round(channel, tourney, seeding = None): this_game, state_init = prepare_game(this_game) state_init["is_league"] = True + state_init["title"] = f"{tourney.name}: Round of {len(games_to_start)*2}" discrim_string = tourney.name print(discrim_string) @@ -973,9 +974,10 @@ async def game_watcher(): def game_over_embed(game): title_string = f"{game.teams['away'].name} at {game.teams['home'].name} ended after {game.inning-1} innings" if (game.inning - 1) > game.max_innings: #if extra innings - title_string += f" with {game.inning - (game.max_innings+1)} extra innings." + title_string += f" with {game.inning - (game.max_innings+1)} extra innings.\n" else: - title_string += "." + title_string += ".\n" + title_string += game.weather.emoji + game.weather.name winning_team = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n" From 63db3ba1bece20d68c5622d6eff48809399ffad8 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 17:16:05 -0500 Subject: [PATCH 7/8] added handling for teams without slogans --- the_prestige.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/the_prestige.py b/the_prestige.py index 46adffb..817e95f 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -924,7 +924,10 @@ async def team_pages(msg, all_teams, search_term=None): embed.set_footer(text = f"Page {page+1} of {page_max}") for i in range(0,25): try: - embed.add_field(name=all_teams[i+25*page].name, value=all_teams[i+25*page].slogan) + if all_teams[i+25*page].slogan.strip() != "": + embed.add_field(name=all_teams[i+25*page].name, value=all_teams[i+25*page].slogan) + else: + embed.add_field(name=all_teams[i+25*page].name, value="404: Slogan not found") except: break pages.append(embed) From ff582d870998fc881b1dae3985aad324ec9b5056 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 23:18:45 -0500 Subject: [PATCH 8/8] finished implementing tournaments, slowed game ticks to 8 seconds --- games.py | 5 +- leagues.py | 55 +++++++++++-- main_controller.py | 2 +- the_prestige.py | 196 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 203 insertions(+), 55 deletions(-) diff --git a/games.py b/games.py index 3f38d7a..26ad1b3 100644 --- a/games.py +++ b/games.py @@ -185,12 +185,11 @@ class team(object): for this_player in self.rotation: for stat in this_player.game_stats.keys(): this_player.game_stats[stat] = 0 - return True + return self def finalize(self): if self.is_ready(): - if self.pitcher is None: - self.set_pitcher() + self.set_pitcher() while len(self.lineup) <= 4: self.lineup.append(random.choice(self.lineup)) return self diff --git a/leagues.py b/leagues.py index 8291c20..0551d91 100644 --- a/leagues.py +++ b/leagues.py @@ -20,14 +20,25 @@ class division(object): self.teams = {} #key: team object, value: {wins; rd (run diff)} class tournament(object): - def __init__(self, name, team_dic, series_length = 5, max_innings = 9): + def __init__(self, name, team_dic, series_length = 5, finals_series_length = 7, max_innings = 9, id = None, secs_between_games = 300, secs_between_rounds = 600): self.name = name self.teams = team_dic #same format as division, wins/losses will be used for seeding later self.bracket = None self.results = None self.series_length = series_length + self.finals_length = finals_series_length self.game_length = max_innings self.active = False + self.delay = secs_between_games + self.round_delay = secs_between_rounds + self.finals = False + self.id = id + + if id is None: + self.id = random.randint(1111,9999) + else: + self.id = id + def build_bracket(self, random_sort = False, by_wins = False): teams_list = list(self.teams.keys()).copy() @@ -45,13 +56,13 @@ class tournament(object): return team_in_list.average_stars() teams_list.sort(key=sorter, reverse=True) + bracket_layers = int(math.ceil(math.log(len(teams_list), 2))) empty_slots = int(math.pow(2, bracket_layers) - len(teams_list)) for i in range(0, empty_slots): teams_list.append(None) - print(teams_list) previous_bracket_layer = teams_list.copy() for i in range(0, bracket_layers - 1): @@ -60,19 +71,27 @@ class tournament(object): if pair % 2 == 0: #if even number this_layer.insert(0+int(pair/2), [previous_bracket_layer.pop(0), previous_bracket_layer.pop(-1)]) #every other pair goes at front of list, moving forward else: - this_layer.insert(0-int((1+pair)/2), [previous_bracket_layer.pop(0), previous_bracket_layer.pop(-1)]) #every other pair goes at end of list, moving backward + this_layer.insert(0-int((1+pair)/2), [previous_bracket_layer.pop(int(len(previous_bracket_layer)/2)-1), previous_bracket_layer.pop(int(len(previous_bracket_layer)/2))]) #every other pair goes at end of list, moving backward previous_bracket_layer = this_layer - print(previous_bracket_layer) self.bracket = bracket(previous_bracket_layer, bracket_layers) - self.bracket.get_bottom_row() + + def round_check(self): + if self.bracket.depth == 1: + self.finals = True + return True + else: + return False class bracket(object): + this_bracket = [] + def __init__(self, bracket_list, depth): self.this_bracket = bracket_list self.depth = depth self.bottom_row = [] def get_bottom_row(self): + self.depth = 1 self.bottom_row = [] self.dive(self.this_bracket) return self.bottom_row @@ -81,4 +100,28 @@ class bracket(object): if not isinstance(branch[0], list): #if it's a pair of games self.bottom_row.append(branch) else: - return self.dive(branch[0]), self.dive(branch[1]) \ No newline at end of file + self.depth += 1 + return self.dive(branch[0]), self.dive(branch[1]) + + #def set_winners(self, branch, winners_list): + #new_bracket = + + def set_winners_dive(self, winners_list, index = 0, branch = None, parent = None): + if branch is None: + branch = self.this_bracket.copy() + if not isinstance(branch[0], list): #if it's a pair of games + if branch[0].name in winners_list or branch[1] is None: + winner = branch[0] + if parent is not None: + parent[index] = winner + elif branch[1].name in winners_list: + winner = branch[1] + if parent is not None: + parent[index] = winner + else: + self.set_winners_dive(winners_list, index = 0, branch = branch[0], parent = branch) + self.set_winners_dive(winners_list, index = 1, branch = branch[1], parent = branch) + + if parent is None: + self.this_bracket = branch + return branch \ No newline at end of file diff --git a/main_controller.py b/main_controller.py index 65892a4..2347120 100644 --- a/main_controller.py +++ b/main_controller.py @@ -153,4 +153,4 @@ def update_loop(): }) socketio.emit("states_update", data_to_send) - time.sleep(6) + time.sleep(8) diff --git a/the_prestige.py b/the_prestige.py index 817e95f..a986fe4 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -427,25 +427,38 @@ class AssignOwnerCommand(Command): class StartTournamentCommand(Command): name = "starttournament" - template = "m;starttournament" - description = "We'll DM you and get your own tournament set up. Just follow our instructions and we'll be right as rain." + template = """m;starttournament + [tournament name] + [list of teams, each on a new line]""" + description = "Starts a tournament with the teams given. Byes will be given to teams to allow for numbers other than powers of two. The current tournament format is:\nBest of 5 until the finals, which are Best of 7" async def execute(self, msg, command): - test_bracket = { - games.get_team("Milwaukee Lockpicks") : {"wins": 10, "rd": 0}, - games.get_team("Madagascar Penguins") : {"wins": 2, "rd": 0}, - games.get_team("Twin Cities Evening") : {"wins": 1, "rd": 0}, - games.get_team("Washington State Houses") : {"wins": 9, "rd": 0}, - games.get_team("Appalachian Underground") : {"wins": 8, "rd": 0}, - games.get_team("Pacific2 Rams") : {"wins": 3, "rd": 0}, - games.get_team("New Jersey Radio") : {"wins": 45, "rd": 0}, - games.get_team("Moline Jolenes") : {"wins": 44, "rd": 0}, - games.get_team("California Commissioners") : {"wins": 41, "rd": 0}, - games.get_team("Pigeon’s Reckoning") : {"wins": 45, "rd": 0}, - games.get_team("Kernow Technologists") : {"wins": 42, "rd": 0} - } - tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) - tourney.build_bracket(by_wins=True) + to_parse = command.split("\n")[0] + if "--rounddelay " in to_parse: + try: + round_delay = int(to_parse.split("--rounddelay ")[1].split(" ")[0]) + except ValueError: + await msg.channel.send("The delay between rounds should be a whole number.") + return + if round_delay < 1 or round_delay > 120: + await msg.channel.send("The delay between rounds has to be between 1 and 120 minutes.") + else: + round_delay = 10 + + tourney_name = command.split("\n")[1] + list_of_team_names = command.split("\n")[2:] + team_dic = {} + for name in list_of_team_names: + team = get_team_fuzzy_search(name.strip()) + if team == None: + await msg.channel.send(f"We couldn't find {name}. Try again?") + return + team_dic[team] = {"wins": 0} + + id = random.randint(1111,9999) + + tourney = leagues.tournament(tourney_name, team_dic, id=id, secs_between_rounds = round_delay * 60) + tourney.build_bracket(random_sort = True) await start_tournament_round(msg.channel, tourney) @@ -477,7 +490,7 @@ commands = [ client = discord.Client() gamesarray = [] -gamesqueue = [] +active_tournaments = [] setupmessages = {} thread1 = threading.Thread(target=main_controller.update_loop) @@ -684,7 +697,10 @@ async def watch_game(channel, newgame, user = None, league = None): discrim_string = league state_init["is_league"] = True elif user is not None: - discrim_string = f"Started by {user.name}" + if isinstance(user, str): + discrim_string = f"Started by {user}" + else: + discrim_string = f"Started by {user.name}" state_init["is_league"] = False else: discrim_string = "Unclaimed game." @@ -739,42 +755,113 @@ async def start_tournament_round(channel, tourney, seeding = None): for pair in games_to_start: if pair[0] is not None and pair[1] is not None: - this_game = games.game(pair[0].finalize(), pair[1].finalize(), length = tourney.game_length) + this_game = games.game(pair[0].prepare_for_save().finalize(), pair[1].prepare_for_save().finalize(), length = tourney.game_length) this_game, state_init = prepare_game(this_game) state_init["is_league"] = True - state_init["title"] = f"{tourney.name}: Round of {len(games_to_start)*2}" + state_init["title"] = f"0 - 0" discrim_string = tourney.name - print(discrim_string) timestamp = str(time.time() * 1000.0 + random.randint(0,3000)) current_games.append((this_game, timestamp)) main_controller.master_games_dic[timestamp] = (this_game, state_init, discrim_string) ext = "?league=" + urllib.parse.quote_plus(tourney.name) - - await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") - await tourney_watcher(channel, tourney, current_games) - -async def tourney_watcher(channel, tourney, games_list): + if tourney.round_check(): #if finals + await channel.send(f"The {tourney.name} finals are starting now, at {config()['simmadome_url']+ext}") + finals = True + else: + await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") + finals = False + await tourney_round_watcher(channel, tourney, current_games, config()['simmadome_url']+ext, finals) + +async def continue_tournament_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 + + state_init["title"] = f"{wins_in_series[oldgame.teams['away'].name]} - {wins_in_series[oldgame.teams['home'].name]}" + + discrim_string = tourney.name + + timestamp = str(time.time() * 1000.0 + random.randint(0,3000)) + games_list.append((this_game, timestamp)) + main_controller.master_games_dic[timestamp] = (this_game, state_init, discrim_string) + + return games_list + +async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals = False): tourney.active = True - 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"] <= 9: - final_embed = game_over_embed(game) - await channel.send(f"A {tourney.name} game just ended!") - await channel.send(embed=final_embed) - games_list.pop(i) - break - except: - print("something went wrong in tourney_watcher") + active_tournaments.append(tourney) + wins_in_series = {} + winner_list = [] + while tourney.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"] <= 9: + if game.teams['home'].name not in wins_in_series.keys(): + wins_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 + + winner_name = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name + + if winner_name in wins_in_series.keys(): + wins_in_series[winner_name] += 1 + else: + wins_in_series[winner_name] = 1 + + final_embed = game_over_embed(game) + await channel.send(f"A {tourney.name} game just ended!") + await channel.send(embed=final_embed) + if wins_in_series[winner_name] >= int((tourney.series_length+1)/2) and not finals: + winner_list.append(winner_name) + elif wins_in_series[winner_name] >= int((tourney.finals_length+1)/2): + winner_list.append(winner_name) + else: + queued_games.append(game) + + games_list.pop(i) + break + except: + print("something went wrong in tourney_watcher") + await asyncio.sleep(4) + + + if len(queued_games) > 0: + await channel.send(f"The next batch of games for {tourney.name} will start in {int(tourney.delay/60)} minutes.") + await asyncio.sleep(tourney.delay) + await channel.send(f"{len(queued_games)} games for {tourney.name}, starting at {filter_url}") + games_list = await continue_tournament_series(tourney, queued_games, games_list, wins_in_series) + else: + tourney.active = False + + if finals: #if this last round was finals + embed = discord.Embed(color = discord.Color.dark_purple(), title = f"{winner_list[0]} win the {tourney.name} finals!") + await channel.send(embed=embed) + active_tournaments.pop(active_tournaments.index(tourney)) + return + + tourney.bracket.set_winners_dive(winner_list) + + winners_string = "" + for game in tourney.bracket.get_bottom_row(): + winners_string += f"{game[0].name}\n{game[1].name}\n" + await channel.send(f""" +This round of games for {tourney.name} is now complete! The next round will be starting in {int(tourney.round_delay/60)} minutes. +Advancing teams: +{winners_string}""") + await asyncio.sleep(tourney.round_delay) + await start_tournament_round(channel, tourney) - await asyncio.sleep(4) - tourney.active = False - await channel.send(f"This round of games for {tourney.name} is now complete!") async def team_delete_confirm(channel, team, owner): @@ -843,8 +930,8 @@ def team_from_collection(newteam_json): raise CommandError("We've given you 100 characters for the slogan. Discord puts limits on us and thus, we put limits on you. C'est la vie.") if len(newteam_json["lineup"]) > 20: raise CommandError("20 players in the lineup, maximum. We're being really generous here.") - if not len(newteam_json["rotation"]) == 1: - raise CommandError("One and only one pitcher per team, thanks.") + if not len(newteam_json["rotation"]) > 8: + raise CommandError("8 pitchers on the rotation, max. That's a *lot* of pitchers.") for player in newteam_json["lineup"] + newteam_json["rotation"]: if len(player["name"]) > 70: raise CommandError(f"{player['name']} is too long, chief. 70 or less.") @@ -1003,4 +1090,23 @@ def get_team_fuzzy_search(team_name): team = teams[0] return team -client.run(config()["token"]) + +#test_bracket = { +# "Milwaukee Lockpicks" : {"wins": 4, "rd": 0}, +# "Madagascar Penguins" : {"wins": 2, "rd": 0}, +# "Twin Cities Evening" : {"wins": 1, "rd": 0}, +# "Washington State Houses" : {"wins": 9, "rd": 0}, +# "Appalachian Underground" : {"wins": 8, "rd": 0}, +# "Pacific2 Rams" : {"wins": 3, "rd": 0}, +# "New Jersey Radio" : {"wins": 11, "rd": 0}, +# "Moline Jolenes" : {"wins": 6, "rd": 0}, +# "California Commissioners" : {"wins": 10, "rd": 0}, +# "Pigeon’s Reckoning" : {"wins": 7, "rd": 0}, +# "Kernow Technologists" : {"wins": 5, "rd": 0} +# } +#tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) +#tourney.build_bracket(by_wins=True) +#tourney.bracket.set_winners_dive(['Twin Cities Evening','Madagascar Penguins', 'Pacific2 Rams']) +#print(tourney.bracket.this_bracket) + +client.run(config()["token"]) \ No newline at end of file