Merge remote-tracking branch 'upstream/indev' into leagues

This commit is contained in:
Elijah Steres 2021-01-16 01:14:15 -05:00
commit b713de220e
12 changed files with 1526 additions and 575 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
venv/
matteo_env/
__pycache__/
simmadome/node_modules
data/
.git/

4
.gitignore vendored
View File

@ -344,6 +344,7 @@ config.json
games_config.json games_config.json
weather_config.json weather_config.json
ids ids
data/
# database # database
matteo.db matteo.db
@ -351,5 +352,8 @@ matteo.db-wal
matteo.db-shm matteo.db-shm
/data/leagues/* /data/leagues/*
/matteo_env/Lib/site-packages/flask_socketio/__init__.py /matteo_env/Lib/site-packages/flask_socketio/__init__.py
Pipfile
env env
/data/leagues
/simmadome/build

View File

@ -1,9 +1,20 @@
FROM python:3.8 # - Build stage 1: frontend (simmadome/ directory)
EXPOSE 5000 FROM node:alpine AS frontend
WORKDIR /app WORKDIR /app
COPY . ./ COPY simmadome/package.json simmadome/package-lock.json ./
RUN npm install
COPY simmadome/ ./
RUN npm run build
# - Build stage 2: backend (Python)
FROM python:3.8
EXPOSE 5000
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY . ./
COPY --from=frontend /app/build/ simmadome/build/
CMD ["python", "the_prestige.py"] CMD ["python", "the_prestige.py"]

1111
LICENSE.md

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
blaseball, blaseball, is back! in an unofficial capacity. this project is completely unaffiliated with the game band. blaseball, blaseball, is back! in an unofficial capacity. this project is completely unaffiliated with the game band.
we have custom players (generated by onomancer), custom teams, custom leagues (that last one is coming soon™), all set up in discord and watchable at https://simsim.sibr.dev! we have custom players (generated by onomancer), custom teams, custom leagues, all set up in discord and watchable at https://simsim.sibr.dev!
if you would like to add matteo to your server to be able to set up teams and games, you can do so with this link: https://discord.com/api/oauth2/authorize?client_id=789956166796574740&permissions=388160&scope=bot if you would like to add matteo to your server to be able to set up teams and games, you can do so with this link: https://discord.com/api/oauth2/authorize?client_id=789956166796574740&permissions=388160&scope=bot
@ -12,7 +12,6 @@ accepting pull requests, check the issues for to-dos.
## commands: (everything here is case sensitive, and can be prefixed with either m; or m!) ## commands: (everything here is case sensitive, and can be prefixed with either m; or m!)
### team commands: ### team commands:
#### creation and deletion: #### creation and deletion:
- m;saveteam - m;saveteam
- saves a team to the database allowing it to be used for games. use this command at the top of a list with entries separated by new lines: - saves a team to the database allowing it to be used for games. use this command at the top of a list with entries separated by new lines:
@ -27,7 +26,6 @@ accepting pull requests, check the issues for to-dos.
- allows you to delete the team with the provided name. you'll get an embed with a confirmation to prevent accidental deletions. hit the 👍 and your team will be deleted. - allows you to delete the team with the provided name. you'll get an embed with a confirmation to prevent accidental deletions. hit the 👍 and your team will be deleted.
- m;import - m;import
- imports an onomancer collection as a new team. you can use the new onomancer simsim setting to ensure compatibility. similarly to saveteam, you'll get a team embed with a prompt to confirm, hit the 👍 and your team will be saved! - imports an onomancer collection as a new team. you can use the new onomancer simsim setting to ensure compatibility. similarly to saveteam, you'll get a team embed with a prompt to confirm, hit the 👍 and your team will be saved!
#### editing (all of these commands require ownership and exact spelling of the team name): #### editing (all of these commands require ownership and exact spelling of the team name):
- m;addplayer batter/pitcher [team name] \[player name] - m;addplayer batter/pitcher [team name] \[player name]
- adds a new player to the end of your team, either in the lineup or the rotation depending on which version you use. use addplayer batter or addplayer pitcher at the top of a list with entries separated by new lines: - adds a new player to the end of your team, either in the lineup or the rotation depending on which version you use. use addplayer batter or addplayer pitcher at the top of a list with entries separated by new lines:
@ -46,7 +44,6 @@ accepting pull requests, check the issues for to-dos.
- removes a player from your team. if there are multiple copies of the same player on a team this will only delete the first one. use this command at the top of a list with entries separated by new lines: - removes a player from your team. if there are multiple copies of the same player on a team this will only delete the first one. use this command at the top of a list with entries separated by new lines:
- the name of the team you want to remove the player from. - the name of the team you want to remove the player from.
- the name of the player you want to remove. - the name of the player you want to remove.
#### viewing and searching: #### viewing and searching:
- m;showteam [name] - m;showteam [name]
- shows the lineup, rotation, and slogan of any saved team in a discord embed with primary stat star ratings for all of the players. this command has fuzzy search so you don't need to type the full name of the team as long as you give enough to identify the team you're looking for. - shows the lineup, rotation, and slogan of any saved team in a discord embed with primary stat star ratings for all of the players. this command has fuzzy search so you don't need to type the full name of the team as long as you give enough to identify the team you're looking for.
@ -55,14 +52,6 @@ accepting pull requests, check the issues for to-dos.
- m;showallteams - m;showallteams
- shows a paginated list of all teams available for games which can be scrolled through. - shows a paginated list of all teams available for games which can be scrolled through.
### player commands:
- m;showplayer [name]
- displays any name's stars, there's a limit of 70 characters. that should be *plenty*. note: if you want to lookup a lot of different players you can do it on onomancer instead of spamming this command a bunch and clogging up discord: https://onomancer.sibr.dev/reflect
- m;idolize [name]
- records any name as your idol, mostly for fun.
- m;showidol
- displays your idol's name and stars in a discord embed.
### game commands: ### game commands:
- m;startgame --day # or -d # - m;startgame --day # or -d #
- starts a game with premade teams made using saveteam. provides a link to the website where you can watch the game. - starts a game with premade teams made using saveteam. provides a link to the website where you can watch the game.
@ -81,6 +70,49 @@ accepting pull requests, check the issues for to-dos.
- the name of the tournament. - the name of the tournament.
- the name of each participating team on its own line. - the name of each participating team on its own line.
### draft commands
- m;startdraft
- starts a draft with an arbitrary number of participants. use this command at the top of a list with entries separated by new lines:
- for each participant's entry you need three lines:
- their discord @
- their team name
- their team slogan
- post this with all three of these things for all participants and the draft will begin.
- the draft will begin once all participants have given a 👍 and will proceed in the order that participants were entered. each participant will select 12 hitters and 1 pitcher from a pool of 20 random players which will refresh automatically when it becomes small.
- m;draft [name]
- use this on your turn during a draft to pick your player.
- you can also just use a 'd' instead of the full command.
### league commands
- all of these commands are for leagues that have already been started. to start a league, click the 'create a league' button on the website and fill out the info for your league there, then use the m;claimleague command in discord to set yourself as the owner.
- commissioner commands (all of these except for m;claimleague require ownership of the specified league):
- m;claimleague [leaguename]
- sets yourself as the owner of an unclaimed league created on the website. make sure to do this as soon as possible since if someone does this before you, you will not have access to the league.
- m;addleagueowner [leaguename]
- use this command at the top of a list of @mentions, with entries separated by new lines, of people you want to have owner powers in your league.
- m;startleague [leaguename] --queue #/-q # --noautopostseason
- send this command with the number of games per hour you want on the next line, minimum 1 (one game every hour), maximum 12 (one game every 5 minutes, uses spillover rules).
- starts the playing of league games at the pace specified, by default will play the entire season and the postseason unless an owner pauses the league with the m;pauseleague command.
- if you use the --queue #/-q # flag, the league will only play # series' at a time before automatically pausing until you use this command again.
- if you use the --noautopostseason flag, instead of starting automatically, the league will pause at the end of the regular season and not start the postseason until you use this command again.
- m;pauseleague [leaguename]
- pauses the specified league after the current series finishes until the league is started again with m;startleague.
- general commands (all of these can be used by anyone):
- m;leaguestandings [leaguename]
- displays the current standings for the specified league.
- m;leaguewildcard [leaguename]
- displays the wild card standings for the specified league. if the league doesn't have wild cards, it will instead tell you that.
- m;leagueschedule [leaguename]
- displays the upcoming schedule for the specified league. shows the current series and the next three series after that for every team.
### player commands:
- m;showplayer [name]
- displays any name's stars, there's a limit of 70 characters. that should be *plenty*. note: if you want to lookup a lot of different players you can do it on onomancer instead of spamming this command a bunch and clogging up discord: https://onomancer.sibr.dev/reflect
- m;idolize [name]
- records any name as your idol, mostly for fun.
- m;showidol
- displays your idol's name and stars in a discord embed.
### other commands: ### other commands:
- m;help [command] - m;help [command]
- shows instructions for a given command. if no command is provided, it will instead provide a list of all of the commands that instructions can be provided for. - shows instructions for a given command. if no command is provided, it will instead provide a list of all of the commands that instructions can be provided for.

View File

@ -31,13 +31,14 @@ def config():
def all_weathers(): def all_weathers():
weathers_dic = { weathers_dic = {
#"Supernova" : weather("Supernova", "🌟"), #"Supernova" : weather("Supernova", "🌟"),
"Midnight": weather("Midnight", "🕶"), #"Midnight": weather("Midnight", "🕶"),
"Slight Tailwind": weather("Slight Tailwind", "🏌️‍♀️"), "Slight Tailwind": weather("Slight Tailwind", "🏌️‍♀️"),
"Heavy Snow": weather("Heavy Snow", "") "Heavy Snow": weather("Heavy Snow", ""),
"Twilight" : weather("Twilight", "👻"),
"Thinned Veil" : weather("Thinned Veil", "🌌")
} }
return weathers_dic return weathers_dic
class appearance_outcomes(Enum): class appearance_outcomes(Enum):
strikeoutlooking = "strikes out looking." strikeoutlooking = "strikes out looking."
strikeoutswinging = "strikes out swinging." strikeoutswinging = "strikes out swinging."
@ -269,6 +270,13 @@ class game(object):
pb_system_stat = (random.gauss(1*math.erf((bat_stat - pitch_stat)*1.5)-1.8,2.2)) pb_system_stat = (random.gauss(1*math.erf((bat_stat - pitch_stat)*1.5)-1.8,2.2))
hitnum = random.gauss(2*math.erf(bat_stat/4)-1,3) hitnum = random.gauss(2*math.erf(bat_stat/4)-1,3)
if self.weather.name == "Twilight":
error_line = - (math.log(defender.stlats["defense_stars"] + 1)/50) + 1
error_roll = random.random()
if error_roll > error_line:
outcome["error"] = True
outcome["defender"] = defender
pb_system_stat = 0.1
if pb_system_stat <= 0: if pb_system_stat <= 0:
@ -313,7 +321,7 @@ class game(object):
outcome["ishit"] = True outcome["ishit"] = True
if hitnum < 1: if hitnum < 1:
outcome["text"] = appearance_outcomes.single outcome["text"] = appearance_outcomes.single
elif hitnum < 2.85: elif hitnum < 2.85 or "error" in outcome.keys():
outcome["text"] = appearance_outcomes.double outcome["text"] = appearance_outcomes.double
elif hitnum < 3.1: elif hitnum < 3.1:
outcome["text"] = appearance_outcomes.triple outcome["text"] = appearance_outcomes.triple
@ -384,6 +392,11 @@ class game(object):
if base is not None: if base is not None:
runs += 1 runs += 1
self.bases = {1 : None, 2 : None, 3 : None} self.bases = {1 : None, 2 : None, 3 : None}
if "veil" in outcome.keys():
if runs < 4:
self.bases[runs] = self.get_batter()
else:
runs += 1
return runs return runs
elif "advance" in outcome.keys(): elif "advance" in outcome.keys():
@ -535,6 +548,10 @@ class game(object):
elif result["text"] == appearance_outcomes.homerun or result["text"] == appearance_outcomes.grandslam: elif result["text"] == appearance_outcomes.homerun or result["text"] == appearance_outcomes.grandslam:
self.get_batter().game_stats["total_bases"] += 4 self.get_batter().game_stats["total_bases"] += 4
self.get_batter().game_stats["home_runs"] += 1 self.get_batter().game_stats["home_runs"] += 1
if self.weather.name == "Thinned Veil":
result["veil"] = True
scores_to_add += self.baserunner_check(defender, result) scores_to_add += self.baserunner_check(defender, result)

View File

@ -21,6 +21,23 @@ def create_connection(league_name):
print("oops, db connection no work") print("oops, db connection no work")
return conn return conn
def create_season_connection(league_name, season_num):
#create connection, create db if doesn't exist
conn = None
try:
if not os.path.exists(os.path.join(data_dir, league_dir, league_name)):
os.makedirs(os.path.join(data_dir, league_dir, league_name))
conn = sql.connect(os.path.join(data_dir, league_dir, league_name, season_num, f"{league_name}.db"))
# enable write-ahead log for performance and resilience
conn.execute('pragma journal_mode=wal')
return conn
except:
print("oops, db connection no work")
return conn
def state(league_name): def state(league_name):
if not os.path.exists(os.path.dirname(os.path.join(data_dir, league_dir, league_name, f"{league_name}.state"))): if not os.path.exists(os.path.dirname(os.path.join(data_dir, league_dir, league_name, f"{league_name}.state"))):
os.makedirs(os.path.dirname(os.path.join(data_dir, league_dir, league_name, f"{league_name}.state"))) os.makedirs(os.path.dirname(os.path.join(data_dir, league_dir, league_name, f"{league_name}.state")))
@ -73,32 +90,21 @@ def init_league_db(league):
for pitcher in team.rotation: for pitcher in team.rotation:
c.execute(player_string, (pitcher.name, team.name)) c.execute(player_string, (pitcher.name, team.name))
state_dic = {
"day" : league.day,
"schedule" : league.schedule,
"game_length" : league.game_length,
"series_length" : league.series_length,
"games_per_hour" : league.games_per_hour,
"owner" : None,
"historic" : False
}
if not os.path.exists(os.path.dirname(os.path.join(data_dir, league_dir, league.name, f"{league.name}.state"))):
os.makedirs(os.path.dirname(os.path.join(data_dir, league_dir, league.name, f"{league.name}.state")))
with open(os.path.join(data_dir, league_dir, league.name, f"{league.name}.state"), "w") as state_file:
json.dump(state_dic, state_file, indent=4)
conn.commit() conn.commit()
conn.close() conn.close()
def save_league(league): def save_league(league):
if league_exists(league.name): if league_exists(league.name):
state_dic = { state_dic = {
"season" : league.season,
"day" : league.day, "day" : league.day,
"constraints" : league.constraints,
"schedule" : league.schedule, "schedule" : league.schedule,
"game_length" : league.game_length, "game_length" : league.game_length,
"series_length" : league.series_length, "series_length" : league.series_length,
"games_per_hour" : league.games_per_hour, "games_per_hour" : league.games_per_hour,
"owner" : league.owner, "owner" : league.owner,
"champion" : league.champion,
"historic" : league.historic "historic" : league.historic
} }
with open(os.path.join(data_dir, league_dir, league.name, f"{league.name}.state"), "w") as state_file: with open(os.path.join(data_dir, league_dir, league.name, f"{league.name}.state"), "w") as state_file:
@ -149,11 +155,49 @@ def get_standings(league_name):
conn.close() conn.close()
return standings_array return standings_array
def season_save(league):
if league_exists(league.name):
seasons = 1
with os.scandir(os.path.join(data_dir, league_dir, league.name)) as folder:
for item in folder:
if "." not in item.name:
seasons += 1
new_dir = os.path.join(data_dir, league_dir, league.name, str(seasons))
os.makedirs(new_dir)
with os.scandir(os.path.join(data_dir, league_dir, league.name)) as folder:
for item in folder:
if "." in item.name:
os.rename(os.path.join(data_dir, league_dir, league.name, item.name), os.path.join(new_dir, item.name))
def get_past_standings(league_name, season_num):
if league_exists(league_name):
with os.scandir(os.path.join(data_dir, league_dir, league_name)) as folder:
for item in folder:
if item.name == str(season_num):
conn = create_season_connection(league_name, str(item.name))
if conn is not None:
c = conn.cursor()
c.execute("SELECT name, wins, losses, run_diff FROM teams",)
standings_array = c.fetchall()
conn.close()
return standings_array
def get_past_champion(league_name, season_num):
if league_exists(league_name):
with os.scandir(os.path.join(data_dir, league_dir, league_name)) as folder:
for item in folder:
if item.name == str(season_num):
with open(os.path.join(data_dir, league_dir, league_name, item.name, f"{league_name}.state")) as state_file:
state_dic = json.load(state_file)
return state_dic["champion"]
def league_exists(league_name): def league_exists(league_name):
with os.scandir(os.path.join(data_dir, league_dir)) as folder: with os.scandir(os.path.join(data_dir, league_dir)) as folder:
for subfolder in folder: for subfolder in folder:
if league_name in subfolder.name: if league_name == subfolder.name:
return not state(league_name)["historic"] with os.scandir(subfolder.path) as league_folder:
for item in league_folder:
if item.name == f"{league_name}.db":
return True
return False return False

View File

@ -12,13 +12,18 @@ class league_structure(object):
self.name = name self.name = name
self.historic = False self.historic = False
self.owner = None self.owner = None
self.season = 1
self.autoplay = -1
self.champion = None
def setup(self, league_dic, division_games = 1, inter_division_games = 1, inter_league_games = 1, games_per_hour = 2): def setup(self, league_dic, division_games = 1, inter_division_games = 1, inter_league_games = 1, games_per_hour = 2):
self.league = league_dic # { subleague name : { division name : [team object] } } self.league = league_dic # { subleague name : { division name : [team object] } }
self.constraints = { self.constraints = {
"division_games" : division_games, "division_games" : division_games,
"inter_div_games" : inter_division_games, "inter_div_games" : inter_division_games,
"inter_league_games" : inter_league_games "inter_league_games" : inter_league_games,
"division_leaders" : 0,
"wild_cards" : 0
} }
self.day = 1 self.day = 1
self.schedule = {} self.schedule = {}
@ -27,6 +32,14 @@ class league_structure(object):
self.active = False self.active = False
self.games_per_hour = games_per_hour self.games_per_hour = games_per_hour
def season_reset(self):
self.season += 1
self.day = 1
self.champion = None
self.schedule = {}
self.generate_schedule()
save_league(self)
def add_stats_from_game(self, players_dic): def add_stats_from_game(self, players_dic):
league_db.add_stats(self.name, players_dic) league_db.add_stats(self.name, players_dic)
@ -40,6 +53,41 @@ class league_structure(object):
def day_to_series_num(self, day): def day_to_series_num(self, day):
return math.ceil((self.day)/self.series_length) return math.ceil((self.day)/self.series_length)
def tiebreaker_required(self):
standings = {}
matchups = []
tournaments = []
for team_name, wins, losses, run_diff in league_db.get_standings(self.name):
standings[team_name] = {"wins" : wins, "losses" : losses, "run_diff" : run_diff}
for subleague in iter(self.league.keys()):
team_dic = {}
subleague_array = []
wildcard_leaders = []
for division in iter(self.league[subleague].keys()):
division_standings = []
division_standings += self.division_standings(self.league[subleague][division], standings)
division_leaders = division_standings[:self.constraints["division_leaders"]]
for division_team, wins, losses, diff, gb in division_standings[self.constraints["division_leaders"]:]:
if division_team.name != division_leaders[-1][0].name and standings[division_team.name]["wins"] == standings[division_leaders[-1][0].name]["wins"]:
matchups.append((division_team, division_standings[self.constraints["division_leaders"]-1][0], f"{division} Tiebreaker"))
this_div_wildcard = [this_team for this_team, wins, losses, diff, gb in self.division_standings(self.league[subleague][division], standings)[self.constraints["division_leaders"]:]]
subleague_array += this_div_wildcard
if self.constraints["wild_cards"] > 0:
wildcard_standings = self.division_standings(subleague_array, standings)
wildcard_leaders = wildcard_standings[:self.constraints["wild_cards"]]
for wildcard_team, wins, losses, diff, gb in wildcard_standings[self.constraints["wild_cards"]:]:
if wildcard_team.name != wildcard_leaders[-1][0].name and standings[wildcard_team.name]["wins"] == standings[wildcard_leaders[-1][0].name]["wins"]:
matchups.append((wildcard_team, wildcard_standings[self.constraints["wild_cards"]-1][0], f"{subleague} Wildcard Tiebreaker"))
for team_a, team_b, type in matchups:
tourney = tournament(f"{self.name} {type}",{team_a : {"wins" : 1}, team_b : {"wins" : 0}}, finals_series_length=1, secs_between_games=int(3600/self.games_per_hour), secs_between_rounds=int(7200/self.games_per_hour))
tourney.build_bracket(by_wins = True)
tourney.league = self
tournaments.append(tourney)
return tournaments
def find_team(self, team_name): def find_team(self, team_name):
for subleague in iter(self.league.keys()): for subleague in iter(self.league.keys()):
for division in iter(self.league[subleague].keys()): for division in iter(self.league[subleague].keys()):
@ -179,30 +227,144 @@ class league_structure(object):
scheduled = True scheduled = True
day += 1 day += 1
def standings_embed(self): def division_standings(self, division, standings):
def sorter(team_in_list):
if team_in_list[2] == 0 and team_in_list[1] == 0:
return (0, team_in_list[3])
return (team_in_list[1]/(team_in_list[1]+team_in_list[2]), team_in_list[3])
teams = division.copy()
for index in range(0, len(teams)):
this_team = teams[index]
teams[index] = [this_team, standings[teams[index].name]["wins"], standings[teams[index].name]["losses"], standings[teams[index].name]["run_diff"], 0]
teams.sort(key=sorter, reverse=True)
return teams
def past_standings(self, season_num):
this_embed = Embed(color=Color.purple(), title=self.name) this_embed = Embed(color=Color.purple(), title=self.name)
standings = {} standings = {}
for team_name, wins, losses, run_diff in league_db.get_past_standings(self.name, season_num):
standings[team_name] = {"wins" : wins, "losses" : losses, "run_diff" : run_diff}
this_embed.add_field(name=league_db.get_past_champion(self.name, season_num), value=f"Season {season_num} champions", inline = False)
for subleague in iter(self.league.keys()):
this_embed.add_field(name="Subleague:", value=f"**{subleague}**", inline = False)
for division in iter(self.league[subleague].keys()):
teams = self.division_standings(self.league[subleague][division], standings)
for index in range(0, len(teams)):
if index == self.constraints["division_leaders"] - 1:
teams[index][4] = "-"
else:
games_behind = ((teams[self.constraints["division_leaders"] - 1][1] - teams[index][1]) + (teams[index][2] - teams[self.constraints["division_leaders"] - 1][2]))/2
teams[index][4] = games_behind
teams_string = ""
for this_team in teams:
if this_team[2] != 0 or this_team[1] != 0:
teams_string += f"**{this_team[0].name}\n**{this_team[1]} - {this_team[2]} WR: {round(this_team[1]/(this_team[1]+this_team[2]), 3)} GB: {this_team[4]}\n\n"
else:
teams_string += f"**{this_team[0].name}\n**{this_team[1]} - {this_team[2]} WR: - GB: {this_team[4]}\n\n"
this_embed.add_field(name=f"{division} Division:", value=teams_string, inline = False)
this_embed.set_footer(text=f"Season {season_num} Final Standings")
return this_embed
def season_length(self):
return int(list(self.schedule.keys())[-1]) * self.series_length
def standings_embed(self):
this_embed = Embed(color=Color.purple(), title=f"{self.name} Season {self.season}")
standings = {}
for team_name, wins, losses, run_diff in league_db.get_standings(self.name): for team_name, wins, losses, run_diff in league_db.get_standings(self.name):
standings[team_name] = {"wins" : wins, "losses" : losses, "run_diff" : run_diff} standings[team_name] = {"wins" : wins, "losses" : losses, "run_diff" : run_diff}
for subleague in iter(self.league.keys()): for subleague in iter(self.league.keys()):
this_embed.add_field(name="Subleague:", value=f"**{subleague}**", inline = False) this_embed.add_field(name="Subleague:", value=f"**{subleague}**", inline = False)
for division in iter(self.league[subleague].keys()): for division in iter(self.league[subleague].keys()):
this_embed.add_field(name="Division:", value=f"**{division}**", inline = False) teams = self.division_standings(self.league[subleague][division], standings)
teams = self.league[subleague][division].copy()
for index in range(0, len(teams)):
this_team = teams[index]
teams[index] = (this_team, standings[teams[index].name]["wins"], standings[teams[index].name]["losses"], standings[teams[index].name]["run_diff"],)
def sorter(team_in_list): for index in range(0, len(teams)):
return (team_in_list[1], team_in_list[3]) if index == self.constraints["division_leaders"] - 1:
teams.sort(key=sorter, reverse=True) teams[index][4] = "-"
else:
games_behind = ((teams[self.constraints["division_leaders"] - 1][1] - teams[index][1]) + (teams[index][2] - teams[self.constraints["division_leaders"] - 1][2]))/2
teams[index][4] = games_behind
teams_string = ""
for this_team in teams:
if this_team[2] != 0 or this_team[1] != 0:
teams_string += f"**{this_team[0].name}\n**{this_team[1]} - {this_team[2]} WR: {round(this_team[1]/(this_team[1]+this_team[2]), 3)} GB: {this_team[4]}\n\n"
else:
teams_string += f"**{this_team[0].name}\n**{this_team[1]} - {this_team[2]} WR: - GB: {this_team[4]}\n\n"
this_embed.add_field(name=f"{division} Division:", value=teams_string, inline = False)
this_embed.set_footer(text=f"Standings as of day {self.day-1} / {self.season_length()}")
return this_embed
def wildcard_embed(self):
this_embed = Embed(color=Color.purple(), title=f"{self.name} Wildcard Race")
standings = {}
for team_name, wins, losses, run_diff in league_db.get_standings(self.name):
standings[team_name] = {"wins" : wins, "losses" : losses, "run_diff" : run_diff}
for subleague in iter(self.league.keys()):
subleague_array = []
for division in iter(self.league[subleague].keys()):
this_div = [this_team for this_team, wins, losses, diff, gb in self.division_standings(self.league[subleague][division], standings)[self.constraints["division_leaders"]:]]
subleague_array += this_div
teams = self.division_standings(subleague_array, standings)
teams_string = ""
for index in range(0, len(teams)):
if index == self.constraints["wild_cards"] - 1:
teams[index][4] = "-"
else:
games_behind = ((teams[self.constraints["wild_cards"] - 1][1] - teams[index][1]) + (teams[index][2] - teams[self.constraints["wild_cards"] - 1][2]))/2
teams[index][4] = games_behind
for this_team in teams: for this_team in teams:
this_embed.add_field(name=this_team[0].name, value=f"{this_team[1]} - {this_team[2]} Diff: {this_team[3]}", inline = False) if this_team[2] != 0 or this_team[1] != 0:
teams_string += f"**{this_team[0].name}\n**{this_team[1]} - {this_team[2]} WR: {round(this_team[1]/(this_team[1]+this_team[2]), 3)} GB: {this_team[4]}\n\n"
else:
teams_string += f"**{this_team[0].name}\n**{this_team[1]} - {this_team[2]} WR: - GB: {this_team[4]}\n\n"
this_embed.set_footer(text=f"Standings as of day {self.day-1}") this_embed.add_field(name=f"{subleague} League:", value=teams_string, inline = False)
this_embed.set_footer(text=f"Wildcard standings as of day {self.day-1}")
return this_embed return this_embed
def champ_series(self):
tournaments = []
standings = {}
for team_name, wins, losses, run_diff in league_db.get_standings(self.name):
standings[team_name] = {"wins" : wins, "losses" : losses, "run_diff" : run_diff}
for subleague in iter(self.league.keys()):
team_dic = {}
division_leaders = []
subleague_array = []
wildcard_leaders = []
for division in iter(self.league[subleague].keys()):
division_leaders += self.division_standings(self.league[subleague][division], standings)[:self.constraints["division_leaders"]]
this_div_wildcard = [this_team for this_team, wins, losses, diff, gb in self.division_standings(self.league[subleague][division], standings)[self.constraints["division_leaders"]:]]
subleague_array += this_div_wildcard
if self.constraints["wild_cards"] > 0:
wildcard_leaders = self.division_standings(subleague_array, standings)[:self.constraints["wild_cards"]]
for this_team, wins, losses, diff, gb in division_leaders + wildcard_leaders:
team_dic[this_team] = {"wins" : wins}
subleague_tournament = tournament(f"{self.name} {subleague} Subleague Series", team_dic, series_length=3, finals_series_length=5, secs_between_games=int(3600/self.games_per_hour), secs_between_rounds=int(7200/self.games_per_hour))
subleague_tournament.build_bracket(by_wins = True)
subleague_tournament.league = self
tournaments.append(subleague_tournament)
return tournaments
class tournament(object): class tournament(object):
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): 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.name = name
@ -217,6 +379,9 @@ class tournament(object):
self.round_delay = secs_between_rounds self.round_delay = secs_between_rounds
self.finals = False self.finals = False
self.id = id self.id = id
self.league = None
self.winner = None
self.day = None
if id is None: if id is None:
self.id = random.randint(1111,9999) self.id = random.randint(1111,9999)
@ -314,8 +479,6 @@ def save_league(this_league):
with open(os.path.join(data_dir, league_dir, this_league.name, f"{this_league.name}.league"), "w") as league_file: with open(os.path.join(data_dir, league_dir, this_league.name, f"{this_league.name}.league"), "w") as league_file:
league_json_string = jsonpickle.encode(this_league.league, keys=True) league_json_string = jsonpickle.encode(this_league.league, keys=True)
json.dump(league_json_string, league_file, indent=4) json.dump(league_json_string, league_file, indent=4)
return True
else:
league_db.save_league(this_league) league_db.save_league(this_league)
def load_league_file(league_name): def load_league_file(league_name):
@ -329,7 +492,15 @@ def load_league_file(league_name):
this_league.day = state_dic["day"] this_league.day = state_dic["day"]
this_league.schedule = state_dic["schedule"] this_league.schedule = state_dic["schedule"]
this_league.constraints = state_dic["constraints"]
this_league.game_length = state_dic["game_length"] this_league.game_length = state_dic["game_length"]
this_league.series_length = state_dic["series_length"] this_league.series_length = state_dic["series_length"]
this_league.owner = state_dic["owner"] this_league.owner = state_dic["owner"]
this_league.games_per_hour = state_dic["games_per_hour"]
this_league.historic = state_dic["historic"]
this_league.season = state_dic["season"]
try:
this_league.champion = state_dic["champion"]
except:
this_league.champion = None
return this_league return this_league

View File

@ -97,6 +97,8 @@ def create_league():
inter_division_games=config['inter_division_series'], inter_division_games=config['inter_division_series'],
inter_league_games=config['inter_league_series'], inter_league_games=config['inter_league_series'],
) )
new_league.constraints["division_leaders"] = config["top_postseason"]
new_league.constraints["wild_cards"] = config["wildcards"]
new_league.generate_schedule() new_league.generate_schedule()
leagues.save_league(new_league) leagues.save_league(new_league)
@ -199,6 +201,8 @@ def update_loop():
if this_game.last_update[0]["defender"] != "": if this_game.last_update[0]["defender"] != "":
punc = ". " punc = ". "
if "fc_out" in this_game.last_update[0].keys(): if "fc_out" in this_game.last_update[0].keys():
name, base_string = this_game.last_update[0]['fc_out'] name, base_string = this_game.last_update[0]['fc_out']
updatestring = f"{this_game.last_update[0]['batter']} {this_game.last_update[0]['text'].value.format(name, base_string)} {this_game.last_update[0]['defender']}{punc}" updatestring = f"{this_game.last_update[0]['batter']} {this_game.last_update[0]['text'].value.format(name, base_string)} {this_game.last_update[0]['defender']}{punc}"
@ -210,6 +214,13 @@ def update_loop():
state["update_emoji"] = "🏏" state["update_emoji"] = "🏏"
state["update_text"] = updatestring state["update_text"] = updatestring
if "veil" in this_game.last_update[0].keys():
state["update_emoji"] = "🌌"
state["update_text"] += f" {this_game.last_update[0]['batter']}'s will manifests on {games.base_string(this_game.last_update[1])} base."
elif "error" in this_game.last_update[0].keys():
state["update_emoji"] = "👻"
state["update_text"] = f"{this_game.last_update[0]['batter']}'s hit goes ethereal, and {this_game.last_update[0]['defender']} can't catch it! {this_game.last_update[0]['batter']} reaches base safely."
state["bases"] = this_game.named_bases() state["bases"] = this_game.named_bases()
state["top_of_inning"] = this_game.top_of_inning state["top_of_inning"] = this_game.top_of_inning
@ -230,4 +241,4 @@ def update_loop():
state["update_pause"] -= 1 state["update_pause"] -= 1
socketio.emit("states_update", game_states) socketio.emit("states_update", game_states)
time.sleep(1) time.sleep(8)

1
simmadome/.eslintcache Normal file
View File

@ -0,0 +1 @@
[{"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\index.tsx":"1","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\reportWebVitals.ts":"2","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesPage.tsx":"3","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamePage.tsx":"4","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesUtil.tsx":"5","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\Game.tsx":"6","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\CreateLeague.tsx":"7","M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\util.tsx":"8"},{"size":2425,"mtime":1610685584594,"results":"9","hashOfConfig":"10"},{"size":440,"mtime":1610521673158,"results":"11","hashOfConfig":"10"},{"size":4866,"mtime":1610685584593,"results":"12","hashOfConfig":"10"},{"size":1897,"mtime":1610685584589,"results":"13","hashOfConfig":"10"},{"size":1157,"mtime":1610685584594,"results":"14","hashOfConfig":"10"},{"size":3173,"mtime":1610583643836,"results":"15","hashOfConfig":"10"},{"size":17241,"mtime":1610685584587,"results":"16","hashOfConfig":"10"},{"size":1029,"mtime":1610685584594,"results":"17","hashOfConfig":"10"},{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1jg8ts7",{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"22"},{"filePath":"23","messages":"24","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"25","messages":"26","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"27","messages":"28","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"22"},{"filePath":"31","messages":"32","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"33","messages":"34","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\index.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\reportWebVitals.ts",[],["35","36"],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesPage.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamePage.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\GamesUtil.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\Game.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\CreateLeague.tsx",[],"M:\\Documents\\Code\\matteo\\the-prestige\\simmadome\\src\\util.tsx",["37"],{"ruleId":"38","replacedBy":"39"},{"ruleId":"40","replacedBy":"41"},{"ruleId":"42","severity":1,"message":"43","line":1,"column":9,"nodeType":"44","messageId":"45","endLine":1,"endColumn":15},"no-native-reassign",["46"],"no-negated-in-lhs",["47"],"@typescript-eslint/no-unused-vars","'useRef' is defined but never used.","Identifier","unusedVar","no-global-assign","no-unsafe-negation"]

View File

@ -6,4 +6,4 @@ SELECT name,
ROUND(strikeouts_given*27.0/(outs_pitched*1.0),3) as kper9, ROUND(strikeouts_given*27.0/(outs_pitched*1.0),3) as kper9,
ROUND(strikeouts_given*1.0/walks_allowed*1.0,3) as kperbb ROUND(strikeouts_given*1.0/walks_allowed*1.0,3) as kperbb
FROM stats WHERE outs_pitched > 150 FROM stats WHERE outs_pitched > 150
ORDER BY bbper9 ASC; ORDER BY era ASC;

View File

@ -1,7 +1,7 @@
import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues, datetime import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues, datetime
import database as db import database as db
import onomancer as ono import onomancer as ono
from league_storage import league_exists from league_storage import league_exists, season_save
from the_draft import Draft, DRAFT_ROUNDS from the_draft import Draft, DRAFT_ROUNDS
from flask import Flask from flask import Flask
from uuid import uuid4 from uuid import uuid4
@ -78,7 +78,7 @@ class IdolizeCommand(Command):
else: else:
meme = False meme = False
player_name = discord.utils.escape_mentions(command) player_name = discord.utils.escape_mentions(command.strip())
if len(player_name) >= 70: if len(player_name) >= 70:
await msg.channel.send("That name is too long. Please keep it below 70 characters, for my sake and yours.") await msg.channel.send("That name is too long. Please keep it below 70 characters, for my sake and yours.")
return return
@ -202,7 +202,7 @@ class StartRandomGameCommand(Command):
channel = msg.channel channel = msg.channel
await msg.delete() await msg.delete()
await channel.send("Rolling the bones...") await channel.send("Rolling the bones... This might take a while.")
teamslist = games.get_all_teams() teamslist = games.get_all_teams()
game = games.game(random.choice(teamslist).finalize(), random.choice(teamslist).finalize()) game = games.game(random.choice(teamslist).finalize(), random.choice(teamslist).finalize())
@ -562,6 +562,11 @@ class StartTournamentCommand(Command):
if team == None: if team == None:
await msg.channel.send(f"We couldn't find {name}. Try again?") await msg.channel.send(f"We couldn't find {name}. Try again?")
return return
add = True
for extant_team in team_dic.keys():
if extant_team.name == team.name:
add = False
if add:
team_dic[team] = {"wins": 0} team_dic[team] = {"wins": 0}
channel = msg.channel channel = msg.channel
@ -596,8 +601,8 @@ class DraftPlayerCommand(Command):
class StartDraftCommand(Command): class StartDraftCommand(Command):
name = "startdraft" name = "startdraft"
template = "m;startdraft [mention] [teamname] [slogan]" template = "m;startdraft\n[mention]\n[teamname]\n[slogan]"
description = """Starts a draft with an arbitrary number of participants. Send this command at the top of the list with each mention, teamname, and slogan on a new line (shift+enter in discord). description = """Starts a draft with an arbitrary number of participants. Send this command at the top of the list with each mention, teamname, and slogan on their own lines (shift+enter in discord).
- The draft will proceed in the order that participants were entered. - The draft will proceed in the order that participants were entered.
- 20 players will be available for draft at a time, and the pool will refresh automatically when it becomes small. - 20 players will be available for draft at a time, and the pool will refresh automatically when it becomes small.
- Each participant will be asked to draft 12 hitters then finally one pitcher. - Each participant will be asked to draft 12 hitters then finally one pitcher.
@ -726,7 +731,7 @@ class StartDraftCommand(Command):
return draft_message return draft_message
class DebugLeagueStart(Command): class DebugLeagueStart(Command):
name = "startleague" name = "startdebugleague"
async def execute(self, msg, command): async def execute(self, msg, command):
if not league_exists("test2"): if not league_exists("test2"):
@ -743,18 +748,212 @@ class DebugLeagueStart(Command):
}, division_games=6, inter_division_games=3, inter_league_games=3, games_per_hour = 12) }, division_games=6, inter_division_games=3, inter_league_games=3, games_per_hour = 12)
league.generate_schedule() league.generate_schedule()
leagues.save_league(league) leagues.save_league(league)
else:
league = leagues.load_league_file("test2")
await start_league_day(msg.channel, league, autoplay = 2)
class DebugLeagueDisplay(Command): class DebugLeagueDisplay(Command):
name = "displayleague" name = "displaydebugleague"
async def execute(self, msg, command): async def execute(self, msg, command):
if league_exists("test2"): if league_exists("Midseries"):
league = leagues.load_league_file("test2") league = leagues.load_league_file("Midseries")
await msg.channel.send(embed=league.standings_embed()) league.champion = "Butts"
leagues.save_league(league)
season_save(league)
league.season_reset()
await msg.channel.send(embed=league.past_standings(1))
class StartLeagueCommand(Command):
name = "startleague"
template = "m;startleague [league name]\n[games per hour]"
description = """Optional flags for the first line: `--queue X` or `-q X` to play X number of series before stopping; `--noautopostseason` will pause the league before starting postseason.
Plays a league with a given name, provided that league has been saved on the website. The games per hour sets how often the games will start. (e.g. GPH 2 will start games at X:00 and X:30)"""
async def execute(self, msg, command):
if config()["game_freeze"]:
await msg.channel.send("Patch incoming. We're not allowing new games right now.")
return
league_name = command.split("-")[0].split("\n")[0].strip()
autoplay = None
try:
if "--queue " in command:
autoplay = int(command.split("--queue ")[1].split("\n")[0])
elif "-q " in command:
autoplay = int(command.split("-q ")[1].split("\n")[0])
if autoplay is not None and autoplay <= 0:
raise ValueError
elif autoplay is None:
autoplay = -1
except ValueError:
await msg.channel.send("Sorry boss, the queue flag needs a natural number. Any whole number over 0 will do just fine.")
return
except IndexError:
await msg.channel.send("We need a games per hour number in the second line.")
return
try:
gph = int(command.split("\n")[1].strip())
if gph < 1 or gph > 12:
raise ValueError
except ValueError:
await msg.channel.send("Chief, we need a games per hour number between 1 and 12. We think that's reasonable.")
return
if league_exists(league_name):
league = leagues.load_league_file(league_name)
if "--noautopostseason" in command:
autoplay = int(list(league.schedule.keys())[-1]) - league.day_to_series_num(league.day) + 1
if league.historic:
await msg.channel.send("That league is done and dusted, chief. Sorry.")
return
for active_league in active_leagues:
if active_league.name == league.name:
await msg.channel.send("That league is already running, boss. Patience is a virtue, you know.")
return
if (league.owner is not None and msg.author.id in league.owner) or msg.author.id in config()["owners"] or league.owner is None:
league.autoplay = autoplay
league.games_per_hour = gph
if str(league.day_to_series_num(league.day)) not in league.schedule.keys():
await league_postseason(msg.channel, league)
elif league.day % league.series_length == 1:
await start_league_day(msg.channel, league)
else:
await start_league_day(msg.channel, league, partial = True)
else:
await msg.channel.send("You don't have permission to manage that league.")
return
else:
await msg.channel.send("Couldn't find that league, boss. Did you save it on the website?")
class LeagueDisplayCommand(Command):
name = "leaguestandings"
template = "m;leaguestandings [league name]"
description = "Displays the current standings for the given league. Use `--season X` or `-s X` to get standings from season X of that league."
async def execute(self, msg, command):
if league_exists(command.split("-")[0].strip()):
league = leagues.load_league_file(command.split("-")[0].strip())
try:
if "--season " in command:
season_num = int(command.split("--season ")[1])
await msg.channel.send(embed=league.past_standings(season_num))
elif "-s " in command:
season_num = int(command.split("-s ")[1])
await msg.channel.send(embed=league.past_standings(season_num))
else:
await msg.channel.send(embed=league.standings_embed())
except ValueError:
await msg.channel.send("Give us a proper number, boss.")
except TypeError:
await msg.channel.send("That season hasn't been played yet, chief.")
else:
await msg.channel.send("Can't find that league, boss.")
class LeagueWildcardCommand(Command):
name = "leaguewildcard"
template = "m;leaguewildcard [league name]"
description = "Displays the current wildcard race for the given league, if the league has wildcard slots."
async def execute(self, msg, command):
if league_exists(command.strip()):
league = leagues.load_league_file(command.strip())
if league.constraints["wild_cards"] > 0:
await msg.channel.send(embed=league.wildcard_embed())
else:
await msg.channel.send("That league doesn't have wildcards, boss.")
else:
await msg.channel.send("Can't find that league, boss.")
class LeaguePauseCommand(Command):
name = "pauseleague"
template = "m;pauseleague [league name]"
descripton = "Tells a currently running league to stop running automatically after the current series."
async def execute(self, msg, command):
league_name = command.strip()
for active_league in active_leagues:
if active_league.name == league_name:
if (active_league.owner is not None and msg.author.id in active_league.owner) or msg.author.id in config()["owners"]:
active_league.autoplay = 0
await msg.channel.send(f"Loud and clear, chief. {league_name} will stop after this series is over.")
return
else:
await msg.channel.send("You don't have permission to manage that league.")
return
await msg.channel.send("That league either doesn't exist or isn't running.")
class LeagueClaimCommand(Command):
name = "claimleague"
template = "m;claimleague [league name]"
description = "Claims an unclaimed league. Do this as soon as possible after creating the league, or it will remain unclaimed."
async def execute(self, msg, command):
league_name = command.strip()
if league_exists(league_name):
league = leagues.load_league_file(league_name)
if league.owner is None:
league.owner = [msg.author.id]
leagues.save_league(league)
await msg.channel.send(f"The {league.name} commissioner is doing a great job. That's you, by the way.")
return
else:
await msg.channel.send("That league has already been claimed!")
else:
await msg.channel.send("Can't find that league, boss.")
class LeagueAddOwnersCommand(Command):
name = "addleagueowner"
template = "m;addleagueowner [league name]\n[user mentions]"
description = "Adds additional owners to a league."
async def execute(self, msg, command):
league_name = command.split("\n")[0].strip()
if league_exists(league_name):
league = leagues.load_league_file(league_name)
if (league.owner is not None and msg.author.id in league.owner) or (league.owner is not None and msg.author.id in config()["owners"]):
for user in msg.mentions:
if user.id not in league.owner:
league.owner.append(user.id)
leagues.save_league(league)
await msg.channel.send(f"The new {league.name} front office is now up and running.")
return
else:
await msg.channel.send(f"That league isn't yours, boss.")
return
else:
await msg.channel.send("Can't find that league, boss.")
class LeagueScheduleCommand(Command):
name = "leagueschedule"
template = "m;leagueschedule [league name]"
description = "Sends an embed with the given league's schedule for the next 4 series."
async def execute(self, msg, command):
league_name = command.strip()
if league_exists(league_name):
league = leagues.load_league_file(league_name)
current_series = league.day_to_series_num(league.day)
if str(current_series+1) in league.schedule.keys():
sched_embed = discord.Embed(title=f"{league.name}'s Schedule:")
days = [0,1,2,3]
for day in days:
if str(current_series+day) in league.schedule.keys():
schedule_text = ""
for game in league.schedule[str(current_series+day)]:
schedule_text += f"**{game[0]}** @ **{game[1]}**\n"
sched_embed.add_field(name=f"Days {((current_series+day-1)*league.series_length) + 1} - {(current_series+day)*(league.series_length)}", value=schedule_text, inline = False)
await msg.channel.send(embed=sched_embed)
else:
await msg.channel.send("That league's already finished with this season, boss.")
else:
await msg.channel.send("We can't find that league. Typo?")
commands = [ commands = [
@ -776,8 +975,15 @@ commands = [
ShowAllTeamsCommand(), ShowAllTeamsCommand(),
SearchTeamsCommand(), SearchTeamsCommand(),
StartGameCommand(), StartGameCommand(),
StartTournamentCommand(),
StartRandomGameCommand(), StartRandomGameCommand(),
StartTournamentCommand(),
LeagueClaimCommand(),
LeagueAddOwnersCommand(),
StartLeagueCommand(),
LeaguePauseCommand(),
LeagueDisplayCommand(),
LeagueWildcardCommand(),
LeagueScheduleCommand(),
CreditCommand(), CreditCommand(),
RomanCommand(), RomanCommand(),
HelpCommand(), HelpCommand(),
@ -1058,7 +1264,16 @@ async def start_tournament_round(channel, tourney, seeding = None):
for pair in games_to_start: for pair in games_to_start:
if pair[0] is not None and pair[1] is not None: 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 = tourney.game_length) team_a = get_team_fuzzy_search(pair[0].name)
team_b = get_team_fuzzy_search(pair[1].name)
if tourney.league is not None:
if tourney.day is None:
tourney.day = tourney.league.day
team_a.set_pitcher(rotation_slot = tourney.day)
team_b.set_pitcher(rotation_slot = tourney.day)
this_game = games.game(team_a.finalize(), team_b.finalize(), length = tourney.game_length)
this_game, state_init = prepare_game(this_game) this_game, state_init = prepare_game(this_game)
state_init["is_league"] = True state_init["is_league"] = True
@ -1089,6 +1304,14 @@ async def continue_tournament_series(tourney, queue, games_list, wins_in_series)
for oldgame in queue: for oldgame in queue:
away_team = games.get_team(oldgame.teams["away"].name) away_team = games.get_team(oldgame.teams["away"].name)
home_team = games.get_team(oldgame.teams["home"].name) home_team = games.get_team(oldgame.teams["home"].name)
if tourney.league is not None:
if tourney.day is None:
tourney.day = tourney.league.day
away_team.set_pitcher(rotation_slot = tourney.day)
home_team.set_pitcher(rotation_slot = tourney.day)
this_game = games.game(away_team.finalize(), home_team.finalize(), length = tourney.game_length) this_game = games.game(away_team.finalize(), home_team.finalize(), length = tourney.game_length)
this_game, state_init = prepare_game(this_game) this_game, state_init = prepare_game(this_game)
@ -1120,7 +1343,7 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
try: try:
for i in range(0, len(games_list)): for i in range(0, len(games_list)):
game, key = games_list[i] game, key = games_list[i]
if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8: if game.over and ((key in main_controller.master_games_dic.keys() and main_controller.master_games_dic[key][1]["end_delay"] <= 8) or not key in main_controller.master_games_dic.keys()):
if game.teams['home'].name not in wins_in_series.keys(): if game.teams['home'].name not in wins_in_series.keys():
wins_in_series[game.teams["home"].name] = 0 wins_in_series[game.teams["home"].name] = 0
if game.teams['away'].name not in wins_in_series.keys(): if game.teams['away'].name not in wins_in_series.keys():
@ -1134,6 +1357,7 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
wins_in_series[winner_name] = 1 wins_in_series[winner_name] = 1
final_embed = game_over_embed(game) final_embed = game_over_embed(game)
final_embed.add_field(name="Series score:", value=f"{wins_in_series[game.teams['away'].name]} - {wins_in_series[game.teams['home'].name]}")
await channel.send(f"A {tourney.name} game just ended!") await channel.send(f"A {tourney.name} game just ended!")
await channel.send(embed=final_embed) await channel.send(embed=final_embed)
if wins_in_series[winner_name] >= int((tourney.series_length+1)/2) and not finals: if wins_in_series[winner_name] >= int((tourney.series_length+1)/2) and not finals:
@ -1148,9 +1372,34 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
except: except:
print("something went wrong in tourney_watcher") print("something went wrong in tourney_watcher")
await asyncio.sleep(4) await asyncio.sleep(4)
if tourney.league is not None:
tourney.day += 1
if len(queued_games) > 0: if len(queued_games) > 0:
if tourney.league is not None:
now = datetime.datetime.now()
validminutes = [int((60 * div)/tourney.league.games_per_hour) for div in range(0,tourney.league.games_per_hour)]
for i in range(0, len(validminutes)):
if now.minute > validminutes[i]:
if i <= len(validminutes)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else:
delta = datetime.timedelta(minutes= (60 - now.minute))
next_start = (now + delta).replace(second=0, microsecond=0)
wait_seconds = (next_start - now).seconds
await channel.send(f"The next batch of games for the {tourney.name} will start in {math.ceil(wait_seconds/60)} minutes.")
await asyncio.sleep(wait_seconds)
else:
await channel.send(f"The next batch of games for {tourney.name} will start in {int(tourney.delay/60)} minutes.") 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 asyncio.sleep(tourney.delay)
await channel.send(f"{len(queued_games)} games for {tourney.name}, starting at {filter_url}") await channel.send(f"{len(queued_games)} games for {tourney.name}, starting at {filter_url}")
@ -1160,7 +1409,10 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
if finals: #if this last round was finals 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!") embed = discord.Embed(color = discord.Color.dark_purple(), title = f"{winner_list[0]} win the {tourney.name} finals!")
if tourney.day > tourney.league.day:
tourney.league.day = tourney.day
await channel.send(embed=embed) await channel.send(embed=embed)
tourney.winner = get_team_fuzzy_search(winner_list[0])
active_tournaments.pop(active_tournaments.index(tourney)) active_tournaments.pop(active_tournaments.index(tourney))
return return
@ -1169,6 +1421,32 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
winners_string = "" winners_string = ""
for game in tourney.bracket.get_bottom_row(): for game in tourney.bracket.get_bottom_row():
winners_string += f"{game[0].name}\n{game[1].name}\n" winners_string += f"{game[0].name}\n{game[1].name}\n"
if tourney.league is not None:
now = datetime.datetime.now()
validminutes = [int((60 * div)/tourney.league.games_per_hour) for div in range(0,tourney.league.games_per_hour)]
for i in range(0, len(validminutes)):
if now.minute > validminutes[i]:
if i <= len(validminutes)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else:
delta = datetime.timedelta(minutes= (60 - now.minute))
next_start = (now + delta).replace(second=0, microsecond=0)
wait_seconds = (next_start - now).seconds
await channel.send(f"""This round of games for the {tourney.name} is now complete! The next round will start in {math.ceil(wait_seconds/60)} minutes.
Advancing teams:
{winners_string}""")
await asyncio.sleep(wait_seconds)
else:
await channel.send(f""" 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. 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: Advancing teams:
@ -1373,7 +1651,7 @@ async def game_watcher():
this_array = gamesarray.copy() this_array = gamesarray.copy()
for i in range(0,len(this_array)): for i in range(0,len(this_array)):
game, channel, user, key = this_array[i] game, channel, user, key = this_array[i]
if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8: if game.over and ((key in main_controller.master_games_dic.keys() and main_controller.master_games_dic[key][1]["end_delay"] <= 8) or not key in main_controller.master_games_dic.keys()):
final_embed = game_over_embed(game) final_embed = game_over_embed(game)
if isinstance(user, str): if isinstance(user, str):
await channel.send(f"A game started by {user} just ended.") await channel.send(f"A game started by {user} just ended.")
@ -1394,7 +1672,6 @@ def game_over_embed(game):
title_string += f" with {game.inning - (game.max_innings+1)} extra innings.\n" title_string += f" with {game.inning - (game.max_innings+1)} extra innings.\n"
else: else:
title_string += ".\n" 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 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" winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n"
@ -1406,7 +1683,10 @@ def game_over_embed(game):
winstring += f"{winning_team} wins!" winstring += f"{winning_team} wins!"
embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string)
embed.add_field(name="Final score:", value=winstring) embed.add_field(name="Final score:", value=winstring, inline=False)
embed.add_field(name=f"{game.teams['away'].name} pitcher:", value=game.teams['away'].pitcher.name)
embed.add_field(name=f"{game.teams['home'].name} pitcher:", value=game.teams['home'].pitcher.name)
embed.set_footer(text=game.weather.emoji + game.weather.name)
return embed return embed
def get_team_fuzzy_search(team_name): def get_team_fuzzy_search(team_name):
@ -1417,7 +1697,7 @@ def get_team_fuzzy_search(team_name):
team = teams[0] team = teams[0]
return team return team
async def start_league_day(channel, league, autoplay = 1): async def start_league_day(channel, league, partial = False):
current_games = [] current_games = []
games_to_start = league.schedule[str(league.day_to_series_num(league.day))] games_to_start = league.schedule[str(league.day_to_series_num(league.day))]
@ -1437,8 +1717,11 @@ async def start_league_day(channel, league, autoplay = 1):
this_game, state_init = prepare_game(this_game) this_game, state_init = prepare_game(this_game)
state_init["is_league"] = True state_init["is_league"] = True
series_string = f"Series score:" if not partial:
series_string = "Series score:"
state_init["title"] = f"{series_string} 0 - 0" state_init["title"] = f"{series_string} 0 - 0"
else:
state_init["title"] = "Interrupted series!"
discrim_string = league.name discrim_string = league.name
id = str(uuid4()) id = str(uuid4())
@ -1448,29 +1731,34 @@ async def start_league_day(channel, league, autoplay = 1):
ext = "?league=" + urllib.parse.quote_plus(league.name) ext = "?league=" + urllib.parse.quote_plus(league.name)
if league.last_series_check(): #if finals 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}") await channel.send(f"The final series of the {league.name} regular season is starting now, at {config()['simmadome_url']+ext}")
last = True last = True
else: else:
await channel.send(f"The day {league.day} series of the {league.name} is starting now, at {config()['simmadome_url']+ext}") await channel.send(f"The day {league.day} series of the {league.name} is starting now, at {config()['simmadome_url']+ext}")
last = False last = False
await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, autoplay, last) if partial:
missed_games = (league.day % league.series_length) - 1
await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, last, missed = missed_games)
else:
await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, last)
async def league_day_watcher(channel, league, games_list, filter_url, autoplay, last = False): async def league_day_watcher(channel, league, games_list, filter_url, last = False, missed = 0):
league.active = True league.active = True
autoplay -= 1 league.autoplay -= 1
if league not in active_leagues:
active_leagues.append(league) active_leagues.append(league)
series_results = {} series_results = {}
while league.active: while league.active:
queued_games = [] queued_games = []
while len(games_list) > 0: while len(games_list) > 0:
#try: try:
for i in range(0, len(games_list)): for i in range(0, len(games_list)):
game, key = games_list[i] game, key = games_list[i]
if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8: if game.over and ((key in main_controller.master_games_dic.keys() and main_controller.master_games_dic[key][1]["end_delay"] <= 8) or not key in main_controller.master_games_dic.keys()):
if game.teams['home'].name not in series_results.keys(): if game.teams['home'].name not in series_results.keys():
series_results[game.teams["home"].name] = {} series_results[game.teams["home"].name] = {}
series_results[game.teams["home"].name]["wins"] = 0 series_results[game.teams["home"].name]["wins"] = 0
@ -1500,15 +1788,17 @@ async def league_day_watcher(channel, league, games_list, filter_url, autoplay,
league.update_standings({winner_name : winner_dic, loser_name : loser_dic}) league.update_standings({winner_name : winner_dic, loser_name : loser_dic})
leagues.save_league(league) leagues.save_league(league)
final_embed = game_over_embed(game) final_embed = game_over_embed(game)
final_embed.add_field(name="Day:", value=league.day)
final_embed.add_field(name="Series score:", value=f"{series_results[game.teams['away'].name]['wins']} - {series_results[game.teams['home'].name]['wins']}")
await channel.send(f"A {league.name} game just ended!") await channel.send(f"A {league.name} game just ended!")
await channel.send(embed=final_embed) await channel.send(embed=final_embed)
if series_results[winner_name]["wins"] + series_results[winner_name]["losses"] < league.series_length: if series_results[winner_name]["wins"] + series_results[winner_name]["losses"] + missed < league.series_length:
queued_games.append(game) queued_games.append(game)
games_list.pop(i) games_list.pop(i)
break break
#except: except:
#print("something went wrong in league_day_watcher") print("something went wrong in league_day_watcher")
await asyncio.sleep(1) await asyncio.sleep(2)
league.day += 1 league.day += 1
if len(queued_games) > 0: if len(queued_games) > 0:
@ -1518,7 +1808,15 @@ async def league_day_watcher(channel, league, games_list, filter_url, autoplay,
validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)] validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)]
for i in range(0, len(validminutes)): for i in range(0, len(validminutes)):
if now.minute > validminutes[i]: if now.minute > validminutes[i]:
if i <= len(validminutes)-1: if i <= len(validminutes)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute)) delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else: else:
delta = datetime.timedelta(minutes= (60 - now.minute)) delta = datetime.timedelta(minutes= (60 - now.minute))
@ -1532,30 +1830,72 @@ async def league_day_watcher(channel, league, games_list, filter_url, autoplay,
leagues.save_league(league) leagues.save_league(league)
await asyncio.sleep(wait_seconds) await asyncio.sleep(wait_seconds)
await channel.send(f"A {league.name} series is continuing now at {filter_url}") 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, series_results) games_list = await continue_league_series(league, queued_games, games_list, series_results, missed)
else: else:
league.active = False league.active = False
if league.autoplay == 0 or config()["game_freeze"]: #if number of series to autoplay has been reached
if last or autoplay == 0: #if this series was the last of the season OR number of series to autoplay has been reached
await channel.send(embed=league.standings_embed()) await channel.send(embed=league.standings_embed())
await channel.send(f"The {league.name} is no longer autoplaying.") await channel.send(f"The {league.name} is no longer autoplaying.")
if config()["game_freeze"]:
await channel.send("Patch incoming.")
leagues.save_league(league)
active_leagues.pop(active_leagues.index(league)) active_leagues.pop(active_leagues.index(league))
return return
if last: #if last game of the season
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)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else:
delta = datetime.timedelta(minutes= (60 - now.minute))
next_start = (now + delta).replace(second=0, microsecond=0)
wait_seconds = (next_start - now).seconds
await channel.send(f"This {league.name} season is now over! The postseason (with any necessary tiebreakers) will be starting in {math.ceil(wait_seconds/60)} minutes.")
await asyncio.sleep(wait_seconds)
await league_postseason(channel, league)
#need to reset league to new season here
return
now = datetime.datetime.now() now = datetime.datetime.now()
validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)] validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)]
for i in range(0, len(validminutes)): for i in range(0, len(validminutes)):
if now.minute > validminutes[i]: if now.minute > validminutes[i]:
if i < len(validminutes)-1: if i <= len(validminutes)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute)) delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else: else:
delta = datetime.timedelta(minutes= (60 - now.minute)) delta = datetime.timedelta(minutes= (60 - now.minute))
next_start = (now + delta).replace(microsecond=0) next_start = (now + delta).replace(second=0, microsecond=0)
wait_seconds = (next_start - now).seconds wait_seconds = (next_start - now).seconds
leagues.save_league(league) leagues.save_league(league)
@ -1563,9 +1903,9 @@ async def league_day_watcher(channel, league, games_list, filter_url, autoplay,
await channel.send(f"""This {league.name} series is now complete! The next series will be starting in {int(wait_seconds/60)} minutes.""") 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) await asyncio.sleep(wait_seconds)
await start_league_day(channel, league, autoplay) await start_league_day(channel, league)
async def continue_league_series(league, queue, games_list, series_results): async def continue_league_series(league, queue, games_list, series_results, missed):
for oldgame in queue: for oldgame in queue:
away_team = games.get_team(oldgame.teams["away"].name) away_team = games.get_team(oldgame.teams["away"].name)
away_team.set_pitcher(rotation_slot=league.day) away_team.set_pitcher(rotation_slot=league.day)
@ -1576,7 +1916,12 @@ async def continue_league_series(league, queue, games_list, series_results):
state_init["is_league"] = True state_init["is_league"] = True
series_string = f"Series score:" series_string = f"Series score:"
if missed <= 0:
series_string = "Series score:"
state_init["title"] = f"{series_string} {series_results[away_team.name]['wins']} - {series_results[home_team.name]['wins']}" state_init["title"] = f"{series_string} {series_results[away_team.name]['wins']} - {series_results[home_team.name]['wins']}"
else:
state_init["title"] = "Interrupted series!"
discrim_string = league.name discrim_string = league.name
id = str(uuid4()) id = str(uuid4())
@ -1585,8 +1930,80 @@ async def continue_league_series(league, queue, games_list, series_results):
return games_list return games_list
async def league_postseason(channel, league):
embed = league.standings_embed()
embed.set_footer(text="Final Standings")
await channel.send(embed=embed)
tiebreakers = league.tiebreaker_required()
if tiebreakers != []:
await channel.send("Tiebreakers required!")
await asyncio.gather(*[start_tournament_round(channel, tourney) for tourney in tiebreakers])
for tourney in tiebreakers:
league.update_standings({tourney.winner.name : {"wins" : 1}})
leagues.save_league(league)
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)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else:
delta = datetime.timedelta(minutes= (60 - now.minute))
next_start = (now + delta).replace(second=0, microsecond=0)
wait_seconds = (next_start - now).seconds
await channel.send(f"Tiebreakers complete! Postseason starting in {math.ceil(wait_seconds/60)} minutes.")
await asyncio.sleep(wait_seconds)
tourneys = league.champ_series()
await asyncio.gather(*[start_tournament_round(channel, tourney) for tourney in tourneys])
champs = {}
for tourney in tourneys:
for team in tourney.teams.keys():
if team.name == tourney.winner.name:
champs[tourney.winner] = {"wins" : tourney.teams[team]["wins"]}
world_series = leagues.tournament(f"{league.name} Championship Series", champs, series_length=7, secs_between_games=int(3600/league.games_per_hour), secs_between_rounds=int(7200/league.games_per_hour))
world_series.build_bracket(by_wins = True)
world_series.league = league
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)-3:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (validminutes[i+2] - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
elif i <= len(validminutes)-2:
if validminutes[i+1] == now.minute:
delta = datetime.timedelta(minutes= (60 - now.minute))
else:
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
else:
delta = datetime.timedelta(minutes= (60 - now.minute))
next_start = (now + delta).replace(second=0, microsecond=0)
wait_seconds = (next_start - now).seconds
await channel.send(f"The {league.name} Championship Series is starting in {math.ceil(wait_seconds/60)} minutes!")
await asyncio.sleep(wait_seconds)
await start_tournament_round(channel, world_series)
league.champion = world_series.winner.name
leagues.save_league(league)
season_save(league)
league.season_reset()