diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index e6e72de..7e9dfe6 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -1,4 +1,3 @@ - import asyncio import copy import logging @@ -14,28 +13,80 @@ from api_calls import db_delete, db_get, db_post from dice import DTwentyRoll, d_twenty_roll, frame_plate_check, sa_fielding_roll from exceptions import * from gauntlets import post_result -from helpers import COLORS, DEFENSE_LITERAL, DEFENSE_NO_PITCHER_LITERAL, SBA_COLOR, get_channel, position_name_to_abbrev, team_role +from helpers import ( + COLORS, + DEFENSE_LITERAL, + DEFENSE_NO_PITCHER_LITERAL, + SBA_COLOR, + get_channel, + position_name_to_abbrev, + team_role, +) from in_game.ai_manager import get_starting_lineup from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check -from in_game.gameplay_models import BattingCard, Card, Game, Lineup, PositionRating, RosterLink, Team, Play -from in_game.gameplay_queries import get_active_games_by_team, get_available_batters, get_batter_card, get_batting_statline, get_game_cardset_links, get_or_create_ai_card, get_pitcher_runs_by_innings, get_pitching_statline, get_plays_by_pitcher, get_position, get_available_pitchers, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_game_lineups, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards +from in_game.gameplay_models import ( + BattingCard, + Card, + Game, + Lineup, + PositionRating, + RosterLink, + Team, + Play, +) +from in_game.gameplay_queries import ( + get_active_games_by_team, + get_available_batters, + get_batter_card, + get_batting_statline, + get_game_cardset_links, + get_or_create_ai_card, + get_pitcher_runs_by_innings, + get_pitching_statline, + get_plays_by_pitcher, + get_position, + get_available_pitchers, + get_card_or_none, + get_channel_game_or_none, + get_db_ready_decisions, + get_db_ready_plays, + get_game_lineups, + get_last_team_play, + get_one_lineup, + get_player_id_from_dict, + get_player_name_from_dict, + get_player_or_none, + get_sorted_lineups, + get_team_or_none, + get_players_last_pa, + post_game_rewards, +) from in_game.managerai_responses import DefenseResponse from utilities.buttons import ButtonOptions, Confirm, ask_confirm, ask_with_buttons -from utilities.dropdown import DropdownView, SelectBatterSub, SelectDefensiveChange, SelectReliefPitcher, SelectStartingPitcher, SelectViewDefense +from utilities.dropdown import ( + DropdownView, + SelectBatterSub, + SelectDefensiveChange, + SelectReliefPitcher, + SelectStartingPitcher, + SelectViewDefense, +) from utilities.embeds import image_embed from utilities.pages import Pagination -logger = logging.getLogger('discord_app') -WPA_DF = pd.read_csv(f'storage/wpa_data.csv').set_index('index') -TO_BASE = { - 2: 'to second', - 3: 'to third', - 4: 'home' -} +logger = logging.getLogger("discord_app") +WPA_DF = pd.read_csv(f"storage/wpa_data.csv").set_index("index") +TO_BASE = {2: "to second", 3: "to third", 4: "home"} -def safe_wpa_lookup(inning_half: str, inning_num: int, starting_outs: int, on_base_code: int, run_diff: int) -> float: +def safe_wpa_lookup( + inning_half: str, + inning_num: int, + starting_outs: int, + on_base_code: int, + run_diff: int, +) -> float: """ Safely lookup win probability from WPA_DF with fallback logic for missing keys. @@ -55,90 +106,88 @@ def safe_wpa_lookup(inning_half: str, inning_num: int, starting_outs: int, on_ba float: Home team win expectancy (0.0 to 1.0) """ # Construct the primary key - key = f'{inning_half}_{inning_num}_{starting_outs}_out_{on_base_code}_obc_{run_diff}_home_run_diff' + key = f"{inning_half}_{inning_num}_{starting_outs}_out_{on_base_code}_obc_{run_diff}_home_run_diff" # Try exact lookup first try: - return float(WPA_DF.loc[key, 'home_win_ex']) + return float(WPA_DF.loc[key, "home_win_ex"]) except KeyError: - logger.warning(f'WPA key not found: {key}, attempting fallback') + logger.warning(f"WPA key not found: {key}, attempting fallback") # Fallback 1: Try with simplified on_base_code (bases empty = 0) if on_base_code != 0: - fallback_key = f'{inning_half}_{inning_num}_{starting_outs}_out_0_obc_{run_diff}_home_run_diff' + fallback_key = f"{inning_half}_{inning_num}_{starting_outs}_out_0_obc_{run_diff}_home_run_diff" try: - result = float(WPA_DF.loc[fallback_key, 'home_win_ex']) - logger.info(f'WPA fallback successful using bases empty state: {fallback_key}') + result = float(WPA_DF.loc[fallback_key, "home_win_ex"]) + logger.info( + f"WPA fallback successful using bases empty state: {fallback_key}" + ) return result except KeyError: - logger.warning(f'WPA fallback key not found: {fallback_key}') + logger.warning(f"WPA fallback key not found: {fallback_key}") # Fallback 2: Return 0.0 if no data available - logger.warning(f'WPA using fallback value 0.0 for missing key: {key}') + logger.warning(f"WPA using fallback value 0.0 for missing key: {key}") return 0.0 -AT_BASE = { - 2: 'at second', - 3: 'at third', - 4: 'at home' -} -RANGE_CHECKS = { - 1: 3, - 2: 7, - 3: 11, - 4: 15, - 5: 19 -} -async def get_scorebug_embed(session: Session, this_game: Game, full_length: bool = True, classic: bool = True, live_scorecard: bool = False) -> discord.Embed: - gt_string = ' - Unlimited' - if this_game.game_type == 'minor-league': - gt_string = ' - Minor League' - elif this_game.game_type == 'major-league': - gt_string = ' - Major League' - elif this_game.game_type == 'hall-of-fame': - gt_string = ' - Hall of Fame' - elif 'gauntlet' in this_game.game_type: - gt_string = ' - Gauntlet' - elif 'flashback' in this_game.game_type: - gt_string = ' - Flashback' - elif 'exhibition' in this_game.game_type: - gt_string = ' - Exhibition' - logger.info(f'get_scorebug_embed - this_game: {this_game} / gt_string: {gt_string}') +AT_BASE = {2: "at second", 3: "at third", 4: "at home"} +RANGE_CHECKS = {1: 3, 2: 7, 3: 11, 4: 15, 5: 19} + + +async def get_scorebug_embed( + session: Session, + this_game: Game, + full_length: bool = True, + classic: bool = True, + live_scorecard: bool = False, +) -> discord.Embed: + gt_string = " - Unlimited" + if this_game.game_type == "minor-league": + gt_string = " - Minor League" + elif this_game.game_type == "major-league": + gt_string = " - Major League" + elif this_game.game_type == "hall-of-fame": + gt_string = " - Hall of Fame" + elif "gauntlet" in this_game.game_type: + gt_string = " - Gauntlet" + elif "flashback" in this_game.game_type: + gt_string = " - Flashback" + elif "exhibition" in this_game.game_type: + gt_string = " - Exhibition" + logger.info(f"get_scorebug_embed - this_game: {this_game} / gt_string: {gt_string}") curr_play = this_game.current_play_or_none(session) embed = discord.Embed( - title=f'{this_game.away_team.sname} @ {this_game.home_team.sname}{gt_string}' + title=f"{this_game.away_team.sname} @ {this_game.home_team.sname}{gt_string}" ) if curr_play is None: - logger.info(f'There is no play in game {this_game.id}, trying to initialize play') + logger.info( + f"There is no play in game {this_game.id}, trying to initialize play" + ) try: curr_play = this_game.initialize_play(session) except LineupsMissingException as e: - logger.info(f'get_scorebug_embed - Could not initialize play') + logger.info(f"get_scorebug_embed - Could not initialize play") if curr_play is not None: - embed.add_field( - name='Game State', - value=curr_play.scorebug_ascii, - inline=False - ) - logger.info(f'curr_play: {curr_play}') + embed.add_field(name="Game State", value=curr_play.scorebug_ascii, inline=False) + logger.info(f"curr_play: {curr_play}") if curr_play.pitcher.is_fatigued: - embed_color = COLORS['red'] + embed_color = COLORS["red"] elif curr_play.pitcher.after_play == curr_play.play_num - 1: - embed_color = COLORS['white'] + embed_color = COLORS["white"] elif curr_play.is_new_inning: - embed_color = COLORS['yellow'] + embed_color = COLORS["yellow"] else: - embed_color = COLORS['sba'] + embed_color = COLORS["sba"] embed.color = embed_color def steal_string(batting_card: BattingCard) -> str: - steal_string = '-/- (---)' + steal_string = "-/- (---)" if batting_card.steal_jump > 0: jump_chances = round(batting_card.steal_jump * 36) @@ -155,125 +204,130 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo elif jump_chances == 1: good_jump = 2 elif jump_chances == 7: - good_jump = '4,5' + good_jump = "4,5" elif jump_chances == 8: - good_jump = '4,6' + good_jump = "4,6" elif jump_chances == 9: - good_jump = '3-5' + good_jump = "3-5" elif jump_chances == 10: - good_jump = '2-5' + good_jump = "2-5" elif jump_chances == 11: - good_jump = '6,7' + good_jump = "6,7" elif jump_chances == 12: - good_jump = '4-6' + good_jump = "4-6" elif jump_chances == 13: - good_jump = '2,4-6' + good_jump = "2,4-6" elif jump_chances == 14: - good_jump = '3-6' + good_jump = "3-6" elif jump_chances == 15: - good_jump = '2-6' + good_jump = "2-6" elif jump_chances == 16: - good_jump = '2,5-6' + good_jump = "2,5-6" elif jump_chances == 17: - good_jump = '3,5-6' + good_jump = "3,5-6" elif jump_chances == 18: - good_jump = '4-6' + good_jump = "4-6" elif jump_chances == 19: - good_jump = '2,4-7' + good_jump = "2,4-7" elif jump_chances == 20: - good_jump = '3-7' + good_jump = "3-7" elif jump_chances == 21: - good_jump = '2-7' + good_jump = "2-7" elif jump_chances == 22: - good_jump = '2-7,12' + good_jump = "2-7,12" elif jump_chances == 23: - good_jump = '2-7,11' + good_jump = "2-7,11" elif jump_chances == 24: - good_jump = '2,4-8' + good_jump = "2,4-8" elif jump_chances == 25: - good_jump = '3-8' + good_jump = "3-8" elif jump_chances == 26: - good_jump = '2-8' + good_jump = "2-8" elif jump_chances == 27: - good_jump = '2-8,12' + good_jump = "2-8,12" elif jump_chances == 28: - good_jump = '2-8,11' + good_jump = "2-8,11" elif jump_chances == 29: - good_jump = '3-9' + good_jump = "3-9" elif jump_chances == 30: - good_jump = '2-9' + good_jump = "2-9" elif jump_chances == 31: - good_jump = '2-9,12' + good_jump = "2-9,12" elif jump_chances == 32: - good_jump = '2-9,11' + good_jump = "2-9,11" elif jump_chances == 33: - good_jump = '2-10' + good_jump = "2-10" elif jump_chances == 34: - good_jump = '3-11' + good_jump = "3-11" elif jump_chances == 35: - good_jump = '2-11' + good_jump = "2-11" else: - good_jump = '2-12' - steal_string = f'{"`*`" if batting_card.steal_auto else ""}{good_jump}/- ({batting_card.steal_high}-{batting_card.steal_low})' + good_jump = "2-12" + steal_string = f"{'`*`' if batting_card.steal_auto else ''}{good_jump}/- ({batting_card.steal_high}-{batting_card.steal_low})" return steal_string - - baserunner_string = '' + + baserunner_string = "" if curr_play.on_first is not None: runcard = curr_play.on_first.card.batterscouting.battingcard - baserunner_string += f'On First: {curr_play.on_first.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}\n' + baserunner_string += f"On First: {curr_play.on_first.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}\n" if curr_play.on_second is not None: runcard = curr_play.on_second.card.batterscouting.battingcard - baserunner_string += f'On Second: {curr_play.on_second.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}\n' + baserunner_string += f"On Second: {curr_play.on_second.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}\n" if curr_play.on_third is not None: runcard = curr_play.on_third.card.batterscouting.battingcard - baserunner_string += f'On Third: {curr_play.on_third.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}\n' - logger.info(f'gameplay_models - get_scorebug_embed - baserunner_string: {baserunner_string}') + baserunner_string += f"On Third: {curr_play.on_third.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}\n" + logger.info( + f"gameplay_models - get_scorebug_embed - baserunner_string: {baserunner_string}" + ) pitchingcard = curr_play.pitcher.card.pitcherscouting.pitchingcard battingcard = curr_play.batter.card.batterscouting.battingcard - pit_string = f'{pitchingcard.hand.upper()}HP | {curr_play.pitcher.player.name_card_link('pitching')}' + pit_string = f"{pitchingcard.hand.upper()}HP | {curr_play.pitcher.player.name_card_link('pitching')}" if curr_play.pitcher.is_fatigued: - pit_string += f'\n***F A T I G U E D***' + pit_string += f"\n***F A T I G U E D***" if len(baserunner_string) > 0: - logger.info(f'Adding pitcher hold to scorebug') + logger.info(f"Adding pitcher hold to scorebug") pitchingcard = curr_play.pitcher.card.pitcherscouting.pitchingcard - pit_string += f'\nHold: {"+" if pitchingcard.hold > 0 else ""}{pitchingcard.hold}, WP: {pitchingcard.wild_pitch}, Bk: {pitchingcard.balk}' + pit_string += f"\nHold: {'+' if pitchingcard.hold > 0 else ''}{pitchingcard.hold}, WP: {pitchingcard.wild_pitch}, Bk: {pitchingcard.balk}" # battingcard = curr_play.batter.card.batterscouting.battingcard # bat_string += f'\nBunt: {battingcard.bunting}, HnR: {battingcard.hit_and_run}' - - pit_string += f'\n{get_pitching_statline(session, curr_play.pitcher)}' - embed.add_field( - name='Pitcher', - value=pit_string - ) - bat_string = f'{curr_play.batter.batting_order}. {battingcard.hand.upper()} | {curr_play.batter.player.name_card_link('batting')}\n{get_batting_statline(session, curr_play.batter)}' - embed.add_field( - name='Batter', - value=bat_string - ) + pit_string += f"\n{get_pitching_statline(session, curr_play.pitcher)}" + embed.add_field(name="Pitcher", value=pit_string) - logger.info(f'Setting embed image to batter card') + bat_string = f"{curr_play.batter.batting_order}. {battingcard.hand.upper()} | {curr_play.batter.player.name_card_link('batting')}\n{get_batting_statline(session, curr_play.batter)}" + embed.add_field(name="Batter", value=bat_string) + + logger.info(f"Setting embed image to batter card") embed.set_image(url=curr_play.batter.player.batter_card_url) if len(baserunner_string) > 0: - logger.info(f'Adding baserunner info to embed') - embed.add_field(name=' ', value=' ', inline=False) - embed.add_field(name='Baserunners', value=baserunner_string) - - c_query = session.exec(select(PositionRating).where(PositionRating.player_id == curr_play.catcher.card.player.id, PositionRating.position == 'C', PositionRating.variant == curr_play.catcher.card.variant)).all() + logger.info(f"Adding baserunner info to embed") + embed.add_field(name=" ", value=" ", inline=False) + embed.add_field(name="Baserunners", value=baserunner_string) + + c_query = session.exec( + select(PositionRating).where( + PositionRating.player_id == curr_play.catcher.card.player.id, + PositionRating.position == "C", + PositionRating.variant == curr_play.catcher.card.variant, + ) + ).all() if len(c_query) > 0: catcher_rating = c_query[0] else: - log_exception(PositionNotFoundException, f'No catcher rating found for {curr_play.catcher.card.player.name}') + log_exception( + PositionNotFoundException, + f"No catcher rating found for {curr_play.catcher.card.player.name}", + ) + + cat_string = f"{curr_play.catcher.player.name_card_link('batter')}\nArm: {'+' if catcher_rating.arm > 0 else ''}{catcher_rating.arm}, PB: {catcher_rating.pb}, OT: {catcher_rating.overthrow}" + embed.add_field(name="Catcher", value=cat_string) - cat_string = f'{curr_play.catcher.player.name_card_link('batter')}\nArm: {"+" if catcher_rating.arm > 0 else ""}{catcher_rating.arm}, PB: {catcher_rating.pb}, OT: {catcher_rating.overthrow}' - embed.add_field(name='Catcher', value=cat_string) - if curr_play.ai_is_batting and curr_play.on_base_code > 0: - logger.info(f'Checking on baserunners for jump') + logger.info(f"Checking on baserunners for jump") if curr_play.on_base_code in [2, 4]: to_base = 3 elif curr_play.on_base_code in [1, 5]: @@ -281,144 +335,206 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo else: to_base = 4 - jump_resp = curr_play.managerai.check_jump(session, this_game, to_base=to_base) + jump_resp = curr_play.managerai.check_jump( + session, this_game, to_base=to_base + ) ai_note = jump_resp.ai_note else: - logger.info(f'Checking defensive alignment') + logger.info(f"Checking defensive alignment") def_align = curr_play.managerai.defense_alignment(session, this_game) - logger.info(f'def_align: {def_align}') + logger.info(f"def_align: {def_align}") ai_note = def_align.ai_note - logger.info(f'gameplay_models - get_scorebug_embed - ai_note: {ai_note}') + logger.info(f"gameplay_models - get_scorebug_embed - ai_note: {ai_note}") if len(ai_note) > 0 and not live_scorecard: - gm_name = this_game.home_team.gmname if this_game.ai_team == 'home' else this_game.away_team.gmname - embed.add_field(name=f'{gm_name} will...', value=ai_note, inline=False) + gm_name = ( + this_game.home_team.gmname + if this_game.ai_team == "home" + else this_game.away_team.gmname + ) + embed.add_field(name=f"{gm_name} will...", value=ai_note, inline=False) else: - embed.add_field(name=' ', value=' ', inline=False) - + embed.add_field(name=" ", value=" ", inline=False) + if full_length: embed.add_field( - name=f'{this_game.away_team.abbrev} Lineup', - value=this_game.team_lineup(session, this_game.away_team, with_links=False) + name=f"{this_game.away_team.abbrev} Lineup", + value=this_game.team_lineup( + session, this_game.away_team, with_links=False + ), ) embed.add_field( - name=f'{this_game.home_team.abbrev} Lineup', - value=this_game.team_lineup(session, this_game.home_team, with_links=False) + name=f"{this_game.home_team.abbrev} Lineup", + value=this_game.team_lineup( + session, this_game.home_team, with_links=False + ), ) else: - logger.info(f'There is no play in game {this_game.id}, posting lineups') - logger.info(f'Pulling away lineup') + logger.info(f"There is no play in game {this_game.id}, posting lineups") + logger.info(f"Pulling away lineup") embed.add_field( - name=f'{this_game.away_team.abbrev} Lineup', - value=this_game.team_lineup(session, this_game.away_team) + name=f"{this_game.away_team.abbrev} Lineup", + value=this_game.team_lineup(session, this_game.away_team), ) - logger.info(f'Pulling home lineup') + logger.info(f"Pulling home lineup") embed.add_field( - name=f'{this_game.home_team.abbrev} Lineup', - value=this_game.team_lineup(session, this_game.home_team) + name=f"{this_game.home_team.abbrev} Lineup", + value=this_game.team_lineup(session, this_game.home_team), ) return embed -async def new_game_checks(session: Session, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str): +async def new_game_checks( + session: Session, + interaction: discord.Interaction, + away_team_abbrev: str, + home_team_abbrev: str, +): try: - logger.info(f'Checking for game conflicts in {interaction.channel.name}') + logger.info(f"Checking for game conflicts in {interaction.channel.name}") await new_game_conflicts(session, interaction) except GameException as e: return None - logger.info(f'Getting teams') + logger.info(f"Getting teams") away_team = await get_team_or_none(session, team_abbrev=away_team_abbrev) if away_team is None: - logger.error(f'Away team not found') + logger.error(f"Away team not found") await interaction.edit_original_response( - content=f'Hm. I\'m not sure who **{away_team_abbrev}** is - check on that and try again!' + content=f"Hm. I'm not sure who **{away_team_abbrev}** is - check on that and try again!" ) return None - + home_team = await get_team_or_none(session, team_abbrev=home_team_abbrev) if home_team is None: - logger.error(f'Home team not found') + logger.error(f"Home team not found") await interaction.edit_original_response( - content=f'Hm. I\'m not sure who **{home_team_abbrev}** is - check on that and try again!' + content=f"Hm. I'm not sure who **{home_team_abbrev}** is - check on that and try again!" ) return None - + human_team = away_team if home_team.is_ai else home_team - logger.info(f'Checking for other team games') + logger.info(f"Checking for other team games") conflict_games = get_active_games_by_team(session, team=human_team) if len(conflict_games) > 0: - logger.error(f'Conflict creating a new game in channel: {interaction.channel.name}') + logger.error( + f"Conflict creating a new game in channel: {interaction.channel.name}" + ) await interaction.edit_original_response( - content=f'Ope. The {human_team.sname} are already playing over in {interaction.guild.get_channel(conflict_games[0].channel_id).mention}' + content=f"Ope. The {human_team.sname} are already playing over in {interaction.guild.get_channel(conflict_games[0].channel_id).mention}" ) return None - + if interaction.user.id not in [away_team.gmid, home_team.gmid]: if interaction.user.id != 258104532423147520: await interaction.edit_original_response( - content='You can only start a new game if you GM one of the teams.' + content="You can only start a new game if you GM one of the teams." ) return None else: - await interaction.channel.send('Sigh. Cal is cheating again starting a game for someone else.') - - return { - 'away_team': away_team, - 'home_team': home_team - } + await interaction.channel.send( + "Sigh. Cal is cheating again starting a game for someone else." + ) + + return {"away_team": away_team, "home_team": home_team} -def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team, game_type: str = None, responders: list[discord.User] = None): - pitchers = get_available_pitchers(session, this_game, human_team, sort='starter-desc') - logger.info(f'sorted pitchers: {pitchers}') +def starting_pitcher_dropdown_view( + session: Session, + this_game: Game, + human_team: Team, + game_type: str = None, + responders: list[discord.User] = None, +): + pitchers = get_available_pitchers( + session, this_game, human_team, sort="starter-desc" + ) + logger.info(f"sorted pitchers: {pitchers}") if len(pitchers) == 0: - log_exception(MissingRosterException, 'No pitchers were found to select SP') + log_exception(MissingRosterException, "No pitchers were found to select SP") sp_selection = SelectStartingPitcher( this_game=this_game, this_team=human_team, session=session, league_name=this_game.game_type if game_type is None else game_type, - options=[SelectOption(label=f'{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})', value=x.id) for x in pitchers], - placeholder='Select your starting pitcher', - responders=responders + options=[ + SelectOption( + label=f"{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})", + value=x.id, + ) + for x in pitchers + ], + placeholder="Select your starting pitcher", + responders=responders, ) return DropdownView(dropdown_objects=[sp_selection]) -def relief_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User] = None): +def relief_pitcher_dropdown_view( + session: Session, + this_game: Game, + human_team: Team, + batting_order: int, + responders: list[discord.User] = None, +): pitchers = get_available_pitchers(session, this_game, human_team) - logger.info(f'sorted pitchers: {pitchers}') + logger.info(f"sorted pitchers: {pitchers}") if len(pitchers) == 0: - log_exception(MissingRosterException, 'No pitchers were found to select RP') + log_exception(MissingRosterException, "No pitchers were found to select RP") rp_selection = SelectReliefPitcher( this_game=this_game, this_team=human_team, batting_order=batting_order, session=session, - options=[SelectOption(label=f'{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})', value=x.id) for x in pitchers], - placeholder='Select your relief pitcher', - responders=responders + options=[ + SelectOption( + label=f"{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})", + value=x.id, + ) + for x in pitchers + ], + placeholder="Select your relief pitcher", + responders=responders, ) return DropdownView(dropdown_objects=[rp_selection]) -async def defender_dropdown_view(session: Session, this_game: Game, human_team: Team, new_position: DEFENSE_NO_PITCHER_LITERAL, responders: list[discord.User] = None): +async def defender_dropdown_view( + session: Session, + this_game: Game, + human_team: Team, + new_position: DEFENSE_NO_PITCHER_LITERAL, + responders: list[discord.User] = None, +): active_players = get_game_lineups(session, this_game, human_team, is_active=True) - first_pass = [x for x in active_players if x.position != 'P' and x.batting_order != 10] + first_pass = [ + x for x in active_players if x.position != "P" and x.batting_order != 10 + ] if len(first_pass) == 0: - log_exception(MissingRosterException, 'No active defenders were found to make defensive change') - + log_exception( + MissingRosterException, + "No active defenders were found to make defensive change", + ) + defender_list = [] for x in first_pass: - this_pos = session.exec(select(PositionRating).where(PositionRating.player_id == x.player.id, PositionRating.position == position_name_to_abbrev(new_position), PositionRating.variant == x.card.variant)).all() - + this_pos = session.exec( + select(PositionRating).where( + PositionRating.player_id == x.player.id, + PositionRating.position == position_name_to_abbrev(new_position), + PositionRating.variant == x.card.variant, + ) + ).all() + if len(this_pos) > 0: defender_list.append(x) - + if len(defender_list) == 0: - log_exception(PlayerNotFoundException, f'I dont see any legal defenders for {new_position} on the field.') + log_exception( + PlayerNotFoundException, + f"I dont see any legal defenders for {new_position} on the field.", + ) defender_selection = SelectDefensiveChange( this_game=this_game, @@ -426,64 +542,97 @@ async def defender_dropdown_view(session: Session, this_game: Game, human_team: new_position=new_position, session=session, responders=responders, - placeholder=f'Who is moving to {new_position}?', - options=[SelectOption(label=f'{x.player.name_with_desc}', value=x.id) for x in defender_list] + placeholder=f"Who is moving to {new_position}?", + options=[ + SelectOption(label=f"{x.player.name_with_desc}", value=x.id) + for x in defender_list + ], ) return DropdownView(dropdown_objects=[defender_selection]) -def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User]): +def sub_batter_dropdown_view( + session: Session, + this_game: Game, + human_team: Team, + batting_order: int, + responders: list[discord.User], +): batters = get_available_batters(session, this_game, human_team) - logger.info(f'batters: {batters}') + logger.info(f"batters: {batters}") bat_selection = SelectBatterSub( this_game=this_game, this_team=human_team, session=session, batting_order=batting_order, - options=[SelectOption(label=f'{x.batterscouting.battingcard.hand.upper()} | {x.player.name_with_desc}', value=x.id) for x in batters], - placeholder='Select your Sub', - responders=responders + options=[ + SelectOption( + label=f"{x.batterscouting.battingcard.hand.upper()} | {x.player.name_with_desc}", + value=x.id, + ) + for x in batters + ], + placeholder="Select your Sub", + responders=responders, ) return DropdownView(dropdown_objects=[bat_selection]) -async def read_lineup(session: Session, interaction: discord.Interaction, this_game: Game, lineup_team: Team, sheets_auth, lineup_num: int, league_name: str): +async def read_lineup( + session: Session, + interaction: discord.Interaction, + this_game: Game, + lineup_team: Team, + sheets_auth, + lineup_num: int, + league_name: str, +): """ Commits lineups and rosterlinks - """ + """ existing_lineups = get_game_lineups( - session=session, - this_game=this_game, - specific_team=lineup_team, - is_active=True + session=session, this_game=this_game, specific_team=lineup_team, is_active=True ) if len(existing_lineups) > 1: await interaction.edit_original_response( - f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitute` to make changes.' + f"It looks like the {lineup_team.sname} already have a lineup. Run `/substitute` to make changes." ) return - - await interaction.edit_original_response(content='Okay, let\'s put this lineup card together...') + + await interaction.edit_original_response( + content="Okay, let's put this lineup card together..." + ) session.add(this_game) - - human_lineups = await get_lineups_from_sheets(session, sheets_auth, this_game, this_team=lineup_team, lineup_num=lineup_num, roster_num=this_game.away_roster_id if this_game.home_team.is_ai else this_game.home_roster_id) - await interaction.edit_original_response(content='Heard from sheets, pulling in scouting data...') + human_lineups = await get_lineups_from_sheets( + session, + sheets_auth, + this_game, + this_team=lineup_team, + lineup_num=lineup_num, + roster_num=this_game.away_roster_id + if this_game.home_team.is_ai + else this_game.home_roster_id, + ) + + await interaction.edit_original_response( + content="Heard from sheets, pulling in scouting data..." + ) for batter in human_lineups: session.add(batter) - + session.commit() for batter in human_lineups: - if batter.position != 'DH': + if batter.position != "DH": await get_position(session, batter.card, batter.position) return this_game.initialize_play(session) -def get_obc(on_first = None, on_second = None, on_third = None) -> int: +def get_obc(on_first=None, on_second=None, on_third=None) -> int: if on_third is not None: if on_second is not None: if on_first is not None: @@ -504,10 +653,12 @@ def get_obc(on_first = None, on_second = None, on_third = None) -> int: else: obc = 0 - return obc + return obc -def get_re24(this_play: Play, runs_scored: int, new_obc: int, new_starting_outs: int) -> float: +def get_re24( + this_play: Play, runs_scored: int, new_obc: int, new_starting_outs: int +) -> float: re_data = { 0: [0.457, 0.231, 0.077], 1: [0.793, 0.438, 0.171], @@ -516,11 +667,15 @@ def get_re24(this_play: Play, runs_scored: int, new_obc: int, new_starting_outs: 3: [1.340, 0.874, 0.287], 5: [1.687, 1.042, 0.406], 6: [1.973, 1.311, 0.448], - 7: [2.295, 1.440, 0.618] + 7: [2.295, 1.440, 0.618], } start_re24 = re_data[this_play.on_base_code][this_play.starting_outs] - end_re24 = 0 if this_play.starting_outs + this_play.outs > 2 else re_data[new_obc][new_starting_outs] + end_re24 = ( + 0 + if this_play.starting_outs + this_play.outs > 2 + else re_data[new_obc][new_starting_outs] + ) return round(end_re24 - start_re24 + runs_scored, 3) @@ -541,208 +696,299 @@ def get_wpa(this_play: Play, next_play: Play): old_rd = -6 # print(f'get_wpa: new_rd = {new_rd} / old_rd = {old_rd}') - if (next_play.inning_num >= 9 and new_rd > 0 and next_play.inning_half == 'bot') or (next_play.inning_num > 9 and new_rd > 0 and next_play.is_new_inning): + if ( + next_play.inning_num >= 9 and new_rd > 0 and next_play.inning_half == "bot" + ) or (next_play.inning_num > 9 and new_rd > 0 and next_play.is_new_inning): # print(f'manually setting new_win_ex to 1.0') new_win_ex = 1.0 else: inning_num = 9 if next_play.inning_num > 9 else next_play.inning_num - new_win_ex = safe_wpa_lookup(next_play.inning_half, inning_num, next_play.starting_outs, next_play.on_base_code, new_rd) + new_win_ex = safe_wpa_lookup( + next_play.inning_half, + inning_num, + next_play.starting_outs, + next_play.on_base_code, + new_rd, + ) # print(f'new_win_ex = {new_win_ex}') inning_num = 9 if this_play.inning_num > 9 else this_play.inning_num - old_win_ex = safe_wpa_lookup(this_play.inning_half, inning_num, this_play.starting_outs, this_play.on_base_code, old_rd) + old_win_ex = safe_wpa_lookup( + this_play.inning_half, + inning_num, + this_play.starting_outs, + this_play.on_base_code, + old_rd, + ) # print(f'old_win_ex = {old_win_ex}') wpa = float(round(new_win_ex - old_win_ex, 3)) # print(f'final wpa: {wpa}') - if this_play.inning_half == 'top': + if this_play.inning_half == "top": return wpa * -1.0 return wpa -def complete_play(session:Session, this_play: Play): +def complete_play(session: Session, this_play: Play): """ Commits this_play and new_play """ - logger.info(f'Completing play {this_play.id} in game {this_play.game.id}') + logger.info(f"Completing play {this_play.id} in game {this_play.game.id}") nso = this_play.starting_outs + this_play.outs runs_scored = 0 on_first, on_second, on_third = None, None, None - logger.info(f'Running bulk checks') + logger.info(f"Running bulk checks") if nso >= 3: switch_sides = True obc = 0 nso = 0 - nih = 'bot' if this_play.inning_half == 'top' else 'top' + nih = "bot" if this_play.inning_half == "top" else "top" away_score = this_play.away_score home_score = this_play.home_score try: - opponent_play = get_last_team_play(session, this_play.game, this_play.pitcher.team) + opponent_play = get_last_team_play( + session, this_play.game, this_play.pitcher.team + ) nbo = opponent_play.batting_order + 1 except PlayNotFoundException as e: - logger.info(f'logic_gameplay - complete_play - No last play found for {this_play.pitcher.team.sname}, setting upcoming batting order to 1') + logger.info( + f"logic_gameplay - complete_play - No last play found for {this_play.pitcher.team.sname}, setting upcoming batting order to 1" + ) nbo = 1 finally: - new_batter_team = this_play.game.away_team if nih == 'top' else this_play.game.home_team - new_pitcher_team = this_play.game.away_team if nih == 'bot' else this_play.game.home_team - inning = this_play.inning_num if nih == 'bot' else this_play.inning_num + 1 - - logger.info(f'Calculate runs scored') + new_batter_team = ( + this_play.game.away_team if nih == "top" else this_play.game.home_team + ) + new_pitcher_team = ( + this_play.game.away_team if nih == "bot" else this_play.game.home_team + ) + inning = this_play.inning_num if nih == "bot" else this_play.inning_num + 1 + + logger.info(f"Calculate runs scored") for this_runner, runner_dest in [ - (this_play.batter, this_play.batter_final), (this_play.on_first, this_play.on_first_final), (this_play.on_second, this_play.on_second_final), (this_play.on_third, this_play.on_third_final) + (this_play.batter, this_play.batter_final), + (this_play.on_first, this_play.on_first_final), + (this_play.on_second, this_play.on_second_final), + (this_play.on_third, this_play.on_third_final), ]: if runner_dest is not None: if runner_dest == 1: - logger.info(f'{this_runner} advances to first') + logger.info(f"{this_runner} advances to first") if on_first is not None: - log_exception(ValueError, f'Cannot place {this_runner.player.name} on first; {on_first.player.name} is already placed there') + log_exception( + ValueError, + f"Cannot place {this_runner.player.name} on first; {on_first.player.name} is already placed there", + ) if not switch_sides: on_first = this_runner elif runner_dest == 2: - logger.info(f'{this_runner} advances to second') + logger.info(f"{this_runner} advances to second") if on_second is not None: - log_exception(ValueError, f'Cannot place {this_runner.player.name} on second; {on_second.player.name} is already placed there') + log_exception( + ValueError, + f"Cannot place {this_runner.player.name} on second; {on_second.player.name} is already placed there", + ) if not switch_sides: on_second = this_runner elif runner_dest == 3: - logger.info(f'{this_runner} advances to third') + logger.info(f"{this_runner} advances to third") if on_third is not None: - log_exception(ValueError, f'Cannot place {this_runner.player.name} on third; {on_third.player.name} is already placed there') + log_exception( + ValueError, + f"Cannot place {this_runner.player.name} on third; {on_third.player.name} is already placed there", + ) if not switch_sides: on_third = this_runner elif runner_dest == 4: - logger.info(f'{this_runner} advances to home') + logger.info(f"{this_runner} advances to home") runs_scored += 1 - + else: switch_sides = False - nbo = this_play.batting_order + 1 if this_play.pa == 1 else this_play.batting_order + nbo = ( + this_play.batting_order + 1 + if this_play.pa == 1 + else this_play.batting_order + ) nih = this_play.inning_half new_batter_team = this_play.batter.team new_pitcher_team = this_play.pitcher.team - inning = this_play.inning_num - - logger.info(f'Calculate runs scored') + inning = this_play.inning_num + + logger.info(f"Calculate runs scored") for this_runner, runner_dest in [ - (this_play.batter, this_play.batter_final), (this_play.on_first, this_play.on_first_final), (this_play.on_second, this_play.on_second_final), (this_play.on_third, this_play.on_third_final) + (this_play.batter, this_play.batter_final), + (this_play.on_first, this_play.on_first_final), + (this_play.on_second, this_play.on_second_final), + (this_play.on_third, this_play.on_third_final), ]: if runner_dest is not None: if runner_dest == 1: - logger.info(f'{this_runner} advances to first') + logger.info(f"{this_runner} advances to first") if on_first is not None: - log_exception(ValueError, f'Cannot place {this_runner.player.name} on first; {on_first.player.name} is already placed there') + log_exception( + ValueError, + f"Cannot place {this_runner.player.name} on first; {on_first.player.name} is already placed there", + ) if not switch_sides: on_first = this_runner elif runner_dest == 2: - logger.info(f'{this_runner} advances to second') + logger.info(f"{this_runner} advances to second") if on_second is not None: - log_exception(ValueError, f'Cannot place {this_runner.player.name} on second; {on_second.player.name} is already placed there') + log_exception( + ValueError, + f"Cannot place {this_runner.player.name} on second; {on_second.player.name} is already placed there", + ) if not switch_sides: on_second = this_runner elif runner_dest == 3: - logger.info(f'{this_runner} advances to third') + logger.info(f"{this_runner} advances to third") if on_third is not None: - log_exception(ValueError, f'Cannot place {this_runner.player.name} on third; {on_third.player.name} is already placed there') + log_exception( + ValueError, + f"Cannot place {this_runner.player.name} on third; {on_third.player.name} is already placed there", + ) if not switch_sides: on_third = this_runner elif runner_dest == 4: - logger.info(f'{this_runner} advances to home') + logger.info(f"{this_runner} advances to home") runs_scored += 1 - + obc = get_obc(on_first, on_second, on_third) - - - if this_play.inning_half == 'top': + + if this_play.inning_half == "top": away_score = this_play.away_score + runs_scored home_score = this_play.home_score - - logger.info(f'Check for go-ahead run') - if runs_scored > 0 and this_play.away_score <= this_play.home_score and away_score > home_score: + + logger.info(f"Check for go-ahead run") + if ( + runs_scored > 0 + and this_play.away_score <= this_play.home_score + and away_score > home_score + ): this_play.is_go_ahead = True - + else: away_score = this_play.away_score home_score = this_play.home_score + runs_scored - - logger.info(f'Check for go-ahead run') - if runs_scored > 0 and this_play.home_score <= this_play.away_score and home_score > away_score: - this_play.is_go_ahead = True - logger.info(f'Calculating re24') - this_play.re24 = get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso) + logger.info(f"Check for go-ahead run") + if ( + runs_scored > 0 + and this_play.home_score <= this_play.away_score + and home_score > away_score + ): + this_play.is_go_ahead = True + + logger.info(f"Calculating re24") + this_play.re24 = get_re24( + this_play, runs_scored, new_obc=obc, new_starting_outs=nso + ) if nbo > 9: nbo = 1 - new_batter = get_one_lineup(session, this_play.game, new_batter_team, batting_order=nbo) - logger.info(f'new_batter: {new_batter}') + new_batter = get_one_lineup( + session, this_play.game, new_batter_team, batting_order=nbo + ) + logger.info(f"new_batter: {new_batter}") - new_pitcher = get_one_lineup(session, this_play.game, new_pitcher_team, position='P') - logger.info(f'Check for {new_pitcher.player.name} POW') - outs = session.exec(select(func.sum(Play.outs)).where( + new_pitcher = get_one_lineup( + session, this_play.game, new_pitcher_team, position="P" + ) + logger.info(f"Check for {new_pitcher.player.name} POW") + outs = session.exec( + select(func.sum(Play.outs)).where( Play.game == this_play.game, Play.pitcher == new_pitcher - )).one() + ) + ).one() if outs is None: outs = 0 - logger.info(f'Outs recorded: {outs}') - + logger.info(f"Outs recorded: {outs}") + if new_pitcher.replacing_id is None: pow_outs = new_pitcher.card.pitcherscouting.pitchingcard.starter_rating * 3 - logger.info(f'Using starter rating, POW outs: {pow_outs}') + logger.info(f"Using starter rating, POW outs: {pow_outs}") else: pow_outs = new_pitcher.card.pitcherscouting.pitchingcard.relief_rating * 3 - logger.info(f'Using relief rating, POW outs: {pow_outs}') - + logger.info(f"Using relief rating, POW outs: {pow_outs}") + if not new_pitcher.is_fatigued: - logger.info(f'Pitcher is not currently fatigued') + logger.info(f"Pitcher is not currently fatigued") if outs >= pow_outs: - logger.info(f'Pitcher is beyond POW - adding fatigue') + logger.info(f"Pitcher is beyond POW - adding fatigue") new_pitcher.is_fatigued = True elif new_pitcher.replacing_id is None: - logger.info(f'Pitcher is not in POW yet') - total_runs = session.exec(select(func.count(Play.id)).where( - Play.game == this_play.game, Play.pitcher == new_pitcher, Play.run == 1 - )).one() - logger.info(f'Runs allowed by {new_pitcher.player.name_with_desc}: {total_runs}') + logger.info(f"Pitcher is not in POW yet") + total_runs = session.exec( + select(func.count(Play.id)).where( + Play.game == this_play.game, + Play.pitcher == new_pitcher, + Play.run == 1, + ) + ).one() + logger.info( + f"Runs allowed by {new_pitcher.player.name_with_desc}: {total_runs}" + ) if total_runs >= 5: if total_runs >= 7: - logger.info(f'Starter has allowed 7+ runs - adding fatigue') + logger.info(f"Starter has allowed 7+ runs - adding fatigue") new_pitcher.is_fatigued = True else: - last_two = [x for x in range(this_play.inning_num, this_play.inning_num - 2, -1) if x > 0] - runs_last_two = get_pitcher_runs_by_innings(session, this_play.game, new_pitcher, last_two) - logger.info(f'Runs allowed last two innings: {runs_last_two}') + last_two = [ + x + for x in range( + this_play.inning_num, this_play.inning_num - 2, -1 + ) + if x > 0 + ] + runs_last_two = get_pitcher_runs_by_innings( + session, this_play.game, new_pitcher, last_two + ) + logger.info(f"Runs allowed last two innings: {runs_last_two}") if runs_last_two >= 6: - logger.info(f'Starter has allowed at least six in the last two - adding fatigue') + logger.info( + f"Starter has allowed at least six in the last two - adding fatigue" + ) new_pitcher.is_fatigued = True - + else: - runs_this_inning = get_pitcher_runs_by_innings(session, this_play.game, new_pitcher, [this_play.inning_num]) - logger.info(f'Runs allowed this inning: {runs_this_inning}') + runs_this_inning = get_pitcher_runs_by_innings( + session, this_play.game, new_pitcher, [this_play.inning_num] + ) + logger.info(f"Runs allowed this inning: {runs_this_inning}") if runs_this_inning >= 5: - logger.info(f'Starter has allowed at least five this inning - adding fatigue') + logger.info( + f"Starter has allowed at least five this inning - adding fatigue" + ) new_pitcher.is_fatigued = True session.add(new_pitcher) if outs >= (pow_outs - 3): in_pow = True if not new_pitcher.is_fatigued: - logger.info(f'checking for runners in POW') - runners_in_pow = session.exec(select(func.count(Play.id)).where( - Play.game == this_play.game, Play.pitcher == new_pitcher, Play.in_pow == True, or_(Play.hit == 1, Play.bb == 1), Play.ibb == 0 - )).one() # change to hits and walks - logger.info(f'runners in pow: {runners_in_pow}') + logger.info(f"checking for runners in POW") + runners_in_pow = session.exec( + select(func.count(Play.id)).where( + Play.game == this_play.game, + Play.pitcher == new_pitcher, + Play.in_pow == True, + or_(Play.hit == 1, Play.bb == 1), + Play.ibb == 0, + ) + ).one() # change to hits and walks + logger.info(f"runners in pow: {runners_in_pow}") if runners_in_pow >= 3: new_pitcher.is_fatigued = True else: in_pow = False new_play = Play( - game=this_play.game, + game=this_play.game, play_num=this_play.play_num + 1, batting_order=nbo, inning_half=nih, @@ -754,7 +1000,7 @@ def complete_play(session:Session, this_play: Play): batter=new_batter, batter_pos=new_batter.position, pitcher=new_pitcher, - catcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='C'), + catcher=get_one_lineup(session, this_play.game, new_pitcher_team, position="C"), is_new_inning=switch_sides, is_tied=away_score == home_score, on_first=on_first, @@ -762,15 +1008,15 @@ def complete_play(session:Session, this_play: Play): on_third=on_third, managerai=this_play.managerai, in_pow=in_pow, - re24=get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso) + re24=get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso), ) this_play.wpa = get_wpa(this_play, new_play) this_play.locked = False this_play.complete = True - logger.info(f'this_play: {this_play}') - logger.info(f'new_play: {new_play}') + logger.info(f"this_play: {this_play}") + logger.info(f"new_play: {new_play}") session.add(this_play) session.add(new_play) @@ -780,16 +1026,23 @@ def complete_play(session:Session, this_play: Play): return new_play -async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, this_team: Team, lineup_num: int, roster_num: int) -> list[Lineup]: - logger.info(f'get_lineups_from_sheets - sheets: {sheets}') +async def get_lineups_from_sheets( + session: Session, + sheets, + this_game: Game, + this_team: Team, + lineup_num: int, + roster_num: int, +) -> list[Lineup]: + logger.info(f"get_lineups_from_sheets - sheets: {sheets}") this_sheet = sheets.open_by_key(this_team.gsheet) - logger.info(f'this_sheet: {this_sheet}') + logger.info(f"this_sheet: {this_sheet}") - r_sheet = this_sheet.worksheet_by_title('My Rosters') - logger.info(f'r_sheet: {r_sheet}') - - logger.info(f'lineup_num: {roster_num}') + r_sheet = this_sheet.worksheet_by_title("My Rosters") + logger.info(f"r_sheet: {r_sheet}") + + logger.info(f"lineup_num: {roster_num}") if lineup_num == 1: row_start = 9 row_end = 17 @@ -797,44 +1050,51 @@ async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, thi row_start = 18 row_end = 26 - logger.info(f'roster_num: {roster_num}') + logger.info(f"roster_num: {roster_num}") if int(roster_num) == 1: - l_range = f'H{row_start}:I{row_end}' + l_range = f"H{row_start}:I{row_end}" elif int(roster_num) == 2: - l_range = f'J{row_start}:K{row_end}' + l_range = f"J{row_start}:K{row_end}" else: - l_range = f'L{row_start}:M{row_end}' + l_range = f"L{row_start}:M{row_end}" - logger.info(f'l_range: {l_range}') + logger.info(f"l_range: {l_range}") raw_cells = r_sheet.range(l_range) - logger.info(f'raw_cells: {raw_cells}') + logger.info(f"raw_cells: {raw_cells}") try: lineup_cells = [(row[0].value, int(row[1].value)) for row in raw_cells] - logger.info(f'lineup_cells: {lineup_cells}') + logger.info(f"lineup_cells: {lineup_cells}") except ValueError as e: - logger.error(f'Could not pull roster for {this_team.abbrev}: {e}') - log_exception(GoogleSheetsException, f'Uh oh. Looks like your lineup might not be saved. I am reading blanks when I try to get the card IDs') - + logger.error(f"Could not pull roster for {this_team.abbrev}: {e}") + log_exception( + GoogleSheetsException, + f"Uh oh. Looks like your lineup might not be saved. I am reading blanks when I try to get the card IDs", + ) + all_lineups = [] all_pos = [] card_ids = [] for index, row in enumerate(lineup_cells): - if '' in row: + if "" in row: break if row[0].upper() not in all_pos: all_pos.append(row[0].upper()) else: - raise SyntaxError(f'You have more than one {row[0].upper()} in this lineup. Please update and set the lineup again.') + raise SyntaxError( + f"You have more than one {row[0].upper()} in this lineup. Please update and set the lineup again." + ) this_card = await get_card_or_none(session, card_id=int(row[1])) if this_card is None: raise LookupError( - f'Your {row[0].upper()} has a Card ID of {int(row[1])} and I cannot find that card. Did you sell it by chance? Or maybe you sold a duplicate and the bot sold the one you were using?' + f"Your {row[0].upper()} has a Card ID of {int(row[1])} and I cannot find that card. Did you sell it by chance? Or maybe you sold a duplicate and the bot sold the one you were using?" ) if this_card.team_id != this_team.id: - raise SyntaxError(f'Easy there, champ. Looks like card ID {row[1]} belongs to the {this_card.team.lname}. Try again with only cards you own.') + raise SyntaxError( + f"Easy there, champ. Looks like card ID {row[1]} belongs to the {this_card.team.lname}. Try again with only cards you own." + ) card_id = row[1] card_ids.append(str(card_id)) @@ -844,136 +1104,202 @@ async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, thi game=this_game, team=this_team, player=this_card.player, - card=this_card + card=this_card, ) all_lineups.append(this_lineup) - + legal_data = await legal_check([card_ids], difficulty_name=this_game.league_name) - logger.debug(f'legal_data: {legal_data}') - if not legal_data['legal']: - raise CardLegalityException(f'The following cards appear to be illegal for this game mode:\n{legal_data["error_string"]}') + logger.debug(f"legal_data: {legal_data}") + if not legal_data["legal"]: + raise CardLegalityException( + f"The following cards appear to be illegal for this game mode:\n{legal_data['error_string']}" + ) if len(all_lineups) != 9: - raise Exception(f'I was only able to pull in {len(all_lineups)} batters from Sheets. Please check your saved lineup and try again.') + raise Exception( + f"I was only able to pull in {len(all_lineups)} batters from Sheets. Please check your saved lineup and try again." + ) return all_lineups -async def get_full_roster_from_sheets(session: Session, interaction: discord.Interaction, sheets, this_game: Game, this_team: Team, roster_num: int) -> list[RosterLink]: +async def get_full_roster_from_sheets( + session: Session, + interaction: discord.Interaction, + sheets, + this_game: Game, + this_team: Team, + roster_num: int, +) -> list[RosterLink]: """ Commits roster links """ - logger.debug(f'get_full_roster_from_sheets - sheets: {sheets}') + logger.debug(f"get_full_roster_from_sheets - sheets: {sheets}") this_sheet = sheets.open_by_key(this_team.gsheet) this_sheet = sheets.open_by_key(this_team.gsheet) - logger.debug(f'this_sheet: {this_sheet}') + logger.debug(f"this_sheet: {this_sheet}") - r_sheet = this_sheet.worksheet_by_title('My Rosters') - logger.debug(f'r_sheet: {r_sheet}') + r_sheet = this_sheet.worksheet_by_title("My Rosters") + logger.debug(f"r_sheet: {r_sheet}") if roster_num == 1: - l_range = 'B3:B28' + l_range = "B3:B28" elif roster_num == 2: - l_range = 'B29:B54' + l_range = "B29:B54" else: - l_range = 'B55:B80' + l_range = "B55:B80" - roster_message = await interaction.channel.send(content='I\'m diving into Sheets - wish me luck.') - - logger.info(f'l_range: {l_range}') + roster_message = await interaction.channel.send( + content="I'm diving into Sheets - wish me luck." + ) + + logger.info(f"l_range: {l_range}") raw_cells = r_sheet.range(l_range) - logger.info(f'raw_cells: {raw_cells}') + logger.info(f"raw_cells: {raw_cells}") - await roster_message.edit(content='Got your roster, now to find these cards in your collection...') + await roster_message.edit( + content="Got your roster, now to find these cards in your collection..." + ) try: card_ids = [row[0].value for row in raw_cells] - logger.info(f'card_ids: {card_ids}') + logger.info(f"card_ids: {card_ids}") except (ValueError, TypeError) as e: - logger.error(f'Could not pull roster for {this_team.abbrev}: {e}') - logger.error(f'raw_cells type: {type(raw_cells)} / raw_cells: {raw_cells}') - raise GoogleSheetsException(f'Uh oh. I had trouble reading your roster from Google Sheets (range {l_range}). Please make sure your roster is saved properly and all cells contain valid card IDs.') + logger.error(f"Could not pull roster for {this_team.abbrev}: {e}") + logger.error(f"raw_cells type: {type(raw_cells)} / raw_cells: {raw_cells}") + raise GoogleSheetsException( + f"Uh oh. I had trouble reading your roster from Google Sheets (range {l_range}). Please make sure your roster is saved properly and all cells contain valid card IDs." + ) for x in card_ids: this_card = await get_card_or_none(session, card_id=x) if this_card is None: - logger.error(f'Card ID {x} not found while loading roster for team {this_team.abbrev}') - raise CardNotFoundException(f'Card ID {x} was not found in your collection. Please check your roster sheet and make sure all card IDs are valid.') - session.add(RosterLink( - game=this_game, - card=this_card, - team=this_team - )) - + logger.error( + f"Card ID {x} not found while loading roster for team {this_team.abbrev}" + ) + raise CardNotFoundException( + f"Card ID {x} was not found in your collection. Please check your roster sheet and make sure all card IDs are valid." + ) + session.add(RosterLink(game=this_game, card=this_card, team=this_team)) + session.commit() - await roster_message.edit(content='Your roster is logged and scouting data is available.') - return session.exec(select(RosterLink).where(RosterLink.game == this_game, RosterLink.team == this_team)).all() + await roster_message.edit( + content="Your roster is logged and scouting data is available." + ) + return session.exec( + select(RosterLink).where( + RosterLink.game == this_game, RosterLink.team == this_team + ) + ).all() -async def checks_log_interaction(session: Session, interaction: discord.Interaction, command_name: str) -> tuple[Game, Team, Play]: +async def checks_log_interaction( + session: Session, interaction: discord.Interaction, command_name: str +) -> tuple[Game, Team, Play]: """ Commits this_play """ - logger.info(f'log interaction checks for {interaction.user.name} in channel {interaction.channel.name}') + logger.info( + f"log interaction checks for {interaction.user.name} in channel {interaction.channel.name}" + ) await interaction.response.defer(thinking=True) this_game = get_channel_game_or_none(session, interaction.channel_id) if this_game is None: - raise GameNotFoundException('I don\'t see an active game in this channel.') - + raise GameNotFoundException("I don't see an active game in this channel.") + owner_team = await get_team_or_none(session, gm_id=interaction.user.id) if owner_team is None: - logger.exception(f'{command_name} command: No team found for GM ID {interaction.user.id}') - raise TeamNotFoundException(f'Do I know you? I cannot find your team.') - - if 'gauntlet' in this_game.game_type: - gauntlet_abbrev = f'Gauntlet-{owner_team.abbrev}' + logger.exception( + f"{command_name} command: No team found for GM ID {interaction.user.id}" + ) + raise TeamNotFoundException(f"Do I know you? I cannot find your team.") + + if "gauntlet" in this_game.game_type: + gauntlet_abbrev = f"Gauntlet-{owner_team.abbrev}" owner_team = await get_team_or_none(session, team_abbrev=gauntlet_abbrev) if owner_team is None: - logger.exception(f'{command_name} command: No gauntlet team found with abbrev {gauntlet_abbrev}') - raise TeamNotFoundException(f'Hm, I was not able to find a gauntlet team for you.') - + logger.exception( + f"{command_name} command: No gauntlet team found with abbrev {gauntlet_abbrev}" + ) + raise TeamNotFoundException( + f"Hm, I was not able to find a gauntlet team for you." + ) + if not owner_team.id in [this_game.away_team_id, this_game.home_team_id]: if interaction.user.id != 258104532423147520: - logger.exception(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.') - raise TeamNotFoundException('Bruh. Only GMs of the active teams can log plays.') + logger.exception( + f"{interaction.user.display_name} tried to run a command in Game {this_game.id} when they aren't a GM in the game." + ) + raise TeamNotFoundException( + "Bruh. Only GMs of the active teams can log plays." + ) else: - await interaction.channel.send(f'Cal is bypassing the GM check to run the {command_name} command') - + await interaction.channel.send( + f"Cal is bypassing the GM check to run the {command_name} command" + ) + this_play = this_game.current_play_or_none(session) if this_play is None: - logger.error(f'{command_name} command: No play found for Game ID {this_game.id} - attempting to initialize play') + logger.error( + f"{command_name} command: No play found for Game ID {this_game.id} - attempting to initialize play" + ) this_play = activate_last_play(session, this_game) + if this_play.locked: + logger.warning( + f"{interaction.user.name} attempted {command_name} on locked play {this_play.id} " + f"in game {this_game.id}. Rejecting duplicate submission." + ) + from exceptions import PlayLockedException + + raise PlayLockedException( + "This play is already being processed. Please wait for the current action to complete.\n\n" + "If this play appears stuck, wait 30 seconds and try again (auto-unlock will trigger)." + ) + this_play.locked = True session.add(this_play) session.commit() session.refresh(this_play) - + return this_game, owner_team, this_play -def log_run_scored(session: Session, runner: Lineup, this_play: Play, is_earned: bool = True): +def log_run_scored( + session: Session, runner: Lineup, this_play: Play, is_earned: bool = True +): """ Commits last_ab """ - logger.info(f'Logging a run for runner ID {runner.id} in Game {this_play.game.id}') + logger.info(f"Logging a run for runner ID {runner.id} in Game {this_play.game.id}") last_ab = get_players_last_pa(session, lineup_member=runner) last_ab.run = 1 - logger.info(f'last_ab: {last_ab}') + logger.info(f"last_ab: {last_ab}") + + errors = session.exec( + select(func.count(Play.id)).where( + Play.game == this_play.game, + Play.inning_num == last_ab.inning_num, + Play.inning_half == last_ab.inning_half, + Play.error == 1, + Play.complete == True, + ) + ).one() + outs = session.exec( + select(func.sum(Play.outs)).where( + Play.game == this_play.game, + Play.inning_num == last_ab.inning_num, + Play.inning_half == last_ab.inning_half, + Play.complete == True, + ) + ).one() + logger.info(f"errors: {errors} / outs: {outs}") - errors = session.exec(select(func.count(Play.id)).where( - Play.game == this_play.game, Play.inning_num == last_ab.inning_num, Play.inning_half == last_ab.inning_half, Play.error == 1, Play.complete == True - )).one() - outs = session.exec(select(func.sum(Play.outs)).where( - Play.game == this_play.game, Play.inning_num == last_ab.inning_num, Play.inning_half == last_ab.inning_half, Play.complete == True - )).one() - logger.info(f'errors: {errors} / outs: {outs}') - if outs is not None: if errors + outs + this_play.error >= 3: - logger.info(f'unearned run') + logger.info(f"unearned run") is_earned = False last_ab.e_run = 1 if is_earned else 0 @@ -982,7 +1308,9 @@ def log_run_scored(session: Session, runner: Lineup, this_play: Play, is_earned: return True -def create_pinch_runner_entry_play(session: Session, game: Game, current_play: Play, pinch_runner_lineup: Lineup) -> Play: +def create_pinch_runner_entry_play( + session: Session, game: Game, current_play: Play, pinch_runner_lineup: Lineup +) -> Play: """ Creates a "pinch runner entry" Play record when a pinch runner substitutes for a player on base. This Play has PA=0, AB=0 so it doesn't affect counting stats, but provides a record to mark @@ -999,10 +1327,14 @@ def create_pinch_runner_entry_play(session: Session, game: Game, current_play: P Returns: The newly created entry Play """ - logger.info(f'Creating pinch runner entry Play for {pinch_runner_lineup.player.name_with_desc} in Game {game.id}') + logger.info( + f"Creating pinch runner entry Play for {pinch_runner_lineup.player.name_with_desc} in Game {game.id}" + ) # Get the next play number - max_play_num = session.exec(select(func.max(Play.play_num)).where(Play.game == game)).one() + max_play_num = session.exec( + select(func.max(Play.play_num)).where(Play.game == game) + ).one() next_play_num = max_play_num + 1 if max_play_num else 1 # Create the entry Play @@ -1029,29 +1361,37 @@ def create_pinch_runner_entry_play(session: Session, game: Game, current_play: P is_tied=current_play.is_tied, is_go_ahead=False, is_new_inning=False, - managerai_id=current_play.managerai_id + managerai_id=current_play.managerai_id, ) session.add(entry_play) session.commit() session.refresh(entry_play) - logger.info(f'Created entry Play #{entry_play.play_num} for pinch runner {pinch_runner_lineup.player.name_with_desc}') + logger.info( + f"Created entry Play #{entry_play.play_num} for pinch runner {pinch_runner_lineup.player.name_with_desc}" + ) return entry_play -def advance_runners(session: Session, this_play: Play, num_bases: int, only_forced: bool = False, earned_bases: int = None) -> Play: +def advance_runners( + session: Session, + this_play: Play, + num_bases: int, + only_forced: bool = False, + earned_bases: int = None, +) -> Play: """ No commits """ - logger.info(f'Advancing runners {num_bases} bases in game {this_play.game.id}') + logger.info(f"Advancing runners {num_bases} bases in game {this_play.game.id}") if earned_bases is None: earned_bases = num_bases this_play.rbi = 0 er_from = { 3: True if earned_bases >= 1 else False, 2: True if earned_bases >= 2 else False, - 1: True if earned_bases >= 3 else False + 1: True if earned_bases >= 3 else False, } if num_bases == 0: @@ -1061,24 +1401,28 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, only_forc this_play.on_second_final = 2 if this_play.on_third_id is not None: this_play.on_third_final = 3 - + elif only_forced: if not this_play.on_first: if this_play.on_second: this_play.on_second_final = 2 if this_play.on_third: this_play.on_third_final = 3 - + elif this_play.on_second: if this_play.on_third: if num_bases > 0: this_play.on_third_final = 4 - log_run_scored(session, this_play.on_third, this_play, is_earned=er_from[3]) + log_run_scored( + session, this_play.on_third, this_play, is_earned=er_from[3] + ) this_play.rbi += 1 if er_from[3] else 0 if num_bases > 1: this_play.on_second_final = 4 - log_run_scored(session, this_play.on_second, this_play, is_earned=er_from[2]) + log_run_scored( + session, this_play.on_second, this_play, is_earned=er_from[2] + ) this_play.rbi += 1 if er_from[2] else 0 elif num_bases == 1: this_play.on_second_final = 3 @@ -1103,7 +1447,9 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, only_forc if this_play.on_third: if num_bases > 0: this_play.on_third_final = 4 - log_run_scored(session, this_play.on_third, this_play, is_earned=er_from[3]) + log_run_scored( + session, this_play.on_third, this_play, is_earned=er_from[3] + ) this_play.rbi += 1 if er_from[3] else 0 else: this_play.on_third_final = 3 @@ -1111,7 +1457,9 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, only_forc if this_play.on_second: if num_bases > 1: this_play.on_second_final = 4 - log_run_scored(session, this_play.on_second, this_play, is_earned=er_from[2]) + log_run_scored( + session, this_play.on_second, this_play, is_earned=er_from[2] + ) this_play.rbi += 1 if er_from[2] else 0 elif num_bases == 1: this_play.on_second_final = 3 @@ -1121,7 +1469,9 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, only_forc if this_play.on_first: if num_bases > 2: this_play.on_first_final = 4 - log_run_scored(session, this_play.on_first, this_play, is_earned=er_from[1]) + log_run_scored( + session, this_play.on_first, this_play, is_earned=er_from[1] + ) this_play.rbi += 1 if er_from[1] else 0 elif num_bases == 2: this_play.on_first_final = 3 @@ -1129,49 +1479,68 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, only_forc this_play.on_first_final = 2 else: this_play.on_first_final = 1 - + return this_play -async def show_outfield_cards(session: Session, interaction: discord.Interaction, this_play: Play) -> Lineup: - lf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='LF') - cf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='CF') - rf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='RF') +async def show_outfield_cards( + session: Session, interaction: discord.Interaction, this_play: Play +) -> Lineup: + lf = get_one_lineup( + session, + this_game=this_play.game, + this_team=this_play.pitcher.team, + position="LF", + ) + cf = get_one_lineup( + session, + this_game=this_play.game, + this_team=this_play.pitcher.team, + position="CF", + ) + rf = get_one_lineup( + session, + this_game=this_play.game, + this_team=this_play.pitcher.team, + position="RF", + ) this_team = this_play.pitcher.team - logger.debug(f'lf: {lf.player.name_with_desc}\n\ncf: {cf.player.name_with_desc}\n\nrf: {rf.player.name_with_desc}\n\nteam: {this_team.lname}') + logger.debug( + f"lf: {lf.player.name_with_desc}\n\ncf: {cf.player.name_with_desc}\n\nrf: {rf.player.name_with_desc}\n\nteam: {this_team.lname}" + ) view = Pagination([interaction.user], timeout=10) - view.left_button.label = f'Left Fielder' + view.left_button.label = f"Left Fielder" view.left_button.style = discord.ButtonStyle.secondary lf_embed = image_embed( - image_url=lf.player.image, - title=f'{this_team.sname} LF', - color=this_team.color, - desc=lf.player.name, + image_url=lf.player.image, + title=f"{this_team.sname} LF", + color=this_team.color, + desc=lf.player.name, author_name=this_team.lname, - author_icon=this_team.logo + author_icon=this_team.logo, ) - view.cancel_button.label = f'Center Fielder' + view.cancel_button.label = f"Center Fielder" view.cancel_button.style = discord.ButtonStyle.blurple cf_embed = image_embed( - image_url=cf.player.image, - title=f'{this_team.sname} CF', - color=this_team.color, - desc=cf.player.name, + image_url=cf.player.image, + title=f"{this_team.sname} CF", + color=this_team.color, + desc=cf.player.name, author_name=this_team.lname, - author_icon=this_team.logo + author_icon=this_team.logo, ) - view.right_button.label = f'Right Fielder' + view.right_button.label = f"Right Fielder" view.right_button.style = discord.ButtonStyle.secondary rf_embed = image_embed( - image_url=rf.player.image, - title=f'{this_team.sname} RF', - color=this_team.color, - desc=rf.player.name, + image_url=rf.player.image, + title=f"{this_team.sname} RF", + color=this_team.color, + desc=rf.player.name, author_name=this_team.lname, - author_icon=this_team.logo + author_icon=this_team.logo, ) page_num = 1 @@ -1182,11 +1551,11 @@ async def show_outfield_cards(session: Session, interaction: discord.Interaction await view.wait() if view.value: - if view.value == 'left': + if view.value == "left": page_num = 0 - if view.value == 'cancel': + if view.value == "cancel": page_num = 1 - if view.value == 'right': + if view.value == "right": page_num = 2 else: await msg.edit(content=None, embed=embeds[page_num], view=None) @@ -1214,25 +1583,30 @@ async def show_outfield_cards(session: Session, interaction: discord.Interaction return [lf, cf, rf][page_num] -async def flyballs(session: Session, interaction: discord.Interaction, this_play: Play, flyball_type: Literal['a', 'ballpark', 'b', 'b?', 'c']) -> Play: +async def flyballs( + session: Session, + interaction: discord.Interaction, + this_play: Play, + flyball_type: Literal["a", "ballpark", "b", "b?", "c"], +) -> Play: """ Commits this_play """ this_game = this_play.game num_outs = 1 - if flyball_type == 'a': + if flyball_type == "a": this_play.pa, this_play.ab, this_play.outs = 1, 1, 1 if this_play.starting_outs < 2: this_play = advance_runners(session, this_play, num_bases=1) - + if this_play.on_third: this_play.ab = 0 - elif flyball_type == 'b' or flyball_type == 'ballpark': + elif flyball_type == "b" or flyball_type == "ballpark": this_play.pa, this_play.ab, this_play.outs = 1, 1, 1 - this_play.bpfo = 1 if flyball_type == 'ballpark' else 0 + this_play.bpfo = 1 if flyball_type == "ballpark" else 0 this_play = advance_runners(session, this_play, num_bases=0) if this_play.starting_outs < 2 and this_play.on_third: @@ -1242,241 +1616,258 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play log_run_scored(session, this_play.on_third, this_play) if this_play.starting_outs < 2 and this_play.on_second: - logger.debug(f'calling of embed') + logger.debug(f"calling of embed") this_of = await show_outfield_cards(session, interaction, this_play) of_rating = await get_position(session, this_of.card, this_of.position) of_mod = 0 - if this_of.position == 'LF': + if this_of.position == "LF": of_mod = -2 - elif this_of.position == 'RF': + elif this_of.position == "RF": of_mod = 2 - logger.debug(f'done with of embed') + logger.debug(f"done with of embed") runner_lineup = this_play.on_second runner = runner_lineup.player - - max_safe = runner_lineup.card.batterscouting.battingcard.running + of_rating.arm + of_mod + + max_safe = ( + runner_lineup.card.batterscouting.battingcard.running + + of_rating.arm + + of_mod + ) min_out = 20 + of_rating.arm + of_mod if (min_out >= 20 and max_safe >= 20) or min_out > 20: min_out = 21 min_hold = max_safe + 1 - safe_string = f'1{" - " if max_safe > 1 else ""}' + safe_string = f"1{' - ' if max_safe > 1 else ''}" if max_safe > 1: if max_safe <= 20: - safe_string += f'{max_safe}' + safe_string += f"{max_safe}" else: - safe_string += f'20' + safe_string += f"20" if min_out > 20: - out_string = 'None' + out_string = "None" else: - out_string = f'{min_out}{" - 20" if min_out < 20 else ""}' + out_string = f"{min_out}{' - 20' if min_out < 20 else ''}" - hold_string = '' + hold_string = "" if max_safe != min_out: - hold_string += f'{min_hold}' + hold_string += f"{min_hold}" if min_out - 1 > min_hold: - hold_string += f' - {min_out - 1}' + hold_string += f" - {min_out - 1}" ranges_embed = this_of.team.embed - ranges_embed.title = f'Tag Play' - ranges_embed.description = f'{this_of.team.abbrev} {this_of.position} {this_of.card.player.name}\'s Throw vs {runner.name}' - ranges_embed.add_field(name=f'Runner Speed', value=runner_lineup.card.batterscouting.battingcard.running) - ranges_embed.add_field(name=f'{this_of.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') - ranges_embed.add_field(name=f'{this_of.position} Mod', value=f'{of_mod}') - ranges_embed.add_field(name='', value='', inline=False) - ranges_embed.add_field(name='Safe Range', value=safe_string) - ranges_embed.add_field(name='Hold Range', value=hold_string) - ranges_embed.add_field(name='Out Range', value=out_string) - await interaction.channel.send( - content=None, - embed=ranges_embed + ranges_embed.title = f"Tag Play" + ranges_embed.description = f"{this_of.team.abbrev} {this_of.position} {this_of.card.player.name}'s Throw vs {runner.name}" + ranges_embed.add_field( + name=f"Runner Speed", + value=runner_lineup.card.batterscouting.battingcard.running, ) + ranges_embed.add_field( + name=f"{this_of.position} Arm", + value=f"{'+' if of_rating.arm > 0 else ''}{of_rating.arm}", + ) + ranges_embed.add_field(name=f"{this_of.position} Mod", value=f"{of_mod}") + ranges_embed.add_field(name="", value="", inline=False) + ranges_embed.add_field(name="Safe Range", value=safe_string) + ranges_embed.add_field(name="Hold Range", value=hold_string) + ranges_embed.add_field(name="Out Range", value=out_string) + await interaction.channel.send(content=None, embed=ranges_embed) if this_play.ai_is_batting: tag_resp = this_play.managerai.tag_from_second(session, this_game) - logger.info(f'tag_resp: {tag_resp}') + logger.info(f"tag_resp: {tag_resp}") tagging_from_second = tag_resp.min_safe >= max_safe if tagging_from_second: await interaction.channel.send( - content=f'**{runner.name}** is tagging from second!' + content=f"**{runner.name}** is tagging from second!" ) else: await interaction.channel.send( - content=f'**{runner.name}** is holding at second.' + content=f"**{runner.name}** is holding at second." ) else: tagging_from_second = await ask_confirm( interaction, - question=f'Is {runner.name} attempting to tag up from second?', - label_type='yes', + question=f"Is {runner.name} attempting to tag up from second?", + label_type="yes", ) - + if tagging_from_second: this_roll = d_twenty_roll(this_play.pitcher.team, this_play.game) if min_out is not None and this_roll.d_twenty >= min_out: - result = 'out' + result = "out" elif this_roll.d_twenty <= max_safe: - result = 'safe' + result = "safe" else: - result = 'holds' - + result = "holds" + await interaction.channel.send(content=None, embeds=this_roll.embeds) is_correct = await ask_confirm( - interaction, - question=f'Looks like {runner.name} {"is" if result != 'holds' else ""} **{result.upper()}** at {"third" if result != 'holds' else "second"}! Is that correct?', - label_type='yes', - delete_question=False + interaction, + question=f"Looks like {runner.name} {'is' if result != 'holds' else ''} **{result.upper()}** at {'third' if result != 'holds' else 'second'}! Is that correct?", + label_type="yes", + delete_question=False, ) if not is_correct: view = ButtonOptions( - responders=[interaction.user], timeout=60, - labels=['Safe at 3rd', 'Hold at 2nd', 'Out at 3rd', None, None] + responders=[interaction.user], + timeout=60, + labels=["Safe at 3rd", "Hold at 2nd", "Out at 3rd", None, None], ) question = await interaction.channel.send( - f'What was the result of {runner.name} tagging from second?', view=view + f"What was the result of {runner.name} tagging from second?", + view=view, ) await view.wait() if view.value: await question.delete() - if view.value == 'Tagged Up': - result = 'safe' - elif view.value == 'Out at 3rd': - result = 'out' + if view.value == "Tagged Up": + result = "safe" + elif view.value == "Out at 3rd": + result = "out" else: - result = 'holds' + result = "holds" else: await question.delete() - - if result == 'safe': + + if result == "safe": this_play.on_second_final = 3 - elif result == 'out': + elif result == "out": num_outs += 1 this_play.on_second_final = None this_play.outs = num_outs - elif flyball_type == 'b?': + elif flyball_type == "b?": this_play.pa, this_play.ab, this_play.outs = 1, 1, 1 this_play = advance_runners(session, this_play, 0) if this_play.starting_outs < 2 and this_play.on_third: - logger.debug(f'calling of embed') + logger.debug(f"calling of embed") this_of = await show_outfield_cards(session, interaction, this_play) of_rating = await get_position(session, this_of.card, this_of.position) - logger.debug(f'done with of embed') + logger.debug(f"done with of embed") runner_lineup = this_play.on_third runner = runner_lineup.player - max_safe = runner_lineup.card.batterscouting.battingcard.running + of_rating.arm + max_safe = ( + runner_lineup.card.batterscouting.battingcard.running + of_rating.arm + ) - safe_string = f'1{" - " if max_safe > 1 else ""}' + safe_string = f"1{' - ' if max_safe > 1 else ''}" if max_safe > 1: if max_safe <= 20: - safe_string += f'{max_safe - 1}' + safe_string += f"{max_safe - 1}" else: - safe_string += f'20' - + safe_string += f"20" + if max_safe == 20: - out_string = 'None' - catcher_string = '20' + out_string = "None" + catcher_string = "20" elif max_safe > 20: - out_string = 'None' - catcher_string = 'None' + out_string = "None" + catcher_string = "None" elif max_safe == 19: - out_string = 'None' - catcher_string = '19 - 20' + out_string = "None" + catcher_string = "19 - 20" elif max_safe == 18: - out_string = f'20' - catcher_string = '18 - 19' + out_string = f"20" + catcher_string = "18 - 19" else: - out_string = f'{max_safe + 2} - 20' - catcher_string = f'{max_safe} - {max_safe + 1}' - + out_string = f"{max_safe + 2} - 20" + catcher_string = f"{max_safe} - {max_safe + 1}" + true_max_safe = max_safe - 1 true_min_out = max_safe + 2 ranges_embed = this_play.batter.team.embed - ranges_embed.title = f'Play at the Plate' - ranges_embed.description = f'{runner.name} vs {this_of.card.player.name}\'s Throw' - ranges_embed.add_field(name=f'{this_of.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') - ranges_embed.add_field(name=f'Runner Speed', value=runner_lineup.card.batterscouting.battingcard.running) - ranges_embed.add_field(name="", value="", inline=False) - ranges_embed.add_field(name='Safe Range', value=safe_string) - ranges_embed.add_field(name='Catcher Check', value=catcher_string) - ranges_embed.add_field(name='Out Range', value=out_string) - await interaction.channel.send( - content=None, - embed=ranges_embed + ranges_embed.title = f"Play at the Plate" + ranges_embed.description = ( + f"{runner.name} vs {this_of.card.player.name}'s Throw" ) + ranges_embed.add_field( + name=f"{this_of.position} Arm", + value=f"{'+' if of_rating.arm > 0 else ''}{of_rating.arm}", + ) + ranges_embed.add_field( + name=f"Runner Speed", + value=runner_lineup.card.batterscouting.battingcard.running, + ) + ranges_embed.add_field(name="", value="", inline=False) + ranges_embed.add_field(name="Safe Range", value=safe_string) + ranges_embed.add_field(name="Catcher Check", value=catcher_string) + ranges_embed.add_field(name="Out Range", value=out_string) + await interaction.channel.send(content=None, embed=ranges_embed) if this_play.ai_is_batting: tag_resp = this_play.managerai.tag_from_third(session, this_game) - logger.info(f'tag_resp: {tag_resp}') + logger.info(f"tag_resp: {tag_resp}") tagging_from_third = tag_resp.min_safe <= max_safe if tagging_from_third: await interaction.channel.send( - content=f'**{runner.name}** is tagging from third!' + content=f"**{runner.name}** is tagging from third!" ) else: await interaction.channel.send( - content=f'**{runner.name}** is holding at third.' + content=f"**{runner.name}** is holding at third." ) else: tagging_from_third = await ask_confirm( interaction, - question=f'Is {runner.name} attempting to tag up from third?', - label_type='yes', + question=f"Is {runner.name} attempting to tag up from third?", + label_type="yes", ) - + if tagging_from_third: this_roll = d_twenty_roll(this_play.batter.team, this_play.game) if this_roll.d_twenty <= true_max_safe: - result = 'safe' - q_text = f'Looks like {runner.name} is SAFE at home!' + result = "safe" + q_text = f"Looks like {runner.name} is SAFE at home!" out_at_home = False elif this_roll.d_twenty >= true_min_out: - result = 'out' - q_text = f'Looks like {runner.name} is OUT at home!' + result = "out" + q_text = f"Looks like {runner.name} is OUT at home!" out_at_home = True else: - result = 'catcher' - q_text = f'Looks like this is a check for {this_play.catcher.player.name} to block the plate!' - + result = "catcher" + q_text = f"Looks like this is a check for {this_play.catcher.player.name} to block the plate!" + await interaction.channel.send(content=None, embeds=this_roll.embeds) is_correct = await ask_confirm( - interaction, - question=f'{q_text} Is that correct?', - label_type='yes', - delete_question=False + interaction, + question=f"{q_text} Is that correct?", + label_type="yes", + delete_question=False, ) if not is_correct: out_at_home = await ask_confirm( interaction, - question=f'Was {runner.name} thrown out?', - label_type='yes' + question=f"Was {runner.name} thrown out?", + label_type="yes", + ) + + elif result == "catcher": + catcher_rating = await get_position( + session, this_play.catcher.card, "C" ) - - elif result == 'catcher': - catcher_rating = await get_position(session, this_play.catcher.card, 'C') this_roll = d_twenty_roll(this_play.catcher.team, this_play.game) runner_embed = this_play.batter.team.embed - runner_embed.title = f'{this_play.on_third.player.name} To Home' - runner_embed.description = f'{this_play.catcher.team.abbrev} C {this_play.catcher.player.name} Blocking the Plate' + runner_embed.title = f"{this_play.on_third.player.name} To Home" + runner_embed.description = f"{this_play.catcher.team.abbrev} C {this_play.catcher.player.name} Blocking the Plate" runner_embed.add_field( - name='Catcher Range', - value=catcher_rating.range + name="Catcher Range", value=catcher_rating.range ) - runner_embed.add_field(name='', value='', inline=False) - + runner_embed.add_field(name="", value="", inline=False) + if catcher_rating.range == 1: safe_range = 3 elif catcher_rating.range == 2: @@ -1488,42 +1879,39 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play elif catcher_rating.range == 5: safe_range = 19 - runner_embed.add_field( - name='Safe Range', - value=f'1 - {safe_range}' - ) - out_range = f'{safe_range + 1}' + runner_embed.add_field(name="Safe Range", value=f"1 - {safe_range}") + out_range = f"{safe_range + 1}" if safe_range < 19: - out_range += f' - 20' - - runner_embed.add_field( - name='Out Range', - value=out_range - ) + out_range += f" - 20" + + runner_embed.add_field(name="Out Range", value=out_range) + await interaction.channel.send(content=None, embed=runner_embed) await interaction.channel.send( - content=None, - embed=runner_embed + content=None, embeds=this_roll.embeds ) - await interaction.channel.send(content=None, embeds=this_roll.embeds) if this_roll.d_twenty <= safe_range: - logger.info(f'Roll of {this_roll.d_twenty} is SAFE {AT_BASE[4]}') + logger.info( + f"Roll of {this_roll.d_twenty} is SAFE {AT_BASE[4]}" + ) out_at_home = False - q_text = f'Looks like **{runner.name}** is SAFE {AT_BASE[4]}!' + q_text = f"Looks like **{runner.name}** is SAFE {AT_BASE[4]}!" else: - logger.info(f'Roll of {this_roll.d_twenty} is OUT {AT_BASE[4]}') + logger.info(f"Roll of {this_roll.d_twenty} is OUT {AT_BASE[4]}") out_at_home = True - q_text = f'Looks like **{runner.name}** is OUT {AT_BASE[4]}!' - + q_text = f"Looks like **{runner.name}** is OUT {AT_BASE[4]}!" + is_correct = await ask_confirm( interaction=interaction, - question=f'{q_text} Is that correct?', - label_type='yes' + question=f"{q_text} Is that correct?", + label_type="yes", ) if not is_correct: - logger.info(f'{interaction.user.name} says this result is incorrect - setting out_at_home to {not out_at_home}') - out_at_home = not out_at_home + logger.info( + f"{interaction.user.name} says this result is incorrect - setting out_at_home to {not out_at_home}" + ) + out_at_home = not out_at_home if out_at_home: num_outs += 1 @@ -1535,30 +1923,39 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play this_play.on_third_final = 4 log_run_scored(session, this_play.on_third, this_play) - elif flyball_type == 'c': + elif flyball_type == "c": this_play.pa, this_play.ab, this_play.outs = 1, 1, 1 this_play = advance_runners(session, this_play, num_bases=0) - + session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def lineouts(session: Session, interaction: discord.Interaction, this_play: Play, lineout_type: Literal['one-out', 'ballpark', 'max-outs']) -> Play: +async def lineouts( + session: Session, + interaction: discord.Interaction, + this_play: Play, + lineout_type: Literal["one-out", "ballpark", "max-outs"], +) -> Play: """ Commits this_play """ num_outs = 1 this_play.pa, this_play.ab, this_play.outs = 1, 1, 1 - this_play.bplo = 1 if lineout_type == 'ballpark' else 0 + this_play.bplo = 1 if lineout_type == "ballpark" else 0 this_play = advance_runners(session, this_play, num_bases=0) - if lineout_type == 'max-outs' and this_play.on_base_code > 0 and this_play.starting_outs < 2: - logger.info(f'Lomax going in') + if ( + lineout_type == "max-outs" + and this_play.on_base_code > 0 + and this_play.starting_outs < 2 + ): + logger.info(f"Lomax going in") if this_play.on_base_code <= 3 or this_play.starting_outs == 1: - logger.info(f'Lead runner is out') + logger.info(f"Lead runner is out") this_play.outs = 2 if this_play.on_third is not None: @@ -1566,368 +1963,423 @@ async def lineouts(session: Session, interaction: discord.Interaction, this_play elif this_play.on_second is not None: this_play.on_second_final = None - + elif this_play.on_first is not None: this_play.on_first_final = None - + else: - logger.info(f'Potential triple play') + logger.info(f"Potential triple play") this_roll = d_twenty_roll(this_play.pitcher.team, this_play.game) ranges_embed = this_play.pitcher.team.embed - ranges_embed.title = f'Potential Triple Play' - ranges_embed.description = f'{this_play.pitcher.team.lname}' - ranges_embed.add_field(name=f'Double Play Range', value='1 - 13') - ranges_embed.add_field(name=f'Triple Play Range', value='14 - 20') + ranges_embed.title = f"Potential Triple Play" + ranges_embed.description = f"{this_play.pitcher.team.lname}" + ranges_embed.add_field(name=f"Double Play Range", value="1 - 13") + ranges_embed.add_field(name=f"Triple Play Range", value="14 - 20") await interaction.edit_original_response( - content=None, - embeds=[ranges_embed, *this_roll.embeds] + content=None, embeds=[ranges_embed, *this_roll.embeds] ) if this_roll.d_twenty > 13: - logger.info(f'Roll of {this_roll.d_twenty} is a triple play!') + logger.info(f"Roll of {this_roll.d_twenty} is a triple play!") num_outs = 3 else: - logger.info(f'Roll of {this_roll.d_twenty} is a double play!') + logger.info(f"Roll of {this_roll.d_twenty} is a double play!") num_outs = 2 - + is_correct = await ask_confirm( interaction, - question=f'Looks like this is a {"triple" if num_outs == 3 else "double"} play! Is that correct?' + question=f"Looks like this is a {'triple' if num_outs == 3 else 'double'} play! Is that correct?", ) if not is_correct: - logger.warning(f'{interaction.user.name} marked this result incorrect') + logger.warning(f"{interaction.user.name} marked this result incorrect") num_outs = 2 if num_outs == 3 else 3 - + if num_outs == 2: - logger.info(f'Lead baserunner is out') + logger.info(f"Lead baserunner is out") this_play.outs = 2 out_marked = False if this_play.on_third is not None: this_play.on_third_final = None out_marked = True - + elif this_play.on_second and not out_marked: this_play.on_second_final = None out_marked = True - + elif this_play.on_first and not out_marked: this_play.on_first_final = None out_marked = True - + else: - logger.info(f'Two baserunners are out') + logger.info(f"Two baserunners are out") this_play.outs = 3 outs_marked = 1 if this_play.on_third is not None: this_play.on_third_final = None outs_marked += 1 - + elif this_play.on_second is not None: this_play.on_second_final = None outs_marked += 1 - + elif this_play.on_first is not None and outs_marked < 3: this_play.on_first_final = None outs_marked += 1 - + session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def frame_checks(session: Session, interaction: discord.Interaction, this_play: Play): +async def frame_checks( + session: Session, interaction: discord.Interaction, this_play: Play +): """ Commits this_play """ this_roll = frame_plate_check(this_play.batter.team, this_play.game) - logger.info(f'this_roll: {this_roll}') + logger.info(f"this_roll: {this_roll}") + + await interaction.edit_original_response(content=None, embeds=this_roll.embeds) - await interaction.edit_original_response( - content=None, - embeds=this_roll.embeds - ) - if this_roll.is_walk: - this_play = await walks(session, interaction, this_play, 'unintentional') + this_play = await walks(session, interaction, this_play, "unintentional") else: this_play = await strikeouts(session, interaction, this_play) - + session.add(this_play) session.commit() await asyncio.sleep(1.5) - + session.refresh(this_play) return this_play @log_errors -async def check_uncapped_advance(session: Session, interaction: discord.Interaction, this_play: Play, lead_runner: Lineup, lead_base: int, trail_runner: Lineup, trail_base: int): +async def check_uncapped_advance( + session: Session, + interaction: discord.Interaction, + this_play: Play, + lead_runner: Lineup, + lead_base: int, + trail_runner: Lineup, + trail_base: int, +): this_game = this_play.game outfielder = await show_outfield_cards(session, interaction, this_play) - logger.info(f'throw from {outfielder.player.name_with_desc}') + logger.info(f"throw from {outfielder.player.name_with_desc}") this_roll = d_twenty_roll(this_play.batter.team, this_play.game) block_roll = d_twenty_roll(this_play.catcher.team, this_play.game) - + def_team = this_play.pitcher.team runner_bc = get_batter_card(this_lineup=lead_runner) - of_rating = await get_position(session, this_card=outfielder.card, position=outfielder.position) - c_rating = await get_position(session, this_play.catcher.card, position='C') + of_rating = await get_position( + session, this_card=outfielder.card, position=outfielder.position + ) + c_rating = await get_position(session, this_play.catcher.card, position="C") runner_embed = this_play.batter.team.embed - + safe_range = None - + def_alignment = this_play.managerai.defense_alignment(session, this_play.game) lead_bc = get_batter_card(this_lineup=lead_runner) - logger.info(f'lead runner batting card: {lead_bc}') + logger.info(f"lead runner batting card: {lead_bc}") lead_safe_range = lead_bc.running + of_rating.arm - logger.info(f'lead_safe_range: {lead_safe_range}') + logger.info(f"lead_safe_range: {lead_safe_range}") # Build lead runner embed lead_runner_embed = copy.deepcopy(runner_embed) - lead_runner_embed.title = f'{lead_runner.player.name} To {"Home" if lead_base == 4 else "Third"}' - lead_runner_embed.description = f'{outfielder.team.abbrev} {outfielder.position} {outfielder.player.name}\'s Throw' - lead_runner_embed.add_field(name=f'Runner Speed', value=lead_runner.card.batterscouting.battingcard.running) - lead_runner_embed.add_field(name=f'{outfielder.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') - - if this_play.starting_outs == 2: - logger.info(f'Adding 2 for 2 outs') - lead_safe_range += 2 - lead_runner_embed.add_field(name='2-Out Mod', value=f'+2') + lead_runner_embed.title = ( + f"{lead_runner.player.name} To {'Home' if lead_base == 4 else 'Third'}" + ) + lead_runner_embed.description = f"{outfielder.team.abbrev} {outfielder.position} {outfielder.player.name}'s Throw" + lead_runner_embed.add_field( + name=f"Runner Speed", value=lead_runner.card.batterscouting.battingcard.running + ) + lead_runner_embed.add_field( + name=f"{outfielder.position} Arm", + value=f"{'+' if of_rating.arm > 0 else ''}{of_rating.arm}", + ) - if lead_base == 3 and outfielder.position != 'CF': - of_mod = -2 if outfielder.position == 'LF' else 2 - logger.info(f'{outfielder.position} to 3B mod: {of_mod}') + if this_play.starting_outs == 2: + logger.info(f"Adding 2 for 2 outs") + lead_safe_range += 2 + lead_runner_embed.add_field(name="2-Out Mod", value=f"+2") + + if lead_base == 3 and outfielder.position != "CF": + of_mod = -2 if outfielder.position == "LF" else 2 + logger.info(f"{outfielder.position} to 3B mod: {of_mod}") lead_safe_range += of_mod - lead_runner_embed.add_field(name=f'{outfielder.position} Mod', value=f'{"+" if of_mod > 0 else ""}{of_mod}') - logger.info(f'lead_runner_embed: {lead_runner_embed}') + lead_runner_embed.add_field( + name=f"{outfielder.position} Mod", + value=f"{'+' if of_mod > 0 else ''}{of_mod}", + ) + logger.info(f"lead_runner_embed: {lead_runner_embed}") # Build trail runner embed trail_runner_embed = copy.deepcopy(runner_embed) trail_bc = get_batter_card(this_lineup=trail_runner) - logger.info(f'trail runner batting card: {trail_bc}') - - trail_runner_embed.title = f'{trail_runner.player.name} To {"Third" if trail_base == 3 else "Second"}' - trail_runner_embed.description = f'{outfielder.team.abbrev} {outfielder.position} {outfielder.player.name}\'s Throw' + logger.info(f"trail runner batting card: {trail_bc}") - trail_runner_embed.add_field(name=f'Runner Speed', value=trail_bc.running) - trail_runner_embed.add_field(name=f'{outfielder.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') + trail_runner_embed.title = ( + f"{trail_runner.player.name} To {'Third' if trail_base == 3 else 'Second'}" + ) + trail_runner_embed.description = f"{outfielder.team.abbrev} {outfielder.position} {outfielder.player.name}'s Throw" + + trail_runner_embed.add_field(name=f"Runner Speed", value=trail_bc.running) + trail_runner_embed.add_field( + name=f"{outfielder.position} Arm", + value=f"{'+' if of_rating.arm > 0 else ''}{of_rating.arm}", + ) trail_safe_range = trail_bc.running - 5 + of_rating.arm - logger.info(f'trail_safe_range: {trail_safe_range}') + logger.info(f"trail_safe_range: {trail_safe_range}") + + if trail_base == 3 and outfielder.position != "CF": + of_mod = -2 if outfielder.position == "LF" else 2 + logger.info(f"{outfielder.position} to 3B mod: {of_mod}") - if trail_base == 3 and outfielder.position != 'CF': - of_mod = -2 if outfielder.position == 'LF' else 2 - logger.info(f'{outfielder.position} to 3B mod: {of_mod}') - trail_safe_range += of_mod - trail_runner_embed.add_field(name=f'{outfielder.position} Mod', value=f'{"+" if of_mod > 0 else ""}{of_mod}', inline=False) - - trail_runner_embed.add_field(name='Trail Runner', value='-5') - + trail_runner_embed.add_field( + name=f"{outfielder.position} Mod", + value=f"{'+' if of_mod > 0 else ''}{of_mod}", + inline=False, + ) + + trail_runner_embed.add_field(name="Trail Runner", value="-5") + def at_home_strings(safe_range: int): - safe_string = f'1{" - " if safe_range > 1 else ""}' + safe_string = f"1{' - ' if safe_range > 1 else ''}" if safe_range > 1: if safe_range <= 20: - safe_string += f'{safe_range - 1}' + safe_string += f"{safe_range - 1}" else: - safe_string += f'20' + safe_string += f"20" if safe_range == 20: - out_string = 'None' - catcher_string = '20' + out_string = "None" + catcher_string = "20" elif safe_range > 20: - out_string = 'None' - catcher_string = 'None' + out_string = "None" + catcher_string = "None" elif safe_range == 19: - out_string = 'None' - catcher_string = '19 - 20' + out_string = "None" + catcher_string = "19 - 20" elif safe_range == 18: - out_string = f'20' - catcher_string = '18 - 19' + out_string = f"20" + catcher_string = "18 - 19" else: - out_string = f'{safe_range + 2} - 20' - catcher_string = f'{safe_range} - {safe_range + 1}' - logger.info(f'safe: {safe_string} / catcher: {catcher_string} / out: {out_string}') + out_string = f"{safe_range + 2} - 20" + catcher_string = f"{safe_range} - {safe_range + 1}" + logger.info( + f"safe: {safe_string} / catcher: {catcher_string} / out: {out_string}" + ) - return {'safe': safe_string, 'catcher': catcher_string, 'out': out_string} + return {"safe": safe_string, "catcher": catcher_string, "out": out_string} def at_third_strings(safe_range: int): - safe_string = f'1{" - " if safe_range > 1 else ""}' + safe_string = f"1{' - ' if safe_range > 1 else ''}" if safe_range > 1: if safe_range <= 20: - safe_string += f'{safe_range}' + safe_string += f"{safe_range}" else: - safe_string += f'20' + safe_string += f"20" if safe_range > 19: - out_string = '20' + out_string = "20" else: - out_string = f'{safe_range + 1} - 20' - logger.info(f'safe: {safe_string} / out: {out_string}') - - return {'safe': safe_string, 'out': out_string} + out_string = f"{safe_range + 1} - 20" + logger.info(f"safe: {safe_string} / out: {out_string}") + + return {"safe": safe_string, "out": out_string} async def out_at_home(safe_range: int): if this_roll.d_twenty in [safe_range, safe_range + 1]: - logger.info(f'Roll of {this_roll.d_twenty} is a catcher check with safe range of {safe_range}') + logger.info( + f"Roll of {this_roll.d_twenty} is a catcher check with safe range of {safe_range}" + ) is_block_plate = await ask_confirm( interaction, - question=f'Looks like **{this_play.catcher.player.name}** has a chance to block the plate! Is that correct?', - label_type='yes', - delete_question=False + question=f"Looks like **{this_play.catcher.player.name}** has a chance to block the plate! Is that correct?", + label_type="yes", + delete_question=False, ) if is_block_plate: - logger.info(f'Looks like a block the plate check') + logger.info(f"Looks like a block the plate check") await interaction.channel.send(content=None, embeds=block_roll.embeds) - + if block_roll.d_twenty > RANGE_CHECKS[c_rating.range]: - logger.info(f'Roll of {this_roll.d_twenty} is OUT {AT_BASE[4]}') + logger.info(f"Roll of {this_roll.d_twenty} is OUT {AT_BASE[4]}") runner_thrown_out = True - q_text = f'Looks like **{lead_runner.player.name}** is OUT {AT_BASE[4]}!' + q_text = ( + f"Looks like **{lead_runner.player.name}** is OUT {AT_BASE[4]}!" + ) else: - logger.info(f'Roll of {this_roll.d_twenty} is SAFE {AT_BASE[4]}') + logger.info(f"Roll of {this_roll.d_twenty} is SAFE {AT_BASE[4]}") runner_thrown_out = False - q_text = f'Looks like **{lead_runner.player.name}** is SAFE {AT_BASE[4]}!' - + q_text = f"Looks like **{lead_runner.player.name}** is SAFE {AT_BASE[4]}!" + is_correct = await ask_confirm( interaction=interaction, - question=f'{q_text} Is that correct?', - label_type='yes' + question=f"{q_text} Is that correct?", + label_type="yes", ) if not is_correct: - logger.info(f'{interaction.user.name} says this result is incorrect - setting runner_thrown_out to {not runner_thrown_out}') + logger.info( + f"{interaction.user.name} says this result is incorrect - setting runner_thrown_out to {not runner_thrown_out}" + ) runner_thrown_out = not runner_thrown_out else: runner_thrown_out = await ask_confirm( interaction=interaction, - question=f'Was **{lead_runner.player.name}** thrown out {AT_BASE[4]}?', - label_type='yes', + question=f"Was **{lead_runner.player.name}** thrown out {AT_BASE[4]}?", + label_type="yes", ) else: - logger.info(f'Roll of {this_roll.d_twenty} has a clear result with safe range of {safe_range}') + logger.info( + f"Roll of {this_roll.d_twenty} has a clear result with safe range of {safe_range}" + ) if this_roll.d_twenty > safe_range: - logger.info(f'Roll of {this_roll.d_twenty} is OUT {AT_BASE[4]}') + logger.info(f"Roll of {this_roll.d_twenty} is OUT {AT_BASE[4]}") runner_thrown_out = True - q_text = f'Looks like **{lead_runner.player.name}** is OUT {AT_BASE[4]}!' + q_text = ( + f"Looks like **{lead_runner.player.name}** is OUT {AT_BASE[4]}!" + ) else: - logger.info(f'Roll of {this_roll.d_twenty} is SAFE {AT_BASE[4]}') + logger.info(f"Roll of {this_roll.d_twenty} is SAFE {AT_BASE[4]}") runner_thrown_out = False - q_text = f'Looks like **{lead_runner.player.name}** is SAFE {AT_BASE[4]}!' - + q_text = ( + f"Looks like **{lead_runner.player.name}** is SAFE {AT_BASE[4]}!" + ) + is_correct = await ask_confirm( - interaction, - question=f'{q_text} Is that correct?', - label_type='yes', - delete_question=False + interaction, + question=f"{q_text} Is that correct?", + label_type="yes", + delete_question=False, ) if not is_correct: - logger.warning(f'{interaction.user.name} says call is incorrect; runner is {"not " if runner_thrown_out else ""}thrown out') + logger.warning( + f"{interaction.user.name} says call is incorrect; runner is {'not ' if runner_thrown_out else ''}thrown out" + ) runner_thrown_out = not runner_thrown_out - + return runner_thrown_out async def out_at_base(safe_range: int, this_runner: Lineup, this_base: int): if this_roll.d_twenty > safe_range: - logger.info(f'Roll of {this_roll.d_twenty} is OUT {AT_BASE[this_base]}') + logger.info(f"Roll of {this_roll.d_twenty} is OUT {AT_BASE[this_base]}") runner_thrown_out = True - q_text = f'Looks like **{this_runner.player.name}** is OUT {AT_BASE[this_base]}!' + q_text = ( + f"Looks like **{this_runner.player.name}** is OUT {AT_BASE[this_base]}!" + ) else: - logger.info(f'Roll of {this_roll.d_twenty} is SAFE {AT_BASE[this_base]}') + logger.info(f"Roll of {this_roll.d_twenty} is SAFE {AT_BASE[this_base]}") runner_thrown_out = False - q_text = f'Looks like **{this_runner.player.name}** is SAFE {AT_BASE[this_base]}!' - + q_text = f"Looks like **{this_runner.player.name}** is SAFE {AT_BASE[this_base]}!" + is_correct = await ask_confirm( - interaction, - question=f'{q_text} Is that correct?', - label_type='yes', - delete_question=False + interaction, + question=f"{q_text} Is that correct?", + label_type="yes", + delete_question=False, ) if not is_correct: - logger.warning(f'{interaction.user.name} says call is incorrect; runner is {"not " if runner_thrown_out else ""}thrown out') + logger.warning( + f"{interaction.user.name} says call is incorrect; runner is {'not ' if runner_thrown_out else ''}thrown out" + ) runner_thrown_out = not runner_thrown_out - + return runner_thrown_out # Either there is no AI team or the AI is pitching if not this_game.ai_team or not this_play.ai_is_batting: # Build lead runner embed # Check for lead runner hold - if (lead_runner == this_play.on_second and def_alignment.hold_second) or (lead_runner == this_play.on_first and def_alignment.hold_first): + if (lead_runner == this_play.on_second and def_alignment.hold_second) or ( + lead_runner == this_play.on_first and def_alignment.hold_first + ): lead_safe_range -= 1 - logger.info(f'Lead runner was held, -1 to safe range: {lead_safe_range}') - lead_runner_embed.add_field(name='Runner Held', value='-1') + logger.info(f"Lead runner was held, -1 to safe range: {lead_safe_range}") + lead_runner_embed.add_field(name="Runner Held", value="-1") else: - logger.info(f'Lead runner was not held, +1 to safe range: {lead_safe_range}') + logger.info( + f"Lead runner was not held, +1 to safe range: {lead_safe_range}" + ) lead_safe_range += 1 - lead_runner_embed.add_field(name='Runner Not Held', value='+1') + lead_runner_embed.add_field(name="Runner Not Held", value="+1") + + lead_runner_embed.add_field(name="", value="", inline=False) - lead_runner_embed.add_field(name='', value='', inline=False) - if lead_base == 4: - logger.info(f'lead base is 4, building strings') + logger.info(f"lead base is 4, building strings") lead_strings = at_home_strings(lead_safe_range) - lead_runner_embed.add_field(name='Safe Range', value=lead_strings['safe']) - lead_runner_embed.add_field(name='Catcher Check', value=lead_strings['catcher']) - lead_runner_embed.add_field(name='Out Range', value=lead_strings['out']) - + lead_runner_embed.add_field(name="Safe Range", value=lead_strings["safe"]) + lead_runner_embed.add_field( + name="Catcher Check", value=lead_strings["catcher"] + ) + lead_runner_embed.add_field(name="Out Range", value=lead_strings["out"]) + else: - logger.info(f'lead base is 3, building strings') + logger.info(f"lead base is 3, building strings") lead_strings = at_third_strings(lead_safe_range) - lead_runner_embed.add_field(name='Safe Range', value=lead_strings['safe']) - lead_runner_embed.add_field(name='Out Range', value=lead_strings['out']) - - # Build trail runner embed - if (trail_runner == this_play.on_first and def_alignment.hold_first): - trail_safe_range -= 1 - logger.info(f'Trail runner was held, -1 to safe range: {trail_safe_range}') - trail_runner_embed.add_field(name='Runner Held', value='-1') - elif (trail_runner == this_play.on_first and not def_alignment.hold_first): - trail_safe_range += 1 - logger.info(f'Trail runner was not held, +1 to safe range: {trail_safe_range}') - trail_runner_embed.add_field(name='Runner Not Held', value='+1') - else: - logger.info('Trail runner was not from first base, no hold modifier') - - trail_runner_embed.add_field(name='', value='', inline=False) + lead_runner_embed.add_field(name="Safe Range", value=lead_strings["safe"]) + lead_runner_embed.add_field(name="Out Range", value=lead_strings["out"]) - logger.info(f'Building strings for trail runner') - safe_string = f'1{" - " if trail_safe_range > 1 else ""}' + # Build trail runner embed + if trail_runner == this_play.on_first and def_alignment.hold_first: + trail_safe_range -= 1 + logger.info(f"Trail runner was held, -1 to safe range: {trail_safe_range}") + trail_runner_embed.add_field(name="Runner Held", value="-1") + elif trail_runner == this_play.on_first and not def_alignment.hold_first: + trail_safe_range += 1 + logger.info( + f"Trail runner was not held, +1 to safe range: {trail_safe_range}" + ) + trail_runner_embed.add_field(name="Runner Not Held", value="+1") + else: + logger.info("Trail runner was not from first base, no hold modifier") + + trail_runner_embed.add_field(name="", value="", inline=False) + + logger.info(f"Building strings for trail runner") + safe_string = f"1{' - ' if trail_safe_range > 1 else ''}" if trail_safe_range > 1: if trail_safe_range < 20: - safe_string += f'{trail_safe_range}' + safe_string += f"{trail_safe_range}" else: - logger.info(f'capping safe range at 19') + logger.info(f"capping safe range at 19") trail_safe_range = 19 - safe_string += f'19' + safe_string += f"19" - out_string = f'{trail_safe_range + 1} - 20' - logger.info(f'safe: {safe_string} / out: {out_string}') + out_string = f"{trail_safe_range + 1} - 20" + logger.info(f"safe: {safe_string} / out: {out_string}") - trail_runner_embed.add_field(name='Safe Range', value=safe_string) - trail_runner_embed.add_field(name='Out Range', value=out_string) + trail_runner_embed.add_field(name="Safe Range", value=safe_string) + trail_runner_embed.add_field(name="Out Range", value=out_string) await interaction.channel.send(embeds=[lead_runner_embed, trail_runner_embed]) is_lead_running = await ask_confirm( interaction=interaction, - question=f'Is **{lead_runner.player.name}** being sent {TO_BASE[lead_base]}?', - label_type='yes' + question=f"Is **{lead_runner.player.name}** being sent {TO_BASE[lead_base]}?", + label_type="yes", ) if is_lead_running: @@ -1935,10 +2387,12 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact if this_game.ai_team: throw_resp = this_play.managerai.throw_at_uncapped(session, this_game) - logger.info(f'throw_resp: {throw_resp}') - + logger.info(f"throw_resp: {throw_resp}") + if throw_resp.cutoff: - await interaction.channel.send(f'The {def_team.sname} will cut off the throw {TO_BASE[lead_base]}') + await interaction.channel.send( + f"The {def_team.sname} will cut off the throw {TO_BASE[lead_base]}" + ) if this_play.on_second == lead_runner: this_play.rbi += 1 this_play.on_second_final = 4 @@ -1948,15 +2402,17 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact await asyncio.sleep(1) return this_play - + else: - await interaction.channel.send(content=f'**{outfielder.player.name}** is throwing {TO_BASE[lead_base]}!') + await interaction.channel.send( + content=f"**{outfielder.player.name}** is throwing {TO_BASE[lead_base]}!" + ) else: throw_for_lead = await ask_confirm( interaction=interaction, - question=f'Is the defense throwing {TO_BASE[lead_base]} for {lead_runner.player.name}?', - label_type='yes' + question=f"Is the defense throwing {TO_BASE[lead_base]} for {lead_runner.player.name}?", + label_type="yes", ) # Human defense is cutting off the throw @@ -1967,53 +2423,73 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact this_play.on_second_final = 4 log_run_scored(session, lead_runner, this_play) return this_play - + # Human runner is advancing, defense is throwing trail_advancing = await ask_confirm( interaction=interaction, - question=f'Is **{trail_runner.player.name}** being sent {TO_BASE[trail_base]} as the trail runner?', - label_type='yes' + question=f"Is **{trail_runner.player.name}** being sent {TO_BASE[trail_base]} as the trail runner?", + label_type="yes", ) # Trail runner is advancing if trail_advancing: - throw_lead = False if this_game.ai_team: - - if throw_resp.at_trail_runner and trail_safe_range <= throw_resp.trail_max_safe and trail_safe_range <= throw_resp.trail_max_safe_delta - lead_safe_range: - logger.info(f'defense throwing at trail runner {AT_BASE[trail_base]}') - await interaction.channel.send(f'**{outfielder.player.name}** will throw {TO_BASE[trail_base]}!') + if ( + throw_resp.at_trail_runner + and trail_safe_range <= throw_resp.trail_max_safe + and trail_safe_range + <= throw_resp.trail_max_safe_delta - lead_safe_range + ): + logger.info( + f"defense throwing at trail runner {AT_BASE[trail_base]}" + ) + await interaction.channel.send( + f"**{outfielder.player.name}** will throw {TO_BASE[trail_base]}!" + ) throw_lead = False else: - logger.info(f'defense throwing at lead runner {AT_BASE[lead_base]}') - await interaction.channel.send(f'**{outfielder.player.name}** will throw {TO_BASE[lead_base]}!') + logger.info( + f"defense throwing at lead runner {AT_BASE[lead_base]}" + ) + await interaction.channel.send( + f"**{outfielder.player.name}** will throw {TO_BASE[lead_base]}!" + ) throw_lead = True else: - view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') - view.confirm.label = 'Home Plate' if lead_base == 4 else 'Third Base' - view.cancel.label = 'Third Base' if trail_base == 3 else 'Second Base' + view = Confirm( + responders=[interaction.user], timeout=60, label_type="yes" + ) + view.confirm.label = ( + "Home Plate" if lead_base == 4 else "Third Base" + ) + view.cancel.label = ( + "Third Base" if trail_base == 3 else "Second Base" + ) question = await interaction.channel.send( - f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?', - view=view + f"Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?", + view=view, ) throw_lead = await view.wait() # Throw is going to lead runner if throw_lead: - logger.info(f'Throw is going to lead base') + logger.info(f"Throw is going to lead base") try: await question.delete() except (discord.NotFound, UnboundLocalError): pass if this_play.on_first == trail_runner: - this_play.on_first_final += 1 + this_play.on_first_final += 1 elif this_play.batter == trail_runner: - this_play.batter_final += 1 + this_play.batter_final += 1 else: - log_exception(LineupsMissingException, f'Could not find trail runner to advance') + log_exception( + LineupsMissingException, + f"Could not find trail runner to advance", + ) # Throw is going to trail runner else: @@ -2022,12 +2498,16 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact except (discord.NotFound, UnboundLocalError): pass - await interaction.channel.send(content=None, embeds=this_roll.embeds) - runner_thrown_out = await out_at_base(trail_safe_range, trail_runner, trail_base) + await interaction.channel.send( + content=None, embeds=this_roll.embeds + ) + runner_thrown_out = await out_at_base( + trail_safe_range, trail_runner, trail_base + ) # Trail runner is thrown out if runner_thrown_out: - logger.info(f'logging one one additional out for trail runner') + logger.info(f"logging one one additional out for trail runner") # Log out on play this_play.outs += 1 @@ -2037,53 +2517,60 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact else: this_play.batter_final = None else: - logger.info(f'Runner is safe') + logger.info(f"Runner is safe") if this_play.on_first == trail_runner: this_play.on_first_final += 1 if this_play.on_first_final == 4: - logger.info(f'Add an rbi') + logger.info(f"Add an rbi") this_play.rbi += 1 elif this_play.batter == trail_runner: this_play.batter_final += 1 else: - log_exception(LineupsMissingException, f'Could not find trail runner to advance') - + log_exception( + LineupsMissingException, + f"Could not find trail runner to advance", + ) + # Advance lead runner extra base - logger.info(f'advancing lead runner') + logger.info(f"advancing lead runner") if this_play.on_second == lead_runner: - logger.info(f'run scored from second') + logger.info(f"run scored from second") this_play.rbi += 1 this_play.on_second_final = 4 log_run_scored(session, lead_runner, this_play) - + elif this_play.on_first == lead_runner: this_play.on_first_final += 1 if this_play.on_first_final > 3: - logger.info(f'run scored from first') + logger.info(f"run scored from first") this_play.rbi += 1 log_run_scored(session, lead_runner, this_play) - + if trail_runner != this_play.batter: - logger.info(f'Trail runner is not batter, advancing batter') + logger.info(f"Trail runner is not batter, advancing batter") this_play.batter_final += 1 return this_play - + # Ball is going to lead base, ask if safe await interaction.channel.send(content=None, embeds=this_roll.embeds) - runner_thrown_out = await out_at_home(lead_safe_range) if lead_base == 4 else await out_at_base(lead_safe_range, lead_runner, lead_base) + runner_thrown_out = ( + await out_at_home(lead_safe_range) + if lead_base == 4 + else await out_at_base(lead_safe_range, lead_runner, lead_base) + ) # Lead runner is thrown out if runner_thrown_out: - logger.info(f'Lead runner is thrown out.') + logger.info(f"Lead runner is thrown out.") this_play.outs += 1 # Lead runner is safe else: - logger.info(f'Lead runner is safe.') - + logger.info(f"Lead runner is safe.") + if this_play.on_second == lead_runner: - logger.info(f'setting lead runner on_second_final') + logger.info(f"setting lead runner on_second_final") if runner_thrown_out: this_play.on_second_final = None else: @@ -2092,7 +2579,7 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact log_run_scored(session, this_play.on_second, this_play) elif this_play.on_first == lead_runner: - logger.info(f'setting lead runner on_first') + logger.info(f"setting lead runner on_first") if runner_thrown_out: this_play.on_first_final = None @@ -2101,84 +2588,95 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact if lead_base == 4: log_run_scored(session, this_play.on_first, this_play) else: - log_exception(LineupsMissingException, f'Could not find lead runner to set final destination') - + log_exception( + LineupsMissingException, + f"Could not find lead runner to set final destination", + ) + # Human lead runner is not advancing else: return this_play - + elif this_play.ai_is_batting: - run_resp = this_play.managerai.uncapped_advance(session, this_game, lead_base, trail_base) + run_resp = this_play.managerai.uncapped_advance( + session, this_game, lead_base, trail_base + ) lead_runner_held = await ask_confirm( interaction=interaction, - question=f'Was **{lead_runner.player.name}** held at {"second" if lead_runner == this_play.on_second else "first"} before the pitch?', - label_type='yes' + question=f"Was **{lead_runner.player.name}** held at {'second' if lead_runner == this_play.on_second else 'first'} before the pitch?", + label_type="yes", ) if lead_runner_held: lead_safe_range -= 1 - lead_runner_embed.add_field(name='Runner Held', value='-1') - logger.info(f'runner was held, -1 to lead safe range: {lead_safe_range}') + lead_runner_embed.add_field(name="Runner Held", value="-1") + logger.info(f"runner was held, -1 to lead safe range: {lead_safe_range}") else: lead_safe_range += 1 - lead_runner_embed.add_field(name='Runner Not Held', value='+1') - logger.info(f'runner was not held, +1 to lead safe range: {lead_safe_range}') - - if lead_safe_range < run_resp.min_safe: - logger.info(f'AI is not advancing with lead runner') - return this_play - - logger.info(f'Building embeds') + lead_runner_embed.add_field(name="Runner Not Held", value="+1") + logger.info( + f"runner was not held, +1 to lead safe range: {lead_safe_range}" + ) + + if lead_safe_range < run_resp.min_safe: + logger.info(f"AI is not advancing with lead runner") + return this_play + + logger.info(f"Building embeds") + + lead_runner_embed.add_field(name="", value="", inline=False) - lead_runner_embed.add_field(name='', value='', inline=False) - if lead_base == 4: - logger.info(f'lead base is 4, building strings') + logger.info(f"lead base is 4, building strings") lead_strings = at_home_strings(lead_safe_range) - lead_runner_embed.add_field(name='Safe Range', value=lead_strings['safe']) - lead_runner_embed.add_field(name='Catcher Check', value=lead_strings['catcher']) - lead_runner_embed.add_field(name='Out Range', value=lead_strings['out']) - + lead_runner_embed.add_field(name="Safe Range", value=lead_strings["safe"]) + lead_runner_embed.add_field( + name="Catcher Check", value=lead_strings["catcher"] + ) + lead_runner_embed.add_field(name="Out Range", value=lead_strings["out"]) + else: - logger.info(f'lead base is 3, building strings') + logger.info(f"lead base is 3, building strings") lead_strings = at_third_strings(lead_safe_range) - lead_runner_embed.add_field(name='Safe Range', value=lead_strings['safe']) - lead_runner_embed.add_field(name='Out Range', value=lead_strings['out']) - + lead_runner_embed.add_field(name="Safe Range", value=lead_strings["safe"]) + lead_runner_embed.add_field(name="Out Range", value=lead_strings["out"]) + if trail_runner == this_play.on_first: trail_runner_held = await ask_confirm( interaction=interaction, - question=f'Was **{trail_runner.player.name}** held at first before the pitch?', - label_type='yes' + question=f"Was **{trail_runner.player.name}** held at first before the pitch?", + label_type="yes", ) - logger.info(f'Trail runner held: {trail_runner_held}') + logger.info(f"Trail runner held: {trail_runner_held}") if trail_runner_held: - trail_runner_embed.add_field(name='Runner Held', value=f'-1') + trail_runner_embed.add_field(name="Runner Held", value=f"-1") trail_safe_range -= 1 - logger.info(f'Trail runner held, -1 to safe range: {trail_safe_range}') + logger.info(f"Trail runner held, -1 to safe range: {trail_safe_range}") else: - trail_runner_embed.add_field(name='Runner Not Held', value='+1') + trail_runner_embed.add_field(name="Runner Not Held", value="+1") trail_safe_range += 1 - logger.info(f'Trail runner not held, +1 to safe range: {trail_safe_range}') + logger.info( + f"Trail runner not held, +1 to safe range: {trail_safe_range}" + ) - trail_strings = at_third_strings(trail_safe_range) - trail_runner_embed.add_field(name='', value='', inline=False) - trail_runner_embed.add_field(name='Safe Range', value=trail_strings['safe']) - trail_runner_embed.add_field(name='Out Range', value=trail_strings['out']) + trail_strings = at_third_strings(trail_safe_range) + trail_runner_embed.add_field(name="", value="", inline=False) + trail_runner_embed.add_field(name="Safe Range", value=trail_strings["safe"]) + trail_runner_embed.add_field(name="Out Range", value=trail_strings["out"]) await interaction.channel.send(embeds=[lead_runner_embed, trail_runner_embed]) is_defense_throwing = await ask_confirm( interaction=interaction, - question=f'{lead_runner.player.name} is advancing {TO_BASE[lead_base]} with a safe range of **1->{lead_safe_range if lead_base == 3 else lead_safe_range - 1}**! Is the defense throwing?', - label_type='yes' + question=f"{lead_runner.player.name} is advancing {TO_BASE[lead_base]} with a safe range of **1->{lead_safe_range if lead_base == 3 else lead_safe_range - 1}**! Is the defense throwing?", + label_type="yes", ) # Human defense is not throwing for lead runner if not is_defense_throwing: - logger.info(f'Defense is not throwing for lead runner') + logger.info(f"Defense is not throwing for lead runner") if this_play.on_second == lead_runner: this_play.rbi += 1 this_play.on_second_final = 4 @@ -2197,42 +2695,47 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact # Human throw is not being cut off if run_resp.send_trail: await interaction.channel.send( - f'**{trail_runner.player.name}** is advancing {TO_BASE[trail_base]} as the trail runner!', + f"**{trail_runner.player.name}** is advancing {TO_BASE[trail_base]} as the trail runner!", ) is_throwing_lead = await ask_confirm( interaction=interaction, - question=f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?', - label_type='yes', - custom_confirm_label='Home Plate' if lead_base == 4 else 'Third Base', - custom_cancel_label='Third Base' if trail_base == 3 else 'Second Base' + question=f"Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?", + label_type="yes", + custom_confirm_label="Home Plate" if lead_base == 4 else "Third Base", + custom_cancel_label="Third Base" if trail_base == 3 else "Second Base", ) # Trail runner advances, throwing for lead runner if is_throwing_lead: if this_play.on_first == trail_runner: - this_play.on_first_final += 1 + this_play.on_first_final += 1 elif this_play.batter == trail_runner: - this_play.batter_final += 1 + this_play.batter_final += 1 else: - log_exception(LineupsMissingException, f'Could not find trail runner to advance') + log_exception( + LineupsMissingException, + f"Could not find trail runner to advance", + ) # Throw is going to trail runner else: - runner_thrown_out = await out_at_base(trail_safe_range, trail_runner, trail_base) + runner_thrown_out = await out_at_base( + trail_safe_range, trail_runner, trail_base + ) if runner_thrown_out: - logger.info(f'Runner was thrown out') + logger.info(f"Runner was thrown out") # Log out on play this_play.outs += 1 # Remove trail runner - logger.info(f'Remove trail runner') + logger.info(f"Remove trail runner") if this_play.on_first == trail_runner: this_play.on_first_final = None else: this_play.batter_final = None else: - logger.info(f'Runner is safe') + logger.info(f"Runner is safe") if this_play.on_first == trail_runner: this_play.on_first_final += 1 if this_play.on_first_final == 4: @@ -2240,35 +2743,43 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact elif this_play.batter == trail_runner: this_play.batter_final += 1 else: - log_exception(LineupsMissingException, f'Could not find trail runner to advance') + log_exception( + LineupsMissingException, + f"Could not find trail runner to advance", + ) - # Advance lead runner extra base - logger.info(f'Advance lead runner extra base') + logger.info(f"Advance lead runner extra base") if this_play.on_second == lead_runner: this_play.rbi += 1 this_play.on_second_final = 4 log_run_scored(session, lead_runner, this_play) - + elif this_play.on_first == lead_runner: this_play.on_first_final += 1 if this_play.on_first_final > 3: this_play.rbi += 1 log_run_scored(session, lead_runner, this_play) - + if trail_runner != this_play.batter: - logger.info(f'Trail runner is not batter, advancing batter') + logger.info(f"Trail runner is not batter, advancing batter") this_play.batter_final += 1 return this_play else: - await interaction.channel.send(content=f'**{trail_runner.player.name}** is NOT trailing to {TO_BASE[trail_base]}.') + await interaction.channel.send( + content=f"**{trail_runner.player.name}** is NOT trailing to {TO_BASE[trail_base]}." + ) # Ball is going to lead base, ask if safe - logger.info(f'Throw is going to lead base') + logger.info(f"Throw is going to lead base") await interaction.channel.send(content=None, embeds=this_roll.embeds) - runner_thrown_out = await out_at_home(lead_safe_range) if lead_base == 4 else await out_at_base(lead_safe_range, trail_runner, trail_base) + runner_thrown_out = ( + await out_at_home(lead_safe_range) + if lead_base == 4 + else await out_at_base(lead_safe_range, trail_runner, trail_base) + ) # runner_thrown_out = await ask_confirm( # interaction=interaction, # question=f'Was **{lead_runner.player.name}** thrown out {AT_BASE[lead_base]}?', @@ -2277,11 +2788,11 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact # Lead runner is thrown out if runner_thrown_out: - logger.info(f'Lead runner is thrown out.') + logger.info(f"Lead runner is thrown out.") this_play.outs += 1 - + if this_play.on_second == lead_runner: - logger.info(f'setting lead runner on_second_final') + logger.info(f"setting lead runner on_second_final") if runner_thrown_out: this_play.on_second_final = None @@ -2290,7 +2801,7 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact if lead_base == 4: log_run_scored(session, this_play.on_second, this_play) elif this_play.on_first == lead_runner: - logger.info(f'setting lead runner on_first_final') + logger.info(f"setting lead runner on_first_final") if runner_thrown_out: this_play.on_first_final = None @@ -2299,25 +2810,33 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact if lead_base == 4: log_run_scored(session, this_play.on_first, this_play) else: - log_exception(LineupsMissingException, f'Could not find lead runner to set final destination') - + log_exception( + LineupsMissingException, + f"Could not find lead runner to set final destination", + ) + return this_play -async def singles(session: Session, interaction: discord.Interaction, this_play: Play, single_type: Literal['*', '**', 'ballpark', 'uncapped']) -> Play: +async def singles( + session: Session, + interaction: discord.Interaction, + this_play: Play, + single_type: Literal["*", "**", "ballpark", "uncapped"], +) -> Play: """ Commits this_play """ this_play.hit, this_play.batter_final = 1, 1 - - if single_type == '**': + + if single_type == "**": this_play = advance_runners(session, this_play, num_bases=2) - elif single_type in ['*', 'ballpark']: + elif single_type in ["*", "ballpark"]: this_play = advance_runners(session, this_play, num_bases=1) - this_play.bp1b = 1 if single_type == 'ballpark' else 0 + this_play.bp1b = 1 if single_type == "ballpark" else 0 - elif single_type == 'uncapped': + elif single_type == "uncapped": this_play = advance_runners(session, this_play, 1) if this_play.on_base_code in [1, 2, 4, 5, 6, 7]: @@ -2328,47 +2847,68 @@ async def singles(session: Session, interaction: discord.Interaction, this_play: if this_play.on_first: trail_runner = this_play.on_first trail_base = 3 - + else: trail_runner = this_play.batter trail_base = 2 - + else: lead_runner = this_play.on_first lead_base = 3 trail_runner = this_play.batter - trail_base = 2 - - this_play = await check_uncapped_advance(session, interaction, this_play, lead_runner, lead_base, trail_runner, trail_base) + trail_base = 2 + + this_play = await check_uncapped_advance( + session, + interaction, + this_play, + lead_runner, + lead_base, + trail_runner, + trail_base, + ) session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def doubles(session: Session, interaction: discord.Interaction, this_play: Play, double_type: Literal['**', '***', 'uncapped']) -> Play: +async def doubles( + session: Session, + interaction: discord.Interaction, + this_play: Play, + double_type: Literal["**", "***", "uncapped"], +) -> Play: """ Commits this_play """ this_play.hit, this_play.double, this_play.batter_final = 1, 1, 2 - if double_type == '**': + if double_type == "**": this_play = advance_runners(session, this_play, num_bases=2) - - elif double_type == '***': + + elif double_type == "***": this_play = advance_runners(session, this_play, num_bases=3) - - elif double_type == 'uncapped': + + elif double_type == "uncapped": this_play = advance_runners(session, this_play, num_bases=2) if this_play.on_first: - this_play = await check_uncapped_advance(session, interaction, this_play, lead_runner=this_play.on_first, lead_base=4, trail_runner=this_play.batter, trail_base=3) + this_play = await check_uncapped_advance( + session, + interaction, + this_play, + lead_runner=this_play.on_first, + lead_base=4, + trail_runner=this_play.batter, + trail_base=3, + ) session.add(this_play) session.commit() - + session.refresh(this_play) return this_play @@ -2382,41 +2922,53 @@ async def triples(session: Session, interaction: discord.Interaction, this_play: session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def homeruns(session: Session, interaction: discord.Interaction, this_play: Play, homerun_type: Literal['ballpark', 'no-doubt']): +async def homeruns( + session: Session, + interaction: discord.Interaction, + this_play: Play, + homerun_type: Literal["ballpark", "no-doubt"], +): this_play.hit, this_play.homerun, this_play.batter_final, this_play.run = 1, 1, 4, 1 - this_play.bphr = 1 if homerun_type == 'ballpark' else 0 + this_play.bphr = 1 if homerun_type == "ballpark" else 0 this_play = advance_runners(session, this_play, num_bases=4) this_play.rbi += 1 log_run_scored(session, this_play.batter, this_play) - + session.refresh(this_play) return this_play -async def walks(session: Session, interaction: discord.Interaction, this_play: Play, walk_type: Literal['unintentional', 'intentional'] = 'unintentional'): +async def walks( + session: Session, + interaction: discord.Interaction, + this_play: Play, + walk_type: Literal["unintentional", "intentional"] = "unintentional", +): this_play.ab, this_play.bb, this_play.batter_final = 0, 1, 1 - this_play.ibb = 1 if walk_type == 'intentional' else 0 + this_play.ibb = 1 if walk_type == "intentional" else 0 this_play = advance_runners(session, this_play, num_bases=1, only_forced=True) session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def strikeouts(session: Session, interaction: discord.Interaction, this_play: Play): +async def strikeouts( + session: Session, interaction: discord.Interaction, this_play: Play +): this_play.so, this_play.outs = 1, 1 this_play = advance_runners(session, this_play, num_bases=0) session.add(this_play) session.commit() - + session.refresh(this_play) return this_play @@ -2427,58 +2979,65 @@ async def popouts(session: Session, interaction: discord.Interaction, this_play: session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def hit_by_pitch(session: Session, interaction: discord.Interaction, this_play: Play): +async def hit_by_pitch( + session: Session, interaction: discord.Interaction, this_play: Play +): this_play.ab, this_play.hbp = 0, 1 this_play.batter_final = 1 this_play = advance_runners(session, this_play, num_bases=1, only_forced=True) session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def bunts(session: Session, interaction: discord.Interaction, this_play: Play, bunt_type: Literal['sacrifice', 'bad', 'popout', 'double-play', 'defense']): - this_play.ab = 1 if bunt_type != 'sacrifice' else 0 - this_play.sac = 1 if bunt_type == 'sacrifice' else 0 +async def bunts( + session: Session, + interaction: discord.Interaction, + this_play: Play, + bunt_type: Literal["sacrifice", "bad", "popout", "double-play", "defense"], +): + this_play.ab = 1 if bunt_type != "sacrifice" else 0 + this_play.sac = 1 if bunt_type == "sacrifice" else 0 this_play.outs = 1 - if bunt_type == 'sacrifice': + if bunt_type == "sacrifice": this_play = advance_runners(session, this_play, num_bases=1) - elif bunt_type == 'popout': + elif bunt_type == "popout": this_play = advance_runners(session, this_play, num_bases=0) - elif bunt_type == 'bad': + elif bunt_type == "bad": this_play = advance_runners(session, this_play, num_bases=1) this_play.batter_final = 1 - + if this_play.on_third is not None: this_play.on_third_final = None elif this_play.on_second is not None: this_play.on_second_final = None - + elif this_play.on_first is not None: this_play.on_first_final = None - elif bunt_type == 'double-play': + elif bunt_type == "double-play": this_play = advance_runners(session, this_play, num_bases=0) this_play.outs = 2 if this_play.starting_outs < 2 else 1 - + if this_play.on_third is not None: this_play.on_third_final = None elif this_play.on_second is not None: this_play.on_second_final = None - + elif this_play.on_first is not None: this_play.on_first_final = None - elif bunt_type == 'defense': + elif bunt_type == "defense": if this_play.on_third is not None: runner = this_play.on_third lead_base = 4 @@ -2486,60 +3045,72 @@ async def bunts(session: Session, interaction: discord.Interaction, this_play: P elif this_play.on_second is not None: runner = this_play.on_second lead_base = 3 - + elif this_play.on_first is not None: runner = this_play.on_first lead_base = 2 take_sure_out = await ask_confirm( interaction=interaction, - question=f'Will you take the sure out at first or throw {TO_BASE[lead_base]} for **{runner.player.name}**?', - custom_confirm_label='Out at first', - custom_cancel_label=f'Throw {TO_BASE[lead_base]}' + question=f"Will you take the sure out at first or throw {TO_BASE[lead_base]} for **{runner.player.name}**?", + custom_confirm_label="Out at first", + custom_cancel_label=f"Throw {TO_BASE[lead_base]}", ) if take_sure_out: this_play.ab = 0 this_play.sac = 1 this_play = advance_runners(session, this_play, num_bases=1) - + else: view = ButtonOptions( responders=[interaction.user], timeout=30, - labels=['Pitcher', 'Catcher', 'First Base', 'Third Base', None] + labels=["Pitcher", "Catcher", "First Base", "Third Base", None], ) question = await interaction.channel.send( - content='Which defender is fielding the bunt? This is determined by the first d6 in your AB roll.', - view=view + content="Which defender is fielding the bunt? This is determined by the first d6 in your AB roll.", + view=view, ) await view.wait() if view.value: await question.delete() - if view.value == 'Pitcher': + if view.value == "Pitcher": defender = this_play.pitcher - elif view.value == 'Catcher': + elif view.value == "Catcher": defender = this_play.catcher - elif view.value == 'First Base': - defender = get_one_lineup(session, this_play.game, this_play.batter.team, position='1B') - elif view.value == 'Third Base': - defender = get_one_lineup(session, this_play.game, this_play.batter.team, position='3B') + elif view.value == "First Base": + defender = get_one_lineup( + session, this_play.game, this_play.batter.team, position="1B" + ) + elif view.value == "Third Base": + defender = get_one_lineup( + session, this_play.game, this_play.batter.team, position="3B" + ) else: - log_exception(NoPlayerResponseException, f'I do not know which defender fielded that ball.') - + log_exception( + NoPlayerResponseException, + f"I do not know which defender fielded that ball.", + ) + else: - await question.edit(content='You keep thinking on it and try again.', view=None) - log_exception(NoPlayerResponseException, f'{interaction.user.name} did not know who was fielding the bunt.') + await question.edit( + content="You keep thinking on it and try again.", view=None + ) + log_exception( + NoPlayerResponseException, + f"{interaction.user.name} did not know who was fielding the bunt.", + ) def_pos = await get_position(session, defender.card, defender.position) lead_runner_out = await ask_confirm( interaction=interaction, - question=f'{runner.player.name}\'s safe range is **1->{runner.card.batterscouting.battingcard.running - 4 + def_pos.range}**. Is the runner out {AT_BASE[lead_base]}?', - custom_confirm_label=f'Out {AT_BASE[lead_base]}', - custom_cancel_label=f'Safe {AT_BASE[lead_base]}' + question=f"{runner.player.name}'s safe range is **1->{runner.card.batterscouting.battingcard.running - 4 + def_pos.range}**. Is the runner out {AT_BASE[lead_base]}?", + custom_confirm_label=f"Out {AT_BASE[lead_base]}", + custom_cancel_label=f"Safe {AT_BASE[lead_base]}", ) if lead_runner_out: @@ -2552,44 +3123,49 @@ async def bunts(session: Session, interaction: discord.Interaction, this_play: P this_play.on_second_final = None elif this_play.on_first is not None: this_play.on_first_final = None - + else: this_play.outs = 0 this_play.batter_final = 1 this_play = advance_runners(session, this_play, 1) else: - log_exception(KeyError, f'Bunt type {bunt_type} is not yet implemented') + log_exception(KeyError, f"Bunt type {bunt_type} is not yet implemented") session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def chaos(session: Session, interaction: discord.Interaction, this_play: Play, chaos_type: Literal['wild-pitch', 'passed-ball', 'balk', 'pickoff']): +async def chaos( + session: Session, + interaction: discord.Interaction, + this_play: Play, + chaos_type: Literal["wild-pitch", "passed-ball", "balk", "pickoff"], +): """ Commits this_play """ this_play.pa, this_play.ab = 0, 0 - if chaos_type == 'wild-pitch': + if chaos_type == "wild-pitch": this_play = advance_runners(session, this_play, 1) this_play.rbi = 0 this_play.wild_pitch = 1 - - elif chaos_type == 'passed-ball': + + elif chaos_type == "passed-ball": this_play = advance_runners(session, this_play, 1) this_play.rbi = 0 this_play.passed_ball = 1 - - elif chaos_type == 'balk': + + elif chaos_type == "balk": this_play = advance_runners(session, this_play, 1) this_play.rbi = 0 this_play.balk = 1 - - elif chaos_type == 'pickoff': + + elif chaos_type == "pickoff": this_play = advance_runners(session, this_play, 0) this_play.pick_off = 1 this_play.outs = 1 @@ -2600,21 +3176,27 @@ async def chaos(session: Session, interaction: discord.Interaction, this_play: P this_play.on_second_final = None elif this_play.on_first: this_play.on_first_final = None - + session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def steals(session: Session, interaction: discord.Interaction, this_play: Play, steal_type: Literal['stolen-base', 'caught-stealing', 'steal-plus-overthrow'], to_base: Literal[2, 3, 4]) -> Play: +async def steals( + session: Session, + interaction: discord.Interaction, + this_play: Play, + steal_type: Literal["stolen-base", "caught-stealing", "steal-plus-overthrow"], + to_base: Literal[2, 3, 4], +) -> Play: this_play = advance_runners(session, this_play, 0) this_play.pa = 0 - if steal_type in ['stolen-base', 'steal-plus-overthrow']: + if steal_type in ["stolen-base", "steal-plus-overthrow"]: this_play.sb = 1 - this_play.error = 1 if steal_type == 'steal-plus-overthrow' else 0 + this_play.error = 1 if steal_type == "steal-plus-overthrow" else 0 if to_base == 4 and this_play.on_third: this_play.runner = this_play.on_third @@ -2623,34 +3205,36 @@ async def steals(session: Session, interaction: discord.Interaction, this_play: if this_play.on_second: this_play.on_second_final = 3 - if steal_type == 'steal-plus-overthrow': + if steal_type == "steal-plus-overthrow": this_play.on_second_final = 4 - log_run_scored(session, this_play.on_second, this_play, is_earned=False) + log_run_scored( + session, this_play.on_second, this_play, is_earned=False + ) if this_play.on_first: - this_play.on_first_final = 2 if steal_type == 'stolen-base' else 3 - + this_play.on_first_final = 2 if steal_type == "stolen-base" else 3 + elif to_base == 3 and this_play.on_second: this_play.runner = this_play.on_second this_play.on_second_final = 3 if this_play.on_first: this_play.on_first_final = 2 - - if steal_type == 'steal-plus-overthrow': + + if steal_type == "steal-plus-overthrow": this_play.on_second_final = 4 log_run_scored(session, this_play.on_second, this_play, is_earned=False) if this_play.on_first: this_play.on_first_final = 3 - + else: this_play.runner = this_play.on_first - this_play.on_first_final = 2 if steal_type == 'stolen-base' else 3 + this_play.on_first_final = 2 if steal_type == "stolen-base" else 3 - if steal_type == 'steal-plus-overthrow' and this_play.on_third: + if steal_type == "steal-plus-overthrow" and this_play.on_third: this_play.on_third_final = 4 log_run_scored(session, this_play.on_third, this_play, is_earned=False) - - elif steal_type == 'caught-stealing': + + elif steal_type == "caught-stealing": this_play.outs = 1 if to_base == 4 and this_play.on_third: @@ -2661,33 +3245,35 @@ async def steals(session: Session, interaction: discord.Interaction, this_play: this_play.on_second_final = 3 if this_play.on_first: this_play.on_first_final = 2 - + elif to_base == 3 and this_play.on_second: this_play.runner = this_play.on_second this_play.on_second_final = None if this_play.on_first: this_play.on_first_final = 2 - + else: this_play.runner = this_play.on_first this_play.on_first_final = None - session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def xchecks(session: Session, interaction: discord.Interaction, this_play: Play, position: str, debug: bool = False) -> Play: +async def xchecks( + session: Session, + interaction: discord.Interaction, + this_play: Play, + position: str, + debug: bool = False, +) -> Play: defense_team = this_play.pitcher.team this_defender = get_one_lineup( - session, - this_play.game, - this_team=defense_team, - position=position + session, this_play.game, this_team=defense_team, position=position ) this_play.defender = this_defender this_play.check_pos = position @@ -2696,49 +3282,48 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: playing_in = False defender_embed = defense_team.embed - defender_embed.title = f'{defense_team.sname} {position} Check' - defender_embed.description = f'{this_defender.player.name}' + defender_embed.title = f"{defense_team.sname} {position} Check" + defender_embed.description = f"{this_defender.player.name}" defender_embed.set_image(url=this_defender.player.image) - logger.info(f'defender_embed: {defender_embed}') + logger.info(f"defender_embed: {defender_embed}") # if not debug: # await interaction.edit_original_response(content=None, embeds=embeds) this_rating = await get_position(session, this_defender.card, position) - logger.info(f'position rating: {this_rating}') + logger.info(f"position rating: {this_rating}") if this_play.on_third is not None: if not this_play.ai_is_batting and defender_is_in: playing_in = True - + elif this_play.ai_is_batting: playing_in = await ask_confirm( interaction, - question=f'Was {this_defender.card.player.name} playing in?', - label_type='yes' + question=f"Was {this_defender.card.player.name} playing in?", + label_type="yes", ) - + if playing_in: this_rating.range = min(this_rating.range + 1, 5) this_roll = sa_fielding_roll(defense_team, this_play, position, this_rating) - logger.info(f'this_roll: {this_roll}') + logger.info(f"this_roll: {this_roll}") if not debug: - question = f'Looks like this is a **{this_roll.hit_result}**' + question = f"Looks like this is a **{this_roll.hit_result}**" if this_roll.is_chaos: - question += ' **rare play**' + question += " **rare play**" elif this_roll.error_result is not None: - question += f' plus {this_roll.error_result}-base error' - question += f'. Is that correct?' + question += f" plus {this_roll.error_result}-base error" + question += f". Is that correct?" await interaction.edit_original_response( - content=None, - embeds=[defender_embed, *this_roll.embeds] + content=None, embeds=[defender_embed, *this_roll.embeds] ) is_correct = await ask_confirm( interaction, question, - label_type='yes', + label_type="yes", timeout=30, ) else: @@ -2748,144 +3333,149 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: error_result = this_roll.error_result is_rare_play = this_roll.is_chaos - logger.info(f'X-Check in Game #{this_play.game_id} at {this_play.check_pos} for {this_play.defender.card.player.name_with_desc} of the {this_play.pitcher.team.sname} / hit_result: {hit_result} / error_result: {error_result} / is_rare_play: {is_rare_play} / is_correct: {is_correct}') + logger.info( + f"X-Check in Game #{this_play.game_id} at {this_play.check_pos} for {this_play.defender.card.player.name_with_desc} of the {this_play.pitcher.team.sname} / hit_result: {hit_result} / error_result: {error_result} / is_rare_play: {is_rare_play} / is_correct: {is_correct}" + ) if not is_correct: - logger.error(f'{interaction.user.name} says the result was wrong.') - logger.info(f'Asking if there was a hit') + logger.error(f"{interaction.user.name} says the result was wrong.") + logger.info(f"Asking if there was a hit") allow_hit = await ask_confirm( interaction, - f'Did **{this_defender.player.name}** allow a hit?', - label_type='yes' + f"Did **{this_defender.player.name}** allow a hit?", + label_type="yes", ) if allow_hit: - if position in ['1B', '2B', '3B', 'SS', 'P']: - hit_options = ['SI1', 'SI2'] - elif position in ['LF', 'CF', 'RF']: - hit_options = ['SI2', 'DO2', 'DO3', 'TR'] + if position in ["1B", "2B", "3B", "SS", "P"]: + hit_options = ["SI1", "SI2"] + elif position in ["LF", "CF", "RF"]: + hit_options = ["SI2", "DO2", "DO3", "TR"] else: - hit_options = ['SI1', 'SPD'] - logger.info(f'Setting hit options to {hit_options}') + hit_options = ["SI1", "SPD"] + logger.info(f"Setting hit options to {hit_options}") else: - if position in ['1B', '2B', '3B', 'SS', 'P']: - hit_options = ['G3#', 'G3', 'G2#', 'G2', 'G1'] - elif position in ['LF', 'CF', 'RF']: - hit_options = ['F1', 'F2', 'F3'] + if position in ["1B", "2B", "3B", "SS", "P"]: + hit_options = ["G3#", "G3", "G2#", "G2", "G1"] + elif position in ["LF", "CF", "RF"]: + hit_options = ["F1", "F2", "F3"] else: - hit_options = ['G3', 'G2', 'G1', 'PO', 'FO'] - logger.info(f'Setting hit options to {hit_options}') - + hit_options = ["G3", "G2", "G1", "PO", "FO"] + logger.info(f"Setting hit options to {hit_options}") + new_hit_result = await ask_with_buttons( interaction, button_options=hit_options, - question=f'Which result did **{this_defender.player.name}** allow?' + question=f"Which result did **{this_defender.player.name}** allow?", ) if new_hit_result is None: - logger.error(f'No hit result was returned') + logger.error(f"No hit result was returned") return - logger.info(f'new hit result: {new_hit_result}') - - logger.info(f'Asking if there was an error') + logger.info(f"new hit result: {new_hit_result}") + + logger.info(f"Asking if there was an error") allow_error = await ask_confirm( interaction, - f'Did **{this_defender.player.name}** commit an error?', - label_type='yes' + f"Did **{this_defender.player.name}** commit an error?", + label_type="yes", ) if allow_error: - if position in ['1B', '2B', '3B', 'SS', 'P', 'C']: - error_options = ['1 base', '2 bases'] + if position in ["1B", "2B", "3B", "SS", "P", "C"]: + error_options = ["1 base", "2 bases"] else: - error_options = ['1 base', '2 bases', '3 bases'] - logger.info(f'Setting error options to {error_options}') + error_options = ["1 base", "2 bases", "3 bases"] + logger.info(f"Setting error options to {error_options}") new_error_result = await ask_with_buttons( interaction, button_options=error_options, - question=f'How many bases was **{this_defender.player.name}**\'s error?' + question=f"How many bases was **{this_defender.player.name}**'s error?", ) if new_error_result is None: - logger.error(f'No error result was returned') + logger.error(f"No error result was returned") return else: - if '1' in new_error_result: + if "1" in new_error_result: new_error_result = 1 - elif '2' in new_error_result: + elif "2" in new_error_result: new_error_result = 2 else: new_error_result = 3 else: new_error_result = None - logger.info(f'new_error_result: {new_error_result}') + logger.info(f"new_error_result: {new_error_result}") - logger.info(f'Setting hit and error results and continuing with processing.') + logger.info(f"Setting hit and error results and continuing with processing.") hit_result = new_hit_result error_result = new_error_result - logger.info(f'hit_result == "SPD" ({hit_result == 'SPD'}) and not is_rare_play ({not is_rare_play})') - if hit_result == 'SPD' and not is_rare_play: - logger.info(f'Non-rare play SPD check') + logger.info( + f'hit_result == "SPD" ({hit_result == "SPD"}) and not is_rare_play ({not is_rare_play})' + ) + if hit_result == "SPD" and not is_rare_play: + logger.info(f"Non-rare play SPD check") runner_speed = this_play.batter.card.batterscouting.battingcard.running speed_embed = this_play.batter.team.embed - speed_embed.title = f'Catcher X-Check - Speed Check' - speed_embed.description = f'{this_play.batter.player.name} Speed Check' - speed_embed.add_field( - name=f'Runner Speed', - value=f'{runner_speed}' - ) + speed_embed.title = f"Catcher X-Check - Speed Check" + speed_embed.description = f"{this_play.batter.player.name} Speed Check" + speed_embed.add_field(name=f"Runner Speed", value=f"{runner_speed}") speed_embed.add_field(name="", value="", inline=False) - speed_embed.add_field(name='Safe Range', value=f'1 - {runner_speed}') - speed_embed.add_field(name='Out Range', value=f'{runner_speed + 1} - 20') + speed_embed.add_field(name="Safe Range", value=f"1 - {runner_speed}") + speed_embed.add_field(name="Out Range", value=f"{runner_speed + 1} - 20") this_roll = d_twenty_roll(this_play.batter.team, this_play.game) if this_roll.d_twenty <= runner_speed: - result = 'SAFE' + result = "SAFE" else: - result = 'OUT' - logger.info(f'SPD check roll: {this_roll.d_twenty} / runner_speed: {runner_speed} / result: {result}') + result = "OUT" + logger.info( + f"SPD check roll: {this_roll.d_twenty} / runner_speed: {runner_speed} / result: {result}" + ) await interaction.channel.send( - content=None, - embeds=[speed_embed, *this_roll.embeds] + content=None, embeds=[speed_embed, *this_roll.embeds] ) is_correct = await ask_confirm( interaction, - f'Looks like **{this_play.batter.player.name}** is {result} at first! Is that correct?', - label_type='yes' + f"Looks like **{this_play.batter.player.name}** is {result} at first! Is that correct?", + label_type="yes", ) if is_correct: - logger.info(f'Result is correct') - if result == 'OUT': - hit_result = 'G3' + logger.info(f"Result is correct") + if result == "OUT": + hit_result = "G3" else: - hit_result = 'SI1' + hit_result = "SI1" else: - logger.info(f'Result is NOT correct') - if result == 'OUT': - hit_result = 'SI1' + logger.info(f"Result is NOT correct") + if result == "OUT": + hit_result = "SI1" else: - hit_result = 'G3' - logger.info(f'Final SPD check result: {hit_result}') - - if '#' in hit_result: - logger.info(f'Checking if the # result becomes a hit') + hit_result = "G3" + logger.info(f"Final SPD check result: {hit_result}") + + if "#" in hit_result: + logger.info(f"Checking if the # result becomes a hit") if this_play.ai_is_batting: - if (position in ['1B', '3B', 'P', 'C'] and def_alignment.corners_in) or (position in ['1B, ''2B', '3B', 'SS', 'P', 'C'] and def_alignment.infield_in): - hit_result = 'SI2' + if (position in ["1B", "3B", "P", "C"] and def_alignment.corners_in) or ( + position in ["1B, 2B", "3B", "SS", "P", "C"] + and def_alignment.infield_in + ): + hit_result = "SI2" elif this_play.on_base_code > 0: is_holding = False if not playing_in: - if position == '1B' and this_play.on_first is not None: + if position == "1B" and this_play.on_first is not None: is_holding = await ask_confirm( interaction, - question=f'Was {this_play.on_second.card.player.name} held at first base?', - label_type='yes' + question=f"Was {this_play.on_second.card.player.name} held at first base?", + label_type="yes", ) - + # elif position == '3B' and this_play.on_second is not None: # is_holding = await ask_confirm( # interaction, @@ -2893,114 +3483,181 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: # label_type='yes' # ) - elif position == '2B' and (this_play.on_first is not None or this_play.on_second is not None) and (this_play.batter.card.batterscouting.battingcard.hand == 'R' or (this_play.batter.card.batterscouting.battingcard.hand == 'S' and this_play.pitcher.card.pitcherscouting.pitchingcard.hand == 'L')): + elif ( + position == "2B" + and ( + this_play.on_first is not None + or this_play.on_second is not None + ) + and ( + this_play.batter.card.batterscouting.battingcard.hand == "R" + or ( + this_play.batter.card.batterscouting.battingcard.hand == "S" + and this_play.pitcher.card.pitcherscouting.pitchingcard.hand + == "L" + ) + ) + ): if this_play.on_second is not None: is_holding = await ask_confirm( interaction, - question=f'Was {this_play.on_second.card.player.name} held at second base?', - label_type='yes' + question=f"Was {this_play.on_second.card.player.name} held at second base?", + label_type="yes", ) elif this_play.on_first is not None: is_holding = await ask_confirm( interaction, - question=f'Was {this_play.on_first.card.player.name} held at first base?', - label_type='yes' + question=f"Was {this_play.on_first.card.player.name} held at first base?", + label_type="yes", ) - - elif position == 'SS' and (this_play.on_first is not None or this_play.on_second is not None) and (this_play.batter.card.batterscouting.battingcard.hand == 'L' or (this_play.batter.card.batterscouting.battingcard.hand == 'S' and this_play.pitcher.card.pitcherscouting.pitchingcard.hand == 'R')): + + elif ( + position == "SS" + and ( + this_play.on_first is not None + or this_play.on_second is not None + ) + and ( + this_play.batter.card.batterscouting.battingcard.hand == "L" + or ( + this_play.batter.card.batterscouting.battingcard.hand == "S" + and this_play.pitcher.card.pitcherscouting.pitchingcard.hand + == "R" + ) + ) + ): if this_play.on_second is not None: is_holding = await ask_confirm( interaction, - question=f'Was {this_play.on_second.card.player.name} held at second base?', - label_type='yes' + question=f"Was {this_play.on_second.card.player.name} held at second base?", + label_type="yes", ) elif this_play.on_first is not None: is_holding = await ask_confirm( interaction, - question=f'Was {this_play.on_first.card.player.name} held at first base?', - label_type='yes' + question=f"Was {this_play.on_first.card.player.name} held at first base?", + label_type="yes", ) if is_holding or playing_in: - hit_result = 'SI2' + hit_result = "SI2" if is_rare_play: - logger.info(f'Is rare play') - if hit_result == 'SI1': - this_play = await singles(session, interaction, this_play, '*') + logger.info(f"Is rare play") + if hit_result == "SI1": + this_play = await singles(session, interaction, this_play, "*") if this_play.on_first is None: this_play.error = 1 this_play.batter_final = 2 - - elif hit_result == 'SI2': - this_play = await singles(session, interaction, this_play, '**') + + elif hit_result == "SI2": + this_play = await singles(session, interaction, this_play, "**") this_play.batter_final = None this_play.outs = 1 - - elif 'DO' in hit_result: - this_play = await doubles(session, interaction, this_play, '***') + + elif "DO" in hit_result: + this_play = await doubles(session, interaction, this_play, "***") this_play.batter_final = None this_play.outs = 1 - - elif hit_result == 'TR': + + elif hit_result == "TR": this_play = await triples(session, interaction, this_play) this_play.batter_final = 4 this_play.run = 1 this_play.error = 1 - - elif hit_result == 'PO': + + elif hit_result == "PO": this_play = advance_runners(session, this_play, 1, earned_bases=0) this_play.ab, this_play.error, this_play.batter_final = 1, 1, 1 - - elif hit_result == 'FO': - this_play = advance_runners(session, this_play, 1, is_error=True, only_forced=True) + + elif hit_result == "FO": + this_play = advance_runners( + session, this_play, 1, is_error=True, only_forced=True + ) this_play.ab, this_play.error, this_play.batter_final = 1, 1, 1 - - elif hit_result == 'G1': + + elif hit_result == "G1": if this_play.on_first is not None and this_play.starting_outs < 2: - this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=playing_in) + this_play = await gb_letter( + session, + interaction, + this_play, + "B", + position=this_play.check_pos, + defender_is_in=playing_in, + ) else: - this_play = await gb_letter(session, interaction, this_play, 'A', position=this_play.check_pos, defender_is_in=playing_in) - - elif hit_result == 'G2': + this_play = await gb_letter( + session, + interaction, + this_play, + "A", + position=this_play.check_pos, + defender_is_in=playing_in, + ) + + elif hit_result == "G2": if this_play.on_base_code > 0: - this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=playing_in) - + this_play = await gb_letter( + session, + interaction, + this_play, + "C", + position=this_play.check_pos, + defender_is_in=playing_in, + ) + else: - this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=playing_in) - - elif hit_result == 'G3': + this_play = await gb_letter( + session, + interaction, + this_play, + "B", + position=this_play.check_pos, + defender_is_in=playing_in, + ) + + elif hit_result == "G3": if this_play.on_base_code > 0: - this_play = await singles(session, interaction, this_play, '*') + this_play = await singles(session, interaction, this_play, "*") else: - this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=playing_in) - - elif hit_result == 'SPD': - this_play = await singles(session, interaction, this_play, '*') - - elif hit_result == 'F1': + this_play = await gb_letter( + session, + interaction, + this_play, + "C", + position=this_play.check_pos, + defender_is_in=playing_in, + ) + + elif hit_result == "SPD": + this_play = await singles(session, interaction, this_play, "*") + + elif hit_result == "F1": this_play.outs = 1 this_play.ab = 1 if this_play.on_third is None else 0 if this_play.on_base_code > 0 and this_play.starting_outs < 2: this_play = advance_runners(session, this_play, 1) - + if this_play.on_second is not None: this_play.on_second_final = 4 - log_run_scored(session, this_play.on_second, this_play, is_earned=False) - + log_run_scored( + session, this_play.on_second, this_play, is_earned=False + ) + elif this_play.on_first is not None: this_play.on_first_final = 3 - - elif hit_result == 'F2': + + elif hit_result == "F2": this_play.outs = 1 this_play.ab = 1 if this_play.on_third is None else 0 - + if this_play.on_base_code > 0 and this_play.starting_outs < 2: this_play.on_third_final = None this_play.outs = 2 - + else: this_play.outs = 1 this_play.ab = 1 @@ -3008,105 +3665,121 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: if this_play.on_third: this_play.outs = 2 this_play.on_third_final = None - + elif this_play.on_second: this_play.outs = 2 this_play.on_second_final = None - + elif this_play.on_first: this_play.outs = 2 this_play.on_first_final = None - elif hit_result not in ['SI1', 'SI2', 'DO2', 'DO3', 'TR'] and error_result is None: - logger.info(f'Not a hit, not an error') + elif hit_result not in ["SI1", "SI2", "DO2", "DO3", "TR"] and error_result is None: + logger.info(f"Not a hit, not an error") if this_play.on_base_code == 0: this_play = await gb_result(session, interaction, this_play, 1) - - else: - to_mif = position in ['2B', 'SS'] - to_right_side = position in ['1B', '2B'] - if 'G3' in hit_result: + else: + to_mif = position in ["2B", "SS"] + to_right_side = position in ["1B", "2B"] + + if "G3" in hit_result: if this_play.on_base_code == 2 and not playing_in: - this_play = await gb_result(session, interaction, this_play, 12) - + this_play = await gb_result(session, interaction, this_play, 12) + elif playing_in and this_play.on_base_code == 5: - this_play = await gb_result(session, interaction, this_play, 7, to_mif, to_right_side) - + this_play = await gb_result( + session, interaction, this_play, 7, to_mif, to_right_side + ) + elif playing_in and this_play.on_base_code in [3, 6]: - this_play = await gb_decide(session, interaction=interaction, this_play=this_play) - + this_play = await gb_decide( + session, interaction=interaction, this_play=this_play + ) + elif playing_in and this_play.on_base_code == 7: this_play = await gb_result(session, interaction, this_play, 11) - + else: this_play = await gb_result(session, interaction, this_play, 3) - - elif 'G2' in hit_result: + + elif "G2" in hit_result: if this_play.on_base_code == 7 and playing_in: this_play = await gb_result(session, interaction, this_play, 11) - + elif not playing_in and this_play.on_base_code in [3, 6]: - this_play = await gb_result(session, interaction, this_play, 5, to_mif=to_mif) - + this_play = await gb_result( + session, interaction, this_play, 5, to_mif=to_mif + ) + elif playing_in and this_play.on_base_code in [3, 5, 6]: this_play = await gb_result(session, interaction, this_play, 1) - + elif this_play.on_base_code == 2: this_play = await gb_result(session, interaction, this_play, 12) - + else: this_play = await gb_result(session, interaction, this_play, 4) - - elif 'G1' in hit_result: + + elif "G1" in hit_result: if this_play.on_base_code == 7 and playing_in: this_play = await gb_result(session, interaction, this_play, 10) - + elif not playing_in and this_play.on_base_code == 4: this_play = await gb_result(session, interaction, this_play, 13) - + elif not playing_in and this_play.on_base_code in [3, 6]: this_play = await gb_result(session, interaction, this_play, 3) - + elif playing_in and this_play.on_base_code in [3, 5, 6]: this_play = await gb_result(session, interaction, this_play, 1) - + elif this_play.on_base_code == 2: this_play = await gb_result(session, interaction, this_play, 12) - + else: this_play = await gb_result(session, interaction, this_play, 2) - - elif 'F1' in hit_result: - this_play = await flyballs(session, interaction, this_play, 'a') - - elif 'F2' in hit_result: - this_play = await flyballs(session, interaction, this_play, 'b') - - elif 'F3' in hit_result: - this_play = await flyballs(session, interaction, this_play, 'c') - + + elif "F1" in hit_result: + this_play = await flyballs(session, interaction, this_play, "a") + + elif "F2" in hit_result: + this_play = await flyballs(session, interaction, this_play, "b") + + elif "F3" in hit_result: + this_play = await flyballs(session, interaction, this_play, "c") + # FO and PO else: this_play.ab, this_play.outs = 1, 1 this_play = advance_runners(session, this_play, 0) - elif hit_result not in ['SI1', 'SI2', 'DO2', 'DO3', 'TR'] and error_result is not None: - logger.info(f'Not a hit, {error_result}-base error') + elif ( + hit_result not in ["SI1", "SI2", "DO2", "DO3", "TR"] + and error_result is not None + ): + logger.info(f"Not a hit, {error_result}-base error") this_play = advance_runners(session, this_play, error_result, earned_bases=0) this_play.ab, this_play.error, this_play.batter_final = 1, 1, error_result - + else: - logger.info(f'Hit result: {hit_result}, Error: {error_result}') - if hit_result == 'SI1' and error_result is None: - this_play = await singles(session, interaction, this_play, '*') - elif hit_result == 'SI1': - this_play.ab, this_play.hit, this_play.error, this_play.batter_final = 1, 1, 1, 2 - this_play = advance_runners(session, this_play, num_bases=error_result + 1, earned_bases=1) - - elif hit_result == 'SI2' and error_result is None: - this_play = await singles(session, interaction, this_play, '**') - elif hit_result == 'SI2': + logger.info(f"Hit result: {hit_result}, Error: {error_result}") + if hit_result == "SI1" and error_result is None: + this_play = await singles(session, interaction, this_play, "*") + elif hit_result == "SI1": + this_play.ab, this_play.hit, this_play.error, this_play.batter_final = ( + 1, + 1, + 1, + 2, + ) + this_play = advance_runners( + session, this_play, num_bases=error_result + 1, earned_bases=1 + ) + + elif hit_result == "SI2" and error_result is None: + this_play = await singles(session, interaction, this_play, "**") + elif hit_result == "SI2": this_play.ab, this_play.hit, this_play.error = 1, 1, 1 if error_result > 1: num_bases = 3 @@ -3114,51 +3787,67 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: else: num_bases = 2 this_play.batter_final = 2 - this_play = advance_runners(session, this_play, num_bases=num_bases, earned_bases=2) - - elif hit_result == 'DO2' and error_result is None: - this_play = await doubles(session, interaction, this_play, '**') - elif hit_result == 'DO2': + this_play = advance_runners( + session, this_play, num_bases=num_bases, earned_bases=2 + ) + + elif hit_result == "DO2" and error_result is None: + this_play = await doubles(session, interaction, this_play, "**") + elif hit_result == "DO2": this_play.ab, this_play.hit, this_play.error, this_play.double = 1, 1, 1, 1 num_bases = 3 - + if error_result == 3: this_play.batter_final = 4 else: this_play.batter_final = 3 - - this_play = advance_runners(session, this_play, num_bases=num_bases, earned_bases=2) - - elif hit_result == 'DO3' and error_result is None: - this_play = await doubles(session, interaction, this_play, '***') - elif hit_result == 'DO3': + + this_play = advance_runners( + session, this_play, num_bases=num_bases, earned_bases=2 + ) + + elif hit_result == "DO3" and error_result is None: + this_play = await doubles(session, interaction, this_play, "***") + elif hit_result == "DO3": this_play.ab, this_play.hit, this_play.error, this_play.double = 1, 1, 1, 1 - + if error_result == 1: this_play.batter_final = 3 else: this_play.batter_final = 4 - + this_play = advance_runners(session, this_play, num_bases=4, earned_bases=2) - - elif hit_result == 'TR' and error_result is None: + + elif hit_result == "TR" and error_result is None: this_play = await triples(session, interaction, this_play) else: - this_play.ab, this_play.hit, this_play.error, this_play.run, this_play.triple, this_play.batter_final = 1, 1, 1, 1, 1, 4 + ( + this_play.ab, + this_play.hit, + this_play.error, + this_play.run, + this_play.triple, + this_play.batter_final, + ) = 1, 1, 1, 1, 1, 4 this_play = advance_runners(session, this_play, num_bases=4, earned_bases=3) session.add(this_play) session.commit() - + session.refresh(this_play) return this_play def activate_last_play(session: Session, this_game: Game) -> Play: - logger.info(f'Pulling last play to complete and advance') - p_query = session.exec(select(Play).where(Play.game == this_game).order_by(Play.play_num.desc()).limit(1)).all() - - logger.info(f'last play: {p_query[0].id}') + logger.info(f"Pulling last play to complete and advance") + p_query = session.exec( + select(Play) + .where(Play.game == this_game) + .order_by(Play.play_num.desc()) + .limit(1) + ).all() + + logger.info(f"last play: {p_query[0].id}") this_play = complete_play(session, p_query[0]) return this_play @@ -3167,72 +3856,97 @@ def activate_last_play(session: Session, this_game: Game) -> Play: def undo_play(session: Session, this_play: Play): this_game = this_play.game after_play_min = max(1, this_play.play_num - 2) - - last_two_plays = session.exec(select(Play).where(Play.game == this_game).order_by(Play.play_num.desc()).limit(2)).all() + + last_two_plays = session.exec( + select(Play) + .where(Play.game == this_game) + .order_by(Play.play_num.desc()) + .limit(2) + ).all() for play in last_two_plays: - for runner, to_base in [(play.on_first, play.on_first_final), (play.on_second, play.on_second_final), (play.on_third, play.on_third_final)]: + for runner, to_base in [ + (play.on_first, play.on_first_final), + (play.on_second, play.on_second_final), + (play.on_third, play.on_third_final), + ]: if to_base == 4: last_pa = get_players_last_pa(session, runner) last_pa.run, last_pa.e_run = 0, 0 session.add(last_pa) - + last_two_ids = [last_two_plays[0].id, last_two_plays[1].id] - logger.warning(f'Deleting plays: {last_two_ids}') + logger.warning(f"Deleting plays: {last_two_ids}") session.exec(delete(Play).where(Play.id.in_(last_two_ids))) new_player_ids = [] - new_players = session.exec(select(Lineup).where(Lineup.game == this_game, Lineup.after_play >= after_play_min)).all() - logger.info(f'Subs to roll back: {new_players}') + new_players = session.exec( + select(Lineup).where( + Lineup.game == this_game, Lineup.after_play >= after_play_min + ) + ).all() + logger.info(f"Subs to roll back: {new_players}") for x in new_players: - logger.info(f'Marking {x} for deletion') + logger.info(f"Marking {x} for deletion") new_player_ids.append(x.id) old_player = session.get(Lineup, x.replacing_id) old_player.active = True session.add(old_player) - logger.warning(f'Deleting lineup IDs: {new_player_ids}') + logger.warning(f"Deleting lineup IDs: {new_player_ids}") session.exec(delete(Lineup).where(Lineup.id.in_(new_player_ids))) session.commit() try: - logger.info(f'Attempting to initialize play for Game {this_game.id}...') + logger.info(f"Attempting to initialize play for Game {this_game.id}...") this_play = this_game.initialize_play(session) - logger.info(f'Initialized play: {this_play.id}') + logger.info(f"Initialized play: {this_play.id}") except PlayInitException: - logger.info(f'Plays found, attempting to active the last play') + logger.info(f"Plays found, attempting to active the last play") this_play = activate_last_play(session, this_game) - logger.info(f'Re-activated play: {this_play.id}') + logger.info(f"Re-activated play: {this_play.id}") return this_play -async def show_defense_cards(session: Session, interaction: discord.Interaction, this_play: Play, first_position: DEFENSE_LITERAL): +async def show_defense_cards( + session: Session, + interaction: discord.Interaction, + this_play: Play, + first_position: DEFENSE_LITERAL, +): position_map = { - 'Pitcher': 'P', - 'Catcher': 'C', - 'First Base': '1B', - 'Second Base': '2B', - 'Third Base': '3B', - 'Shortstop': 'SS', - 'Left Field': 'LF', - 'Center Field': 'CF', - 'Right Field': 'RF' + "Pitcher": "P", + "Catcher": "C", + "First Base": "1B", + "Second Base": "2B", + "Third Base": "3B", + "Shortstop": "SS", + "Left Field": "LF", + "Center Field": "CF", + "Right Field": "RF", } this_position = position_map[first_position] - + sorted_lineups = get_sorted_lineups(session, this_play.game, this_play.pitcher.team) select_player_options = [ - discord.SelectOption(label=f'{x.position} - {x.player.name}', value=f'{x.id}', default=this_position == x.position) for x in sorted_lineups + discord.SelectOption( + label=f"{x.position} - {x.player.name}", + value=f"{x.id}", + default=this_position == x.position, + ) + for x in sorted_lineups ] - - this_lineup = get_one_lineup(session, this_play.game, this_play.pitcher.team, position=this_position) + + this_lineup = get_one_lineup( + session, this_play.game, this_play.pitcher.team, position=this_position + ) player_embed = image_embed( - image_url=this_lineup.player.image, - color=this_play.pitcher.team.color, + image_url=this_lineup.player.image, + color=this_play.pitcher.team.color, author_name=this_play.pitcher.team.lname, - author_icon=this_play.pitcher.team.logo + author_icon=this_play.pitcher.team.logo, ) player_dropdown = SelectViewDefense( options=select_player_options, @@ -3240,285 +3954,323 @@ async def show_defense_cards(session: Session, interaction: discord.Interaction, base_embed=player_embed, session=session, sorted_lineups=sorted_lineups, - responders=[interaction.user] + responders=[interaction.user], ) dropdown_view = DropdownView(dropdown_objects=[player_dropdown], timeout=60) - await interaction.edit_original_response(content=None, embed=player_embed, view=dropdown_view) + await interaction.edit_original_response( + content=None, embed=player_embed, view=dropdown_view + ) def is_game_over(this_play: Play) -> bool: - if this_play.inning_num < 9 and (abs(this_play.away_score - this_play.home_score) < 10): + if this_play.inning_num < 9 and ( + abs(this_play.away_score - this_play.home_score) < 10 + ): return False - + if abs(this_play.away_score - this_play.home_score) >= 10: - if ((this_play.home_score - this_play.away_score) >= 10) and this_play.inning_half == 'bot': + if ( + (this_play.home_score - this_play.away_score) >= 10 + ) and this_play.inning_half == "bot": return True - - elif ((this_play.away_score - this_play.home_score) >= 10) and this_play.is_new_inning and this_play.inning_half == 'top': + + elif ( + ((this_play.away_score - this_play.home_score) >= 10) + and this_play.is_new_inning + and this_play.inning_half == "top" + ): return True - - if this_play.inning_num > 9 and this_play.inning_half == 'top' and this_play.is_new_inning and this_play.home_score != this_play.away_score: + + if ( + this_play.inning_num > 9 + and this_play.inning_half == "top" + and this_play.is_new_inning + and this_play.home_score != this_play.away_score + ): return True - - if this_play.inning_num >= 9 and this_play.inning_half == 'bot' and this_play.home_score > this_play.away_score: + + if ( + this_play.inning_num >= 9 + and this_play.inning_half == "bot" + and this_play.home_score > this_play.away_score + ): return True return False -async def get_game_summary_embed(session: Session, interaction: discord.Interaction, this_play: Play, db_game_id: int, winning_team: Team, losing_team: Team, num_potg: int = 1, num_poop: int = 0): - game_summary = await db_get(f'plays/game-summary/{db_game_id}', params=[('tp_max', num_potg)]) +async def get_game_summary_embed( + session: Session, + interaction: discord.Interaction, + this_play: Play, + db_game_id: int, + winning_team: Team, + losing_team: Team, + num_potg: int = 1, + num_poop: int = 0, +): + game_summary = await db_get( + f"plays/game-summary/{db_game_id}", params=[("tp_max", num_potg)] + ) this_game = this_play.game - final_inning = this_play.inning_num if this_play.inning_half == 'bot' else this_play.inning_num - 1 + final_inning = ( + this_play.inning_num + if this_play.inning_half == "bot" + else this_play.inning_num - 1 + ) game_embed = winning_team.embed - game_embed.title = f'{this_game.away_team.lname} {this_play.away_score} @ {this_play.home_score} {this_game.home_team.lname} - F/{this_play.inning_num}' + game_embed.title = f"{this_game.away_team.lname} {this_play.away_score} @ {this_play.home_score} {this_game.home_team.lname} - F/{this_play.inning_num}" game_embed.add_field( - name='Location', - value=f'{interaction.guild.get_channel(this_game.channel_id).mention}' + name="Location", + value=f"{interaction.guild.get_channel(this_game.channel_id).mention}", ) - game_embed.add_field(name='Game ID', value=f'{db_game_id}') + game_embed.add_field(name="Game ID", value=f"{db_game_id}") - if this_game.game_type == 'major-league': - game_des = 'Major League' - elif this_game.game_type == 'minor-league': - game_des = 'Minor League' - elif this_game.game_type == 'hall-of-fame': - game_des = 'Hall of Fame' - elif this_game.game_type == 'flashback': - game_des = 'Flashback' + if this_game.game_type == "major-league": + game_des = "Major League" + elif this_game.game_type == "minor-league": + game_des = "Minor League" + elif this_game.game_type == "hall-of-fame": + game_des = "Hall of Fame" + elif this_game.game_type == "flashback": + game_des = "Flashback" elif this_game.ranked: - game_des = 'Ranked' - elif 'gauntlet' in this_game.game_type: - game_des = 'Gauntlet' + game_des = "Ranked" + elif "gauntlet" in this_game.game_type: + game_des = "Gauntlet" else: - game_des = 'Unlimited' - - game_embed.description = f'Score Report - {game_des}' + game_des = "Unlimited" + + game_embed.description = f"Score Report - {game_des}" game_embed.add_field( - name='Box Score', - value=f'```\n' - f'Team | R | H | E |\n' - f'{this_game.away_team.abbrev.replace("Gauntlet-", ""): <4} | {game_summary["runs"]["away"]: >2} | ' - f'{game_summary["hits"]["away"]: >2} | {game_summary["errors"]["away"]: >2} |\n' - f'{this_game.home_team.abbrev.replace("Gauntlet-", ""): <4} | {game_summary["runs"]["home"]: >2} | ' - f'{game_summary["hits"]["home"]: >2} | {game_summary["errors"]["home"]: >2} |\n' - f'\n```', - inline=False + name="Box Score", + value=f"```\n" + f"Team | R | H | E |\n" + f"{this_game.away_team.abbrev.replace('Gauntlet-', ''): <4} | {game_summary['runs']['away']: >2} | " + f"{game_summary['hits']['away']: >2} | {game_summary['errors']['away']: >2} |\n" + f"{this_game.home_team.abbrev.replace('Gauntlet-', ''): <4} | {game_summary['runs']['home']: >2} | " + f"{game_summary['hits']['home']: >2} | {game_summary['errors']['home']: >2} |\n" + f"\n```", + inline=False, ) - logger.info(f'getting top players string') - potg_string = '' - for tp in game_summary['top-players']: - player_name = f'{get_player_name_from_dict(tp['player'])}' - potg_line = f'{player_name} - ' - if 'hr' in tp: - potg_line += f'{tp["hit"]}-{tp["ab"]}' - if tp['hr'] > 0: - num = f'{tp["hr"]} ' if tp["hr"] > 1 else "" - potg_line += f', {num}HR' - if tp['triple'] > 0: - num = f'{tp["triple"]} ' if tp["triple"] > 1 else "" - potg_line += f', {num}3B' - if tp['double'] > 0: - num = f'{tp["double"]} ' if tp["double"] > 1 else "" - potg_line += f', {num}2B' - if tp['run'] > 0: - potg_line += f', {tp["run"]} R' - if tp['rbi'] > 0: - potg_line += f', {tp["rbi"]} RBI' + logger.info(f"getting top players string") + potg_string = "" + for tp in game_summary["top-players"]: + player_name = f"{get_player_name_from_dict(tp['player'])}" + potg_line = f"{player_name} - " + if "hr" in tp: + potg_line += f"{tp['hit']}-{tp['ab']}" + if tp["hr"] > 0: + num = f"{tp['hr']} " if tp["hr"] > 1 else "" + potg_line += f", {num}HR" + if tp["triple"] > 0: + num = f"{tp['triple']} " if tp["triple"] > 1 else "" + potg_line += f", {num}3B" + if tp["double"] > 0: + num = f"{tp['double']} " if tp["double"] > 1 else "" + potg_line += f", {num}2B" + if tp["run"] > 0: + potg_line += f", {tp['run']} R" + if tp["rbi"] > 0: + potg_line += f", {tp['rbi']} RBI" else: - potg_line = f'{player_name} - {tp["ip"]} IP, {tp["run"]} R' - if tp['run'] != tp['e_run']: - potg_line += f' ({tp["e_run"]} ER)' - potg_line += f', {tp["hit"]} H, {tp["so"]} K' - potg_line += f', {tp["re24"]:.2f} re24\n' + potg_line = f"{player_name} - {tp['ip']} IP, {tp['run']} R" + if tp["run"] != tp["e_run"]: + potg_line += f" ({tp['e_run']} ER)" + potg_line += f", {tp['hit']} H, {tp['so']} K" + potg_line += f", {tp['re24']:.2f} re24\n" potg_string += potg_line - game_embed.add_field( - name='Players of the Game', - value=potg_string, - inline=False - ) + game_embed.add_field(name="Players of the Game", value=potg_string, inline=False) + + logger.info(f"getting pooper string") + poop_string = "" + if "pooper" in game_summary and game_summary["pooper"] is not None: + if isinstance(game_summary["pooper"], dict): + all_poop = [game_summary["pooper"]] + elif isinstance(game_summary["pooper"], list): + all_poop = game_summary["pooper"] - logger.info(f'getting pooper string') - poop_string = '' - if 'pooper' in game_summary and game_summary['pooper'] is not None: - if isinstance(game_summary['pooper'], dict): - all_poop = [game_summary['pooper']] - elif isinstance(game_summary['pooper'], list): - all_poop = game_summary['pooper'] - for line in all_poop: - player_name = f'{get_player_name_from_dict(line['player'])}' - poop_line = f'{player_name} - ' - - if 'hr' in line: - poop_line += f'{line["hit"]}-{line["ab"]}' + player_name = f"{get_player_name_from_dict(line['player'])}" + poop_line = f"{player_name} - " + + if "hr" in line: + poop_line += f"{line['hit']}-{line['ab']}" else: - poop_line += f'{line["ip"]} IP, {line["run"]} R' - if tp['run'] != line['e_run']: - poop_line += f' ({line["e_run"]} ER)' - poop_line += f', {line["hit"]} H, {line["so"]} K' - poop_line += f', {line["re24"]:.2f} re24\n' + poop_line += f"{line['ip']} IP, {line['run']} R" + if tp["run"] != line["e_run"]: + poop_line += f" ({line['e_run']} ER)" + poop_line += f", {line['hit']} H, {line['so']} K" + poop_line += f", {line['re24']:.2f} re24\n" poop_string += poop_line - + if len(poop_string) > 0: - game_embed.add_field( - name='Pooper of the Game', - value=poop_string, - inline=False - ) + game_embed.add_field(name="Pooper of the Game", value=poop_string, inline=False) - - pit_string = f'Win: {game_summary["pitchers"]["win"]["p_name"]}\nLoss: {game_summary["pitchers"]["loss"]["p_name"]}\n' + pit_string = f"Win: {game_summary['pitchers']['win']['p_name']}\nLoss: {game_summary['pitchers']['loss']['p_name']}\n" hold_string = None - for player in game_summary['pitchers']['holds']: - player_name = f'{get_player_name_from_dict(player)}' + for player in game_summary["pitchers"]["holds"]: + player_name = f"{get_player_name_from_dict(player)}" if hold_string is None: - hold_string = f'Holds: {player_name}' + hold_string = f"Holds: {player_name}" else: - hold_string += f', {player_name}' + hold_string += f", {player_name}" if hold_string is not None: - pit_string += f'{hold_string}\n' + pit_string += f"{hold_string}\n" - if game_summary['pitchers']['save'] is not None: - player_name = f'{get_player_name_from_dict(game_summary["pitchers"]["save"])}' - pit_string += f'Save: {player_name}' + if game_summary["pitchers"]["save"] is not None: + player_name = f"{get_player_name_from_dict(game_summary['pitchers']['save'])}" + pit_string += f"Save: {player_name}" game_embed.add_field( - name=f'Pitching', + name=f"Pitching", value=pit_string, ) def name_list(raw_list: list) -> str: - logger.info(f'raw_list: {raw_list}') + logger.info(f"raw_list: {raw_list}") player_dict = {} for x in raw_list: - if x['player_id'] not in player_dict: - player_dict[x['player_id']] = x + if x["player_id"] not in player_dict: + player_dict[x["player_id"]] = x data_dict = {} for x in raw_list: - if x['player_id'] not in data_dict: - data_dict[x['player_id']] = 1 + if x["player_id"] not in data_dict: + data_dict[x["player_id"]] = 1 else: - data_dict[x['player_id']] += 1 + data_dict[x["player_id"]] += 1 - r_string = '' - logger.info(f'players: {player_dict} / data: {data_dict}') + r_string = "" + logger.info(f"players: {player_dict} / data: {data_dict}") first = True for p_id in data_dict: - r_string += f'{", " if not first else ""}{player_dict[p_id]["p_name"]}' + r_string += f"{', ' if not first else ''}{player_dict[p_id]['p_name']}" if data_dict[p_id] > 1: - r_string += f' {data_dict[p_id]}' + r_string += f" {data_dict[p_id]}" first = False return r_string - logger.info(f'getting running string') - if len(game_summary['running']['sb']) + len(game_summary['running']['csc']) > 0: - run_string = '' - if len(game_summary['running']['sb']) > 0: - run_string += f'SB: {name_list(game_summary["running"]["sb"])}\n' + logger.info(f"getting running string") + if len(game_summary["running"]["sb"]) + len(game_summary["running"]["csc"]) > 0: + run_string = "" + if len(game_summary["running"]["sb"]) > 0: + run_string += f"SB: {name_list(game_summary['running']['sb'])}\n" - if len(game_summary['running']['csc']) > 0: - run_string += f'CSc: {name_list(game_summary["running"]["csc"])}' + if len(game_summary["running"]["csc"]) > 0: + run_string += f"CSc: {name_list(game_summary['running']['csc'])}" - game_embed.add_field( - name=f'Baserunning', - value=run_string - ) + game_embed.add_field(name=f"Baserunning", value=run_string) - logger.info(f'getting xbh string') - if len(game_summary['xbh']['2b']) + len(game_summary['xbh']['3b']) + len(game_summary['xbh']['hr']) > 0: - bat_string = '' - if len(game_summary['xbh']['2b']) > 0: - bat_string += f'2B: {name_list(game_summary["xbh"]["2b"])}\n' + logger.info(f"getting xbh string") + if ( + len(game_summary["xbh"]["2b"]) + + len(game_summary["xbh"]["3b"]) + + len(game_summary["xbh"]["hr"]) + > 0 + ): + bat_string = "" + if len(game_summary["xbh"]["2b"]) > 0: + bat_string += f"2B: {name_list(game_summary['xbh']['2b'])}\n" - if len(game_summary['xbh']['3b']) > 0: - bat_string += f'3B: {name_list(game_summary["xbh"]["3b"])}\n' + if len(game_summary["xbh"]["3b"]) > 0: + bat_string += f"3B: {name_list(game_summary['xbh']['3b'])}\n" - if len(game_summary['xbh']['hr']) > 0: - bat_string += f'HR: {name_list(game_summary["xbh"]["hr"])}\n' + if len(game_summary["xbh"]["hr"]) > 0: + bat_string += f"HR: {name_list(game_summary['xbh']['hr'])}\n" else: - bat_string = 'Oops! All bitches! No XBH from either team.' - - game_embed.add_field( - name='Batting', - value=bat_string, - inline=False - ) + bat_string = "Oops! All bitches! No XBH from either team." + + game_embed.add_field(name="Batting", value=bat_string, inline=False) return game_embed -async def complete_game(session: Session, interaction: discord.Interaction, this_play: Play, bot: discord.Client = None): +async def complete_game( + session: Session, + interaction: discord.Interaction, + this_play: Play, + bot: discord.Client = None, +): # if interaction is not None: # salutation = await interaction.channel.send('GGs, I\'ll tally this game up...') - # Add button with {winning_team} wins! and another with "Roll Back" - + # Add button with {winning_team} wins! and another with "Roll Back" + this_game = this_play.game - async def roll_back(db_game_id: int, game: bool = True, plays: bool = False, decisions: bool = False): + async def roll_back( + db_game_id: int, game: bool = True, plays: bool = False, decisions: bool = False + ): if decisions: try: - await db_delete('decisions/game', object_id=db_game_id) + await db_delete("decisions/game", object_id=db_game_id) except DatabaseError as e: - logger.warning(f'Could not delete decisions for game {db_game_id}: {e}') - + logger.warning(f"Could not delete decisions for game {db_game_id}: {e}") + if plays: try: - await db_delete('plays/game', object_id=db_game_id) + await db_delete("plays/game", object_id=db_game_id) except DatabaseError as e: - logger.warning(f'Could not delete plays for game {db_game_id}: {e}') + logger.warning(f"Could not delete plays for game {db_game_id}: {e}") if game: try: - await db_delete('games', object_id=db_game_id) + await db_delete("games", object_id=db_game_id) except DatabaseError as e: - logger.warning(f'Could not delete game {db_game_id}: {e}') + logger.warning(f"Could not delete game {db_game_id}: {e}") # Post completed game to API game_data = this_game.model_dump() - game_data['home_team_ranking'] = this_game.home_team.ranking - game_data['away_team_ranking'] = this_game.away_team.ranking - game_data['home_team_value'] = this_game.home_team.team_value - game_data['away_team_value'] = this_game.away_team.team_value - game_data['away_score'] = this_play.away_score - game_data['home_score'] = this_play.home_score + game_data["home_team_ranking"] = this_game.home_team.ranking + game_data["away_team_ranking"] = this_game.away_team.ranking + game_data["home_team_value"] = this_game.home_team.team_value + game_data["away_team_value"] = this_game.away_team.team_value + game_data["away_score"] = this_play.away_score + game_data["home_score"] = this_play.home_score - winning_team = this_game.home_team if this_play.home_score > this_play.away_score else this_game.away_team - losing_team = this_game.home_team if this_play.away_score > this_play.home_score else this_game.away_team + winning_team = ( + this_game.home_team + if this_play.home_score > this_play.away_score + else this_game.away_team + ) + losing_team = ( + this_game.home_team + if this_play.away_score > this_play.home_score + else this_game.away_team + ) try: - db_game = await db_post('games', payload=game_data) - db_ready_plays = get_db_ready_plays(session, this_game, db_game['id']) - db_ready_decisions = get_db_ready_decisions(session, this_game, db_game['id']) + db_game = await db_post("games", payload=game_data) + db_ready_plays = get_db_ready_plays(session, this_game, db_game["id"]) + db_ready_decisions = get_db_ready_decisions(session, this_game, db_game["id"]) except Exception as e: - await roll_back(db_game['id']) - log_exception(e, msg='Unable to post game to API, rolling back') - + await roll_back(db_game["id"]) + log_exception(e, msg="Unable to post game to API, rolling back") + # Post game stats to API try: - resp = await db_post('plays', payload=db_ready_plays) + resp = await db_post("plays", payload=db_ready_plays) except Exception as e: - await roll_back(db_game['id'], plays=True) - log_exception(e, msg='Unable to post plays to API, rolling back') - + await roll_back(db_game["id"], plays=True) + log_exception(e, msg="Unable to post plays to API, rolling back") + if len(resp) > 0: pass try: - resp = await db_post('decisions', payload={'decisions': db_ready_decisions}) + resp = await db_post("decisions", payload={"decisions": db_ready_decisions}) except Exception as e: - await roll_back(db_game['id'], plays=True, decisions=True) - log_exception(e, msg='Unable to post decisions to API, rolling back') - + await roll_back(db_game["id"], plays=True, decisions=True) + log_exception(e, msg="Unable to post decisions to API, rolling back") + if len(resp) > 0: pass @@ -3528,23 +4280,23 @@ async def complete_game(session: Session, interaction: discord.Interaction, this session, winning_team=winning_team, losing_team=losing_team, - this_game=this_game + this_game=this_game, ) - if 'gauntlet' in this_game.game_type: - logger.info(f'Posting gauntlet results') + if "gauntlet" in this_game.game_type: + logger.info(f"Posting gauntlet results") await post_result( - run_id=int(this_game.game_type.split('-')[3]), + run_id=int(this_game.game_type.split("-")[3]), is_win=winning_team.gmid == interaction.user.id, this_team=this_game.human_team, bot=bot, channel=interaction.channel, - responders=[interaction.user] + responders=[interaction.user], ) except Exception as e: - await roll_back(db_game['id'], plays=True, decisions=True) - log_exception(e, msg='Error while posting game rewards') - + await roll_back(db_game["id"], plays=True, decisions=True) + log_exception(e, msg="Error while posting game rewards") + session.delete(this_play) session.commit() @@ -3553,30 +4305,24 @@ async def complete_game(session: Session, interaction: discord.Interaction, this session, interaction, this_play, - db_game['id'], + db_game["id"], winning_team=winning_team, losing_team=losing_team, num_potg=3, - num_poop=1 + num_poop=1, ) + summary_embed.add_field(name=f"{winning_team.abbrev} Rewards", value=win_reward) + summary_embed.add_field(name=f"{losing_team.abbrev} Rewards", value=loss_reward) summary_embed.add_field( - name=f'{winning_team.abbrev} Rewards', - value=win_reward - ) - summary_embed.add_field( - name=f'{losing_team.abbrev} Rewards', - value=loss_reward - ) - summary_embed.add_field( - name='Highlights', - value=f'Please share the highlights in {get_channel(interaction, "pd-news-ticker").mention}!', - inline=False + name="Highlights", + value=f"Please share the highlights in {get_channel(interaction, 'pd-news-ticker').mention}!", + inline=False, ) # Create and post game summary to game channel and pd-network-news - news_ticker = get_channel(interaction, 'pd-network-news') + news_ticker = get_channel(interaction, "pd-network-news") if news_ticker is not None: await news_ticker.send(content=None, embed=summary_embed) @@ -3588,266 +4334,296 @@ async def complete_game(session: Session, interaction: discord.Interaction, this session.add(this_game) session.commit() - logger.info(f'Just ended game {game_id}') + logger.info(f"Just ended game {game_id}") -async def update_game_settings(session: Session, interaction: discord.Interaction, this_game: Game, roll_buttons: bool = None, auto_roll: bool = None) -> discord.Embed: +async def update_game_settings( + session: Session, + interaction: discord.Interaction, + this_game: Game, + roll_buttons: bool = None, + auto_roll: bool = None, +) -> discord.Embed: if roll_buttons is not None: this_game.roll_buttons = roll_buttons if auto_roll is not None: this_game.auto_roll = auto_roll - + session.add(this_game) session.commit() session.refresh(this_game) - this_team = this_game.away_team if this_game.away_team.gmid == interaction.user.id else this_game.home_team + this_team = ( + this_game.away_team + if this_game.away_team.gmid == interaction.user.id + else this_game.home_team + ) embed = this_team.embed - - embed.title = f'Game Settings - {this_team.lname}' + + embed.title = f"Game Settings - {this_team.lname}" embed.add_field( - name='Roll Buttons', - value=f'{"ON" if this_game.roll_buttons else "OFF"}' - ) - embed.add_field( - name='Auto Roll', - value=f'{"ON" if this_game.auto_roll else "OFF"}' + name="Roll Buttons", value=f"{'ON' if this_game.roll_buttons else 'OFF'}" ) + embed.add_field(name="Auto Roll", value=f"{'ON' if this_game.auto_roll else 'OFF'}") return embed -async def manual_end_game(session: Session, interaction: discord.Interaction, this_game: Game, current_play: Play): - logger.info(f'manual_end_game - Game {this_game.id}') - GAME_DONE_STRING = 'Okay, it\'s gone. You\'re free to start another one!' - GAME_STAYS_STRING = 'No problem, this game will continue!' +async def manual_end_game( + session: Session, + interaction: discord.Interaction, + this_game: Game, + current_play: Play, +): + logger.info(f"manual_end_game - Game {this_game.id}") + GAME_DONE_STRING = "Okay, it's gone. You're free to start another one!" + GAME_STAYS_STRING = "No problem, this game will continue!" if not is_game_over(current_play): - logger.info(f'manual_end_game - game is not over') + logger.info(f"manual_end_game - game is not over") - if current_play.inning_num == 1 and current_play.play_num < 3 and 'gauntlet' not in this_game.game_type.lower(): - logger.info(f'manual_end_game - {this_game.game_type} game just started, asking for confirmation') + if ( + current_play.inning_num == 1 + and current_play.play_num < 3 + and "gauntlet" not in this_game.game_type.lower() + ): + logger.info( + f"manual_end_game - {this_game.game_type} game just started, asking for confirmation" + ) - await interaction.edit_original_response(content='Looks like this game just started.') + await interaction.edit_original_response( + content="Looks like this game just started." + ) cancel_early = await ask_confirm( interaction, - 'Are you sure you want to cancel it?', - label_type='yes', + "Are you sure you want to cancel it?", + label_type="yes", timeout=30, - delete_question=False + delete_question=False, ) if cancel_early: - logger.info(f'{interaction.user.name} is cancelling the game') + logger.info(f"{interaction.user.name} is cancelling the game") await interaction.channel.send(content=GAME_DONE_STRING) - news_ticker = get_channel(interaction, 'pd-network-news') + news_ticker = get_channel(interaction, "pd-network-news") if news_ticker is not None: - await news_ticker.send(content=f'{interaction.user.display_name} had dinner plans so had to end their game down in {interaction.channel.mention} early.') - + await news_ticker.send( + content=f"{interaction.user.display_name} had dinner plans so had to end their game down in {interaction.channel.mention} early." + ) + this_game.active = False session.add(this_game) session.commit() - - else: - logger.info(f'{interaction.user.name} is not cancelling the game') - await interaction.channel.send_message(content=GAME_STAYS_STRING) - - return - - else: - logger.info(f'manual_end_game - {this_game.game_type} game currently in inning #{current_play.inning_num}, asking for confirmation') - await interaction.edit_original_response(content='It doesn\'t look like this game isn\'t over, yet. I can end it, but no rewards will be paid out and you will take the L.') + else: + logger.info(f"{interaction.user.name} is not cancelling the game") + await interaction.channel.send_message(content=GAME_STAYS_STRING) + + return + + else: + logger.info( + f"manual_end_game - {this_game.game_type} game currently in inning #{current_play.inning_num}, asking for confirmation" + ) + + await interaction.edit_original_response( + content="It doesn't look like this game isn't over, yet. I can end it, but no rewards will be paid out and you will take the L." + ) forfeit_game = await ask_confirm( interaction, - 'Should I end this game?', - label_type='yes', + "Should I end this game?", + label_type="yes", timeout=30, - delete_question=False + delete_question=False, ) if forfeit_game: - logger.info(f'{interaction.user.name} is forfeiting the game') + logger.info(f"{interaction.user.name} is forfeiting the game") game_data = this_game.model_dump() - game_data['home_team_ranking'] = this_game.home_team.ranking - game_data['away_team_ranking'] = this_game.away_team.ranking - game_data['home_team_value'] = this_game.home_team.team_value - game_data['away_team_value'] = this_game.away_team.team_value - game_data['away_score'] = current_play.away_score - game_data['home_score'] = current_play.home_score - game_data['forfeit'] = True + game_data["home_team_ranking"] = this_game.home_team.ranking + game_data["away_team_ranking"] = this_game.away_team.ranking + game_data["home_team_value"] = this_game.home_team.team_value + game_data["away_team_value"] = this_game.away_team.team_value + game_data["away_score"] = current_play.away_score + game_data["home_score"] = current_play.home_score + game_data["forfeit"] = True try: - db_game = await db_post('games', payload=game_data) + db_game = await db_post("games", payload=game_data) except Exception as e: - logger.error(f'Unable to post forfeited game') - + logger.error(f"Unable to post forfeited game") + await interaction.channel.send(content=GAME_DONE_STRING) - news_ticker = get_channel(interaction, 'pd-network-news') + news_ticker = get_channel(interaction, "pd-network-news") if news_ticker is not None: - await news_ticker.send(content=f'{interaction.user.display_name} escorts the {this_game.human_team.sname} out of {interaction.channel.mention} in protest.') - + await news_ticker.send( + content=f"{interaction.user.display_name} escorts the {this_game.human_team.sname} out of {interaction.channel.mention} in protest." + ) + this_game.active = False session.add(this_game) session.commit() - + else: - logger.info(f'{interaction.user.name} is not forfeiting the game') + logger.info(f"{interaction.user.name} is not forfeiting the game") await interaction.channel.send(content=GAME_STAYS_STRING) return - + # else: # logger.info(f'manual_end_game - gauntlet game currently in inning #{current_play.inning_num}, asking for confirmation') # await interaction.edit_original_response(content='It doesn\'t look like this game is over, yet. I can end it, but no rewards will be paid out and you will take the L.') else: - logger.info(f'manual_end_game - game is over') + logger.info(f"manual_end_game - game is over") await complete_game(session, interaction, current_play) -async def groundballs(session: Session, interaction: discord.Interaction, this_play: Play, groundball_letter: Literal['a', 'b', 'c']): +async def groundballs( + session: Session, + interaction: discord.Interaction, + this_play: Play, + groundball_letter: Literal["a", "b", "c"], +): if this_play.starting_outs == 2: return await gb_result(session, interaction, this_play, 1) - - if this_play.on_base_code == 2 and groundball_letter in ['a', 'b']: - logger.info(f'Groundball {groundball_letter} with runner on second') + + if this_play.on_base_code == 2 and groundball_letter in ["a", "b"]: + logger.info(f"Groundball {groundball_letter} with runner on second") to_right_side = await ask_confirm( interaction, - question=f'Was that ball hit to either 1B or 2B?', - label_type='yes' + question=f"Was that ball hit to either 1B or 2B?", + label_type="yes", ) this_play = gb_result_6(session, this_play, to_right_side) - - elif this_play.on_base_code in [3, 6] and groundball_letter in ['a', 'b']: - logger.info(f'Groundball {groundball_letter} with runner on third') + + elif this_play.on_base_code in [3, 6] and groundball_letter in ["a", "b"]: + logger.info(f"Groundball {groundball_letter} with runner on third") def_alignment = this_play.managerai.defense_alignment(session, this_play.game) if this_play.game.ai_team is not None and this_play.pitcher.team.is_ai: if def_alignment.infield_in: - logger.info(f'AI on defense, playing in') - if groundball_letter == 'a': + logger.info(f"AI on defense, playing in") + if groundball_letter == "a": this_play = gb_result_7(session, this_play) - + else: this_play = gb_result_1(session, this_play) else: - logger.info(f'AI on defense, playing back') + logger.info(f"AI on defense, playing back") to_mif = await ask_confirm( interaction, - question=f'Was that ball hit to either 2B or SS?', - label_type='yes' + question=f"Was that ball hit to either 2B or SS?", + label_type="yes", ) - + if to_mif or not def_alignment.corners_in: - logger.info(f'playing back, gb 5') + logger.info(f"playing back, gb 5") this_play = gb_result_5(session, this_play, to_mif) - + else: - logger.info(f'corners in, gb 7') + logger.info(f"corners in, gb 7") this_play = gb_result_7(session, this_play) else: - logger.info(f'Checking if hit to MIF') + logger.info(f"Checking if hit to MIF") to_mif = await ask_confirm( interaction, - question=f'Was that ball hit to either 2B or SS?', - label_type='yes' + question=f"Was that ball hit to either 2B or SS?", + label_type="yes", ) if to_mif: playing_in = await ask_confirm( - interaction, - question=f'Were they playing in?', - label_type='yes', + interaction, + question=f"Were they playing in?", + label_type="yes", ) if playing_in: - logger.info(f'To MIF, batter out, runners hold') + logger.info(f"To MIF, batter out, runners hold") this_play = gb_result_1(session, this_play) else: - logger.info(f'To MIF, playing back, gb 3') + logger.info(f"To MIF, playing back, gb 3") this_play = gb_result_3(session, this_play) else: - logger.info(f'Batter out, runners hold') + logger.info(f"Batter out, runners hold") this_play = gb_result_1(session, this_play) - elif this_play.on_base_code in [5, 7] and groundball_letter == 'a': - logger.info(f'Groundball {groundball_letter} with runners on including third') + elif this_play.on_base_code in [5, 7] and groundball_letter == "a": + logger.info(f"Groundball {groundball_letter} with runners on including third") if this_play.game.ai_team is not None and this_play.pitcher.team.is_ai: - def_alignment = this_play.managerai.defense_alignment(session, this_play.game) - logger.info(f'def_alignment: {def_alignment}') + def_alignment = this_play.managerai.defense_alignment( + session, this_play.game + ) + logger.info(f"def_alignment: {def_alignment}") if def_alignment.infield_in: if this_play.on_base_code == 5: - logger.info(f'playing in, gb 7') + logger.info(f"playing in, gb 7") this_play = gb_result_7(session, this_play) - + else: - logger.info(f'playing in, gb 10') + logger.info(f"playing in, gb 10") this_play = gb_result_10(session, this_play) - + elif def_alignment.corners_in: - logger.info(f'Checking if ball was hit to 1B/3B') + logger.info(f"Checking if ball was hit to 1B/3B") to_cif = await ask_confirm( - interaction, - f'Was that ball hit to 1B/3B?', - label_type='yes' + interaction, f"Was that ball hit to 1B/3B?", label_type="yes" ) if to_cif: if this_play.on_base_code == 5: - logger.info(f'Corners in, gb 7') + logger.info(f"Corners in, gb 7") this_play = gb_result_7(session, this_play) - + else: - logger.info(f'Corners in, gb 10') + logger.info(f"Corners in, gb 10") this_play = gb_result_10(session, this_play) else: - logger.info(f'Corners back, gb 2') + logger.info(f"Corners back, gb 2") this_play = gb_result_2(session, this_play) - + else: - logger.info(f'playing back, gb 2') + logger.info(f"playing back, gb 2") this_play = gb_result_2(session, this_play) else: playing_in = await ask_confirm( - interaction, - question='Was the defender playing in?', - label_type='yes' + interaction, question="Was the defender playing in?", label_type="yes" ) if playing_in and this_play.on_base_code == 5: - logger.info(f'playing in, gb 7') + logger.info(f"playing in, gb 7") this_play = gb_result_7(session, this_play) - + elif playing_in: - logger.info(f'playing in, gb 10') + logger.info(f"playing in, gb 10") this_play = gb_result_10(session, this_play) - + else: - logger.info(f'playing back, gb 2') + logger.info(f"playing back, gb 2") this_play = gb_result_2(session, this_play) - elif this_play.on_base_code in [5, 7] and groundball_letter == 'b': - logger.info(f'Groundball {groundball_letter} with runners on including third') + elif this_play.on_base_code in [5, 7] and groundball_letter == "b": + logger.info(f"Groundball {groundball_letter} with runners on including third") playing_in = False if this_play.game.ai_team is not None and this_play.pitcher.team.is_ai: - def_alignment = this_play.managerai.defense_alignment(session, this_play.game) - logger.info(f'def_alignment: {def_alignment}') + def_alignment = this_play.managerai.defense_alignment( + session, this_play.game + ) + logger.info(f"def_alignment: {def_alignment}") to_mif = await ask_confirm( - interaction, - question='Was that hit to 2B/SS?', - label_type='yes' + interaction, question="Was that hit to 2B/SS?", label_type="yes" ) if def_alignment.infield_in or not to_mif and def_alignment.corners_in: @@ -3855,21 +4631,19 @@ async def groundballs(session: Session, interaction: discord.Interaction, this_p else: playing_in = await ask_confirm( - interaction, - question='Was the defender playing in?', - label_type='yes' + interaction, question="Was the defender playing in?", label_type="yes" ) - + if playing_in and this_play.on_base_code == 7: - logger.info(f'playing in, gb 11') + logger.info(f"playing in, gb 11") this_play = gb_result_11(session, this_play) - + elif playing_in: - logger.info(f'playing in, gb 9') + logger.info(f"playing in, gb 9") this_play = gb_result_9(session, this_play) - + else: - logger.info(f'playing back, gb 4') + logger.info(f"playing back, gb 4") this_play = gb_result_4(session, this_play) else: @@ -3880,8 +4654,8 @@ async def groundballs(session: Session, interaction: discord.Interaction, this_p else: to_mif = await ask_confirm( interaction, - question='Was that ball hit to 2B/SS?', - label_type='yes' + question="Was that ball hit to 2B/SS?", + label_type="yes", ) if not to_mif and def_align.corners_in: @@ -3891,66 +4665,97 @@ async def groundballs(session: Session, interaction: discord.Interaction, this_p else: playing_in = False - this_play = await gb_letter(session, interaction, this_play, groundball_letter.upper(), 'None', playing_in) - + this_play = await gb_letter( + session, + interaction, + this_play, + groundball_letter.upper(), + "None", + playing_in, + ) + session.add(this_play) session.commit() - + session.refresh(this_play) return this_play -async def gb_letter(session: Session, interaction: discord.Interaction, this_play: Play, groundball_letter: Literal['A', 'B', 'C'], position: str, defender_is_in: bool): +async def gb_letter( + session: Session, + interaction: discord.Interaction, + this_play: Play, + groundball_letter: Literal["A", "B", "C"], + position: str, + defender_is_in: bool, +): """ Commits this_play """ if not defender_is_in: if this_play.on_base_code == 0: return await gb_result(session, interaction, this_play, 1) - - elif groundball_letter == 'C': + + elif groundball_letter == "C": return await gb_result(session, interaction, this_play, 3) - - elif groundball_letter == 'A' and this_play.on_base_code in [1, 4, 5, 7]: + + elif groundball_letter == "A" and this_play.on_base_code in [1, 4, 5, 7]: return await gb_result(session, interaction, this_play, 2) - - elif groundball_letter == 'B' and this_play.on_base_code in [1, 4, 5, 7]: + + elif groundball_letter == "B" and this_play.on_base_code in [1, 4, 5, 7]: return await gb_result(session, interaction, this_play, 4) - + elif this_play.on_base_code in [3, 6]: - return await gb_result(session, interaction, this_play, 5, to_mif=position in ['2B', 'SS']) - + return await gb_result( + session, interaction, this_play, 5, to_mif=position in ["2B", "SS"] + ) + else: - return await gb_result(session, interaction, this_play, 6, to_right_side=position in ['1B', '2B']) - + return await gb_result( + session, + interaction, + this_play, + 6, + to_right_side=position in ["1B", "2B"], + ) + else: - if groundball_letter == 'A' and this_play.on_base_code == 7: + if groundball_letter == "A" and this_play.on_base_code == 7: return await gb_result(session, interaction, this_play, 10) - - elif groundball_letter == 'B' and this_play.on_base_code == 5: + + elif groundball_letter == "B" and this_play.on_base_code == 5: return await gb_result(session, interaction, this_play, 9) - + elif this_play.on_base_code == 7: return await gb_result(session, interaction, this_play, 11) - - elif groundball_letter == 'A': + + elif groundball_letter == "A": return await gb_result(session, interaction, this_play, 7) - - elif groundball_letter == 'B': + + elif groundball_letter == "B": return await gb_result(session, interaction, this_play, 1) - + else: return await gb_result(session, interaction, this_play, 8) -async def gb_result(session: Session, interaction: discord.Interaction, this_play: Play, groundball_result: int, to_mif: bool = None, to_right_side: bool = None): +async def gb_result( + session: Session, + interaction: discord.Interaction, + this_play: Play, + groundball_result: int, + to_mif: bool = None, + to_right_side: bool = None, +): """ Commits this_play Result 5 requires to_mif Result 6 requires to_right_side """ - logger.info(f'Starting a groundball result: GB #{groundball_result}, to_mif: {to_mif}, to_right_side: {to_right_side}') + logger.info( + f"Starting a groundball result: GB #{groundball_result}, to_mif: {to_mif}, to_right_side: {to_right_side}" + ) if groundball_result == 1: this_play = gb_result_1(session, this_play) elif groundball_result == 2: @@ -3986,7 +4791,7 @@ async def gb_result(session: Session, interaction: discord.Interaction, this_pla def gb_result_1(session: Session, this_play: Play): - logger.info(f'GB 1') + logger.info(f"GB 1") this_play = advance_runners(session, this_play, 0) this_play.ab, this_play.outs = 1, 1 @@ -3994,7 +4799,7 @@ def gb_result_1(session: Session, this_play: Play): def gb_result_2(session: Session, this_play: Play): - logger.info(f'GB 2') + logger.info(f"GB 2") num_outs = 2 if this_play.starting_outs <= 1 else 1 this_play.ab, this_play.outs = 1, num_outs @@ -4006,21 +4811,21 @@ def gb_result_2(session: Session, this_play: Play): if this_play.on_third: this_play.on_third_final = 4 log_run_scored(session, runner=this_play.on_third, this_play=this_play) - + return this_play def gb_result_3(session: Session, this_play: Play): - logger.info(f'GB 3') + logger.info(f"GB 3") if this_play.starting_outs < 2: this_play = advance_runners(session, this_play, 1) - + this_play.ab, this_play.outs = 1, 1 return this_play def gb_result_4(session: Session, this_play: Play): - logger.info(f'GB 4') + logger.info(f"GB 4") if this_play.starting_outs < 2: this_play = advance_runners(session, this_play, 1) @@ -4032,43 +4837,43 @@ def gb_result_4(session: Session, this_play: Play): def gb_result_5(session: Session, this_play: Play, to_mif: bool): - logger.info(f'GB 5') + logger.info(f"GB 5") this_play.ab, this_play.outs = 1, 1 if to_mif: this_play = gb_result_3(session, this_play) else: this_play = gb_result_1(session, this_play) - + return this_play def gb_result_6(session: Session, this_play: Play, to_right_side: bool): - logger.info(f'GB 6') + logger.info(f"GB 6") this_play.ab, this_play.outs = 1, 1 if to_right_side: this_play = gb_result_3(session, this_play) else: this_play = gb_result_1(session, this_play) - + return this_play def gb_result_7(session: Session, this_play: Play): - logger.info(f'GB 7') + logger.info(f"GB 7") this_play.ab, this_play.outs = 1, 1 this_play = advance_runners(session, this_play, num_bases=1, only_forced=True) return this_play def gb_result_8(session: Session, this_play: Play): - logger.info(f'GB 8') + logger.info(f"GB 8") return gb_result_7(session, this_play) def gb_result_9(session: Session, this_play: Play): - logger.info(f'GB 9') + logger.info(f"GB 9") this_play.ab, this_play.outs = 1, 1 this_play.on_third_final = 3 this_play.on_first_final = 2 @@ -4076,7 +4881,7 @@ def gb_result_9(session: Session, this_play: Play): def gb_result_10(session: Session, this_play: Play): - logger.info(f'GB 10') + logger.info(f"GB 10") num_outs = 2 if this_play.starting_outs <= 1 else 1 this_play.ab, this_play.outs = 1, num_outs this_play.on_second_final = 3 @@ -4085,7 +4890,7 @@ def gb_result_10(session: Session, this_play: Play): def gb_result_11(session: Session, this_play: Play): - logger.info(f'GB 11') + logger.info(f"GB 11") this_play.ab, this_play.outs = 1, 1 this_play.on_first_final = 2 this_play.on_second_final = 3 @@ -4093,68 +4898,78 @@ def gb_result_11(session: Session, this_play: Play): return this_play -async def gb_decide(session: Session, this_play: Play, interaction: discord.Interaction): - logger.info(f'GB Decide') - runner = this_play.on_third if this_play.on_third is not None else this_play.on_second - logger.info(f'runner: {runner}') - pos_rating = await get_position(session, this_play.defender.card, this_play.check_pos) +async def gb_decide( + session: Session, this_play: Play, interaction: discord.Interaction +): + logger.info(f"GB Decide") + runner = ( + this_play.on_third if this_play.on_third is not None else this_play.on_second + ) + logger.info(f"runner: {runner}") + pos_rating = await get_position( + session, this_play.defender.card, this_play.check_pos + ) safe_range = runner.card.batterscouting.battingcard.running - 4 + pos_rating.range advance_base = 4 if this_play.on_third is not None else 3 - logger.info(f'pos_rating: {pos_rating}\nsafe_range: {safe_range}\nadvance_base: {advance_base}') - + logger.info( + f"pos_rating: {pos_rating}\nsafe_range: {safe_range}\nadvance_base: {advance_base}" + ) + if this_play.game.ai_team is not None and this_play.ai_is_batting: run_resp = this_play.managerai.gb_decide_run(session, this_play.game) if this_play.on_second is None and this_play.on_third is None: - log_exception(InvalidResultException, 'Cannot run GB Decide without a runner on base.') - + log_exception( + InvalidResultException, "Cannot run GB Decide without a runner on base." + ) + if safe_range >= run_resp.min_safe: is_lead_running = True else: is_lead_running = False - + else: is_lead_running = await ask_confirm( interaction, - f'Is **{runner.card.player.name}** attempting to advance {TO_BASE[advance_base]} with a **1-{safe_range}** safe range?', - label_type='yes', - delete_question=False + f"Is **{runner.card.player.name}** attempting to advance {TO_BASE[advance_base]} with a **1-{safe_range}** safe range?", + label_type="yes", + delete_question=False, ) - + if not is_lead_running: this_play = advance_runners(session, this_play, 0) this_play.outs = 1 - + else: if this_play.game.ai_team is not None and not this_play.ai_is_batting: throw_resp = this_play.managerai.gb_decide_throw( - session, - this_play.game, - runner_speed=runner.card.batterscouting.battingcard.running, - defender_range=pos_rating.range + session, + this_play.game, + runner_speed=runner.card.batterscouting.battingcard.running, + defender_range=pos_rating.range, ) throw_for_lead = throw_resp.at_lead_runner await interaction.channel.send( - content=f'**{this_play.defender.player.name}** is {"not " if not throw_for_lead else ""}throwing for {runner.player.name}{"!" if throw_for_lead else "."}' + content=f"**{this_play.defender.player.name}** is {'not ' if not throw_for_lead else ''}throwing for {runner.player.name}{'!' if throw_for_lead else '.'}" ) else: throw_for_lead = await ask_confirm( interaction, - f'Is {this_play.defender.player.name} throwing for {runner.player.name}?', - label_type='yes' + f"Is {this_play.defender.player.name} throwing for {runner.player.name}?", + label_type="yes", ) if not throw_for_lead: if this_play.starting_outs < 2: this_play = advance_runners(session, this_play, 1) this_play.outs = 1 - - else: + + else: is_lead_out = await ask_confirm( interaction, - f'Was {runner.card.player.name} thrown out?', - custom_confirm_label='Thrown Out', - custom_cancel_label='Safe' + f"Was {runner.card.player.name} thrown out?", + custom_confirm_label="Thrown Out", + custom_cancel_label="Safe", ) if is_lead_out: @@ -4162,37 +4977,45 @@ async def gb_decide(session: Session, this_play: Play, interaction: discord.Inte this_play.batter_final = 1 if this_play.on_third: - this_play.on_first_final = 2 if this_play.on_first is not None else 0 - this_play.on_second_final = 3 if this_play.on_second is not None else 0 + this_play.on_first_final = ( + 2 if this_play.on_first is not None else 0 + ) + this_play.on_second_final = ( + 3 if this_play.on_second is not None else 0 + ) elif this_play.on_second: - this_play.on_first_final = 2 if this_play.on_first is not None else 0 - + this_play.on_first_final = ( + 2 if this_play.on_first is not None else 0 + ) + else: this_play = advance_runners(session, this_play, num_bases=1) this_play.batter_final = 1 - + return this_play -async def gb_result_12(session: Session, this_play: Play, interaction: discord.Interaction): - logger.info(f'GB 12') - if this_play.check_pos in ['1B', '2B']: +async def gb_result_12( + session: Session, this_play: Play, interaction: discord.Interaction +): + logger.info(f"GB 12") + if this_play.check_pos in ["1B", "2B"]: return gb_result_3(session, this_play) - elif this_play.check_pos == '3B': + elif this_play.check_pos == "3B": return gb_result_1(session, this_play) else: return await gb_decide(session, this_play, interaction) def gb_result_13(session: Session, this_play: Play): - logger.info(f'GB 13') - if this_play.check_pos in ['C', '3B']: + logger.info(f"GB 13") + if this_play.check_pos in ["C", "3B"]: num_outs = 2 if this_play.starting_outs <= 1 else 1 this_play.ab, this_play.outs = 1, num_outs this_play.batter_final = 1 else: this_play = gb_result_2(session, this_play) - + return this_play @@ -4200,70 +5023,97 @@ async def new_game_conflicts(session: Session, interaction: discord.Interaction) conflict = get_channel_game_or_none(session, interaction.channel_id) if conflict is not None: await interaction.edit_original_response( - content=f'Ope. There is already a game going on in this channel. Please wait for it to complete ' - f'before starting a new one.' + content=f"Ope. There is already a game going on in this channel. Please wait for it to complete " + f"before starting a new one." ) - log_exception(GameException, f'{interaction.user} attempted to start a new game in {interaction.channel.name}, but there is another active game') - - if interaction.channel.category is None or interaction.channel.category.name != PUBLIC_FIELDS_CATEGORY_NAME: + log_exception( + GameException, + f"{interaction.user} attempted to start a new game in {interaction.channel.name}, but there is another active game", + ) + + if ( + interaction.channel.category is None + or interaction.channel.category.name != PUBLIC_FIELDS_CATEGORY_NAME + ): await interaction.edit_original_response( - content=f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything pops up?' + content=f"Why don't you head down to one of the Public Fields that way other humans can help if anything pops up?" + ) + log_exception( + GameException, + f"{interaction.user} attempted to start a new game in {interaction.channel.name} so they were redirected to {PUBLIC_FIELDS_CATEGORY_NAME}", ) - log_exception(GameException, f'{interaction.user} attempted to start a new game in {interaction.channel.name} so they were redirected to {PUBLIC_FIELDS_CATEGORY_NAME}') async def select_ai_reliever(session: Session, ai_team: Team, this_play: Play) -> Card: - logger.info(f'Selecting an AI reliever') - ai_score = this_play.away_score if this_play.game.away_team_id == ai_team.id else this_play.home_score - human_score = this_play.home_score if this_play.game.away_team_id == ai_team.id else this_play.away_score + logger.info(f"Selecting an AI reliever") + ai_score = ( + this_play.away_score + if this_play.game.away_team_id == ai_team.id + else this_play.home_score + ) + human_score = ( + this_play.home_score + if this_play.game.away_team_id == ai_team.id + else this_play.away_score + ) - logger.info(f'scores - ai: {ai_score} / human: {human_score}') + logger.info(f"scores - ai: {ai_score} / human: {human_score}") if abs(ai_score - human_score) >= 7: - need = 'length' + need = "length" elif this_play.inning_num >= 9 and abs(ai_score - human_score) <= 3: - need = 'closer' + need = "closer" elif this_play.inning_num in [7, 8] and abs(ai_score - human_score) <= 3: - need = 'setup' + need = "setup" elif abs(ai_score - human_score) <= 3: - need = 'middle' + need = "middle" else: - need = 'length' - logger.info(f'need: {need}') + need = "length" + logger.info(f"need: {need}") used_pitchers = get_game_lineups(session, this_play.game, ai_team, is_active=False) used_player_ids = [this_play.pitcher.player_id] - id_string = f'&used_pitcher_ids={this_play.pitcher.player_id}' + id_string = f"&used_pitcher_ids={this_play.pitcher.player_id}" for x in used_pitchers: used_player_ids.append(x.player_id) - id_string += f'&used_pitcher_ids={x.player_id}' + id_string += f"&used_pitcher_ids={x.player_id}" - logger.info(f'used ids: {used_player_ids}') + logger.info(f"used ids: {used_player_ids}") all_links = get_game_cardset_links(session, this_play.game) if len(all_links) > 0: - cardset_string = '' + cardset_string = "" for x in all_links: - cardset_string += f'&cardset_id={x.cardset_id}' + cardset_string += f"&cardset_id={x.cardset_id}" else: - cardset_string = '' - logger.info(f'cardset_string: {cardset_string}') + cardset_string = "" + logger.info(f"cardset_string: {cardset_string}") - rp_json = await db_get(f'teams/{ai_team.id}/rp/{this_play.game.game_type.split("-run")[0]}?need={need}{id_string}{cardset_string}') - - rp_player = await get_player_or_none(session, player_id=get_player_id_from_dict(rp_json)) + rp_json = await db_get( + f"teams/{ai_team.id}/rp/{this_play.game.game_type.split('-run')[0]}?need={need}{id_string}{cardset_string}" + ) + + rp_player = await get_player_or_none( + session, player_id=get_player_id_from_dict(rp_json) + ) if rp_player is None: - log_exception(PlayerNotFoundException, f'Reliever not found for the {ai_team.lname}') - logger.info(f'rp_player: {rp_player}') - + log_exception( + PlayerNotFoundException, f"Reliever not found for the {ai_team.lname}" + ) + logger.info(f"rp_player: {rp_player}") + rp_card = await get_or_create_ai_card(session, rp_player, ai_team) - logger.info(f'rp_card: {rp_card}') + logger.info(f"rp_card: {rp_card}") return rp_card -def substitute_player(session, this_play: Play, old_player: Lineup, new_player: Card, position: str) -> Lineup: - logger.info(f'Substituting {new_player.player.name_with_desc} in for {old_player.card.player.name_with_desc} at {position}') +def substitute_player( + session, this_play: Play, old_player: Lineup, new_player: Card, position: str +) -> Lineup: + logger.info( + f"Substituting {new_player.player.name_with_desc} in for {old_player.card.player.name_with_desc} at {position}" + ) new_lineup = Lineup( team=old_player.team, player=new_player.player, @@ -4272,16 +5122,16 @@ def substitute_player(session, this_play: Play, old_player: Lineup, new_player: batting_order=old_player.batting_order, game=this_play.game, after_play=max(this_play.play_num - 1, 0), - replacing_id=old_player.id + replacing_id=old_player.id, ) - logger.info(f'new_lineup: {new_lineup}') + logger.info(f"new_lineup: {new_lineup}") session.add(new_lineup) - logger.info(f'De-activating last player') + logger.info(f"De-activating last player") old_player.active = False session.add(old_player) - logger.info(f'Updating play\'s pitcher') + logger.info(f"Updating play's pitcher") this_play.pitcher = new_lineup session.add(this_play) diff --git a/exceptions.py b/exceptions.py index 8bc7b7a..2f0c83f 100644 --- a/exceptions.py +++ b/exceptions.py @@ -129,3 +129,14 @@ class LegalityCheckNotRequired(GameException): class InvalidResponder(GameException): pass + + +class PlayLockedException(GameException): + """ + Raised when attempting to process a play that is already locked by another interaction. + + This prevents concurrent modification of the same play record, which could cause + database deadlocks or data corruption. + """ + + pass diff --git a/health_server.py b/health_server.py index e8ad95c..b4ccbf2 100644 --- a/health_server.py +++ b/health_server.py @@ -3,6 +3,7 @@ HTTP health check server for Paper Dynasty Discord bot. Provides health and readiness endpoints for container monitoring and orchestration. """ + import asyncio import logging from typing import Optional @@ -10,13 +11,13 @@ from aiohttp import web import discord from discord.ext import commands -logger = logging.getLogger('discord_app.health') +logger = logging.getLogger("discord_app.health") class HealthServer: """HTTP server for health checks and metrics.""" - def __init__(self, bot: commands.Bot, host: str = '0.0.0.0', port: int = 8080): + def __init__(self, bot: commands.Bot, host: str = "0.0.0.0", port: int = 8080): """ Initialize health server. @@ -33,9 +34,10 @@ class HealthServer: self.site: Optional[web.TCPSite] = None # Setup routes - self.app.router.add_get('/health', self.health_check) - self.app.router.add_get('/ready', self.readiness_check) - self.app.router.add_get('/metrics', self.metrics) + self.app.router.add_get("/health", self.health_check) + self.app.router.add_get("/ready", self.readiness_check) + self.app.router.add_get("/metrics", self.metrics) + self.app.router.add_get("/diagnostics", self.diagnostics) async def health_check(self, request: web.Request) -> web.Response: """ @@ -43,10 +45,9 @@ class HealthServer: Returns 200 if the server is responsive. """ - return web.json_response({ - 'status': 'healthy', - 'service': 'paper-dynasty-discord-bot' - }) + return web.json_response( + {"status": "healthy", "service": "paper-dynasty-discord-bot"} + ) async def readiness_check(self, request: web.Request) -> web.Response: """ @@ -57,16 +58,19 @@ class HealthServer: 503 if bot is not ready """ if self.bot.is_ready(): - return web.json_response({ - 'status': 'ready', - 'discord_connected': True, - 'latency_ms': round(self.bot.latency * 1000, 2) if self.bot.latency else None - }) + return web.json_response( + { + "status": "ready", + "discord_connected": True, + "latency_ms": round(self.bot.latency * 1000, 2) + if self.bot.latency + else None, + } + ) else: - return web.json_response({ - 'status': 'not_ready', - 'discord_connected': False - }, status=503) + return web.json_response( + {"status": "not_ready", "discord_connected": False}, status=503 + ) async def metrics(self, request: web.Request) -> web.Response: """ @@ -75,33 +79,65 @@ class HealthServer: Provides detailed information about bot state for external monitoring systems. """ metrics_data = { - 'bot': { - 'is_ready': self.bot.is_ready(), - 'is_closed': self.bot.is_closed(), - 'latency_ms': round(self.bot.latency * 1000, 2) if self.bot.latency else None, + "bot": { + "is_ready": self.bot.is_ready(), + "is_closed": self.bot.is_closed(), + "latency_ms": round(self.bot.latency * 1000, 2) + if self.bot.latency + else None, }, - 'guilds': { - 'count': len(self.bot.guilds), - 'guild_ids': [g.id for g in self.bot.guilds] + "guilds": { + "count": len(self.bot.guilds), + "guild_ids": [g.id for g in self.bot.guilds], }, - 'users': { - 'count': len(self.bot.users) - }, - 'cogs': { - 'loaded': list(self.bot.cogs.keys()), - 'count': len(self.bot.cogs) - } + "users": {"count": len(self.bot.users)}, + "cogs": {"loaded": list(self.bot.cogs.keys()), "count": len(self.bot.cogs)}, } return web.json_response(metrics_data) + async def diagnostics(self, request: web.Request) -> web.Response: + """ + Detailed diagnostics for troubleshooting frozen bot. + Captures state before container restart. + """ + import sys + + tasks_info = [] + try: + for task in asyncio.all_tasks(): + tasks_info.append( + { + "name": task.get_name(), + "done": task.done(), + "cancelled": task.cancelled(), + } + ) + except Exception as e: + tasks_info = [f"Error capturing tasks: {e}"] + + diagnostics_data = { + "bot": { + "is_ready": self.bot.is_ready(), + "is_closed": self.bot.is_closed(), + "latency_ms": round(self.bot.latency * 1000, 2) + if self.bot.latency + else None, + }, + "tasks": {"count": len(tasks_info), "tasks": tasks_info[:20]}, + "cogs": {"loaded": list(self.bot.cogs.keys()), "count": len(self.bot.cogs)}, + "python_version": sys.version, + } + + return web.json_response(diagnostics_data) + async def start(self): """Start the health check server.""" self.runner = web.AppRunner(self.app) await self.runner.setup() self.site = web.TCPSite(self.runner, self.host, self.port) await self.site.start() - logger.info(f'Health check server started on {self.host}:{self.port}') + logger.info(f"Health check server started on {self.host}:{self.port}") async def stop(self): """Stop the health check server.""" @@ -109,10 +145,10 @@ class HealthServer: await self.site.stop() if self.runner: await self.runner.cleanup() - logger.info('Health check server stopped') + logger.info("Health check server stopped") -async def run_health_server(bot: commands.Bot, host: str = '0.0.0.0', port: int = 8080): +async def run_health_server(bot: commands.Bot, host: str = "0.0.0.0", port: int = 8080): """ Run health server as a background task.