From b6e4c1069c65c1cd47c82099d2a7744ca1a17be5 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Wed, 13 Jan 2021 04:23:48 -0500 Subject: [PATCH] lots of league database foundation --- .gitignore | 1 + games.py | 6 ++- league_storage.py | 124 ++++++++++++++++++++++++++++++++++++++++++++ leagues.py | 64 +++++++++++++++-------- main_controller.py | 2 +- the-prestige.pyproj | 3 ++ the_prestige.py | 97 ++++++++++++++++++++-------------- 7 files changed, 233 insertions(+), 64 deletions(-) create mode 100644 league_storage.py diff --git a/.gitignore b/.gitignore index 6b7e8e2..b4a7181 100644 --- a/.gitignore +++ b/.gitignore @@ -349,6 +349,7 @@ ids matteo.db matteo.db-wal matteo.db-shm +/data/leagues/* /matteo_env/Lib/site-packages/flask_socketio/__init__.py env diff --git a/games.py b/games.py index 8ac5014..2e4e1cb 100644 --- a/games.py +++ b/games.py @@ -674,6 +674,10 @@ class game(object): return "Game not started." def add_stats(self): + players = self.get_stats() + db.add_stats(players) + + def get_stats(self): players = [] for this_player in self.teams["away"].lineup: players.append((this_player.name, this_player.game_stats)) @@ -681,7 +685,7 @@ class game(object): players.append((this_player.name, this_player.game_stats)) players.append((self.teams["home"].pitcher.name, self.teams["home"].pitcher.game_stats)) players.append((self.teams["away"].pitcher.name, self.teams["away"].pitcher.game_stats)) - db.add_stats(players) + return players diff --git a/league_storage.py b/league_storage.py new file mode 100644 index 0000000..4c9f777 --- /dev/null +++ b/league_storage.py @@ -0,0 +1,124 @@ +import os, json, re, jsonpickle +import sqlite3 as sql + +data_dir = "data" +league_dir = "leagues" + +def create_connection(league_name): + #create connection, create db if doesn't exist + conn = None + try: + conn = sql.connect(os.path.join(data_dir, league_dir, f"{league_name}.db")) + + # enable write-ahead log for performance and resilience + conn.execute('pragma journal_mode=wal') + + return conn + except: + print("oops, db connection no work") + return conn + +def state(league_name): + with open(os.path.join(data_dir, league_dir, f"{league_name}.state")) as state_file: + return json.load(state_file) + +def init_league_db(league): + conn = create_connection(league.name) + + player_stats_table_check_string = """ CREATE TABLE IF NOT EXISTS stats ( + counter integer PRIMARY KEY, + id text, + name text, + team_name text, + outs_pitched integer DEFAULT 0, + walks_allowed integer DEFAULT 0, + hits_allowed integer DEFAULT 0, + strikeouts_given integer DEFAULT 0, + runs_allowed integer DEFAULT 0, + plate_appearances integer DEFAULT 0, + walks_taken integer DEFAULT 0, + sacrifices integer DEFAULT 0, + hits integer DEFAULT 0, + home_runs integer DEFAULT 0, + total_bases integer DEFAULT 0, + rbis integer DEFAULT 0, + strikeouts_taken integer DEFAULT 0 + );""" + + teams_table_check_string = """ CREATE TABLE IF NOT EXISTS teams ( + counter integer PRIMARY KEY, + name text NOT NULL, + wins integer DEFAULT 0, + losses integer DEFAULT 0, + run_diff integer DEFAULT 0 + ); """ + + if conn is not None: + c = conn.cursor() + c.execute(player_stats_table_check_string) + c.execute(teams_table_check_string) + + for team in league.teams_in_league(): + c.execute("INSERT INTO teams (name) VALUES (?)", (team.name,)) + + player_string = "INSERT INTO stats (name, team_name) VALUES (?,?)" + for batter in team.lineup: + c.execute(player_string, (batter.name, team.name)) + for pitcher in team.rotation: + c.execute(player_string, (pitcher.name, team.name)) + + state_dic = { + "day" : league.day, + "schedule" : league.schedule, + "game_length" : league.game_length, + "series_length" : league.series_length, + "games_per_hour" : league.games_per_hour, + "historic" : False + } + with open(os.path.join(data_dir, league_dir, 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: + c=conn.cursor() + for (name, player_stats_dic) in player_game_stats_list: + c.execute("SELECT * FROM stats WHERE name=?",(name,)) + this_player = c.fetchone() + if this_player is not None: + for stat in player_stats_dic.keys(): + c.execute(f"SELECT {stat} FROM stats WHERE name=?",(name,)) + old_value = int(c.fetchone()[0]) + c.execute(f"UPDATE stats SET {stat} = ? WHERE name=?",(player_stats_dic[stat]+old_value,name)) + else: + c.execute("INSERT INTO stats(name) VALUES (?)",(name,)) + for stat in player_stats_dic.keys(): + c.execute(f"UPDATE stats SET {stat} = ? WHERE name=?",(player_stats_dic[stat],name)) + conn.commit() + conn.close() + +def update_standings(league_name, update_dic): + if league_exists(league_name): + conn = create_connection(league_name) + if conn is not None: + c = conn.cursor() + + for team_name in update_dic.keys(): + for stat_type in update_dic[team_name].keys(): #wins, losses, run_diff + c.execute(f"SELECT {stat_type} FROM teams WHERE name = ?", (team_name,)) + old_value = int(c.fetchone()[0]) + c.execute(f"UPDATE teams SET {stat_type} = ? WHERE name = ?", (update_dic[team_name][stat_type]+old_value, team_name)) + conn.commit() + conn.close() + + + +def league_exists(league_name): + with os.scandir(os.path.join(data_dir, league_dir)) as folder: + for file in folder: + if file.name == f"{league_name}.state": + return not state(league_name)["historic"] + return False \ No newline at end of file diff --git a/leagues.py b/leagues.py index ffa532e..d7cc5d9 100644 --- a/leagues.py +++ b/leagues.py @@ -1,39 +1,39 @@ -import time, asyncio, jsonpickle, random, math +import time, asyncio, json, jsonpickle, random, math, os +import league_storage as league_db from itertools import chain from games import team, game from discord import Embed, Color -import database as db - - - +data_dir = "data" +league_dir = "leagues" class league_structure(object): - def __init__(self, name, league_dic, division_games = 1, inter_division_games = 1, inter_league_games = 1, games_per_hour = 2): - self.league = league_dic #key: subleague, value: {division : teams} + def __init__(self, name): + self.name = name + + 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.constraints = { "division_games" : division_games, "inter_div_games" : inter_division_games, "inter_league_games" : inter_league_games } self.day = 1 - self.name = name self.schedule = {} self.series_length = 3 #can be changed self.game_length = None self.active = False self.games_per_hour = games_per_hour - self.standings = {} - for this_team in self.teams_in_league(): - self.standings[this_team.name] = { - "wins" : 0, - "losses" : 0, - "run differential" : 0, - } + def add_stats_from_game(self, players_list): + league_db.add_stats(players_list) + + def update_standings(self, results_dic): + league_db.update_standings(self.name, results_dic) + def last_series_check(self): - return day + 1 in self.schedule.keys() + return self.day + 1 in self.schedule.keys() def find_team(self, team_name): for subleague in iter(self.league.keys()): @@ -93,9 +93,9 @@ class league_structure(object): a_home = True for team_a, team_b in zip(league_a, league_b): if a_home: - matchups.append([team_b, team_a]) + matchups.append([team_b.name, team_a.name]) else: - matchups.append([team_a, team_b]) + matchups.append([team_a.name, team_b.name]) a_home != a_home for i in range(0, self.constraints["inter_div_games"]): #inter-division matchups @@ -127,9 +127,9 @@ class league_structure(object): a_home = True for team_a, team_b in zip(divs_a, divs_b): if a_home: - matchups.append([team_b, team_a]) + matchups.append([team_b.name, team_a.name]) else: - matchups.append([team_a, team_b]) + matchups.append([team_a.name, team_b.name]) a_home != a_home @@ -146,9 +146,9 @@ class league_structure(object): for team_a, team_b in zip(teams_a, teams_b): for j in range(0, self.constraints["division_games"]): if i % 2 == 0: - matchups.append([team_b, team_a]) + matchups.append([team_b.name, team_a.name]) else: - matchups.append([team_a, team_b]) + matchups.append([team_a.name, team_b.name]) division.insert(1, division.pop()) return matchups @@ -277,4 +277,22 @@ class bracket(object): if parent is None: self.this_bracket = branch - return branch \ No newline at end of file + return branch + +def save_league(this_league): + if not league_db.league_exists(this_league.name): + league_db.init_league_db(this_league) + with open(os.path.join(data_dir, league_dir, 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 + +def load_league_file(league_name): + if league_db.league_exists(league_name): + state = league_db.state(league_name) + this_league = league_structure(league_name) + with open(os.path.join(data_dir, league_dir, f"{this_league.name}.league")) as league_file: + this_league.league = jsonpickle.decode(json.load(league_file), keys=True, classes=team) + with open(os.path.join(data_dir, league_dir, f"{this_league.name}.state")) as state_file: + state_dic = json.load(state_file) + return this_league \ No newline at end of file diff --git a/main_controller.py b/main_controller.py index 707c9f7..73da0a7 100644 --- a/main_controller.py +++ b/main_controller.py @@ -141,4 +141,4 @@ def update_loop(): state["update_pause"] -= 1 socketio.emit("states_update", game_states) - time.sleep(8) + time.sleep(1) \ No newline at end of file diff --git a/the-prestige.pyproj b/the-prestige.pyproj index dc816c7..090ed69 100644 --- a/the-prestige.pyproj +++ b/the-prestige.pyproj @@ -32,6 +32,9 @@ Code + + Code + Code diff --git a/the_prestige.py b/the_prestige.py index 29ddc51..1669c48 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -724,6 +724,27 @@ class StartDraftCommand(Command): raise SlowDraftError('Too slow') return draft_message +class DebugLeague(Command): + name = "league" + + 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) + league.generate_schedule() + leagues.save_league(league) + await start_league_day(msg.channel, league, 2) + + + commands = [ IntroduceCommand(), @@ -751,6 +772,7 @@ commands = [ HelpCommand(), StartDraftCommand(), DraftPlayerCommand(), + DebugLeague() ] client = discord.Client() @@ -792,6 +814,7 @@ async def on_ready(): watch_task = asyncio.create_task(game_watcher()) await watch_task + @client.event async def on_reaction_add(reaction, user): if reaction.message in setupmessages.keys(): @@ -1382,7 +1405,7 @@ def get_team_fuzzy_search(team_name): team = teams[0] return team -async def start_league_day(channel, league): +async def start_league_day(channel, league, autoplay = 1): current_games = [] if league.schedule is {}: league.generate_schedule() @@ -1395,7 +1418,9 @@ async def start_league_day(channel, league): for pair in games_to_start: if pair[0] is not None and pair[1] is not None: - this_game = games.game(pair[0].prepare_for_save().finalize(), pair[1].prepare_for_save().finalize(), length = game_length) + away = get_team_fuzzy_search(pair[0]) + home = get_team_fuzzy_search(pair[1]) + this_game = games.game(away.prepare_for_save().finalize(), home.prepare_for_save().finalize(), length = game_length) this_game, state_init = prepare_game(this_game) state_init["is_league"] = True @@ -1417,14 +1442,14 @@ async def start_league_day(channel, league): await channel.send(f"The next series of the {league.name} is starting now, at {config()['simmadome_url']+ext}") last = False - await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, last) + await league_day_watcher(channel, league, current_games, config()['simmadome_url']+ext, autoplay, last) -async def league_day_watcher(channel, league, games_list, filter_url, last = False): +async def league_day_watcher(channel, league, games_list, filter_url, autoplay, last = False): league.active = True + autoplay -= 1 active_leagues.append(league) - wins_in_series = {} - losses_in_series = {} + series_results = {} while league.active: queued_games = [] @@ -1433,30 +1458,30 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal for i in range(0, len(games_list)): game, key = games_list[i] if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 8: - if game.teams['home'].name not in wins_in_series.keys(): - wins_in_series[game.teams["home"].name] = 0 - if game.teams['home'].name not in losses_in_series.keys(): - losses_in_series[game.teams["home"].name] = 0 - if game.teams['away'].name not in wins_in_series.keys(): - wins_in_series[game.teams["away"].name] = 0 - if game.teams['away'].name not in losses_in_series.keys(): - losses_in_series[game.teams["away"].name] = 0 + if game.teams['home'].name not in series_results.keys(): + series_results[game.teams["home"].name]["wins"] = 0 + series_results[game.teams["home"].name]["losses"] = 0 + series_results[game.teams["home"].name]["run_diff"] = 0 + if game.teams['away'].name not in series_results.keys(): + series_results[game.teams["away"].name]["wins"] = 0 + series_results[game.teams["away"].name]["losses"] = 0 + series_results[game.teams["away"].name]["run_diff"] = 0 winner_name = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name loser_name = game.teams['away'].name if game.teams['home'].score > game.teams['away'].score else game.teams['home'].name rd = int(math.fabs(game.teams['home'].score - game.teams['away'].score)) - wins_in_series[winner_name] += 1 - league.standings[winner_name]["wins"] += 1 - league.standings[winner_name]["run differential"] += rd - losses_in_series[loser_name] += 1 - league.standings[loser_name]["losses"] += 1 - league.standings[loser_name]["run differential"] -= rd + series_results[winner_name]["wins"] += 1 + series_results[winner_name]["run_diff"] += rd + series_results[loser_name]["losses"] += 1 + series_results[loser_name]["run_diff"] -= rd + + league.add_stats_from_game(game.get_stats()) final_embed = game_over_embed(game) await channel.send(f"A {league.name} game just ended!") await channel.send(embed=final_embed) - if wins_in_series[winner_name] + losses_in_series[winner_name] < league.series_length: + if series_results[winner_name]["wins"] + series_results[winner_name]["losses"] < league.series_length: queued_games.append(game) games_list.pop(i) break @@ -1484,11 +1509,15 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal await channel.send(f"The next batch of games for the {league.name} will start in {int(wait_seconds/60)} minutes.") await asyncio.sleep(wait_seconds) await channel.send(f"A {league.name} series is continuing now at {filter_url}") - games_list = await continue_league_series(league, queued_games, games_list, wins_in_series) + games_list = await continue_league_series(league, queued_games, games_list, series_results) else: league.active = False - if last: #if this series was the last + + league.update_standings(series_results) + league.day += 1 + + 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 active_leagues.pop(active_leagues.index(league)) return @@ -1508,10 +1537,10 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal await channel.send(f"""This {league.name} series is now complete! The next series will be starting in {int(wait_seconds/60)} minutes.""") await asyncio.sleep(wait_seconds) - league.day += 1 - await start_league_day(channel, league) -async def continue_league_series(tourney, queue, games_list, wins_in_series): + await start_league_day(channel, league, autoplay) + +async def continue_league_series(tourney, queue, games_list, series_results): for oldgame in queue: away_team = games.get_team(oldgame.teams["away"].name) home_team = games.get_team(oldgame.teams["home"].name) @@ -1520,7 +1549,7 @@ async def continue_league_series(tourney, queue, games_list, wins_in_series): state_init["is_league"] = True series_string = f"Series score:" - state_init["title"] = f"{series_string} {wins_in_series[away_team.name]} - {wins_in_series[home_team.name]}" + state_init["title"] = f"{series_string} {series_results[away_team.name]['wins']} - {series_results[home_team.name]['wins']}" discrim_string = league.name id = str(uuid4()) @@ -1530,18 +1559,8 @@ async def continue_league_series(tourney, queue, games_list, wins_in_series): return games_list -league = leagues.league_structure("test", { - "nL" : { - "nL west" : ["a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"], - "nL east" : ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"] - }, - "aL" : { - "aL west" : ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], - "aL east" : ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"] - } -}, division_games=6, inter_division_games=3, inter_league_games=3) -league.generate_schedule() -#print(league.schedule[2]) + + client.run(config()["token"])