Merge branch 'leagues'
This commit is contained in:
commit
68a48b9441
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
venv/
|
||||
matteo_env/
|
||||
__pycache__/
|
||||
simmadome/node_modules
|
||||
data/
|
||||
.git/
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -344,6 +344,7 @@ config.json
|
|||
games_config.json
|
||||
weather_config.json
|
||||
ids
|
||||
data/
|
||||
|
||||
# database
|
||||
matteo.db
|
||||
|
@ -351,5 +352,6 @@ matteo.db-wal
|
|||
matteo.db-shm
|
||||
/data/leagues/*
|
||||
/matteo_env/Lib/site-packages/flask_socketio/__init__.py
|
||||
Pipfile
|
||||
|
||||
env
|
||||
|
|
19
Dockerfile
19
Dockerfile
|
@ -1,9 +1,20 @@
|
|||
FROM python:3.8
|
||||
EXPOSE 5000
|
||||
|
||||
# - Build stage 1: frontend (simmadome/ directory)
|
||||
FROM node:alpine AS frontend
|
||||
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
|
||||
COPY . ./
|
||||
COPY --from=frontend /app/build/ simmadome/build/
|
||||
|
||||
CMD ["python", "the_prestige.py"]
|
||||
|
|
27
games.py
27
games.py
|
@ -31,13 +31,14 @@ def config():
|
|||
def all_weathers():
|
||||
weathers_dic = {
|
||||
#"Supernova" : weather("Supernova", "🌟"),
|
||||
"Midnight": weather("Midnight", "🕶"),
|
||||
#"Midnight": weather("Midnight", "🕶"),
|
||||
"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
|
||||
|
||||
|
||||
class appearance_outcomes(Enum):
|
||||
strikeoutlooking = "strikes out looking."
|
||||
strikeoutswinging = "strikes out swinging."
|
||||
|
@ -173,7 +174,7 @@ class team(object):
|
|||
if rotation_slot is None:
|
||||
self.pitcher = random.choice(temp_rotation)
|
||||
else:
|
||||
self.pitcher = temp_rotation[rotation_slot % len(temp_rotation)]
|
||||
self.pitcher = temp_rotation[(rotation_slot-1) % len(temp_rotation)]
|
||||
|
||||
def is_ready(self):
|
||||
try:
|
||||
|
@ -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))
|
||||
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:
|
||||
|
@ -313,7 +321,7 @@ class game(object):
|
|||
outcome["ishit"] = True
|
||||
if hitnum < 1:
|
||||
outcome["text"] = appearance_outcomes.single
|
||||
elif hitnum < 2.85:
|
||||
elif hitnum < 2.85 or "error" in outcome.keys():
|
||||
outcome["text"] = appearance_outcomes.double
|
||||
elif hitnum < 3.1:
|
||||
outcome["text"] = appearance_outcomes.triple
|
||||
|
@ -384,6 +392,11 @@ class game(object):
|
|||
if base is not None:
|
||||
runs += 1
|
||||
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
|
||||
|
||||
elif "advance" in outcome.keys():
|
||||
|
@ -535,6 +548,10 @@ class game(object):
|
|||
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["home_runs"] += 1
|
||||
if self.weather.name == "Thinned Veil":
|
||||
result["veil"] = True
|
||||
|
||||
|
||||
|
||||
scores_to_add += self.baserunner_check(defender, result)
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ def init_league_db(league):
|
|||
c.execute(teams_table_check_string)
|
||||
|
||||
for team in league.teams_in_league():
|
||||
print(team)
|
||||
c.execute("INSERT INTO teams (name) VALUES (?)", (team.name,))
|
||||
|
||||
player_string = "INSERT INTO stats (name, team_name) VALUES (?,?)"
|
||||
|
@ -72,22 +73,26 @@ def init_league_db(league):
|
|||
for pitcher in team.rotation:
|
||||
c.execute(player_string, (pitcher.name, team.name))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def save_league(league):
|
||||
if league_exists(league.name):
|
||||
state_dic = {
|
||||
"season" : league.season,
|
||||
"day" : league.day,
|
||||
"constraints" : league.constraints,
|
||||
"schedule" : league.schedule,
|
||||
"game_length" : league.game_length,
|
||||
"series_length" : league.series_length,
|
||||
"games_per_hour" : league.games_per_hour,
|
||||
"historic" : False
|
||||
"owner" : league.owner,
|
||||
"champions" : league.champions,
|
||||
"historic" : league.historic
|
||||
}
|
||||
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.close()
|
||||
|
||||
def add_stats(league_name, player_game_stats_list):
|
||||
conn = create_connection(league_name)
|
||||
if conn is not None:
|
||||
|
@ -122,11 +127,22 @@ def update_standings(league_name, update_dic):
|
|||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_standings(league_name):
|
||||
if league_exists(league_name):
|
||||
conn = create_connection(league_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 league_exists(league_name):
|
||||
with os.scandir(os.path.join(data_dir, league_dir)) as folder:
|
||||
for subfolder in folder:
|
||||
if league_name in subfolder.name:
|
||||
return not state(league_name)["historic"]
|
||||
return True
|
||||
return False
|
182
leagues.py
182
leagues.py
|
@ -10,13 +10,20 @@ league_dir = "leagues"
|
|||
class league_structure(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.historic = False
|
||||
self.owner = None
|
||||
self.season = 1
|
||||
self.autoplay = -1
|
||||
self.champions = {}
|
||||
|
||||
def setup(self, league_dic, division_games = 1, inter_division_games = 1, inter_league_games = 1, games_per_hour = 2):
|
||||
self.league = league_dic #key: subleague, value: {division : team_name}
|
||||
self.league = league_dic # { subleague name : { division name : [team object] } }
|
||||
self.constraints = {
|
||||
"division_games" : 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.schedule = {}
|
||||
|
@ -33,11 +40,46 @@ class league_structure(object):
|
|||
|
||||
|
||||
def last_series_check(self):
|
||||
return str(math.ceil((self.day)/self.series_length) + 1) in self.schedule.keys()
|
||||
return str(math.ceil((self.day)/self.series_length) + 1) not in self.schedule.keys()
|
||||
|
||||
def day_to_series_num(self, day):
|
||||
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):
|
||||
for subleague in iter(self.league.keys()):
|
||||
for division in iter(self.league[subleague].keys()):
|
||||
|
@ -164,19 +206,126 @@ class league_structure(object):
|
|||
day = 1
|
||||
while not scheduled:
|
||||
found = False
|
||||
if day in self.schedule.keys():
|
||||
for game_on_day in self.schedule[day]:
|
||||
if str(day) in self.schedule.keys():
|
||||
for game_on_day in self.schedule[str(day)]:
|
||||
for team in game:
|
||||
if team in game_on_day:
|
||||
found = True
|
||||
if not found:
|
||||
self.schedule[day].append(game)
|
||||
self.schedule[str(day)].append(game)
|
||||
scheduled = True
|
||||
else:
|
||||
self.schedule[day] = [game]
|
||||
self.schedule[str(day)] = [game]
|
||||
scheduled = True
|
||||
day += 1
|
||||
|
||||
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 season_length(self):
|
||||
return int(list(self.schedule.keys())[-1]) * self.series_length
|
||||
|
||||
def standings_embed(self):
|
||||
this_embed = Embed(color=Color.purple(), title=self.name)
|
||||
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()):
|
||||
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"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:
|
||||
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"{subleague} League:", value=teams_string, inline = False)
|
||||
|
||||
this_embed.set_footer(text=f"Wildcard standings as of day {self.day-1}")
|
||||
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):
|
||||
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
|
||||
|
@ -191,6 +340,9 @@ class tournament(object):
|
|||
self.round_delay = secs_between_rounds
|
||||
self.finals = False
|
||||
self.id = id
|
||||
self.league = None
|
||||
self.winner = None
|
||||
self.day = None
|
||||
|
||||
if id is None:
|
||||
self.id = random.randint(1111,9999)
|
||||
|
@ -288,7 +440,7 @@ 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:
|
||||
league_json_string = jsonpickle.encode(this_league.league, keys=True)
|
||||
json.dump(league_json_string, league_file, indent=4)
|
||||
return True
|
||||
league_db.save_league(this_league)
|
||||
|
||||
def load_league_file(league_name):
|
||||
if league_db.league_exists(league_name):
|
||||
|
@ -298,4 +450,18 @@ def load_league_file(league_name):
|
|||
this_league.league = jsonpickle.decode(json.load(league_file), keys=True, classes=team)
|
||||
with open(os.path.join(data_dir, league_dir, league_name, f"{this_league.name}.state")) as state_file:
|
||||
state_dic = json.load(state_file)
|
||||
|
||||
this_league.day = state_dic["day"]
|
||||
this_league.schedule = state_dic["schedule"]
|
||||
this_league.constraints = state_dic["constraints"]
|
||||
this_league.game_length = state_dic["game_length"]
|
||||
this_league.series_length = state_dic["series_length"]
|
||||
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.champions = state_dic["champions"]
|
||||
except:
|
||||
this_league.champions = {}
|
||||
return this_league
|
|
@ -1,6 +1,9 @@
|
|||
import asyncio, time, datetime, games, json, threading, jinja2, leagues, os
|
||||
from flask import Flask, url_for, Response, render_template, request, jsonify, send_from_directory
|
||||
import asyncio, time, datetime, games, json, threading, jinja2, leagues, os, leagues
|
||||
from leagues import league_structure
|
||||
from league_storage import league_exists
|
||||
from flask import Flask, url_for, Response, render_template, request, jsonify, send_from_directory, abort
|
||||
from flask_socketio import SocketIO, emit
|
||||
import database as db
|
||||
|
||||
app = Flask("the-prestige", static_folder='simmadome/build')
|
||||
app.config['SECRET KEY'] = 'dev'
|
||||
|
@ -16,6 +19,59 @@ def serve(path):
|
|||
else:
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
### API
|
||||
|
||||
@app.route('/api/teams/search')
|
||||
def search_teams():
|
||||
query = request.args.get('query')
|
||||
page_len = int(request.args.get('page_len'))
|
||||
page_num = int(request.args.get('page_num'))
|
||||
|
||||
if query is None:
|
||||
abort(400, "A query term is required")
|
||||
|
||||
result = db.search_teams(query)
|
||||
if page_len is not None: #pagination should probably be done in the sqlite query but this will do for now
|
||||
if page_num is None:
|
||||
abort(400, "A page_len argument must be accompanied by a page_num argument")
|
||||
result = result[page_num*page_len : (page_num + 1)*page_len]
|
||||
|
||||
return jsonify([json.loads(x[0])['name'] for x in result]) #currently all we need is the name but that can change
|
||||
|
||||
|
||||
@app.route('/api/leagues', methods=['POST'])
|
||||
def create_league():
|
||||
config = json.loads(request.data)
|
||||
|
||||
if (league_exists(config['name'])):
|
||||
abort(400, "A league by that name already exists")
|
||||
|
||||
print(config)
|
||||
league_dic = {
|
||||
subleague['name'] : {
|
||||
division['name'] : [games.get_team(team_name) for team_name in division['teams']]
|
||||
for division in subleague['divisions']
|
||||
}
|
||||
for subleague in config['structure']['subleagues']
|
||||
}
|
||||
|
||||
new_league = league_structure(config['name'])
|
||||
new_league.setup(
|
||||
league_dic,
|
||||
division_games=config['division_series'],
|
||||
inter_division_games=config['inter_division_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()
|
||||
leagues.save_league(new_league)
|
||||
|
||||
return "League created successfully"
|
||||
|
||||
|
||||
|
||||
### SOCKETS
|
||||
|
||||
thread2 = threading.Thread(target=socketio.run,args=(app,'0.0.0.0'))
|
||||
thread2.start()
|
||||
|
@ -110,6 +166,8 @@ def update_loop():
|
|||
if this_game.last_update[0]["defender"] != "":
|
||||
punc = ". "
|
||||
|
||||
|
||||
|
||||
if "fc_out" in this_game.last_update[0].keys():
|
||||
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}"
|
||||
|
@ -120,6 +178,13 @@ def update_loop():
|
|||
|
||||
state["update_emoji"] = "🏏"
|
||||
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()
|
||||
|
||||
|
|
20
simmadome/package-lock.json
generated
20
simmadome/package-lock.json
generated
|
@ -2276,6 +2276,15 @@
|
|||
"pretty-format": "^26.0.0"
|
||||
}
|
||||
},
|
||||
"@types/jquery": {
|
||||
"version": "3.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.5.tgz",
|
||||
"integrity": "sha512-6RXU9Xzpc6vxNrS6FPPapN1SxSHgQ336WC6Jj/N8q30OiaBZ00l1GBgeP7usjVZPivSkGUfL1z/WW6TX989M+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/sizzle": "*"
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
||||
|
@ -2370,6 +2379,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/sizzle": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
|
||||
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/socket.io-client": {
|
||||
"version": "1.4.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.34.tgz",
|
||||
|
@ -9529,6 +9544,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
||||
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"private": true,
|
||||
"proxy": "http://localhost:5000",
|
||||
"dependencies": {
|
||||
"jquery": "^3.5.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-router": "^5.2.0",
|
||||
|
@ -19,6 +20,7 @@
|
|||
"@testing-library/react": "^11.2.2",
|
||||
"@testing-library/user-event": "^12.6.0",
|
||||
"@types/jest": "^26.0.19",
|
||||
"@types/jquery": "^3.5.5",
|
||||
"@types/node": "^12.19.12",
|
||||
"@types/react": "^16.14.2",
|
||||
"@types/react-dom": "^16.9.10",
|
||||
|
|
310
simmadome/src/CreateLeague.css
Normal file
310
simmadome/src/CreateLeague.css
Normal file
|
@ -0,0 +1,310 @@
|
|||
.cl_table_header > .cl_subleague_bg {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.cl_league_structure_table .cl_table_row:last-child .cl_subleague_bg {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cl_league_structure_table .cl_table_row:last-child .cl_division_delete {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
height: 2rem;
|
||||
padding-left: 1rem;
|
||||
background: var(--background-secondary);
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.cl_league_main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.cl_league_name {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.cl_league_options, .cl_league_structure, .cl_confirm_box {
|
||||
display: flex;
|
||||
background: var(--background-tertiary);
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
border-radius: 1rem;
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.cl_confirm_box {
|
||||
min-width: 55rem;
|
||||
padding: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
.cl_league_options {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cl_league_structure, .cl_subleague_add_align {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.cl_league_structure {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.cl_league_structure_table {
|
||||
display: table;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.cl_headers, .cl_table_row {
|
||||
display: table-row;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
.cl_table_header, .cl_delete_filler, .cl_delete_box, .cl_division_cell {
|
||||
display: table-cell;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.cl_delete_box {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cl_league_structure_scrollbox {
|
||||
max-width: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.cl_league_structure_scrollbox::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.cl_league_structure_scrollbox {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.cl_subleague_add_align{
|
||||
margin-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.cl_subleague_header {
|
||||
display: flex;
|
||||
width:100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.cl_subleague_bg {
|
||||
background: var(--background-main);
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0rem 0.5rem;
|
||||
min-width: 22rem;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cl_subleague_name {
|
||||
flex-grow: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.cl_division_name_box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cl_division_name {
|
||||
margin-bottom: 0.5rem;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.cl_newteam_name {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.cl_division {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--background-accent);
|
||||
}
|
||||
|
||||
.cl_team, .cl_team_add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin: 0.4rem 0rem;
|
||||
}
|
||||
|
||||
.cl_team_name {
|
||||
font-size: 14pt;
|
||||
padding: 0 0.5rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cl_team_add {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
.cl_search_list {
|
||||
width: 95%;
|
||||
margin-top: 0.6rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--background-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.cl_search_result {
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.cl_search_result:hover {
|
||||
background: var(--background-main);
|
||||
}
|
||||
|
||||
.cl_league_options {
|
||||
padding: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
width: 55rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cl_option_main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cl_option_submit_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cl_option_box {
|
||||
margin: 1rem;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.cl_option_label, .cl_option_err, .cl_structure_err {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.cl_option_err, .cl_structure_err {
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.cl_option_err {
|
||||
min-height: 1.5rem;
|
||||
margin-bottom: -0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.cl_structure_err {
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
|
||||
.cl_structure_err_div {
|
||||
margin-top: -0.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.cl_structure_err_teams {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
/* button styles */
|
||||
|
||||
button > .emoji {
|
||||
margin: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.cl_subleague_delete, .cl_team_delete, .cl_division_delete, .cl_subleague_add, .cl_division_add {
|
||||
padding: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cl_subleague_delete, .cl_team_delete, .cl_division_delete {
|
||||
background: var(--accent-red);
|
||||
}
|
||||
|
||||
.cl_subleague_add, .cl_division_add {
|
||||
background: var(--accent-green);
|
||||
}
|
||||
|
||||
.cl_subleague_add {
|
||||
position: relative;
|
||||
top: 1.6rem;
|
||||
}
|
||||
|
||||
.cl_division_add {
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.cl_delete_filler {
|
||||
min-width: 3rem;
|
||||
}
|
||||
|
||||
.cl_option_submit {
|
||||
padding: 1rem 2rem;
|
||||
height: 2rem;
|
||||
border: none;
|
||||
border-radius: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-green);
|
||||
font-size: 14pt;
|
||||
}
|
463
simmadome/src/CreateLeague.tsx
Normal file
463
simmadome/src/CreateLeague.tsx
Normal file
|
@ -0,0 +1,463 @@
|
|||
import React, {useState, useRef, useLayoutEffect, useReducer} from 'react';
|
||||
import {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID, DistributiveOmit} from './util';
|
||||
import './CreateLeague.css';
|
||||
import twemoji from 'twemoji';
|
||||
|
||||
// STATE CLASSES
|
||||
|
||||
class LeagueStructureState {
|
||||
subleagues: SubleagueState[]
|
||||
|
||||
constructor(subleagues: SubleagueState[] = []) {
|
||||
this.subleagues = subleagues;
|
||||
}
|
||||
}
|
||||
|
||||
class SubleagueState {
|
||||
name: string
|
||||
divisions: DivisionState[]
|
||||
id: string|number
|
||||
|
||||
constructor(divisions: DivisionState[] = []) {
|
||||
this.name = "";
|
||||
this.divisions = divisions;
|
||||
this.id = getUID();
|
||||
}
|
||||
}
|
||||
|
||||
class DivisionState {
|
||||
name: string
|
||||
teams: TeamState[]
|
||||
id: string|number
|
||||
|
||||
constructor() {
|
||||
this.name = "";
|
||||
this.teams = [];
|
||||
this.id = getUID();
|
||||
}
|
||||
}
|
||||
|
||||
class TeamState {
|
||||
name: string
|
||||
id: string|number
|
||||
|
||||
constructor(name: string = "") {
|
||||
this.name = name;
|
||||
this.id = getUID();
|
||||
}
|
||||
}
|
||||
|
||||
// STRUCTURE REDUCER
|
||||
|
||||
type StructureReducerActions =
|
||||
{type: 'remove_subleague', subleague_index: number} |
|
||||
{type: 'add_subleague'} |
|
||||
{type: 'rename_subleague', subleague_index: number, name: string} |
|
||||
{type: 'remove_divisions', division_index: number} |
|
||||
{type: 'add_divisions'} |
|
||||
{type: 'rename_division', subleague_index: number, division_index: number, name: string} |
|
||||
{type: 'remove_team', subleague_index: number, division_index: number, name:string} |
|
||||
{type: 'add_team', subleague_index:number, division_index:number, name:string}
|
||||
|
||||
function leagueStructureReducer(state: LeagueStructureState, action: StructureReducerActions): LeagueStructureState {
|
||||
switch (action.type) {
|
||||
case 'remove_subleague':
|
||||
return {subleagues: removeIndex(state.subleagues, action.subleague_index)};
|
||||
case 'add_subleague':
|
||||
return {subleagues: append(state.subleagues, new SubleagueState(
|
||||
arrayOf(state.subleagues[0].divisions.length, i =>
|
||||
new DivisionState()
|
||||
)
|
||||
))}
|
||||
case 'rename_subleague':
|
||||
return replaceSubleague(state, action.subleague_index, subleague => {
|
||||
let nSubleague = shallowClone(subleague);
|
||||
nSubleague.name = action.name;
|
||||
return nSubleague;
|
||||
});
|
||||
case 'remove_divisions':
|
||||
return {subleagues: state.subleagues.map(subleague => {
|
||||
let nSubleague = shallowClone(subleague);
|
||||
nSubleague.divisions = removeIndex(subleague.divisions, action.division_index)
|
||||
return nSubleague;
|
||||
})};
|
||||
case 'add_divisions':
|
||||
return {subleagues: state.subleagues.map(subleague => {
|
||||
let nSubleague = shallowClone(subleague);
|
||||
nSubleague.divisions = append(subleague.divisions, new DivisionState())
|
||||
return nSubleague;
|
||||
})};
|
||||
case 'rename_division':
|
||||
return replaceDivision(state, action.subleague_index, action.division_index, division => {
|
||||
let nDivision = shallowClone(division);
|
||||
nDivision.name = action.name;
|
||||
return nDivision;
|
||||
});
|
||||
case 'remove_team':
|
||||
return replaceDivision(state, action.subleague_index, action.division_index, division => {
|
||||
let nDivision = shallowClone(division);
|
||||
nDivision.teams = removeIndex(division.teams, division.teams.findIndex(val => val.name === action.name));
|
||||
return nDivision;
|
||||
});
|
||||
case 'add_team':
|
||||
return replaceDivision(state, action.subleague_index, action.division_index, division => {
|
||||
let nDivision = shallowClone(division);
|
||||
nDivision.teams = append(division.teams, new TeamState(action.name));
|
||||
return nDivision;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function replaceSubleague(state: LeagueStructureState, si: number, func: (val: SubleagueState) => SubleagueState) {
|
||||
return {subleagues: replaceIndex(state.subleagues, si, func(state.subleagues[si]))}
|
||||
}
|
||||
|
||||
function replaceDivision(state: LeagueStructureState, si: number, di: number, func:(val: DivisionState) => DivisionState) {
|
||||
return replaceSubleague(state, si, subleague => {
|
||||
let nSubleague = shallowClone(subleague);
|
||||
nSubleague.divisions = replaceIndex(subleague.divisions, di, func(subleague.divisions[di]));
|
||||
return nSubleague;
|
||||
});
|
||||
}
|
||||
|
||||
// OPTIONS REDUCER
|
||||
|
||||
class LeagueOptionsState {
|
||||
games_series = "3"
|
||||
intra_division_series = "8"
|
||||
inter_division_series = "16"
|
||||
inter_league_series = "8"
|
||||
top_postseason = "1"
|
||||
wildcards = "0"
|
||||
}
|
||||
|
||||
type OptionsReducerActions =
|
||||
{type: 'set_games_series', value: string} |
|
||||
{type: 'set_intra_division_series', value: string} |
|
||||
{type: 'set_inter_division_series', value: string} |
|
||||
{type: 'set_inter_league_series', value: string} |
|
||||
{type: 'set_top_postseason', value: string} |
|
||||
{type: 'set_wildcards', value: string}
|
||||
|
||||
function LeagueOptionsReducer(state: LeagueOptionsState, action: OptionsReducerActions) {
|
||||
let newState = shallowClone(state);
|
||||
switch (action.type) {
|
||||
case 'set_games_series':
|
||||
newState.games_series = action.value;
|
||||
break;
|
||||
case 'set_intra_division_series':
|
||||
newState.intra_division_series = action.value;
|
||||
break;
|
||||
case 'set_inter_division_series':
|
||||
newState.inter_division_series = action.value;
|
||||
break;
|
||||
case 'set_inter_league_series':
|
||||
newState.inter_league_series = action.value;
|
||||
break;
|
||||
case 'set_top_postseason':
|
||||
newState.top_postseason = action.value;
|
||||
break;
|
||||
case 'set_wildcards':
|
||||
newState.wildcards = action.value;
|
||||
break;
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
// CREATE LEAGUE
|
||||
|
||||
let initLeagueStructure = {
|
||||
subleagues: [0, 1].map((val) =>
|
||||
new SubleagueState([0, 1].map((val) =>
|
||||
new DivisionState()
|
||||
))
|
||||
)
|
||||
};
|
||||
|
||||
function CreateLeague() {
|
||||
let [name, setName] = useState("");
|
||||
let [showError, setShowError] = useState(false);
|
||||
let [nameExists, setNameExists] = useState(false);
|
||||
let [createSuccess, setCreateSuccess] = useState(false);
|
||||
let [structure, structureDispatch] = useReducer(leagueStructureReducer, initLeagueStructure);
|
||||
let [options, optionsDispatch] = useReducer(LeagueOptionsReducer, new LeagueOptionsState());
|
||||
|
||||
let self = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (self.current) {
|
||||
twemoji.parse(self.current)
|
||||
}
|
||||
})
|
||||
|
||||
if (createSuccess) {
|
||||
return(
|
||||
<div className="cl_league_main" ref={self}>
|
||||
<div className="cl_confirm_box">
|
||||
League created succesfully!
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cl_league_main" ref={self}>
|
||||
<input type="text" className="cl_league_name" placeholder="League Name" value={name} onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
setNameExists(false);
|
||||
}}/>
|
||||
<div className="cl_structure_err">{
|
||||
name === "" && showError ? "A name is required." :
|
||||
nameExists && showError ? "A league by that name already exists" :
|
||||
""
|
||||
}</div>
|
||||
<LeagueStructre state={structure} dispatch={structureDispatch} showError={showError}/>
|
||||
<div className="cl_league_options">
|
||||
<LeagueOptions state={options} dispatch={optionsDispatch} showError={showError}/>
|
||||
<div className="cl_option_submit_box">
|
||||
<button className="cl_option_submit" onClick={e => {
|
||||
if (!validRequest(name, structure, options)) {
|
||||
setShowError(true);
|
||||
} else {
|
||||
let req = new XMLHttpRequest();
|
||||
let data = makeRequest(name, structure, options);
|
||||
req.open("POST", "/api/leagues", true);
|
||||
req.setRequestHeader("Content-type", "application/json");
|
||||
req.onreadystatechange = () => {
|
||||
if(req.readyState === 4) {
|
||||
if (req.status === 200) {
|
||||
setCreateSuccess(true);
|
||||
}
|
||||
if (req.status === 400) {
|
||||
setNameExists(true);
|
||||
setShowError(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
req.send(data);
|
||||
|
||||
}
|
||||
}}>Submit</button>
|
||||
<div className="cl_option_err">{
|
||||
!validRequest(name, structure, options) && showError ?
|
||||
"Cannot create league. Some information is missing or invalid." : ""
|
||||
}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function makeRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) {
|
||||
return JSON.stringify({
|
||||
name: name,
|
||||
structure: {
|
||||
subleagues: structure.subleagues.map(subleague => ({
|
||||
name: subleague.name,
|
||||
divisions: subleague.divisions.map(division => ({
|
||||
name: division.name,
|
||||
teams: division.teams.map(team => team.name)
|
||||
}))
|
||||
}))
|
||||
},
|
||||
games_per_series: Number(options.games_series),
|
||||
division_series: Number(options.intra_division_series),
|
||||
inter_division_series: Number(options.inter_division_series),
|
||||
inter_league_series: Number(options.inter_league_series),
|
||||
top_postseason: Number(options.top_postseason),
|
||||
wildcards: Number(options.wildcards)
|
||||
});
|
||||
}
|
||||
|
||||
function validRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) {
|
||||
return (
|
||||
name !== "" &&
|
||||
validNumber(options.games_series) &&
|
||||
validNumber(options.intra_division_series) &&
|
||||
validNumber(options.inter_division_series) &&
|
||||
validNumber(options.inter_league_series) &&
|
||||
validNumber(options.top_postseason) &&
|
||||
validNumber(options.wildcards, 0) &&
|
||||
structure.subleagues.length % 2 === 0 &&
|
||||
structure.subleagues.every(subleague =>
|
||||
subleague.name !== "" &&
|
||||
subleague.divisions.every(division =>
|
||||
division.name !== "" &&
|
||||
division.teams.length >= 2
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function validNumber(value: string, min = 1) {
|
||||
return !isNaN(Number(value)) && Number(value) >= min
|
||||
}
|
||||
|
||||
// LEAGUE STRUCUTRE
|
||||
|
||||
function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch<StructureReducerActions>, showError: boolean}) {
|
||||
return (
|
||||
<div className="cl_league_structure">
|
||||
<div className="cl_league_structure_scrollbox">
|
||||
<div className="cl_subleague_add_align">
|
||||
<div className="cl_league_structure_table">
|
||||
<SubleagueHeaders subleagues={props.state.subleagues} dispatch={props.dispatch} showError={props.showError}/>
|
||||
<Divisions subleagues={props.state.subleagues} dispatch={props.dispatch} showError={props.showError}/>
|
||||
</div>
|
||||
<button className="cl_subleague_add" onClick={e => props.dispatch({type: 'add_subleague'})}>➕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cl_structure_err">{props.state.subleagues.length % 2 !== 0 && props.showError ? "Must have an even number of subleagues." : ""}</div>
|
||||
<button className="cl_division_add" onClick={e => props.dispatch({type: 'add_divisions'})}>➕</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SubleagueHeaders(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<StructureReducerActions>, showError:boolean}) {
|
||||
return (
|
||||
<div className="cl_headers">
|
||||
<div key="filler" className="cl_delete_filler"/>
|
||||
{props.subleagues.map((subleague, i) => (
|
||||
<div key={subleague.id} className="cl_table_header">
|
||||
<div className="cl_subleague_bg">
|
||||
<SubleageHeader state={subleague} canDelete={props.subleagues.length > 1} dispatch={action =>
|
||||
props.dispatch(Object.assign({subleague_index: i}, action))
|
||||
}/>
|
||||
<div className="cl_structure_err">{subleague.name === "" && props.showError ? "A name is required." : ""}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SubleageHeader(props: {state: SubleagueState, canDelete: boolean, dispatch:(action: DistributiveOmit<StructureReducerActions, 'subleague_index'>) => void}) {
|
||||
return (
|
||||
<div className="cl_subleague_header">
|
||||
<input type="text" className="cl_subleague_name" placeholder="Subleague Name" value={props.state.name} onChange={e =>
|
||||
props.dispatch({type: 'rename_subleague', name: e.target.value})
|
||||
}/>
|
||||
{props.canDelete ? <button className="cl_subleague_delete" onClick={e => props.dispatch({type: 'remove_subleague'})}>➖</button> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<StructureReducerActions>, showError: boolean}) {
|
||||
return (<>
|
||||
{props.subleagues[0].divisions.map((val, di) => (
|
||||
<div key={val.id} className="cl_table_row">
|
||||
<div key="delete" className="cl_delete_box">
|
||||
{props.subleagues[0].divisions.length > 1 ?
|
||||
<button className="cl_division_delete" onClick={e => props.dispatch({type: 'remove_divisions', division_index: di})}>➖</button> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
{props.subleagues.map((subleague, si) => (
|
||||
<div key={subleague.id} className="cl_division_cell">
|
||||
<div className="cl_subleague_bg">
|
||||
<Division state={subleague.divisions[di]} dispatch={action =>
|
||||
props.dispatch(Object.assign({subleague_index: si, division_index: di}, action))
|
||||
} showError={props.showError}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</>);
|
||||
}
|
||||
|
||||
function Division(props: {state: DivisionState, dispatch:(action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void, showError:boolean}) {
|
||||
let [newName, setNewName] = useState("");
|
||||
let [searchResults, setSearchResults] = useState<string[]>([]);
|
||||
let newNameInput = useRef<HTMLInputElement>(null);
|
||||
let resultList = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (resultList.current) {
|
||||
twemoji.parse(resultList.current)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="cl_division">
|
||||
<div className="cl_division_name_box">
|
||||
<input type="text" className="cl_division_name" placeholder="Division Name" key="input" value={props.state.name} onChange={e =>
|
||||
props.dispatch({type: 'rename_division', name: e.target.value})
|
||||
}/>
|
||||
<div className="cl_structure_err cl_structure_err_div">{props.state.name === "" && props.showError ? "A name is required." : ""}</div>
|
||||
</div>
|
||||
{props.state.teams.map((team, i) => (
|
||||
<div className="cl_team" key={team.id}>
|
||||
<div className="cl_team_name">{team.name}</div>
|
||||
<button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: team.name})}>➖</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="cl_team_add">
|
||||
<input type="text" className="cl_newteam_name" placeholder="Add team..." value={newName} ref={newNameInput}
|
||||
onChange={e => {
|
||||
let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'});
|
||||
fetch("/api/teams/search?" + params.toString())
|
||||
.then(response => response.json())
|
||||
.then(data => setSearchResults(data));
|
||||
setNewName(e.target.value);
|
||||
}}/>
|
||||
</div>
|
||||
{searchResults.length > 0 && newName.length > 0 ?
|
||||
(<div className="cl_search_list" ref={resultList}>
|
||||
{searchResults.map(result =>
|
||||
<div className="cl_search_result" key={result} onClick={e => {
|
||||
props.dispatch({type:'add_team', name: result});
|
||||
setNewName("");
|
||||
if (newNameInput.current) {
|
||||
newNameInput.current.focus();
|
||||
}
|
||||
}}>{result}</div>
|
||||
)}
|
||||
</div>):
|
||||
<div/>
|
||||
}
|
||||
<div className="cl_structure_err cl_structure_err_teams">{props.state.teams.length < 2 && props.showError ? "Must have at least 2 teams." : ""}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// LEAGUE OPTIONS
|
||||
|
||||
function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispatch<OptionsReducerActions>, showError: boolean}) {
|
||||
return (
|
||||
<div className="cl_option_main">
|
||||
<div className="cl_option_column">
|
||||
<NumberInput title="Number of games per series" value={props.state.games_series} setValue={(value: string) =>
|
||||
props.dispatch({type: 'set_games_series', value: value})} showError={props.showError}/>
|
||||
<NumberInput title="Number of teams from top of division to postseason" value={props.state.top_postseason} setValue={(value: string) =>
|
||||
props.dispatch({type: 'set_top_postseason', value: value})} showError={props.showError}/>
|
||||
<NumberInput title="Number of wildcards" value={props.state.wildcards} minValue={0} setValue={(value: string) =>
|
||||
props.dispatch({type: 'set_wildcards', value: value})} showError={props.showError}/>
|
||||
</div>
|
||||
<div className="cl_option_column">
|
||||
<NumberInput title="Number of series with each division opponent" value={props.state.intra_division_series} setValue={(value: string) =>
|
||||
props.dispatch({type: 'set_intra_division_series', value: value})} showError={props.showError}/>
|
||||
<NumberInput title="Number of inter-divisional series" value={props.state.inter_division_series} setValue={(value: string) =>
|
||||
props.dispatch({type: 'set_inter_division_series', value: value})} showError={props.showError}/>
|
||||
<NumberInput title="Number of inter-league series" value={props.state.inter_league_series} setValue={(value: string) =>
|
||||
props.dispatch({type: 'set_inter_league_series', value: value})} showError={props.showError}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue?:number}) {
|
||||
let minValue = 1;
|
||||
if (props.minValue !== undefined) {
|
||||
minValue = props.minValue
|
||||
}
|
||||
return (
|
||||
<div className="cl_option_box">
|
||||
<div className="cl_option_label">{props.title}</div>
|
||||
<input className="cl_option_input" type="number" min={minValue} value={props.value} onChange={e => props.setValue(e.target.value)}/>
|
||||
<div className="cl_option_err">{(!isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than "+minValue : ""}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateLeague;
|
|
@ -1,5 +1,4 @@
|
|||
.game {
|
||||
align-self: stretch;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -3,5 +3,35 @@
|
|||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.history_box {
|
||||
width: 100%;
|
||||
margin-top: 3rem;
|
||||
padding: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
background: var(--background-main);
|
||||
border-radius: 0.25rem;
|
||||
width: 100%;
|
||||
min-width: 32rem;
|
||||
max-width: 44rem;
|
||||
box-sizing: border-box;
|
||||
border: 4px solid;
|
||||
border-radius: 4px;
|
||||
border-color: var(--highlight);
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.history_title {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.history_update {
|
||||
height: 4rem;
|
||||
margin: 0.5rem;
|
||||
}
|
|
@ -1,22 +1,62 @@
|
|||
import React, {useState} from 'react';
|
||||
import React, {useState, useRef, useLayoutEffect} from 'react';
|
||||
import twemoji from 'twemoji';
|
||||
import ReactRouter from 'react-router';
|
||||
import {GameState, useListener} from './GamesUtil';
|
||||
import './GamePage.css';
|
||||
import Game from './Game';
|
||||
import {getUID} from './util';
|
||||
|
||||
function GamePage(props: ReactRouter.RouteComponentProps<{id: string}>) {
|
||||
let [games, setGames] = useState<[string, GameState][]>([]);
|
||||
useListener((newGames) => setGames(newGames));
|
||||
let [game, setGame] = useState<[string, GameState]|undefined>(undefined);
|
||||
let history = useRef<[number, string, string][]>([]);
|
||||
|
||||
useListener((newGames) => {
|
||||
let newGame = newGames.find((gamePair) => gamePair[0] === props.match.params.id);
|
||||
setGame(newGame);
|
||||
console.log(newGame);
|
||||
if (newGame !== undefined && newGame[1].start_delay < 0 && newGame[1].end_delay > 8) {
|
||||
history.current.unshift([getUID(), newGame[1].update_emoji, newGame[1].update_text]);
|
||||
if (history.current.length > 8) {
|
||||
history.current.pop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (game === undefined) {
|
||||
return <div id="game_container">The game you're looking for either doesn't exist or has already ended.</div>
|
||||
}
|
||||
|
||||
let game = games.find((game) => game[0] === props.match.params.id)
|
||||
return (
|
||||
<div id="game_container">
|
||||
{ game ?
|
||||
<Game gameId={game[0]} state={game[1]}/> :
|
||||
"The game you're looking for either doesn't exist or has already ended."
|
||||
<Game gameId={game[0]} state={game[1]}/>
|
||||
{ history.current.length > 0 ?
|
||||
<GameHistory history={history.current}/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GameHistory(props: {history: [number, string, string][]}) {
|
||||
let self = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (self.current) {
|
||||
twemoji.parse(self.current);
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="history_box" ref={self}>
|
||||
<div className="history_title">History</div>
|
||||
{props.history.map((update) => (
|
||||
<div className="update history_update" key={update[0]}>
|
||||
<div className="update_emoji">{update[1]}</div>
|
||||
<div className="update_text">{update[2]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GamePage;
|
|
@ -76,9 +76,4 @@
|
|||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.emptyslot {
|
||||
border: none;
|
||||
min-height: 0px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ function GamesPage() {
|
|||
let [search, setSearch] = useState(window.location.search);
|
||||
useEffect(() => {
|
||||
setSearch(window.location.search);
|
||||
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [window.location.search])
|
||||
|
||||
let searchparams = new URLSearchParams(search);
|
||||
|
|
|
@ -20,6 +20,8 @@ interface GameState {
|
|||
update_text: string
|
||||
is_league: boolean
|
||||
leagueoruser: string
|
||||
start_delay: number
|
||||
end_delay: number
|
||||
}
|
||||
|
||||
type GameList = ([id: string, game: GameState] | null)[];
|
||||
|
@ -32,6 +34,7 @@ const useListener = (onUpdate: (update: [string, GameState][]) => void, url: str
|
|||
socket.on('connect', () => socket.emit('recieved', {}));
|
||||
socket.on('states_update', onUpdate);
|
||||
return () => {socket.disconnect()};
|
||||
//eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [url])
|
||||
}
|
||||
|
||||
|
|
BIN
simmadome/src/img/github.png
Normal file
BIN
simmadome/src/img/github.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
simmadome/src/img/patreon.png
Normal file
BIN
simmadome/src/img/patreon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
BIN
simmadome/src/img/twitter.png
Executable file
BIN
simmadome/src/img/twitter.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
|
@ -58,23 +58,51 @@ h2 {
|
|||
#link_div {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 30px;
|
||||
top: 1rem;
|
||||
right: 2rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#link_div > a {
|
||||
.github_logo, .twitter_logo, .patreon_container {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.patreon_container {
|
||||
border-radius: 1rem;
|
||||
background: #FF424D;
|
||||
}
|
||||
|
||||
.patreon_logo {
|
||||
box-sizing: border-box;
|
||||
padding: 0.35rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
position: relative;
|
||||
left: 0.1rem;
|
||||
bottom: 0.05rem;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#link_div > a:link, #link_div > a:visited {
|
||||
a:link, a:visited {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
#link_div > a:hover {
|
||||
a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#utility_links {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 2rem;
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
|
||||
import './index.css';
|
||||
import GamesPage from './GamesPage';
|
||||
import GamePage from './GamePage';
|
||||
import CreateLeague from './CreateLeague';
|
||||
import discordlogo from "./img/discord.png";
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import patreonLogo from './img/patreon.png';
|
||||
import githubLogo from './img/github.png';
|
||||
import twitterLogo from './img/twitter.png';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
|
@ -13,6 +17,7 @@ ReactDOM.render(
|
|||
<Header />
|
||||
<Switch>
|
||||
<Route path="/game/:id" component={GamePage}/>
|
||||
<Route path="/create_league" component={CreateLeague} />
|
||||
<Route path="/" component={GamesPage}/>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
@ -20,13 +25,25 @@ ReactDOM.render(
|
|||
document.getElementById('root')
|
||||
);
|
||||
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<div id="header">
|
||||
<div id="link_div">
|
||||
<a href="https://www.patreon.com/sixteen" className="link" target="_blank" rel="noopener noreferrer">Patreon</a><br />
|
||||
<a href="https://github.com/Sakimori/matteo-the-prestige" className="link" target="_blank" rel="noopener noreferrer">Github</a><br />
|
||||
<a href="https://twitter.com/intent/follow?screen_name=SIBR_XVI" className="link" target="_blank" rel="noopener noreferrer">Twitter</a>
|
||||
<a href="https://www.patreon.com/sixteen" className="patreon_link" target="_blank" rel="noopener noreferrer">
|
||||
<div className="patreon_container">
|
||||
<img className="patreon_logo" src={patreonLogo} alt="Patreon"/>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://github.com/Sakimori/matteo-the-prestige" className="github_link" target="_blank" rel="noopener noreferrer">
|
||||
<img className="github_logo" src={githubLogo} alt="Github"/>
|
||||
</a>
|
||||
<a href="https://twitter.com/intent/follow?screen_name=SIBR_XVI" className="twitter_link" target="_blank" rel="noopener noreferrer">
|
||||
<img className="twitter_logo" src={twitterLogo} alt="Twitter"/>
|
||||
</a>
|
||||
</div>
|
||||
<div id="utility_links">
|
||||
<Link to="/create_league">Create a League</Link>
|
||||
</div>
|
||||
<a href="/" className="page_header"><h2 className="page_header" style={{fontSize:"50px"} as React.CSSProperties}>THE SIMMADOME</h2></a>
|
||||
<h2 className="page_header">Join SIBR on <a href="https://discord.gg/UhAajY2NCW" className="link"><img src={discordlogo} alt="" height="30"/></a> to start your own games!</h2>
|
||||
|
|
37
simmadome/src/util.tsx
Normal file
37
simmadome/src/util.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import {useRef} from 'react';
|
||||
|
||||
|
||||
function removeIndex(arr: any[], index: number) {
|
||||
return arr.slice(0, index).concat(arr.slice(index+1));
|
||||
}
|
||||
|
||||
function replaceIndex<T>(arr: T[], index: number, val: T) {
|
||||
return arr.slice(0, index).concat([val]).concat(arr.slice(index+1));
|
||||
}
|
||||
|
||||
function append<T>(arr: T[], val: T) {
|
||||
return arr.concat([val]);
|
||||
}
|
||||
|
||||
function arrayOf<T>(length: number, func: (i: number) => T): T[] {
|
||||
var out: T[] = [];
|
||||
for (var i = 0; i < length; i++) {
|
||||
out.push(func(i));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function shallowClone<T>(obj: T): T {
|
||||
return Object.assign({}, obj);
|
||||
}
|
||||
|
||||
let getUID = function() { // does NOT generate UUIDs. Meant to create list keys ONLY
|
||||
let id = 0;
|
||||
return function() { return id++ }
|
||||
}()
|
||||
|
||||
type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
|
||||
//type DistributivePick<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
|
||||
|
||||
export {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID};
|
||||
export type {DistributiveOmit};
|
|
@ -6,4 +6,4 @@ SELECT name,
|
|||
ROUND(strikeouts_given*27.0/(outs_pitched*1.0),3) as kper9,
|
||||
ROUND(strikeouts_given*1.0/walks_allowed*1.0,3) as kperbb
|
||||
FROM stats WHERE outs_pitched > 150
|
||||
ORDER BY bbper9 ASC;
|
||||
ORDER BY era ASC;
|
538
the_prestige.py
538
the_prestige.py
|
@ -1,6 +1,7 @@
|
|||
import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues, datetime
|
||||
import database as db
|
||||
import onomancer as ono
|
||||
from league_storage import league_exists
|
||||
from the_draft import Draft, DRAFT_ROUNDS
|
||||
from flask import Flask
|
||||
from uuid import uuid4
|
||||
|
@ -137,9 +138,9 @@ class StartGameCommand(Command):
|
|||
league = command.split("\n")[0].split("--league ")[1].split("-")[0].strip()
|
||||
try:
|
||||
if "-d " in command.split("\n")[0]:
|
||||
day = int(command.split("\n")[0].split("-d ")[1].split("-")[0].strip())-1
|
||||
day = int(command.split("\n")[0].split("-d ")[1].split("-")[0].strip())
|
||||
elif "--day " in command.split("\n")[0]:
|
||||
day = int(command.split("\n")[0].split("--day ")[1].split("-")[0].strip())-1
|
||||
day = int(command.split("\n")[0].split("--day ")[1].split("-")[0].strip())
|
||||
except ValueError:
|
||||
await msg.channel.send("Make sure you put an integer after the -d flag.")
|
||||
return
|
||||
|
@ -201,7 +202,7 @@ class StartRandomGameCommand(Command):
|
|||
|
||||
channel = msg.channel
|
||||
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()
|
||||
|
||||
game = games.game(random.choice(teamslist).finalize(), random.choice(teamslist).finalize())
|
||||
|
@ -561,7 +562,12 @@ class StartTournamentCommand(Command):
|
|||
if team == None:
|
||||
await msg.channel.send(f"We couldn't find {name}. Try again?")
|
||||
return
|
||||
team_dic[team] = {"wins": 0}
|
||||
add = True
|
||||
for extant_team in team_dic.keys():
|
||||
if extant_team.name == team.name:
|
||||
add = False
|
||||
if add:
|
||||
team_dic[team] = {"wins": 0}
|
||||
|
||||
channel = msg.channel
|
||||
await msg.delete()
|
||||
|
@ -724,27 +730,205 @@ class StartDraftCommand(Command):
|
|||
raise SlowDraftError('Too slow')
|
||||
return draft_message
|
||||
|
||||
class DebugLeague(Command):
|
||||
name = "league"
|
||||
class DebugLeagueStart(Command):
|
||||
name = "startdebugleague"
|
||||
|
||||
async def execute(self, msg, command):
|
||||
league = leagues.league_structure("test2")
|
||||
league.setup({
|
||||
"nL" : {
|
||||
"nL west" : [get_team_fuzzy_search("lockpicks"), get_team_fuzzy_search("liches")],
|
||||
"nL east" : [get_team_fuzzy_search("bethesda soft"), get_team_fuzzy_search("traverse city")]
|
||||
},
|
||||
"aL" : {
|
||||
"aL west" : [get_team_fuzzy_search("deep space"), get_team_fuzzy_search("phoenix")],
|
||||
"aL east" : [get_team_fuzzy_search("cheyenne mountain"), get_team_fuzzy_search("tarot dragons")]
|
||||
}
|
||||
}, division_games=6, inter_division_games=3, inter_league_games=3, games_per_hour = 12)
|
||||
league.generate_schedule()
|
||||
leagues.save_league(league)
|
||||
await start_league_day(msg.channel, league, autoplay = 1)
|
||||
if not league_exists("test2"):
|
||||
league = leagues.league_structure("test2")
|
||||
league.setup({
|
||||
"nL" : {
|
||||
"nL west" : [get_team_fuzzy_search("lockpicks"), get_team_fuzzy_search("liches")],
|
||||
"nL east" : [get_team_fuzzy_search("bethesda soft"), get_team_fuzzy_search("traverse city")]
|
||||
},
|
||||
"aL" : {
|
||||
"aL west" : [get_team_fuzzy_search("deep space"), get_team_fuzzy_search("phoenix")],
|
||||
"aL east" : [get_team_fuzzy_search("cheyenne mountain"), get_team_fuzzy_search("tarot dragons")]
|
||||
}
|
||||
}, division_games=6, inter_division_games=3, inter_league_games=3, games_per_hour = 12)
|
||||
league.generate_schedule()
|
||||
leagues.save_league(league)
|
||||
|
||||
class DebugLeagueDisplay(Command):
|
||||
name = "displaydebugleague"
|
||||
|
||||
async def execute(self, msg, command):
|
||||
if league_exists("test2"):
|
||||
league = leagues.load_league_file("test2")
|
||||
await msg.channel.send(embed=league.standings_embed())
|
||||
|
||||
class StartLeagueCommand(Command):
|
||||
name = "startleague"
|
||||
template = "m;startleague [league name]\n[games per hour]"
|
||||
description = """Optional flag for the first line: `--queue X` or `-q X` to play X number of series before stopping.
|
||||
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
|
||||
command = command.split("\n")[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.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 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"]:
|
||||
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."
|
||||
|
||||
async def execute(self, msg, command):
|
||||
if league_exists(command.strip()):
|
||||
league = leagues.load_league_file(command.strip())
|
||||
await msg.channel.send(embed=league.standings_embed())
|
||||
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} commissioner is doing a great job. That's you, by the way.")
|
||||
else:
|
||||
await msg.channel.send("That league has already been claimed!")
|
||||
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 msg.author.id in config()["owners"]):
|
||||
for user in msg.mentions:
|
||||
if user.id not in league.owner:
|
||||
league.owner.append(user.id)
|
||||
await msg.channel.send(f"The new {league} front office is now up and running.")
|
||||
return
|
||||
else:
|
||||
await msg.channel.send(f"That league hasn't been claimed yet. Try m;claimleague first.")
|
||||
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 = [
|
||||
IntroduceCommand(),
|
||||
|
@ -765,14 +949,22 @@ commands = [
|
|||
ShowAllTeamsCommand(),
|
||||
SearchTeamsCommand(),
|
||||
StartGameCommand(),
|
||||
StartTournamentCommand(),
|
||||
StartRandomGameCommand(),
|
||||
StartTournamentCommand(),
|
||||
LeagueClaimCommand(),
|
||||
LeagueAddOwnersCommand(),
|
||||
StartLeagueCommand(),
|
||||
LeaguePauseCommand(),
|
||||
LeagueDisplayCommand(),
|
||||
LeagueWildcardCommand(),
|
||||
LeagueScheduleCommand(),
|
||||
CreditCommand(),
|
||||
RomanCommand(),
|
||||
HelpCommand(),
|
||||
StartDraftCommand(),
|
||||
DraftPlayerCommand(),
|
||||
DebugLeague()
|
||||
DebugLeagueStart(),
|
||||
DebugLeagueDisplay()
|
||||
]
|
||||
|
||||
client = discord.Client()
|
||||
|
@ -1046,7 +1238,16 @@ 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].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)
|
||||
|
||||
state_init["is_league"] = True
|
||||
|
@ -1077,6 +1278,14 @@ 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)
|
||||
|
||||
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, state_init = prepare_game(this_game)
|
||||
|
||||
|
@ -1108,7 +1317,7 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
|
|||
try:
|
||||
for i in range(0, len(games_list)):
|
||||
game, key = games_list[i]
|
||||
if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8:
|
||||
if game.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():
|
||||
wins_in_series[game.teams["home"].name] = 0
|
||||
if game.teams['away'].name not in wins_in_series.keys():
|
||||
|
@ -1122,6 +1331,7 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
|
|||
wins_in_series[winner_name] = 1
|
||||
|
||||
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(embed=final_embed)
|
||||
if wins_in_series[winner_name] >= int((tourney.series_length+1)/2) and not finals:
|
||||
|
@ -1136,11 +1346,36 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
|
|||
except:
|
||||
print("something went wrong in tourney_watcher")
|
||||
await asyncio.sleep(4)
|
||||
|
||||
if tourney.league is not None:
|
||||
tourney.day += 1
|
||||
|
||||
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)
|
||||
|
||||
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 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:
|
||||
|
@ -1148,7 +1383,10 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, 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!")
|
||||
if tourney.day > tourney.league.day:
|
||||
tourney.league.day = tourney.day
|
||||
await channel.send(embed=embed)
|
||||
tourney.winner = get_team_fuzzy_search(winner_list[0])
|
||||
active_tournaments.pop(active_tournaments.index(tourney))
|
||||
return
|
||||
|
||||
|
@ -1157,11 +1395,37 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals
|
|||
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"""
|
||||
|
||||
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"""
|
||||
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 asyncio.sleep(tourney.round_delay)
|
||||
await start_tournament_round(channel, tourney)
|
||||
|
||||
|
||||
|
@ -1361,7 +1625,7 @@ 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"] <= 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)
|
||||
if isinstance(user, str):
|
||||
await channel.send(f"A game started by {user} just ended.")
|
||||
|
@ -1382,7 +1646,6 @@ def game_over_embed(game):
|
|||
title_string += f" with {game.inning - (game.max_innings+1)} extra innings.\n"
|
||||
else:
|
||||
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"
|
||||
|
@ -1394,7 +1657,10 @@ def game_over_embed(game):
|
|||
winstring += f"{winning_team} wins!"
|
||||
|
||||
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
|
||||
|
||||
def get_team_fuzzy_search(team_name):
|
||||
|
@ -1405,10 +1671,10 @@ def get_team_fuzzy_search(team_name):
|
|||
team = teams[0]
|
||||
return team
|
||||
|
||||
async def start_league_day(channel, league, autoplay = 1):
|
||||
async def start_league_day(channel, league, partial = False):
|
||||
current_games = []
|
||||
|
||||
games_to_start = league.schedule[league.day_to_series_num(league.day)]
|
||||
games_to_start = league.schedule[str(league.day_to_series_num(league.day))]
|
||||
if league.game_length is None:
|
||||
game_length = games.config()["default_length"]
|
||||
else:
|
||||
|
@ -1417,15 +1683,19 @@ async def start_league_day(channel, league, autoplay = 1):
|
|||
for pair in games_to_start:
|
||||
if pair[0] is not None and pair[1] is not None:
|
||||
away = get_team_fuzzy_search(pair[0])
|
||||
away.set_pitcher(rotation_slot=league.day-1)
|
||||
away.set_pitcher(rotation_slot=league.day)
|
||||
home = get_team_fuzzy_search(pair[1])
|
||||
home.set_pitcher(rotation_slot=league.day)
|
||||
|
||||
this_game = games.game(away.prepare_for_save().finalize(), home.prepare_for_save().finalize(), length = game_length)
|
||||
this_game = games.game(away.finalize(), home.finalize(), length = game_length)
|
||||
this_game, state_init = prepare_game(this_game)
|
||||
|
||||
state_init["is_league"] = True
|
||||
series_string = f"Series score:"
|
||||
state_init["title"] = f"{series_string} 0 - 0"
|
||||
if not partial:
|
||||
series_string = "Series score:"
|
||||
state_init["title"] = f"{series_string} 0 - 0"
|
||||
else:
|
||||
state_init["title"] = "Interrupted series!"
|
||||
discrim_string = league.name
|
||||
|
||||
id = str(uuid4())
|
||||
|
@ -1435,20 +1705,25 @@ async def start_league_day(channel, league, autoplay = 1):
|
|||
ext = "?league=" + urllib.parse.quote_plus(league.name)
|
||||
|
||||
if league.last_series_check(): #if finals
|
||||
await channel.send(f"The final series of the {league.name} is starting now, at {config()['simmadome_url']+ext}")
|
||||
await channel.send(f"The final series of the {league.name} regular season is starting now, at {config()['simmadome_url']+ext}")
|
||||
last = True
|
||||
|
||||
else:
|
||||
await channel.send(f"The next series of the {league.name} is starting now, at {config()['simmadome_url']+ext}")
|
||||
await channel.send(f"The day {league.day} series of the {league.name} is starting now, at {config()['simmadome_url']+ext}")
|
||||
last = False
|
||||
|
||||
await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, 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
|
||||
autoplay -= 1
|
||||
active_leagues.append(league)
|
||||
league.autoplay -= 1
|
||||
if league not in active_leagues:
|
||||
active_leagues.append(league)
|
||||
series_results = {}
|
||||
|
||||
while league.active:
|
||||
|
@ -1457,7 +1732,7 @@ async def league_day_watcher(channel, league, games_list, filter_url, autoplay,
|
|||
try:
|
||||
for i in range(0, len(games_list)):
|
||||
game, key = games_list[i]
|
||||
if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8:
|
||||
if game.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():
|
||||
series_results[game.teams["home"].name] = {}
|
||||
series_results[game.teams["home"].name]["wins"] = 0
|
||||
|
@ -1475,21 +1750,29 @@ async def league_day_watcher(channel, league, games_list, filter_url, autoplay,
|
|||
|
||||
series_results[winner_name]["wins"] += 1
|
||||
series_results[winner_name]["run_diff"] += rd
|
||||
|
||||
winner_dic = {"wins" : 1, "run_diff" : rd}
|
||||
|
||||
series_results[loser_name]["losses"] += 1
|
||||
series_results[loser_name]["run_diff"] -= rd
|
||||
|
||||
league.add_stats_from_game(game.get_team_specific_stats())
|
||||
loser_dic = {"losses" : 1, "run_diff" : -rd}
|
||||
|
||||
league.add_stats_from_game(game.get_team_specific_stats())
|
||||
league.update_standings({winner_name : winner_dic, loser_name : loser_dic})
|
||||
leagues.save_league(league)
|
||||
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(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)
|
||||
games_list.pop(i)
|
||||
break
|
||||
except:
|
||||
print("something went wrong in league_day_watcher")
|
||||
await asyncio.sleep(1)
|
||||
await asyncio.sleep(2)
|
||||
league.day += 1
|
||||
|
||||
if len(queued_games) > 0:
|
||||
|
@ -1499,49 +1782,103 @@ 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)]
|
||||
for i in range(0, len(validminutes)):
|
||||
if now.minute > validminutes[i]:
|
||||
if i < len(validminutes)-1:
|
||||
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
|
||||
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(microsecond=0)
|
||||
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 {league.name} will start in {int(wait_seconds/60)} minutes.")
|
||||
leagues.save_league(league)
|
||||
await channel.send(embed=league.standings_embed())
|
||||
await channel.send(f"The day {league.day} games for the {league.name} will start in {math.ceil(wait_seconds/60)} minutes.")
|
||||
leagues.save_league(league)
|
||||
await asyncio.sleep(wait_seconds)
|
||||
await channel.send(f"A {league.name} series is continuing now at {filter_url}")
|
||||
games_list = await continue_league_series(league, queued_games, games_list, series_results)
|
||||
games_list = await continue_league_series(league, queued_games, games_list, series_results, missed)
|
||||
else:
|
||||
league.active = False
|
||||
|
||||
|
||||
league.update_standings(series_results)
|
||||
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))
|
||||
|
||||
if last or autoplay <= 0: #if this series was the last of the season OR number of series to autoplay has been reached
|
||||
#needs some kind of notification that it's over here
|
||||
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
|
||||
|
||||
|
||||
|
||||
if league.autoplay == 0 or config()["game_freeze"]: #if number of series to autoplay has been reached
|
||||
await channel.send(embed=league.standings_embed())
|
||||
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))
|
||||
return
|
||||
|
||||
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
validminutes = [int((60 * div)/league.games_per_hour) for div in range(0,league.games_per_hour)]
|
||||
for i in range(0, len(validminutes)):
|
||||
if now.minute > validminutes[i]:
|
||||
if i < len(validminutes)-1:
|
||||
delta = datetime.timedelta(minutes= (validminutes[i+1] - now.minute))
|
||||
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(microsecond=0)
|
||||
next_start = (now + delta).replace(second=0, microsecond=0)
|
||||
wait_seconds = (next_start - now).seconds
|
||||
|
||||
leagues.save_league(league)
|
||||
await channel.send(embed=league.standings_embed())
|
||||
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 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:
|
||||
away_team = games.get_team(oldgame.teams["away"].name)
|
||||
away_team.set_pitcher(rotation_slot=league.day)
|
||||
|
@ -1552,7 +1889,12 @@ async def continue_league_series(league, queue, games_list, series_results):
|
|||
|
||||
state_init["is_league"] = True
|
||||
series_string = f"Series score:"
|
||||
state_init["title"] = f"{series_string} {series_results[away_team.name]['wins']} - {series_results[home_team.name]['wins']}"
|
||||
|
||||
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']}"
|
||||
else:
|
||||
state_init["title"] = "Interrupted series!"
|
||||
discrim_string = league.name
|
||||
|
||||
id = str(uuid4())
|
||||
|
@ -1561,8 +1903,78 @@ async def continue_league_series(league, queue, games_list, series_results):
|
|||
|
||||
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.champions[str(league.season)] = world_series.winner.name
|
||||
leagues.save_league(league)
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user