diff --git a/cogs/admins.py b/cogs/admins.py index 59e2b15..8cfd206 100644 --- a/cogs/admins.py +++ b/cogs/admins.py @@ -7,6 +7,7 @@ from db_calls import * from discord import Member from discord.ext import commands, tasks from discord import app_commands +import in_game # date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' # logging.basicConfig( @@ -461,9 +462,16 @@ class Admins(commands.Cog): @app_commands.command(name='reset-cache', description='Reset all cached player cards for gameplay') @app_commands.checks.has_any_role('Da Commish') - async def reset_cache_command(self, interaction: discord.Interaction): + async def reset_cache_command( + self, interaction: discord.Interaction, player_cache: Optional[bool] = True, + batting_cache: Optional[bool] = True, pitching_cache: Optional[bool] = True): await interaction.response.defer() - db_calls.PLAYER_CACHE = {} + if player_cache: + in_game.data_cache.PLAYER_CACHE = {} + if batting_cache: + in_game.data_cache.BATTINGCARD_CACHE = {} + if pitching_cache: + in_game.data_cache.PITCHINGCARD_CACHE = {} await interaction.edit_original_response( content=random_gif(random_from_list(['all done', 'yes sir', 'complete'])) ) @@ -475,6 +483,24 @@ class Admins(commands.Cog): await db_post('paperdex/wipe-ai', timeout=15) await ctx.send(f'All done!') + @commands.command(name='get-bc', help='Mod: Test batting card cache') + @commands.is_owner() + async def get_battingcard_command(self, ctx, player_id: int): + await ctx.channel.send(f'Pulling the batting card for player ID: {player_id}') + this_data = None + async with ctx.channel.typing(): + this_data = await in_game.data_cache.get_pd_battingcard(player_id) + await ctx.channel.send(f'Dumping data here:\n\n{this_data}') + + @commands.command(name='get-pc', help='Mod: Test pitching card cache') + @commands.is_owner() + async def get_battingcard_command(self, ctx, player_id: int): + await ctx.channel.send(f'Pulling the pitching card for player ID: {player_id}') + this_data = None + async with ctx.channel.typing(): + this_data = await in_game.data_cache.get_pd_pitchingcard(player_id) + await ctx.channel.send(f'Dumping data here:\n\n{this_data}') + async def setup(bot): await bot.add_cog(Admins(bot)) diff --git a/cogs/economy.py b/cogs/economy.py index 8222431..aba9b50 100644 --- a/cogs/economy.py +++ b/cogs/economy.py @@ -231,7 +231,8 @@ class Economy(commands.Cog): this_embed.add_field( name=p_list[player]['field_name'], value=p_list[player]['message'], inline=False) - await send_to_channel(self.bot, topics[topic]['channel_name'], embed=embed) + if len(p_list) > 0: + await send_to_channel(self.bot, topics[topic]['channel_name'], embed=this_embed) @notif_check.before_loop async def before_notif_check(self): diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 5ddb01e..e0ad26f 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -1,11 +1,10 @@ import asyncio import logging +import discord import math -import copy import os -import ai_manager -import discord +from in_game import ai_manager import dice import gauntlets @@ -13,23 +12,24 @@ from discord import app_commands from discord.app_commands import Choice from discord.ext import commands, tasks from peewee import IntegrityError - -from typing import Optional, Literal +from typing import Literal, Optional from dice import sa_fielding_roll from helpers import SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME, random_conf_gif, SBA_SEASON, PD_SEASON, IMAGES, \ - get_team_embed, Confirm, get_pos_abbrev, SBA_COLOR, get_roster_lineups, Question, give_packs, send_to_channel, \ - get_channel, get_or_create_role, team_role, get_cal_user, get_card_embeds, ButtonOptions, get_ratings_guide, \ - get_team_by_owner, get_roster_sheet, get_role, player_desc, player_pcard, player_bcard -from gameplay_helpers import * + get_pos_abbrev, SBA_COLOR, get_roster_lineups, give_packs, send_to_channel, \ + get_channel, team_role, get_cal_user, ButtonOptions, get_ratings_guide, \ + get_team_by_owner, player_desc, player_pcard, player_bcard, get_team_embed, Confirm, get_sheets +from in_game.ai_manager import check_pitching_sub +from in_game.game_helpers import single_onestar, single_wellhit, double_twostar, double_threestar, triple, \ + runner_on_first, runner_on_second, runner_on_third, gb_result_1, gb_result_2, gb_result_3, gb_result_4, \ + gb_result_5, gb_result_6, gb_result_7, gb_result_8, gb_result_9, gb_result_10, gb_result_11, gb_result_12, \ + gb_result_13, gb_decide, show_outfield_cards, legal_check, get_pitcher from db_calls import db_get, db_patch, db_post, db_delete, get_team_by_abbrev -from db_calls_gameplay import StratGame, StratPlay, StratLineup, StratManagerAi, get_sba_team, get_sba_player, \ - post_game, patch_game, get_game_team, post_lineups, make_sub, get_player, player_link, get_team_lineups, \ +from db_calls_gameplay import StratGame, StratPlay, post_game, patch_game, get_game_team, post_lineups, make_sub, get_player, player_link, get_team_lineups, \ get_current_play, post_play, get_one_lineup, advance_runners, patch_play, complete_play, get_batting_stats, \ get_pitching_stats, undo_play, get_latest_play, advance_one_runner, count_team_games, \ - get_fielding_stats, get_pitching_decisions, get_or_create_bullpen, get_active_games, patch_lineup, \ - get_last_game_ids, get_plays, get_manager, get_one_game, load_ai, ai_batting, undo_subs, get_dbready_plays, \ - convert_stratlineup + get_pitching_decisions, get_or_create_bullpen, get_active_games, patch_lineup, \ + get_plays, get_manager, get_one_game, load_ai, ai_batting, undo_subs, get_dbready_plays class Gameplay(commands.Cog): @@ -349,7 +349,7 @@ class Gameplay(commands.Cog): return game_state - async def get_game_state_embed(self, game: StratGame, full_length=True, for_liveboard=False): + async def initialize_play_plus_embed(self, game: StratGame, full_length=True, for_liveboard=False): game_state = await self.get_game_state(game) logging.debug(f'game_state: {game_state}') @@ -366,25 +366,11 @@ class Gameplay(commands.Cog): elif 'flashback' in game.game_type: gt_string = ' - Flashback' - embed = discord.Embed( - title=f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}{gt_string}', - color=int(SBA_COLOR, 16) - ) - logging.debug(f'got embed') - - if game.is_pd: - footer_text = f'Paper Dynasty Season {PD_SEASON}' - if game.short_game: - footer_text += f' - Reminder: all pitchers have POW(1) in 3-inning games' - embed.set_footer(text=footer_text, icon_url=IMAGES['logo']) - else: - embed.set_footer(text=f'SBa Season {SBA_SEASON}', icon_url=IMAGES['logo']) - if game_state['error']: - # embed = discord.Embed( - # title='Current Lineups', - # color=int(SBA_COLOR, 16) - # ) + embed = discord.Embed( + title='Current Lineups', + color=int(SBA_COLOR, 16) + ) embed.add_field(name='Away Team', value=game_state['away_lineup'] if game_state['away_lineup'] else 'None, yet') embed.add_field(name='Home Team', @@ -395,19 +381,64 @@ class Gameplay(commands.Cog): return embed logging.debug(f'no errors') - embed.add_field(name='Game State', value=game_state['scorebug'], inline=False) + pitching_sub = None + ai_note = '' + gm_name = '' + fatigue = await ai_manager.is_pitcher_fatigued(game_state['curr_play']) + logging.debug(f'do AI stuff') + # If an AI team is playing + if True in [game_state['pitcher']['team']['is_ai'], game_state['batter']['team']['is_ai']]: + logging.debug(f'Checking AI stuff') - logging.debug(f'check mercy') + # AI Team is pitching + if game_state['pitcher']['team']['is_ai']: + if fatigue: + pitching_sub = await check_pitching_sub(game_state['curr_play'], game_state['pitcher']['team']) + if pitching_sub is not None: + game_state = await self.get_game_state(game) + + ai_data = await ai_manager.pitching_ai_note(game_state['curr_play'], game_state['pitcher']) + logging.debug(f'ai_data: {ai_data}') + ai_note = ai_data['note'] + gm_name = ai_data['gm_name'] + + # AI Team is batting + if game_state['batter']['team']['is_ai']: + # embed.set_thumbnail(url=player_pcard(game_state["pitcher"])) + ai_data = ai_manager.batting_ai_note(game_state['curr_play'], game_state['batter']) + ai_note = ai_data['note'] + gm_name = ai_data['gm_name'] + + if pitching_sub is not None or (fatigue and pitching_sub is None) or abs( + game_state['curr_play'].home_score - game_state['curr_play'].away_score) >= 10: + color = discord.Colour.red() + else: + color = int(SBA_COLOR, 16) + + embed = discord.Embed( + title=f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}{gt_string}', + color=color + ) + logging.info(f'got embed') + + footer_text = f'Paper Dynasty Season {PD_SEASON}' + if game.short_game: + footer_text += f' - Reminder: all pitchers have POW(1) in 3-inning games' + embed.set_footer(text=footer_text, icon_url=IMAGES['logo']) + + embed.add_field(name='Game State', value=game_state['scorebug'], inline=False) + embed.set_thumbnail(url=player_pcard(game_state['pitcher'])) + + logging.info(f'check mercy') if abs(game_state['curr_play'].home_score - game_state['curr_play'].away_score) >= 10: embed.description = '***Mercy rule in effect***' - # embed.color = discord.Colour.red() - logging.debug(f'set pitcher string') + logging.info(f'set pitcher string') pitcher_string = f'{player_link(game, game_state["pitcher"])}' batter_string = f'{game_state["curr_play"].batter.batting_order}. {player_link(game, game_state["batter"])} - ' \ f'{game_state["curr_play"].batter.position}' - logging.debug(f'pull bat stats') + logging.info(f'pull bat stats') all_bat_stats = get_batting_stats(game.id, lineup_id=game_state['curr_play'].batter.id) if len(all_bat_stats): b_s = all_bat_stats[0] @@ -420,7 +451,7 @@ class Gameplay(commands.Cog): if num: batter_string += f', {num if num > 1 else ""}{" " if num > 1 else ""}{stat}' - logging.debug(f'pull pitcher stats') + logging.info(f'pull pitcher stats') all_pit_stats = get_pitching_stats(game.id, lineup_id=game_state['curr_play'].pitcher.id) if len(all_pit_stats): p_s = all_pit_stats[0] @@ -433,16 +464,15 @@ class Gameplay(commands.Cog): pitcher_string += f', {num} {stat}' if stat == 'R' and num != p_s['pl_eruns']: pitcher_string += f', {p_s["pl_eruns"]} ER' - if game.short_game and p_s["pl_outs"] >= 3: - pitcher_string += f'\n***F A T I G U E D***' + if fatigue and pitching_sub is None: + pitcher_string += f'\n***F A T I G U E D***' - logging.debug(f'set embed pitcher/batter') - # embed.add_field(name='Matchup', value=f'Pitcher: \nvs\nBatter: ', inline=False) + logging.info(f'set embed pitcher/batter') embed.add_field(name='Pitcher', value=f'{pitcher_string}') embed.add_field(name='Batter', value=f'{batter_string}') embed.set_image(url=player_bcard(game_state['batter'])) - logging.debug(f'get baserunners') + logging.info(f'get baserunners') baserunner_string = '' if game_state['curr_play'].on_first: runner = await get_player(game, game_state['curr_play'].on_first) @@ -454,7 +484,7 @@ class Gameplay(commands.Cog): runner = await get_player(game, game_state['curr_play'].on_third) baserunner_string += f'On Third: {player_link(game, runner)}\n' - logging.debug(f'set baserunners') + logging.info(f'set baserunners') if len(baserunner_string) > 0: embed.add_field(name=' ', value=' ', inline=False) embed.add_field( @@ -468,43 +498,21 @@ class Gameplay(commands.Cog): name='Baserunners', value='None', inline=False ) - logging.debug(f'do AI stuff') - # If an AI team is playing - if True in [game_state['pitcher']['team']['is_ai'], game_state['batter']['team']['is_ai']]: - ai_note = '' - gm_name = '' - logging.debug(f'Checking AI stuff') + if len(ai_note) > 0: + embed.add_field( + name=f'{gm_name} will...', + value=ai_note, + inline=False + ) - # AI Team is pitching - if game_state['pitcher']['team']['is_ai']: - ai_data = await ai_manager.pitching_ai_note(game_state['curr_play'], game_state['pitcher']) - logging.debug(f'ai_data: {ai_data}') - ai_note = ai_data['note'] - gm_name = ai_data['gm_name'] - embed.set_thumbnail(url=player_pcard(ai_data['pitcher'])) + if pitching_sub is not None: + embed.add_field( + name='SUBSTITUTION', + value=f'The {game_state["pitcher"]["team"]["sname"]} have brought in ' + f'**{player_desc(pitching_sub)}** to pitch' + ) - if ai_data['sub'] is not None: - embed.add_field( - name='SUBSTITUTION', - value=f'The {game_state["pitcher"]["team"]["sname"]} have brought in ' - f'{player_desc(ai_data["sub"])} to pitch' - ) - - # AI Team is batting - elif game_state['batter']['team']['is_ai']: - embed.set_thumbnail(url=player_pcard(game_state["pitcher"])) - ai_data = ai_manager.batting_ai_note(game_state['curr_play'], game_state['batter']) - ai_note = ai_data['note'] - gm_name = ai_data['gm_name'] - - if ai_note: - embed.add_field( - name=f'{gm_name} will...', - value=ai_note, - inline=False - ) - - logging.debug(f'if not full length: return embed: {embed}') + logging.info(f'if not full length: return embed: {embed}') if not full_length: return embed @@ -1342,7 +1350,7 @@ class Gameplay(commands.Cog): await interaction.channel.send( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) return @@ -1433,7 +1441,7 @@ class Gameplay(commands.Cog): await interaction.channel.send( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) return @@ -1524,7 +1532,7 @@ class Gameplay(commands.Cog): await interaction.channel.send( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) return @@ -1639,15 +1647,6 @@ class Gameplay(commands.Cog): f'{human_sp_card["team"]["sname"]}. Will you double check that before we get started?') return - if this_game.game_type in ['major-league', 'hall-of-fame']: - l_check = await legal_check([human_sp_card['id']]) - if not l_check['legal']: - await interaction.edit_original_response( - content=f'It looks like this is a Ranked Legal game and {player_desc(human_sp_card["player"])} is ' - f'not legal in Ranked. You can start a new game once you get a new SP.' - ) - return - all_lineups.append({ 'game_id': this_game.id, 'team_id': team['id'], @@ -1715,7 +1714,7 @@ class Gameplay(commands.Cog): await interaction.channel.send( content=f'Game {gauntlets.games_played(this_run) + 1} of the run is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) return @@ -1725,7 +1724,7 @@ class Gameplay(commands.Cog): this_game = get_one_game(channel_id=ctx.channel.id, active=True) try: - await ctx.send(content=None, embed=await self.get_game_state_embed(this_game)) + await ctx.send(content=None, embed=await self.initialize_play_plus_embed(this_game)) except Exception as e: logging.error(f'could not post game state embed: {e}') question = await ctx.send( @@ -1749,7 +1748,7 @@ class Gameplay(commands.Cog): @commands.is_owner() async def check_decisions_command(self, ctx: commands.Context): this_game = get_one_game(channel_id=ctx.channel.id, active=True) - get_pitching_decisions(this_game) + get_pitching_decisions(this_game, this_game.id) await ctx.send(random_conf_gif()) @app_commands.command(name='end-game', description='End game in this channel') @@ -1916,7 +1915,7 @@ class Gameplay(commands.Cog): ) return - await response.edit(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) + await response.edit(content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False)) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.edit_original_response(content=f'Should I end this game?', view=view) await view.wait() @@ -2689,7 +2688,7 @@ class Gameplay(commands.Cog): post_lineups(all_lineups) try: - await interaction.channel.send(content=None, embed=await self.get_game_state_embed(this_game)) + await interaction.channel.send(content=None, embed=await self.initialize_play_plus_embed(this_game)) except IntegrityError as e: logging.debug(f'Unable to pull game_state for game_id {this_game.id} until both lineups are in: {e}') await interaction.response.send_message(f'Game state will be posted once both lineups are in') @@ -2762,7 +2761,7 @@ class Gameplay(commands.Cog): await interaction.edit_original_response( content=None, - embed=await self.get_game_state_embed(this_game) + embed=await self.initialize_play_plus_embed(this_game) ) @group_substitution.command(name='pitcher', description='Make a pitching change') @@ -2804,7 +2803,7 @@ class Gameplay(commands.Cog): await interaction.edit_original_response(content=f'~~{q_text}~~\nI will hold off for now.') await interaction.channel.send( content=None, - embed=await self.get_game_state_embed(this_game) + embed=await self.initialize_play_plus_embed(this_game) ) return @@ -2854,12 +2853,12 @@ class Gameplay(commands.Cog): if new_post: await interaction.channel.send( content=None, - embed=await self.get_game_state_embed(this_game) + embed=await self.initialize_play_plus_embed(this_game) ) else: await interaction.edit_original_response( content=None, - embed=await self.get_game_state_embed(this_game) + embed=await self.initialize_play_plus_embed(this_game) ) @commands.hybrid_command(name='gamestate', help='Post the current game state', aliases=['gs']) @@ -2872,7 +2871,7 @@ class Gameplay(commands.Cog): response = await ctx.send(f'Building the scorebug now...') - await response.edit(content=None, embed=await self.get_game_state_embed(this_game, full_length=include_lineups)) + await response.edit(content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=include_lineups)) async def checks_log_interaction(self, interaction: discord.Interaction, block_rollback: Optional[bool] = False) \ -> (Optional[StratGame], Optional[dict], Optional[StratPlay]): @@ -2932,6 +2931,33 @@ class Gameplay(commands.Cog): ) return + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Whoa there, cowpoke. The AI can automatically make pitcher subs now and will let pitchers cook in some ' + f'cases. Are you sure you want to manually run a sub for them?', + view=view + ) + await view.wait() + + if view.value: + await question.edit( + content=f'~~Whoa there, cowpoke. The AI can automatically make pitcher subs now and will let pitchers ' + f'cook in some cases. Are you sure you want to manually run a sub for them?~~\n\nI let Cal ' + f'know his stupid AI isn\'t working.', + view=None + ) + await send_to_channel( + self.bot, + 'commissioners-office', + content=f'{interaction.user.display_name} just ran a manual sub here: {question.jump_url}') + else: + await question.delete() + await interaction.edit_original_response( + content='Jkjkjk, we will let the ai decide when to make subs.', + embed=await self.initialize_play_plus_embed(this_game, full_length=False) + ) + return + curr_pitcher = get_one_lineup(this_game.id, team_id=ai_team['id'], position='P') pit_plays = get_plays(this_game.id, curr_pitcher.id) logging.debug(f'pit_plays for sub in Game {this_game.id}: {pit_plays}') @@ -2975,7 +3001,7 @@ class Gameplay(commands.Cog): make_sub(await ai_manager.get_relief_pitcher(this_play, ai_team, this_game.game_type)) await interaction.edit_original_response( content=None, - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c') @@ -2992,11 +3018,11 @@ class Gameplay(commands.Cog): await interaction.edit_original_response(content=f'Flyball has been logged') await interaction.channel.send( content=None, - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) else: await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='groundball', description='Groundballs: a, b, c') @@ -3009,7 +3035,7 @@ class Gameplay(commands.Cog): await self.groundballs(interaction, this_game, this_play, groundball_type) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='single', description='Singles: *, **, ballpark, uncapped') @@ -3194,11 +3220,11 @@ class Gameplay(commands.Cog): await interaction.edit_original_response(content=f'Uncapped single has been logged') await interaction.channel.send( content=None, - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) else: await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='frame-pitch', description=f'Walk/strikeout split; determined by home plate umpire') @@ -3228,7 +3254,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id, batter_to_base=batter_to_base) await interaction.channel.send( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='double', description='Doubles: **, ***, uncapped') @@ -3249,7 +3275,7 @@ class Gameplay(commands.Cog): batter_to_base = 2 ai_is_batting = ai_batting(this_game, this_play) - ai_manager = get_manager(this_game) + this_manager = get_manager(this_game) if this_play.on_first: ai_hint = '' @@ -3258,7 +3284,7 @@ class Gameplay(commands.Cog): ai_hint = f'*The runner will attempt to advance*' else: ai_hint = f'*The runner will ' \ - f'{ai_manager.uncapped_advance(4, this_play.starting_outs)}*' + f'{this_manager.uncapped_advance(4, this_play.starting_outs)}*' logging.debug(f'calling of embed') await show_outfield_cards(interaction, this_play) @@ -3275,7 +3301,7 @@ class Gameplay(commands.Cog): await question.delete() ai_hint = '' if this_game.ai_team and not ai_is_batting: - ai_hint = f'*The defense will {ai_manager.throw_lead_runner(4, this_play.starting_outs)}*' + ai_hint = f'*The defense will {this_manager.throw_lead_runner(4, this_play.starting_outs)}*' # Throw for lead runner? view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') @@ -3290,7 +3316,7 @@ class Gameplay(commands.Cog): ai_hint = '' if this_game.ai_team and ai_is_batting: ai_hint = f'*The trail runner will ' \ - f'{ai_manager.trail_advance(3, this_play.starting_outs, True)}*' + f'{this_manager.trail_advance(3, this_play.starting_outs, True)}*' batter_runner = await get_player(this_game, this_play.batter) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') @@ -3305,7 +3331,7 @@ class Gameplay(commands.Cog): ai_hint = '' if this_game.ai_team and not ai_is_batting: ai_hint = f'*The defense will ' \ - f'{ai_manager.throw_which_runner(4, this_play.starting_outs)}*' + f'{this_manager.throw_which_runner(4, this_play.starting_outs)}*' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') view.confirm.label = 'Home Plate' @@ -3391,11 +3417,11 @@ class Gameplay(commands.Cog): await interaction.edit_original_response(content=f'Uncapped double has been logged') await interaction.channel.send( content=None, - embed=await self.get_game_state_embed(this_game, full_length=False) + embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) else: await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='triple', description='Triples: no sub-types') @@ -3408,7 +3434,7 @@ class Gameplay(commands.Cog): triple(this_play) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='homerun', description='Home Runs: ballpark, no-doubt') @@ -3427,7 +3453,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id, batter_to_base=4) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='walk', description='Walks: unintentional, intentional') @@ -3447,7 +3473,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id, batter_to_base=1) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='chaos', description='Chaos: wild-pitch, passed-ball, balk, pickoff') @@ -3527,7 +3553,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='stealing', description='Running: stolen-base, caught-stealing') @@ -3681,7 +3707,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='strikeout', description='Strikeout') @@ -3697,7 +3723,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='popout', description='Popout') @@ -3713,7 +3739,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='lineout', description='Lineouts: one out, ballpark, max outs') @@ -3766,7 +3792,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='hit-by-pitch', description='Batter to first; runners advance if forced') @@ -3783,7 +3809,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id, batter_to_base=1) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='sac-bunt', description='Batter out; runners advance one base') @@ -3800,7 +3826,7 @@ class Gameplay(commands.Cog): complete_play(this_play.id) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='undo-play', description='Remove the most recent play from the log') @@ -3829,7 +3855,7 @@ class Gameplay(commands.Cog): undo_subs(this_game, latest_play.play_num) await interaction.edit_original_response( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) @group_log.command(name='xcheck', description='Defender makes an x-check') @@ -4253,7 +4279,7 @@ class Gameplay(commands.Cog): await question.edit(content=f'**The ball dropped foul; batter swings again.**', view=None) patch_play(this_play.id, defender_id=False, check_pos='false') await interaction.channel.send( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) return else: @@ -4272,7 +4298,7 @@ class Gameplay(commands.Cog): patch_play(this_play.id, locked=False) await interaction.channel.send( - content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + content=None, embed=await self.initialize_play_plus_embed(this_game, full_length=False) ) # @group_log.command(name='xcheck', description='Defender makes an x-check') diff --git a/cogs/players.py b/cogs/players.py index 9b395fe..e3ce20d 100644 --- a/cogs/players.py +++ b/cogs/players.py @@ -12,6 +12,7 @@ import datetime from discord import app_commands, Member from discord.ext import commands, tasks from difflib import get_close_matches +from typing import Optional, Literal from discord.ext.commands import Greedy @@ -22,7 +23,6 @@ from helpers import PD_PLAYERS_ROLE_NAME, IMAGES, PD_SEASON, random_conf_gif, fu fuzzy_search, get_channel, display_cards, get_card_embeds, get_team_embed, cardset_search, get_blank_team_card, \ get_team_by_owner, get_rosters, get_roster_sheet, legal_channel, random_conf_word, embed_pagination, get_cal_user, \ team_summary_embed, SelectView, SelectPaperdexCardset, SelectPaperdexTeam -from typing import Optional, Literal # date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' # logging.basicConfig( @@ -694,7 +694,8 @@ class Players(commands.Cog): @commands.has_any_role(PD_PLAYERS_ROLE_NAME) @commands.check(legal_channel) async def random_card_command(self, ctx: commands.Context): - this_player = await db_get('players/random', params=[('limit', 1)])['players'][0] + p_query = await db_get('players/random', params=[('limit', 1)]) + this_player = p_query['players'][0] this_embed = await get_card_embeds( {'player': this_player, 'team': {'lname': 'Paper Dynasty', 'logo': IMAGES['logo'], 'season': PD_SEASON}} ) diff --git a/db_calls.py b/db_calls.py index 52cf71e..a4ef5fa 100644 --- a/db_calls.py +++ b/db_calls.py @@ -2,7 +2,6 @@ import datetime from dataclasses import dataclass from typing import Optional -import requests import logging import aiohttp import os @@ -10,74 +9,13 @@ import os AUTH_TOKEN = {'Authorization': f'Bearer {os.environ.get("API_TOKEN")}'} DB_URL = 'https://pd.manticorum.com/api' master_debug = True -alt_database = True +alt_database = False PLAYER_CACHE = {} if alt_database == 'dev': DB_URL = 'https://pddev.manticorum.com/api' -@dataclass -class Player: - player_id: int - p_name: str - cost: int - image: str - mlbclub: str - franchise: str - cardset: dict - set_num: int - rarity: dict - pos_1: str - description: str - quantity: Optional[int] = 999 - image2: Optional[str] = None - pos_2: Optional[str] = None - pos_3: Optional[str] = None - pos_4: Optional[str] = None - pos_5: Optional[str] = None - pos_6: Optional[str] = None - pos_7: Optional[str] = None - pos_8: Optional[str] = None - headshot: Optional[str] = None - vanity_card: Optional[str] = None - strat_code: Optional[str] = None - bbref_id: Optional[str] = None - fangr_id: Optional[str] = None - mlbplayer: Optional[dict] = None - created: datetime.datetime = datetime.datetime.now() - - def to_dict(self): - return { - 'player_id': self.player_id, - 'p_name': self.p_name, - 'cost': self.cost, - 'image': self.image, - 'mlbclub': self.mlbclub, - 'franchise': self.franchise, - 'cardset': self.cardset, - 'set_num': self.set_num, - 'rarity': self.rarity, - 'pos_1': self.pos_1, - 'description': self.description, - 'quantity': self.quantity, - 'image2': self.image2, - 'pos_2': self.pos_2, - 'pos_3': self.pos_3, - 'pos_4': self.pos_4, - 'pos_5': self.pos_5, - 'pos_6': self.pos_6, - 'pos_7': self.pos_7, - 'pos_8': self.pos_8, - 'headshot': self.headshot, - 'vanity_card': self.vanity_card, - 'strat_code': self.strat_code, - 'bbref_id': self.bbref_id, - 'fangr_id': self.fangr_id, - 'mlbplayer': self.mlbplayer - } - - def param_char(other_params): if other_params: return '&' @@ -236,27 +174,3 @@ def team_hash(team): hash_string = f'{team["sname"][-1]}{team["gmid"] / 6950123:.0f}{team["sname"][-2]}{team["gmid"] / 42069123:.0f}' return hash_string - -async def get_pd_player(player_id, as_dict: Optional[bool] = True): - if player_id in PLAYER_CACHE: - tdelta = datetime.datetime.now() - PLAYER_CACHE[player_id].created - if tdelta.total_seconds() < 1209600: - logging.debug(f'this_player: {PLAYER_CACHE[player_id]}') - if as_dict: - return PLAYER_CACHE[player_id].to_dict() - else: - return PLAYER_CACHE[player_id] - else: - logging.error(f'Refreshing player {player_id} in cache...') - - this_player = await db_get('players', object_id=player_id) - for bad_key in ['mlbplayer', 'paperdex']: - if bad_key in this_player: - del this_player[bad_key] - logging.debug(f'this_player: {this_player}') - PLAYER_CACHE[player_id] = Player(**this_player) - - if as_dict: - return this_player - return PLAYER_CACHE[player_id] - diff --git a/db_calls_gameplay.py b/db_calls_gameplay.py index e9ce6cf..8ecf812 100644 --- a/db_calls_gameplay.py +++ b/db_calls_gameplay.py @@ -12,7 +12,8 @@ from playhouse.shortcuts import model_to_dict from dataclasses import dataclass from helpers import SBA_SEASON, PD_SEASON, get_player_url, get_sheets -from db_calls import db_get, get_pd_player +from db_calls import db_get +from in_game.data_cache import get_pd_player db = SqliteDatabase( 'storage/gameplay.db', @@ -143,6 +144,9 @@ class ManagerAi(BaseModel): uncapped_third = IntegerField(default=5) uncapped_trail = IntegerField(default=5) bullpen_matchup = IntegerField(default=5) + behind_aggression = IntegerField(default=5) + ahead_aggression = IntegerField(default=5) + decide_throw = IntegerField(default=5) class StratManagerAi(pydantic.BaseModel): @@ -378,6 +382,47 @@ class StratManagerAi(pydantic.BaseModel): return f'{throw_base} {max(throw_range, 0)}-' + def go_to_reliever( + self, this_play, tot_allowed: int, is_starter: bool = False) -> bool: + run_lead = this_play.ai_run_diff() + obc = this_play.on_base_code + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: ' + f'outs: {this_play.starting_outs}, obc: {obc}, run_lead: {run_lead}, ' + f'tot_allowed: {tot_allowed}') + + lead_target = run_lead if is_starter else 3 + + # AI up big + if tot_allowed < 5 and is_starter: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 1') + return False + elif run_lead > 5 or (run_lead > 2 and self.ahead_aggression > 5): + if tot_allowed <= lead_target or obc <= 3 or this_play.starting_outs == 2: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 2') + return False + elif run_lead > 2 or (run_lead >= 0 and self.ahead_aggression > 5): + if tot_allowed < lead_target or obc <= 1 or this_play.starting_outs == 2: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 3') + return False + elif run_lead >= 0 or (run_lead >= -2 and self.behind_aggression > 5): + if tot_allowed < 5 or obc <= run_lead or this_play.starting_outs == 2: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 4') + return False + elif run_lead >= -3 and self.behind_aggression > 5: + if tot_allowed < 5 and obc <= 1: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 5') + return False + elif run_lead <= -5: + if is_starter and this_play.inning_num <= 3: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 6') + return False + if this_play.starting_outs != 0: + logging.info(f'db_calls_gameplay - StratManagerAi - ID: {self.id} - go_to_reliever: False / code 7') + return False + + return True + + def convert_strat_manager(manager: ManagerAi) -> StratManagerAi: manager_dict = model_to_dict(manager) @@ -623,6 +668,7 @@ class Lineup(BaseModel): after_play = IntegerField() replacing_id = IntegerField(null=True) active = BooleanField(default=True) + variant = IntegerField(default=0) @dataclass @@ -637,6 +683,7 @@ class StratLineup: replacing_id: int = None active: bool = True card_id: int = None + variant: int = 0 def convert_stratlineup(lineup: Lineup) -> StratLineup: @@ -887,6 +934,7 @@ class Play(BaseModel): starting_outs = IntegerField() away_score = IntegerField() home_score = IntegerField() + in_pow = BooleanField(default=False) on_first = ForeignKeyField(Lineup, null=True) on_first_final = IntegerField(null=True) @@ -953,6 +1001,7 @@ class StratPlay: away_score: int home_score: int batter_pos: str = None + in_pow: bool = False on_first: StratLineup = None on_first_final: int = None @@ -1004,6 +1053,12 @@ class StratPlay: is_tied: bool = False is_new_inning: bool = False + def ai_run_diff(self): + if self.game.ai_team == 'away': + return self.away_score - self.home_score + else: + return self.home_score - self.away_score + def convert_stratplay(play: Play) -> StratPlay: play_dict = model_to_dict(play) @@ -1050,7 +1105,7 @@ def patch_play( on_first_id: int = None, on_first_final: int = None, on_second_id: int = None, on_second_final: int = None, on_third_id: int = None, on_third_final: int = None, starting_outs: int = None, runner_id: int = None, complete: bool = None, rbi: int = None, wp: int = None, pb: int = None, pick: int = None, balk: int = None, - is_new_inning: bool = None, batter_final: int = None): + is_new_inning: bool = None, batter_final: int = None, in_pow: bool = None): this_play = Play.get_by_id(play_id) if batter_id is not None: @@ -1174,6 +1229,11 @@ def patch_play( this_play.is_new_inning = 0 else: this_play.is_new_inning = 1 + if in_pow is not None: + if not in_pow: + this_play.in_pow = 0 + else: + this_play.in_pow = 1 this_play.save() # return_play = model_to_dict(this_play) @@ -2007,7 +2067,8 @@ def get_fielding_stats(game_id, lineup_id: int = None, team_id: int = None): # return all_stats -def get_pitching_stats(game_id, lineup_id: int = None, team_id: int = None): +def get_pitching_stats( + game_id, lineup_id: int = None, team_id: int = None, in_pow: bool = None, in_innings: list = None): pitching_stats = Play.select( Play.pitcher, fn.SUM(Play.outs).over(partition_by=[Play.pitcher_id]).alias('pl_outs'), @@ -2072,6 +2133,12 @@ def get_pitching_stats(game_id, lineup_id: int = None, team_id: int = None): if lineup_id is not None: pitching_stats = pitching_stats.where(Play.pitcher_id == lineup_id) + if in_pow is not None: + pitching_stats = pitching_stats.where(Play.in_pow == in_pow) + + if in_innings is not None: + pitching_stats = pitching_stats.where(Play.inning_num << in_innings) + tm_earned_runs = None if team_id is not None: tm_er_first = Play.select().where( diff --git a/gauntlets.py b/gauntlets.py index ccffbd4..ac69f80 100644 --- a/gauntlets.py +++ b/gauntlets.py @@ -4,12 +4,10 @@ import logging import random import discord -import requests -from peewee import DatabaseError -import ai_manager +from in_game import ai_manager import helpers -from helpers import RARITY, get_cal_user, get_or_create_role, send_to_channel, get_channel +from helpers import RARITY, get_or_create_role, send_to_channel, get_channel from db_calls import db_get, db_post, db_delete, db_patch diff --git a/helpers.py b/helpers.py index 26f7eb4..6618b34 100644 --- a/helpers.py +++ b/helpers.py @@ -8,6 +8,7 @@ import traceback import discord import pygsheets +import requests from discord.ext import commands from db_calls import * diff --git a/ai_manager.py b/in_game/ai_manager.py similarity index 78% rename from ai_manager.py rename to in_game/ai_manager.py index 3cb4be1..9c6498c 100644 --- a/ai_manager.py +++ b/in_game/ai_manager.py @@ -1,684 +1,799 @@ -import copy -import logging -import random - -import pydantic -from db_calls_gameplay import StratPlay, StratGame, get_one_lineup, get_manager, get_team_lineups, \ - get_last_inning_end_play, make_sub, get_player -from db_calls import db_get, db_post -from peewee import * -from typing import Optional, Literal - -db = SqliteDatabase( - 'storage/ai-database.db', - pragmas={ - 'journal_mode': 'wal', - 'cache_size': -1 * 64000, - 'synchronous': 0 - } -) - -# 2024, Mario -MINOR_CARDSET_PARAMS = [('cardset_id', 17), ('cardset_id', 8)] - -# 2018, 2024, Mario, -MAJOR_CARDSET_PARAMS = [ - ('cardset_id', 13), ('cardset_id', 14), ('cardset_id', 17), ('cardset_id', 18), ('cardset_id', 8) -] - -# 2008, 2012, 2013, 2016, 2019, 2021, 2022, 2023, Mario -HOF_CARDSET_PARAMS = [ - ('cardset_id', 1), ('cardset_id', 3), ('cardset_id', 4), ('cardset_id', 5), ('cardset_id', 6), ('cardset_id', 7), - ('cardset_id', 8), ('cardset_id', 9), ('cardset_id', 10), ('cardset_id', 11), ('cardset_id', 12), ('cardset_id', 13) -] - -# 2008, 2012, 2013, 2016 -GAUNTLET2_PARAMS = [ - ('cardset_id', 8), ('cardset_id', 12), ('cardset_id', 6), ('cardset_id', 7), ('cardset_id', 11) -] - - -class BaseModel(Model): - class Meta: - database = db - - -class Lineup(BaseModel): - team_id = IntegerField() - game_level = CharField() # 'minor', 'major', 'hof' - vs_hand = CharField() # 'left', 'right' - bat_one_card = IntegerField() - bat_one_pos = CharField() - bat_two_card = IntegerField() - bat_two_pos = CharField() - bat_three_card = IntegerField() - bat_three_pos = CharField() - bat_four_card = IntegerField() - bat_four_pos = CharField() - bat_five_card = IntegerField() - bat_five_pos = CharField() - bat_six_card = IntegerField() - bat_six_pos = CharField() - bat_seven_card = IntegerField() - bat_seven_pos = CharField() - bat_eight_card = IntegerField() - bat_eight_pos = CharField() - bat_nine_card = IntegerField() - bat_nine_pos = CharField() - - -class Rotation(BaseModel): - team_id = IntegerField() - game_level = CharField() - sp_one_card = IntegerField() - sp_two_card = IntegerField() - sp_three_card = IntegerField() - sp_four_card = IntegerField() - sp_five_card = IntegerField() - - -class Bullpen(BaseModel): - team_id = IntegerField() - game_level = CharField() # 'minor', 'major', 'hof' - vs_hand = CharField() # 'left', 'right' - - -# def grade_player() -def batter_grading(vs_hand, rg_data): - raw_bat = (rg_data[f'contact-{vs_hand}'] + rg_data[f'power-{vs_hand}'] + - min(rg_data[f'contact-{vs_hand}'], rg_data[f'power-{vs_hand}'])) / 3 - other_metrics = [ - rg_data['vision'], rg_data['speed'], rg_data['stealing'], rg_data['reaction'], rg_data['arm'], - rg_data['fielding'] - ] - blended_rating = sum(sorted(other_metrics, reverse=True)[:4], raw_bat * 4) / 8 - return { - 'overall': rg_data['overall'], - 'raw-bat': raw_bat, - 'blended': blended_rating - } - - -async def get_or_create_card(player: dict, team: dict) -> int: - # get player card; create one if none found - z = 0 - card_id = None - while z < 2 and card_id is None: - z += 1 - c_query = await db_get('cards', params=[('team_id', team['id']), ('player_id', player['player_id'])]) - if c_query['count'] > 0: - card_id = c_query['cards'][0]['id'] - else: - await db_post( - 'cards', - payload={'cards': [ - {'player_id': player['player_id'], 'team_id': team['id'], 'pack_id': 1} - ]} - ) - if card_id is None: - logging.error(f'Could not create card for {player["p_name"]} on the {team["lname"]}') - raise DatabaseError(f'Could not create card for {player["p_name"]} on the {team["lname"]}') - - return card_id - - -# First attempt - pulls ratings guide info -async def build_lineup_graded(team_object: dict, vs_hand: str, league_name: str, num_innings: int, batter_rg): - in_lineup = [] # player ids for quick checking - - players = { - 'c': [], - '1b': [], - '2b': [], - '3b': [], - 'ss': [], - 'lf': [], - 'cf': [], - 'rf': [], - 'dh': [] - } - - # Get all eligible players - try: - p_query = await db_get( - endpoint='players', - params=[('mlbclub', team_object['lname']), ('pos_exclude', 'RP'), ('inc_dex', False)], - timeout=10 - ) - all_players = p_query['players'] - except ConnectionError as e: - raise ConnectionError(f'Error pulling players for the {team_object["lname"]}. Cal help plz.') - - # Grade players and add to position lists - for x in all_players: - if x['pos_1'] not in ['SP', 'RP']: - this_guy = copy.deepcopy(x) - rg_data = next(x for x in batter_rg if x['player_id'] == this_guy['player_id']) - grading = batter_grading(vs_hand, rg_data) - logging.info(f'player: {this_guy} / grading: {grading}') - - this_guy['overall'] = grading['overall'] - this_guy['raw-bat'] = grading['raw-bat'] - this_guy['blended'] = grading['blended'] - - players['dh'].append(this_guy) - if 'C' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['c'].append(this_guy) - if '1B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['1b'].append(this_guy) - if '2B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['2b'].append(this_guy) - if '3B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['3b'].append(this_guy) - if 'SS' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['ss'].append(this_guy) - if 'LF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['lf'].append(this_guy) - if 'CF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['cf'].append(this_guy) - if 'RF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], - this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: - players['rf'].append(this_guy) - - # Select players for lineup - while len(players) > 0: - # Sort players dict by position with fewest eligible players - this_pass_data = sorted(players.items(), key=lambda item: len(item[1])) - logging.info(f'this_pass_data: {this_pass_data}') - # Pull one tuple ('', []) - this_pos_data = this_pass_data[0] - logging.info(f'this_pos_data: {this_pos_data}') - # Sort players at this position by blended rating (raw-bat for DH) - if this_pos_data[0] == 'dh': - this_pos = sorted(this_pos_data[1], key=lambda item: item['raw-bat'], reverse=True) - else: - this_pos = sorted(this_pos_data[1], key=lambda item: item['blended'], reverse=True) - logging.info(f'this_pos: {this_pos}') - - # Add top player to the lineup - in_lineup.append({'position': copy.deepcopy(this_pos_data[0].upper()), 'player': copy.deepcopy(this_pos[0])}) - logging.info(f'adding player: {this_pos[0]}') - logging.info(f'deleting position: {this_pos_data[0]}') - # Remove this position from consideration - del players[this_pos_data[0]] - - for key in players: - for x in players[key]: - # Remove duplicate players (even across cardsets) once in lineup - if x['strat_code'] == this_pos[0]['strat_code']: - players[key].remove(x) - - # Set batting order as list of lists: [ ['', ], ... ] - batting_order = [] - - return batting_order - - -async def build_lineup(team_object: dict, game_id: int, league_name: str, sp_name: str, vs_hand: str = 'r') -> list: - # players = { - # 'C': None, - # '1B': None, - # '2B': None, - # '3B': None, - # 'SS': None, - # 'LF': None, - # 'CF': None, - # 'RF': None, - # 'DH': None - # } - # p_names = [] - # rest_name = None - # - # set_params = [('cardset_id_exclude', 2)] - # if team_object['id'] == 58: - # set_params = [] - # elif league_name == 'minor-league': - # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) - # elif league_name == 'major-league': - # set_params = copy.deepcopy(MAJOR_CARDSET_PARAMS) - # elif league_name == 'hall-of-fame': - # set_params = copy.deepcopy(HOF_CARDSET_PARAMS) - # elif 'gauntlet-1' in league_name: - # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) - # elif 'gauntlet-2' in league_name: - # set_params = random.sample(GAUNTLET2_PARAMS, 2) - # - # # Pull players sorted by current cost - # try: - # params = [ - # ('mlbclub', team_object['lname']), ('pos_include', 'C'), ('pos_include', '1B'), ('pos_include', '2B'), - # ('pos_include', '3B'), ('pos_include', 'SS'), ('pos_include', 'LF'), ('pos_include', 'CF'), - # ('pos_include', 'RF'), ('pos_include', 'DH'), ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 50) - # ] - # params.extend(set_params) - # p_query = await db_get( - # endpoint='players', - # params=params, - # timeout=10 - # ) - # all_players = p_query['players'] - # except ConnectionError as e: - # raise ConnectionError(f'Error pulling batters for the {team_object["lname"]}. Cal help plz.') - # - # logging.info(f'build_lineup - eligible batter count: {len(all_players)}') - # - # # Choose starting nine & position - # # In order of cost: - # # Try to add to pos_1; if unavailable, try remaining pos; if unavailable, add to DH - # # If all pos unavailable; note player's pos_1 and check if incumbent can move; if so, check for new incumbent - # # and recurse - # # If not possible, pass player and continue - # for guy in all_players: - # placed = False - # if guy['p_name'] not in p_names and guy['p_name'] != rest_name: - # for pos in [ - # guy['pos_1'], guy['pos_2'], guy['pos_3'], guy['pos_4'], guy['pos_5'], guy['pos_6'], guy['pos_7'], - # guy['pos_8'] - # ]: - # if pos is None or pos in ['SP', 'RP', 'CP']: - # break - # - # if random.randint(1, 10) == 10 and rest_name is None and False: - # logging.info(f'Resting {guy["p_name"]} in game {game_id}') - # rest_name = guy['p_name'] - # elif players[pos] is None: - # players[pos] = guy - # p_names.append(guy["p_name"]) - # placed = True - # break - # - # if not placed and rest_name != guy['p_name']: - # if players['DH'] is None: - # players['DH'] = guy - # p_names.append(guy["p_name"]) - # else: - # logging.info(f'build_lineup - could not place {guy["p_name"]} in {team_object["sname"]} lineup') - # else: - # logging.info(f'build_lineup - {guy["p_name"]} already in lineup') - # - # if None in players.values(): - # bad_pos = {x for x in players if players[x] is None} - # raise ValueError(f'Could not find a {bad_pos} for the {team_object["sname"]}') - # - # logging.info(f'build_lineup - {players}') - # - # # Sort players into lineup - # # Pseudo-random? Patterns? Something to mix up lineups - # # 421356789 or [123 (rand)][456 (rand)][789 (rand)] (optional: chance to flip 3/4 and 6/7) - # - # # [ (, ), (, ), etc. ] - # sorted_players = sorted(players.items(), key=lambda x: x[1]['cost'], reverse=True) - - build_type = 'fun' - l_query = await db_get( - f'teams/{team_object["id"]}/lineup/{league_name}?pitcher_name={sp_name}&build_type={build_type}', - timeout=6 - ) - sorted_players = l_query['array'] - logging.info(f'sorted_players: {sorted_players}') - - grp_1 = sorted_players[:3] - grp_2 = sorted_players[3:6] - grp_3 = sorted_players[6:] - random.shuffle(grp_1) - random.shuffle(grp_2) - random.shuffle(grp_3) - - lineups = [] - i = 1 - for x in [grp_1, grp_2, grp_3]: - logging.debug(f'group: {x}') - for y in x: - logging.debug(f'y: {y}') - card_id = await get_or_create_card(y[1]['player'], team_object) - - lineups.append({ - 'game_id': game_id, - 'team_id': team_object['id'], - 'player_id': y[1]['player']['player_id'], - 'card_id': card_id, - 'position': y[0], - 'batting_order': i, - 'after_play': 0 - }) - i += 1 - - logging.info(f'build_lineup - final lineup: {lineups}') - - return lineups - - -async def get_starting_pitcher(team_object: dict, game_id: int, is_home: bool, league_name: str = None) -> dict: - # set_params = [('cardset_id_exclude', 2)] - # if league_name == 'minor-league': - # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) - # elif league_name == 'major-league': - # set_params = copy.deepcopy(MAJOR_CARDSET_PARAMS) - # elif league_name == 'hall-of-fame': - # set_params = copy.deepcopy(HOF_CARDSET_PARAMS) - # elif league_name == 'gauntlet-1': - # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) - # elif 'gauntlet-2' in league_name: - # set_params = random.sample(GAUNTLET2_PARAMS) - # - # params = [ - # ('mlbclub', team_object['lname']), ('pos_include', 'SP'), ('pos_exclude', 'RP'), - # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) - # ] - # counter = 0 - # logging.info(f'params: {params}') - # while counter < 2: - # logging.info(f'counter: {counter}') - # counter += 1 - # # Pull starters sorted by current cost - # logging.info(f'counter: {counter} / params: {params}') - # try: - # params.extend(set_params) - # pitchers = await db_get( - # endpoint='players', - # params=params, - # timeout=10 - # ) - # logging.info(f'pitchers: {pitchers}') - # except ConnectionError as e: - # logging.error(f'Could not get pitchers for {team_object["lname"]}: {e}') - # raise ConnectionError(f'Error pulling starting pitchers for the {team_object["lname"]}. Cal help plz.') - # - # if pitchers['count'] == 0: - # logging.info(f'pitchers is None') - # del params - # params = [ - # ('mlbclub', team_object['lname']), ('pos_include', 'SP'), - # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) - # ] - # else: - # break - # - # logging.info(f'build_lineup - eligible starting pitcher count: {len(pitchers)}') - # if pitchers['count'] == 0: - # raise DatabaseError(f'Could not find any SP for {team_object["abbrev"]}. Seems like a Cal issue.') - - d_100 = random.randint(1, 100) - if is_home: - if d_100 <= 30: - sp_rank = 1 - elif d_100 <= 55: - sp_rank = 2 - elif d_100 <= 75: - sp_rank = 3 - elif d_100 <= 90: - sp_rank = 4 - else: - sp_rank = 5 - else: - if d_100 <= 50: - sp_rank = 1 - elif d_100 <= 75: - sp_rank = 2 - elif d_100 <= 85: - sp_rank = 3 - elif d_100 <= 95: - sp_rank = 4 - else: - sp_rank = 5 - starter = await db_get(f'teams/{team_object["id"]}/sp/{league_name}?sp_rank={sp_rank}') - - # get player card; create one if none found - card_id = await get_or_create_card(starter, team_object) - - return { - 'game_id': game_id, - 'team_id': team_object['id'], - 'player_id': starter['player_id'], - 'card_id': card_id, - 'position': 'P', - 'batting_order': 10, - 'after_play': 0 - } - - -async def get_relief_pitcher(this_play: StratPlay, ai_team: dict, league_name: str = None) -> dict: - used_ids = [] - used_players = await get_team_lineups( - game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, as_string=False, pitchers_only=True - ) - for x in used_players: - used_ids.append(f'{x.player_id}') - - logging.debug(f'used ids: {used_ids}') - id_string = "&used_pitcher_ids=".join(used_ids) - this_game = this_play.game - ai_score = this_play.away_score if this_game.away_team_id == ai_team['id'] else this_play.home_score - human_score = this_play.home_score if this_game.away_team_id == ai_team['id'] else this_play.away_score - - logging.debug(f'scores - ai: {ai_score} / human: {human_score}') - if abs(ai_score - human_score) >= 7: - need = 'length' - elif this_play.inning_num >= 9 and abs(ai_score - human_score) <= 3: - need = 'closer' - elif this_play.inning_num in [7, 8] and abs(ai_score - human_score) <= 3: - need = 'setup' - elif abs(ai_score - human_score) <= 3: - need = 'middle' - else: - need = 'length' - - logging.debug(f'need: {need}') - rp_query = await db_get(f'teams/{ai_team["id"]}/rp/{league_name.split("-run")[0]}' - f'?need={need}&used_pitcher_ids={id_string}') - card_id = await get_or_create_card(rp_query, ai_team) - return { - 'game_id': this_play.game.id, - 'team_id': ai_team['id'], - 'player_id': rp_query['player_id'], - 'card_id': card_id, - 'position': 'P', - 'batting_order': 10, - 'after_play': this_play.play_num - 1 - } - - """ - END NEW GET RP - """ - - # used_codes = [] - # used_players = await get_team_lineups( - # game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, as_string=False - # ) - # for x in used_players: - # c_query = await db_get('cards', object_id=x.card_id) - # used_codes.append(c_query["player"]["strat_code"]) - # logging.info(f'get_rp - used_players: {used_codes}') - # - # reliever = None - # attempts = 0 - # while reliever is None: - # attempts += 1 - # if attempts > 3: - # raise ValueError(f'Could not find a reliever for the {ai_team["sname"]}. Cal plz.') - # - # set_params = [('cardset_id_exclude', 2)] - # if league_name == 'minor-league': - # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) - # elif league_name == 'major-league': - # set_params = copy.deepcopy(MAJOR_CARDSET_PARAMS) - # elif league_name == 'hall-of-fame': - # set_params = copy.deepcopy(HOF_CARDSET_PARAMS) - # elif 'gauntlet-1' in league_name: - # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) - # elif 'gauntlet-2' in league_name: - # set_params = copy.deepcopy(GAUNTLET2_PARAMS) - # - # # Pull relievers sorted by current cost - # params = [ - # ('mlbclub', ai_team['lname']), ('pos_include', 'RP'), ('inc_dex', False), ('sort_by', 'cost-desc'), - # ('limit', 15) - # ] - # params.extend(set_params) - # - # use_best = False - # if attempts == 1: - # # Try to get long man - # if this_play.inning_num < 6: - # logging.info(f'get_rp - game {this_play.game.id} try for long man') - # params.append(('pos_include', 'SP')) - # # use_best = True - # - # # Try to get closer - # elif this_play.inning_num > 8: - # if (this_play.inning_half == 'top' and this_play.home_score >= this_play.away_score) or \ - # (this_play.inning_half == 'bot' and this_play.away_score >= this_play.home_score): - # logging.info(f'get_rp - game {this_play.game.id} try for closer') - # params.append(('pos_include', 'CP')) - # use_best = True - # else: - # params.append(('pos_exclude', 'CP')) - # - # # Try to exclude long men - # elif attempts == 1: - # logging.info(f'get_rp - game {this_play.game.id} try to exclude long men') - # params.append(('pos_exclude', 'SP')) - # - # try: - # pitchers = await db_get( - # endpoint='players', - # params=params, - # timeout=10 - # ) - # except ConnectionError as e: - # logging.error(f'Could not get pitchers for {ai_team["lname"]}: {e}') - # raise ConnectionError(f'Error pulling starting pitchers for the {ai_team["lname"]}. Cal help plz.') - # - # if pitchers['count'] > 0: - # if use_best or this_play.inning_num > 9 or attempts > 2: - # start = 0 - # else: - # start = 9 - this_play.inning_num - # - # for count, guy in enumerate(pitchers['players']): - # if count >= start and guy['strat_code'] not in used_codes: - # card_id = await get_or_create_card(guy, ai_team) - # - # return { - # 'game_id': this_play.game.id, - # 'team_id': ai_team['id'], - # 'player_id': guy['player_id'], - # 'card_id': card_id, - # 'position': 'P', - # 'batting_order': 10, - # 'after_play': this_play.play_num - 1 - # } - - -def get_pitcher(this_game: StratGame, this_play: StratPlay): - p_team_id = this_game.home_team_id - if this_play.inning_half == 'top': - p_team_id = this_game.away_team_id - return get_one_lineup(this_game.id, team_id=p_team_id, position='P', active=True) - - -async def pitching_ai_note(this_play: StratPlay, this_pitcher: dict): - this_ai = get_manager(this_play.game) - gm_name = f'{this_pitcher["team"]["gmname"]}' - used_pitchers = await get_team_lineups( - game_id=this_play.game.id, - team_id=this_pitcher["team"]['id'], - inc_inactive=True, - pitchers_only=True, - as_string=False - ) - last_inning_ender = get_last_inning_end_play( - this_play.game.id, - this_play.inning_half, - this_play.inning_num - 1 - ) - - ai_note = '' - pitcher = this_pitcher - - # Pitcher Substitutions - new_pitcher = None - if last_inning_ender and last_inning_ender.pitcher != this_play.pitcher: - logging.debug(f'{this_pitcher["team"]["sname"]} not making a change.') - - ai_note += f'- have {this_pitcher["p_name"]} finish the inning\n' - - elif this_play.is_new_inning and this_play.game.short_game and this_play.inning_num != 1: - logging.debug(f'{this_pitcher["team"]["sname"]} going the to pen.') - - if len(used_pitchers) < 8: - make_sub(await get_relief_pitcher( - this_play, this_pitcher['team'], this_play.game.game_type - )) - pitcher = await get_player(this_play.game, get_pitcher(this_play.game, this_play)) - new_pitcher = pitcher - else: - ai_note += f'- continue with auto-fatigued {this_pitcher["p_name"]}\n' - - else: - if len(used_pitchers) == 1: - ai_note += f'- go to the pen if the pitcher fatigues __and has allowed 5+ baserunners__ ' \ - f'(`/log ai-pitcher-sub`)\n' - elif len(used_pitchers) < 8: - ai_note += f'- go to the pen if the pitcher fatigues (`/log ai-pitcher-sub`)\n' - else: - ai_note += f' - continue with {this_pitcher["p_name"]}\n' - - # Holding Baserunners - if this_play.starting_outs == 2 and this_play.on_base_code > 0: - if this_play.on_base_code in [1, 2]: - ai_note += f'- hold the runner\n' - elif this_play.on_base_code in [4, 7]: - ai_note += f'- hold the runners\n' - elif this_play.on_base_code == 5: - ai_note += f'- hold the runner on first\n' - elif this_play.on_base_code == 6: - ai_note += f'- hold the runner on second\n' - elif this_play.on_base_code in [1, 5]: - ai_note += f'- hold the runner on 1st if they have ***** auto-jump\n' - elif this_play.on_base_code == 2: - ai_note += f'- hold the runner on 2nd if safe range is 14+\n' - - # Defensive Alignment - if this_play.on_third and this_play.starting_outs < 2: - if this_play.on_first: - ai_note += f'- play the corners in\n' - - elif abs(this_play.away_score - this_play.home_score) <= 3: - ai_note += f'- play the whole infield in\n' - - else: - ai_note += f'- play the corners in\n' - - return { - 'note': ai_note, - 'pitcher': pitcher, - 'gm_name': gm_name, - 'sub': new_pitcher - } - - -def batting_ai_note(this_play: StratPlay, this_batter: dict): - this_ai = get_manager(this_play.game) - - ai_note = '' - gm_name = f'{this_batter["team"]["gmname"]}' - - if this_play.on_first and not this_play.on_second: - ai_note += f'- {this_ai.check_jump(2, this_play.starting_outs)}\n' - - elif this_play.on_second and not this_play.on_third: - ai_note += f'- {this_ai.check_jump(3, this_play.starting_outs)}\n' - - return { - 'note': ai_note, - 'batter': this_batter, - 'gm_name': gm_name - } +import copy +import logging +import math +import random + +# import data_cache +from db_calls_gameplay import StratPlay, StratGame, get_one_lineup, get_manager, get_team_lineups, \ + get_last_inning_end_play, make_sub, get_player, StratLineup, get_pitching_stats, patch_play +from db_calls import db_get, db_post +from peewee import * +from typing import Optional, Literal + +from in_game import data_cache + +db = SqliteDatabase( + 'storage/ai-database.db', + pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1 * 64000, + 'synchronous': 0 + } +) + +# 2024, Mario +MINOR_CARDSET_PARAMS = [('cardset_id', 17), ('cardset_id', 8)] + +# 2018, 2024, Mario, +MAJOR_CARDSET_PARAMS = [ + ('cardset_id', 13), ('cardset_id', 14), ('cardset_id', 17), ('cardset_id', 18), ('cardset_id', 8) +] + +# 2008, 2012, 2013, 2016, 2019, 2021, 2022, 2023, Mario +HOF_CARDSET_PARAMS = [ + ('cardset_id', 1), ('cardset_id', 3), ('cardset_id', 4), ('cardset_id', 5), ('cardset_id', 6), ('cardset_id', 7), + ('cardset_id', 8), ('cardset_id', 9), ('cardset_id', 10), ('cardset_id', 11), ('cardset_id', 12), ('cardset_id', 13) +] + +# 2008, 2012, 2013, 2016 +GAUNTLET2_PARAMS = [ + ('cardset_id', 8), ('cardset_id', 12), ('cardset_id', 6), ('cardset_id', 7), ('cardset_id', 11) +] + + +class BaseModel(Model): + class Meta: + database = db + + +class Lineup(BaseModel): + team_id = IntegerField() + game_level = CharField() # 'minor', 'major', 'hof' + vs_hand = CharField() # 'left', 'right' + bat_one_card = IntegerField() + bat_one_pos = CharField() + bat_two_card = IntegerField() + bat_two_pos = CharField() + bat_three_card = IntegerField() + bat_three_pos = CharField() + bat_four_card = IntegerField() + bat_four_pos = CharField() + bat_five_card = IntegerField() + bat_five_pos = CharField() + bat_six_card = IntegerField() + bat_six_pos = CharField() + bat_seven_card = IntegerField() + bat_seven_pos = CharField() + bat_eight_card = IntegerField() + bat_eight_pos = CharField() + bat_nine_card = IntegerField() + bat_nine_pos = CharField() + + +class Rotation(BaseModel): + team_id = IntegerField() + game_level = CharField() + sp_one_card = IntegerField() + sp_two_card = IntegerField() + sp_three_card = IntegerField() + sp_four_card = IntegerField() + sp_five_card = IntegerField() + + +class Bullpen(BaseModel): + team_id = IntegerField() + game_level = CharField() # 'minor', 'major', 'hof' + vs_hand = CharField() # 'left', 'right' + + +# def grade_player() +def batter_grading(vs_hand, rg_data): + raw_bat = (rg_data[f'contact-{vs_hand}'] + rg_data[f'power-{vs_hand}'] + + min(rg_data[f'contact-{vs_hand}'], rg_data[f'power-{vs_hand}'])) / 3 + other_metrics = [ + rg_data['vision'], rg_data['speed'], rg_data['stealing'], rg_data['reaction'], rg_data['arm'], + rg_data['fielding'] + ] + blended_rating = sum(sorted(other_metrics, reverse=True)[:4], raw_bat * 4) / 8 + return { + 'overall': rg_data['overall'], + 'raw-bat': raw_bat, + 'blended': blended_rating + } + + +async def get_or_create_card(player: dict, team: dict) -> int: + # get player card; create one if none found + z = 0 + card_id = None + while z < 2 and card_id is None: + z += 1 + c_query = await db_get('cards', params=[('team_id', team['id']), ('player_id', player['player_id'])]) + if c_query['count'] > 0: + card_id = c_query['cards'][0]['id'] + else: + await db_post( + 'cards', + payload={'cards': [ + {'player_id': player['player_id'], 'team_id': team['id'], 'pack_id': 1} + ]} + ) + if card_id is None: + logging.error(f'Could not create card for {player["p_name"]} on the {team["lname"]}') + raise DatabaseError(f'Could not create card for {player["p_name"]} on the {team["lname"]}') + + return card_id + + +# First attempt - pulls ratings guide info +async def build_lineup_graded(team_object: dict, vs_hand: str, league_name: str, num_innings: int, batter_rg): + in_lineup = [] # player ids for quick checking + + players = { + 'c': [], + '1b': [], + '2b': [], + '3b': [], + 'ss': [], + 'lf': [], + 'cf': [], + 'rf': [], + 'dh': [] + } + + # Get all eligible players + try: + p_query = await db_get( + endpoint='players', + params=[('mlbclub', team_object['lname']), ('pos_exclude', 'RP'), ('inc_dex', False)], + timeout=10 + ) + all_players = p_query['players'] + except ConnectionError as e: + raise ConnectionError(f'Error pulling players for the {team_object["lname"]}. Cal help plz.') + + # Grade players and add to position lists + for x in all_players: + if x['pos_1'] not in ['SP', 'RP']: + this_guy = copy.deepcopy(x) + rg_data = next(x for x in batter_rg if x['player_id'] == this_guy['player_id']) + grading = batter_grading(vs_hand, rg_data) + logging.info(f'player: {this_guy} / grading: {grading}') + + this_guy['overall'] = grading['overall'] + this_guy['raw-bat'] = grading['raw-bat'] + this_guy['blended'] = grading['blended'] + + players['dh'].append(this_guy) + if 'C' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['c'].append(this_guy) + if '1B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['1b'].append(this_guy) + if '2B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['2b'].append(this_guy) + if '3B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['3b'].append(this_guy) + if 'SS' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['ss'].append(this_guy) + if 'LF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['lf'].append(this_guy) + if 'CF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['cf'].append(this_guy) + if 'RF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['rf'].append(this_guy) + + # Select players for lineup + while len(players) > 0: + # Sort players dict by position with fewest eligible players + this_pass_data = sorted(players.items(), key=lambda item: len(item[1])) + logging.info(f'this_pass_data: {this_pass_data}') + # Pull one tuple ('', []) + this_pos_data = this_pass_data[0] + logging.info(f'this_pos_data: {this_pos_data}') + # Sort players at this position by blended rating (raw-bat for DH) + if this_pos_data[0] == 'dh': + this_pos = sorted(this_pos_data[1], key=lambda item: item['raw-bat'], reverse=True) + else: + this_pos = sorted(this_pos_data[1], key=lambda item: item['blended'], reverse=True) + logging.info(f'this_pos: {this_pos}') + + # Add top player to the lineup + in_lineup.append({'position': copy.deepcopy(this_pos_data[0].upper()), 'player': copy.deepcopy(this_pos[0])}) + logging.info(f'adding player: {this_pos[0]}') + logging.info(f'deleting position: {this_pos_data[0]}') + # Remove this position from consideration + del players[this_pos_data[0]] + + for key in players: + for x in players[key]: + # Remove duplicate players (even across cardsets) once in lineup + if x['strat_code'] == this_pos[0]['strat_code']: + players[key].remove(x) + + # Set batting order as list of lists: [ ['', ], ... ] + batting_order = [] + + return batting_order + + +async def build_lineup(team_object: dict, game_id: int, league_name: str, sp_name: str, vs_hand: str = 'r') -> list: + # players = { + # 'C': None, + # '1B': None, + # '2B': None, + # '3B': None, + # 'SS': None, + # 'LF': None, + # 'CF': None, + # 'RF': None, + # 'DH': None + # } + # p_names = [] + # rest_name = None + # + # set_params = [('cardset_id_exclude', 2)] + # if team_object['id'] == 58: + # set_params = [] + # elif league_name == 'minor-league': + # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) + # elif league_name == 'major-league': + # set_params = copy.deepcopy(MAJOR_CARDSET_PARAMS) + # elif league_name == 'hall-of-fame': + # set_params = copy.deepcopy(HOF_CARDSET_PARAMS) + # elif 'gauntlet-1' in league_name: + # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) + # elif 'gauntlet-2' in league_name: + # set_params = random.sample(GAUNTLET2_PARAMS, 2) + # + # # Pull players sorted by current cost + # try: + # params = [ + # ('mlbclub', team_object['lname']), ('pos_include', 'C'), ('pos_include', '1B'), ('pos_include', '2B'), + # ('pos_include', '3B'), ('pos_include', 'SS'), ('pos_include', 'LF'), ('pos_include', 'CF'), + # ('pos_include', 'RF'), ('pos_include', 'DH'), ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 50) + # ] + # params.extend(set_params) + # p_query = await db_get( + # endpoint='players', + # params=params, + # timeout=10 + # ) + # all_players = p_query['players'] + # except ConnectionError as e: + # raise ConnectionError(f'Error pulling batters for the {team_object["lname"]}. Cal help plz.') + # + # logging.info(f'build_lineup - eligible batter count: {len(all_players)}') + # + # # Choose starting nine & position + # # In order of cost: + # # Try to add to pos_1; if unavailable, try remaining pos; if unavailable, add to DH + # # If all pos unavailable; note player's pos_1 and check if incumbent can move; if so, check for new incumbent + # # and recurse + # # If not possible, pass player and continue + # for guy in all_players: + # placed = False + # if guy['p_name'] not in p_names and guy['p_name'] != rest_name: + # for pos in [ + # guy['pos_1'], guy['pos_2'], guy['pos_3'], guy['pos_4'], guy['pos_5'], guy['pos_6'], guy['pos_7'], + # guy['pos_8'] + # ]: + # if pos is None or pos in ['SP', 'RP', 'CP']: + # break + # + # if random.randint(1, 10) == 10 and rest_name is None and False: + # logging.info(f'Resting {guy["p_name"]} in game {game_id}') + # rest_name = guy['p_name'] + # elif players[pos] is None: + # players[pos] = guy + # p_names.append(guy["p_name"]) + # placed = True + # break + # + # if not placed and rest_name != guy['p_name']: + # if players['DH'] is None: + # players['DH'] = guy + # p_names.append(guy["p_name"]) + # else: + # logging.info(f'build_lineup - could not place {guy["p_name"]} in {team_object["sname"]} lineup') + # else: + # logging.info(f'build_lineup - {guy["p_name"]} already in lineup') + # + # if None in players.values(): + # bad_pos = {x for x in players if players[x] is None} + # raise ValueError(f'Could not find a {bad_pos} for the {team_object["sname"]}') + # + # logging.info(f'build_lineup - {players}') + # + # # Sort players into lineup + # # Pseudo-random? Patterns? Something to mix up lineups + # # 421356789 or [123 (rand)][456 (rand)][789 (rand)] (optional: chance to flip 3/4 and 6/7) + # + # # [ (, ), (, ), etc. ] + # sorted_players = sorted(players.items(), key=lambda x: x[1]['cost'], reverse=True) + + build_type = 'fun' + l_query = await db_get( + f'teams/{team_object["id"]}/lineup/{league_name}?pitcher_name={sp_name}&build_type={build_type}', + timeout=6 + ) + sorted_players = l_query['array'] + logging.info(f'sorted_players: {sorted_players}') + + grp_1 = sorted_players[:3] + grp_2 = sorted_players[3:6] + grp_3 = sorted_players[6:] + random.shuffle(grp_1) + random.shuffle(grp_2) + random.shuffle(grp_3) + + lineups = [] + i = 1 + for x in [grp_1, grp_2, grp_3]: + logging.debug(f'group: {x}') + for y in x: + logging.debug(f'y: {y}') + card_id = await get_or_create_card(y[1]['player'], team_object) + + lineups.append({ + 'game_id': game_id, + 'team_id': team_object['id'], + 'player_id': y[1]['player']['player_id'], + 'card_id': card_id, + 'position': y[0], + 'batting_order': i, + 'after_play': 0 + }) + i += 1 + + logging.info(f'build_lineup - final lineup: {lineups}') + + return lineups + + +async def get_starting_pitcher(team_object: dict, game_id: int, is_home: bool, league_name: str = None) -> dict: + # set_params = [('cardset_id_exclude', 2)] + # if league_name == 'minor-league': + # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) + # elif league_name == 'major-league': + # set_params = copy.deepcopy(MAJOR_CARDSET_PARAMS) + # elif league_name == 'hall-of-fame': + # set_params = copy.deepcopy(HOF_CARDSET_PARAMS) + # elif league_name == 'gauntlet-1': + # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) + # elif 'gauntlet-2' in league_name: + # set_params = random.sample(GAUNTLET2_PARAMS) + # + # params = [ + # ('mlbclub', team_object['lname']), ('pos_include', 'SP'), ('pos_exclude', 'RP'), + # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) + # ] + # counter = 0 + # logging.info(f'params: {params}') + # while counter < 2: + # logging.info(f'counter: {counter}') + # counter += 1 + # # Pull starters sorted by current cost + # logging.info(f'counter: {counter} / params: {params}') + # try: + # params.extend(set_params) + # pitchers = await db_get( + # endpoint='players', + # params=params, + # timeout=10 + # ) + # logging.info(f'pitchers: {pitchers}') + # except ConnectionError as e: + # logging.error(f'Could not get pitchers for {team_object["lname"]}: {e}') + # raise ConnectionError(f'Error pulling starting pitchers for the {team_object["lname"]}. Cal help plz.') + # + # if pitchers['count'] == 0: + # logging.info(f'pitchers is None') + # del params + # params = [ + # ('mlbclub', team_object['lname']), ('pos_include', 'SP'), + # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) + # ] + # else: + # break + # + # logging.info(f'build_lineup - eligible starting pitcher count: {len(pitchers)}') + # if pitchers['count'] == 0: + # raise DatabaseError(f'Could not find any SP for {team_object["abbrev"]}. Seems like a Cal issue.') + + d_100 = random.randint(1, 100) + if is_home: + if d_100 <= 30: + sp_rank = 1 + elif d_100 <= 55: + sp_rank = 2 + elif d_100 <= 75: + sp_rank = 3 + elif d_100 <= 90: + sp_rank = 4 + else: + sp_rank = 5 + else: + if d_100 <= 50: + sp_rank = 1 + elif d_100 <= 75: + sp_rank = 2 + elif d_100 <= 85: + sp_rank = 3 + elif d_100 <= 95: + sp_rank = 4 + else: + sp_rank = 5 + starter = await db_get(f'teams/{team_object["id"]}/sp/{league_name}?sp_rank={sp_rank}') + + # get player card; create one if none found + card_id = await get_or_create_card(starter, team_object) + + return { + 'game_id': game_id, + 'team_id': team_object['id'], + 'player_id': starter['player_id'], + 'card_id': card_id, + 'position': 'P', + 'batting_order': 10, + 'after_play': 0 + } + + +async def get_relief_pitcher(this_play: StratPlay, ai_team: dict, league_name: str = None) -> dict: + used_ids = [] + used_players = await get_team_lineups( + game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, as_string=False, pitchers_only=True + ) + for x in used_players: + used_ids.append(f'{x.player_id}') + + logging.debug(f'used ids: {used_ids}') + id_string = "&used_pitcher_ids=".join(used_ids) + this_game = this_play.game + ai_score = this_play.away_score if this_game.away_team_id == ai_team['id'] else this_play.home_score + human_score = this_play.home_score if this_game.away_team_id == ai_team['id'] else this_play.away_score + + logging.debug(f'scores - ai: {ai_score} / human: {human_score}') + if abs(ai_score - human_score) >= 7: + need = 'length' + elif this_play.inning_num >= 9 and abs(ai_score - human_score) <= 3: + need = 'closer' + elif this_play.inning_num in [7, 8] and abs(ai_score - human_score) <= 3: + need = 'setup' + elif abs(ai_score - human_score) <= 3: + need = 'middle' + else: + need = 'length' + + logging.debug(f'need: {need}') + rp_query = await db_get(f'teams/{ai_team["id"]}/rp/{league_name.split("-run")[0]}' + f'?need={need}&used_pitcher_ids={id_string}') + card_id = await get_or_create_card(rp_query, ai_team) + return { + 'game_id': this_play.game.id, + 'team_id': ai_team['id'], + 'player_id': rp_query['player_id'], + 'card_id': card_id, + 'position': 'P', + 'batting_order': 10, + 'after_play': this_play.play_num - 1 + } + + """ + END NEW GET RP + """ + + # used_codes = [] + # used_players = await get_team_lineups( + # game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, as_string=False + # ) + # for x in used_players: + # c_query = await db_get('cards', object_id=x.card_id) + # used_codes.append(c_query["player"]["strat_code"]) + # logging.info(f'get_rp - used_players: {used_codes}') + # + # reliever = None + # attempts = 0 + # while reliever is None: + # attempts += 1 + # if attempts > 3: + # raise ValueError(f'Could not find a reliever for the {ai_team["sname"]}. Cal plz.') + # + # set_params = [('cardset_id_exclude', 2)] + # if league_name == 'minor-league': + # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) + # elif league_name == 'major-league': + # set_params = copy.deepcopy(MAJOR_CARDSET_PARAMS) + # elif league_name == 'hall-of-fame': + # set_params = copy.deepcopy(HOF_CARDSET_PARAMS) + # elif 'gauntlet-1' in league_name: + # set_params = copy.deepcopy(MINOR_CARDSET_PARAMS) + # elif 'gauntlet-2' in league_name: + # set_params = copy.deepcopy(GAUNTLET2_PARAMS) + # + # # Pull relievers sorted by current cost + # params = [ + # ('mlbclub', ai_team['lname']), ('pos_include', 'RP'), ('inc_dex', False), ('sort_by', 'cost-desc'), + # ('limit', 15) + # ] + # params.extend(set_params) + # + # use_best = False + # if attempts == 1: + # # Try to get long man + # if this_play.inning_num < 6: + # logging.info(f'get_rp - game {this_play.game.id} try for long man') + # params.append(('pos_include', 'SP')) + # # use_best = True + # + # # Try to get closer + # elif this_play.inning_num > 8: + # if (this_play.inning_half == 'top' and this_play.home_score >= this_play.away_score) or \ + # (this_play.inning_half == 'bot' and this_play.away_score >= this_play.home_score): + # logging.info(f'get_rp - game {this_play.game.id} try for closer') + # params.append(('pos_include', 'CP')) + # use_best = True + # else: + # params.append(('pos_exclude', 'CP')) + # + # # Try to exclude long men + # elif attempts == 1: + # logging.info(f'get_rp - game {this_play.game.id} try to exclude long men') + # params.append(('pos_exclude', 'SP')) + # + # try: + # pitchers = await db_get( + # endpoint='players', + # params=params, + # timeout=10 + # ) + # except ConnectionError as e: + # logging.error(f'Could not get pitchers for {ai_team["lname"]}: {e}') + # raise ConnectionError(f'Error pulling starting pitchers for the {ai_team["lname"]}. Cal help plz.') + # + # if pitchers['count'] > 0: + # if use_best or this_play.inning_num > 9 or attempts > 2: + # start = 0 + # else: + # start = 9 - this_play.inning_num + # + # for count, guy in enumerate(pitchers['players']): + # if count >= start and guy['strat_code'] not in used_codes: + # card_id = await get_or_create_card(guy, ai_team) + # + # return { + # 'game_id': this_play.game.id, + # 'team_id': ai_team['id'], + # 'player_id': guy['player_id'], + # 'card_id': card_id, + # 'position': 'P', + # 'batting_order': 10, + # 'after_play': this_play.play_num - 1 + # } + + +def get_pitcher(this_game: StratGame, this_play: StratPlay): + p_team_id = this_game.home_team_id + if this_play.inning_half == 'top': + p_team_id = this_game.away_team_id + return get_one_lineup(this_game.id, team_id=p_team_id, position='P', active=True) + + +async def pitching_ai_note(this_play: StratPlay, this_pitcher: dict): + this_ai = get_manager(this_play.game) + gm_name = f'{this_pitcher["team"]["gmname"]}' + # used_pitchers = await get_team_lineups( + # game_id=this_play.game.id, + # team_id=this_pitcher["team"]['id'], + # inc_inactive=True, + # pitchers_only=True, + # as_string=False + # ) + # last_inning_ender = get_last_inning_end_play( + # this_play.game.id, + # this_play.inning_half, + # this_play.inning_num - 1 + # ) + + ai_note = '' + pitcher = this_pitcher + + # # Pitcher Substitutions + # new_pitcher = None + # if last_inning_ender and last_inning_ender.pitcher != this_play.pitcher: + # logging.debug(f'{this_pitcher["team"]["sname"]} not making a change.') + # + # ai_note += f'- have {this_pitcher["p_name"]} finish the inning\n' + # + # elif this_play.is_new_inning and this_play.game.short_game and this_play.inning_num != 1: + # logging.debug(f'{this_pitcher["team"]["sname"]} going the to pen.') + # + # if len(used_pitchers) < 8: + # make_sub(await get_relief_pitcher( + # this_play, this_pitcher['team'], this_play.game.game_type + # )) + # pitcher = await get_player(this_play.game, get_pitcher(this_play.game, this_play)) + # new_pitcher = pitcher + # else: + # ai_note += f'- continue with auto-fatigued {this_pitcher["p_name"]}\n' + # + # else: + # if len(used_pitchers) == 1: + # ai_note += f'- go to the pen if the pitcher fatigues __and has allowed 5+ baserunners__ ' \ + # f'(`/log ai-pitcher-sub`)\n' + # elif len(used_pitchers) < 8: + # ai_note += f'- go to the pen if the pitcher fatigues (`/log ai-pitcher-sub`)\n' + # else: + # ai_note += f' - continue with {this_pitcher["p_name"]}\n' + + # Holding Baserunners + if this_play.starting_outs == 2 and this_play.on_base_code > 0: + if this_play.on_base_code in [1, 2]: + ai_note += f'- hold the runner\n' + elif this_play.on_base_code in [4, 7]: + ai_note += f'- hold the runners\n' + elif this_play.on_base_code == 5: + ai_note += f'- hold the runner on first\n' + elif this_play.on_base_code == 6: + ai_note += f'- hold the runner on second\n' + elif this_play.on_base_code in [1, 5]: + ai_note += f'- hold the runner on 1st if they have ***** auto-jump\n' + elif this_play.on_base_code == 2: + ai_note += f'- hold the runner on 2nd if safe range is 14+\n' + + # Defensive Alignment + if this_play.on_third and this_play.starting_outs < 2: + if this_play.on_first: + ai_note += f'- play the corners in\n' + + elif abs(this_play.away_score - this_play.home_score) <= 3: + ai_note += f'- play the whole infield in\n' + + else: + ai_note += f'- play the corners in\n' + + return { + 'note': ai_note, + 'pitcher': pitcher, + 'gm_name': gm_name, + 'sub': None + } + + +def batting_ai_note(this_play: StratPlay, this_batter: dict): + this_ai = get_manager(this_play.game) + + ai_note = '' + gm_name = f'{this_batter["team"]["gmname"]}' + + if this_play.on_first and not this_play.on_second: + ai_note += f'- {this_ai.check_jump(2, this_play.starting_outs)}\n' + + elif this_play.on_second and not this_play.on_third: + ai_note += f'- {this_ai.check_jump(3, this_play.starting_outs)}\n' + + return { + 'note': ai_note, + 'batter': this_batter, + 'gm_name': gm_name + } + + +async def check_pitching_sub(this_play: StratPlay, ai_team: dict): + used_pitchers = await get_team_lineups( + game_id=this_play.game.id, + team_id=this_play.pitcher.team_id, + inc_inactive=True, + pitchers_only=True, + as_string=False + ) + p_stats = get_pitching_stats(this_play.game.id, lineup_id=this_play.pitcher.id) + if len(p_stats) == 0: + logging.info(f'ai_manager - check_pitching_sub: no stats recorded yet, returning None') + return False + ps = p_stats[0] + logging.info(f'ai_manager - check_pitching_sub: beyond point of weakness') + + this_ai = get_manager(this_play.game) + this_pc = await data_cache.get_pd_pitchingcard(this_play.pitcher.player_id, variant=this_play.pitcher.variant) + pof_weakness = this_pc.card.starter_rating if len(used_pitchers) == 1 else this_pc.card.relief_rating + innof_work = math.ceil((ps['pl_outs'] + 1) / 3) + gtr = this_ai.go_to_reliever( + this_play, + tot_allowed=ps['pl_hit'] + ps['pl_bb'] + ps['pl_hbp'], + is_starter=True if len(used_pitchers) == 1 else False + ) + + if (this_play.game.short_game or gtr or innof_work > pof_weakness + 3) and len(used_pitchers) < 8: + make_sub(await get_relief_pitcher(this_play, ai_team, this_play.game.game_type)) + return await get_player(this_play.game, get_pitcher(this_play.game, this_play)) + + return None + + +async def is_pitcher_fatigued(this_play: StratPlay) -> bool: + used_pitchers = await get_team_lineups( + game_id=this_play.game.id, + team_id=this_play.pitcher.team_id, + inc_inactive=True, + pitchers_only=True, + as_string=False + ) + p_stats = get_pitching_stats(this_play.game.id, lineup_id=this_play.pitcher.id) + if len(p_stats) == 0: + logging.info(f'ai_manager - is_pitcher_fatigued: no stats recorded yet, returning False') + return False + ps = p_stats[0] + + if this_play.game.short_game: + pof_weakness = 1 + else: + this_pc = await data_cache.get_pd_pitchingcard(this_play.pitcher.player_id, variant=this_play.pitcher.variant) + pof_weakness = this_pc.card.starter_rating if len(used_pitchers) == 1 else this_pc.card.relief_rating + + # Check starter fatigue + if len(p_stats) == 1: + if ps['pl_eruns'] >= 7: + logging.info(f'ai_manager - is_pitcher_fatigued: starter allowed 7+, returning True') + return True + if ps['pl_eruns'] >= 6: + logging.info(f'ai_manager - is_pitcher_fatigued: starter allowed 6+, checking for fatigue') + f_query = get_pitching_stats( + this_play.game.id, + lineup_id=this_play.pitcher.id, + in_innings=[this_play.inning_num, this_play.inning_num - 1] + ) + if f_query[0]['pl_eruns'] >= 6: + logging.info(f'ai_manager - is_pitcher_fatigued: starter allowed 6 in 2, returning True') + return True + if ps['pl_eruns'] >= 5: + logging.info(f'ai_manager - is_pitcher_fatigued: starter allowed 5+, checking for fatigue') + f_query = get_pitching_stats( + this_play.game.id, + lineup_id=this_play.pitcher.id, + in_innings=[this_play.inning_num] + ) + if f_query[0]['pl_eruns'] >= 5: + logging.info(f'ai_manager - is_pitcher_fatigued: starter allowed 5 in 1, returning True') + return True + + innof_work = math.ceil((ps['pl_outs'] + 1) / 3) + if innof_work < pof_weakness: + logging.info(f'ai_manager - is_pitcher_fatigued: not point of weakness, returning False') + return False + + elif innof_work == pof_weakness: + patch_play(this_play.id, in_pow=True) + pow_stats = get_pitching_stats(this_play.game.id, lineup_id=this_play.pitcher.id, in_pow=True) + if len(pow_stats) == 0: + logging.info(f'ai_manager - is_pitcher_fatigued: in point of weakness, no stats recorded, returning False') + return False + pows = pow_stats[0] + if pows['pl_hit'] + pows['pl_bb'] + pows['pl_hbp'] < 3: + logging.info(f'ai_manager - is_pitcher_fatigued: in point of weakness, not fatigued, returning False') + return False + + logging.info(f'ai_manager - is_pitcher_fatigued: beyond point of weakness, fatigued, returning True') + return True + + +# async def consider_reliever( +# this_play: StratPlay, this_pitcher: StratLineup, ai_team: dict, run_lead: int, tot_allowed: int, +# used_pitchers: list[StratLineup]): +# this_ai = get_manager(this_play.game) +# +# if (this_play.game.short_game or +# this_ai.go_to_reliever(this_play.starting_outs, this_play.on_base_code, run_lead, tot_allowed)) and \ +# len(used_pitchers) < 8: +# make_sub(await get_relief_pitcher(this_play, ai_team, this_play.game.game_type)) +# return await get_player(this_play.game, get_pitcher(this_play.game, this_play)) +# +# return None diff --git a/in_game/data_cache.py b/in_game/data_cache.py new file mode 100644 index 0000000..3eda75f --- /dev/null +++ b/in_game/data_cache.py @@ -0,0 +1,315 @@ +import datetime +from dataclasses import dataclass +import logging +from typing import Optional + +from db_calls import db_get + +PLAYER_CACHE = {} +BATTINGCARD_CACHE = {} # { : { : BattingWrapper } } +PITCHINGCARD_CACHE = {} # { : { : PitchingWrapper } } + + +@dataclass +class Player: + player_id: int + p_name: str + cost: int + image: str + mlbclub: str + franchise: str + cardset: dict + set_num: int + rarity: dict + pos_1: str + description: str + quantity: Optional[int] = 999 + image2: Optional[str] = None + pos_2: Optional[str] = None + pos_3: Optional[str] = None + pos_4: Optional[str] = None + pos_5: Optional[str] = None + pos_6: Optional[str] = None + pos_7: Optional[str] = None + pos_8: Optional[str] = None + headshot: Optional[str] = None + vanity_card: Optional[str] = None + strat_code: Optional[str] = None + bbref_id: Optional[str] = None + fangr_id: Optional[str] = None + mlbplayer: Optional[dict] = None + created: datetime.datetime = datetime.datetime.now() + + def to_dict(self): + return { + 'player_id': self.player_id, + 'p_name': self.p_name, + 'cost': self.cost, + 'image': self.image, + 'mlbclub': self.mlbclub, + 'franchise': self.franchise, + 'cardset': self.cardset, + 'set_num': self.set_num, + 'rarity': self.rarity, + 'pos_1': self.pos_1, + 'description': self.description, + 'quantity': self.quantity, + 'image2': self.image2, + 'pos_2': self.pos_2, + 'pos_3': self.pos_3, + 'pos_4': self.pos_4, + 'pos_5': self.pos_5, + 'pos_6': self.pos_6, + 'pos_7': self.pos_7, + 'pos_8': self.pos_8, + 'headshot': self.headshot, + 'vanity_card': self.vanity_card, + 'strat_code': self.strat_code, + 'bbref_id': self.bbref_id, + 'fangr_id': self.fangr_id, + 'mlbplayer': self.mlbplayer + } + + +@dataclass +class BattingCard: + player_id: int + variant: int + steal_low: int + steal_high: int + steal_auto: bool + steal_jump: float + bunting: str + hit_and_run: str + running: int + offense_col: int + hand: str + + +@dataclass +class BattingRatings: + homerun: float + bp_homerun: float + triple: float + double_three: float + double_two: float + double_pull: float + single_two: float + single_one: float + single_center: float + bp_single: float + hbp: float + walk: float + strikeout: float + lineout: float + popout: float + flyout_a: float + flyout_bq: float + flyout_lf_b: float + flyout_rf_b: float + groundout_a: float + groundout_b: float + groundout_c: float + avg: float + obp: float + slg: float + pull_rate: float + center_rate: float + slap_rate: float + + +@dataclass +class BattingWrapper: + card: BattingCard + ratings_vl: BattingRatings + ratings_vr: BattingRatings + created: datetime.datetime = datetime.datetime.now() + + +@dataclass +class PitchingCard: + player_id: int + variant: int + balk: int + wild_pitch: int + hold: int + starter_rating: int + relief_rating: int + batting: str + offense_col: int + hand: str + closer_rating: Optional[int] = None + + +@dataclass +class PitchingRatings: + homerun: float + bp_homerun: float + triple: float + double_three: float + double_two: float + double_cf: float + single_two: float + single_one: float + single_center: float + bp_single: float + hbp: float + walk: float + strikeout: float + flyout_lf_b: float + flyout_cf_b: float + flyout_rf_b: float + groundout_a: float + groundout_b: float + xcheck_p: float + xcheck_c: float + xcheck_1b: float + xcheck_2b: float + xcheck_3b: float + xcheck_ss: float + xcheck_lf: float + xcheck_cf: float + xcheck_rf: float + avg: float + obp: float + slg: float + + +@dataclass +class PitchingWrapper: + card: PitchingCard + ratings_vl: PitchingRatings + ratings_vr: PitchingRatings + created: datetime.datetime = datetime.datetime.now() + + +async def get_pd_player(player_id, as_dict: Optional[bool] = True): + if player_id in PLAYER_CACHE: + tdelta = datetime.datetime.now() - PLAYER_CACHE[player_id].created + if tdelta.total_seconds() < 1209600: + logging.debug(f'this_player: {PLAYER_CACHE[player_id]}') + if as_dict: + return PLAYER_CACHE[player_id].to_dict() + else: + return PLAYER_CACHE[player_id] + else: + logging.error(f'Refreshing player {player_id} in cache...') + + this_player = await db_get('players', object_id=player_id) + for bad_key in ['mlbplayer', 'paperdex']: + if bad_key in this_player: + del this_player[bad_key] + logging.debug(f'this_player: {this_player}') + PLAYER_CACHE[player_id] = Player(**this_player) + + if as_dict: + return this_player + return PLAYER_CACHE[player_id] + + +async def get_pd_battingcard(player_id: int, variant: Optional[int] = 0): + if player_id in BATTINGCARD_CACHE and variant in BATTINGCARD_CACHE[player_id]: + tdelta = datetime.datetime.now() - BATTINGCARD_CACHE[player_id][variant].created + if tdelta.total_seconds() < 609600: + logging.info(f'this_battingcard: {BATTINGCARD_CACHE[player_id][variant]}') + return BATTINGCARD_CACHE[player_id][variant] + + vl_data, vr_data = None, None + r_query = await db_get(f'battingcardratings/player/{player_id}') + if r_query['count'] > 0: + for row in r_query['ratings']: + if row['battingcard']['variant'] == variant: + if row['vs_hand'].lower() == 'r': + vr_data = row + elif row['vs_hand'].lower() == 'l': + vl_data = row + if vl_data is not None and vr_data is not None: + break + + if None in [vl_data, vr_data]: + raise KeyError(f'Could not find batting card ratings for player {player_id} variant {variant}') + + logging.info(f'prepping the batting card') + bc_data = vl_data['battingcard'] + bc_data['player_id'] = player_id + del bc_data['id'], bc_data['player'] + this_bc = BattingCard(**bc_data) + + logging.info(f'prepping the vl ratings') + vl_ratings = vl_data + del vl_ratings['battingcard'], vl_ratings['id'], vl_ratings['vs_hand'] + this_vl = BattingRatings(**vl_ratings) + + logging.info(f'prepping the vl ratings') + vr_ratings = vr_data + del vr_ratings['battingcard'], vr_ratings['id'], vr_ratings['vs_hand'] + this_vr = BattingRatings(**vr_ratings) + + logging.info(f'prepping the wrapper') + this_wrapper = BattingWrapper( + card=this_bc, + ratings_vl=this_vl, + ratings_vr=this_vr + ) + + if player_id in BATTINGCARD_CACHE: + BATTINGCARD_CACHE[player_id][variant] = this_wrapper + else: + BATTINGCARD_CACHE[player_id] = {variant: this_wrapper} + + return this_wrapper + + +async def get_pd_pitchingcard(player_id: int, variant: Optional[int] = 0): + if player_id in PITCHINGCARD_CACHE and variant in PITCHINGCARD_CACHE[player_id]: + tdelta = datetime.datetime.now() - PITCHINGCARD_CACHE[player_id][variant].created + if tdelta.total_seconds() < 609600: + logging.debug(f'this_pitchingcard: {PITCHINGCARD_CACHE[player_id][variant]}') + return PITCHINGCARD_CACHE[player_id][variant] + + vl_data, vr_data = None, None + r_query = await db_get(f'pitchingcardratings/player/{player_id}') + if r_query['count'] > 0: + for row in r_query['ratings']: + if row['pitchingcard']['variant'] == variant: + if row['vs_hand'].lower() == 'r': + vr_data = row + elif row['vs_hand'].lower() == 'l': + vl_data = row + if vl_data is not None and vr_data is not None: + break + + if None in [vl_data, vr_data]: + raise KeyError(f'Could not find batting card ratings for player {player_id} variant {variant}') + + logging.debug(f'prepping the pitching card') + pc_data = vl_data['pitchingcard'] + pc_data['player_id'] = player_id + del pc_data['id'], pc_data['player'] + this_pc = PitchingCard(**pc_data) + + logging.debug(f'prepping the vl ratings') + vl_ratings = vl_data + del vl_ratings['pitchingcard'], vl_ratings['id'], vl_ratings['vs_hand'] + this_vl = PitchingRatings(**vl_ratings) + + logging.debug(f'prepping the vr ratings') + vr_ratings = vr_data + del vr_ratings['pitchingcard'], vr_ratings['id'], vr_ratings['vs_hand'] + this_vr = PitchingRatings(**vr_ratings) + + logging.debug(f'prepping the wrapper') + this_wrapper = PitchingWrapper( + card=this_pc, + ratings_vl=this_vl, + ratings_vr=this_vr + ) + + if player_id in PITCHINGCARD_CACHE: + PITCHINGCARD_CACHE[player_id][variant] = this_wrapper + else: + PITCHINGCARD_CACHE[player_id] = {variant: this_wrapper} + + return this_wrapper + + diff --git a/gameplay_helpers.py b/in_game/game_helpers.py similarity index 94% rename from gameplay_helpers.py rename to in_game/game_helpers.py index 65e88a2..3a370f0 100644 --- a/gameplay_helpers.py +++ b/in_game/game_helpers.py @@ -1,466 +1,465 @@ -import asyncio -import copy -import logging -import random - -import discord - -from db_calls_gameplay import StratGame, StratPlay, StratLineup, StratManagerAi, patch_play, advance_runners, \ - complete_play, get_team_lineups, get_or_create_bullpen, get_player, get_sheets, make_sub, get_one_lineup, \ - advance_one_runner, get_one_lineup, ai_batting, get_manager -from db_calls import db_get, db_post, Player, get_pd_player -from helpers import Pagination, get_team_embed, image_embed, Confirm -from typing import Literal, Optional - - -def single_onestar(this_play: StratPlay, comp_play: bool = True): - patch_play(this_play.id, locked=True) - advance_runners(this_play.id, num_bases=1) - patch_play(this_play.id, pa=1, ab=1, hit=1) - if comp_play: - complete_play(this_play.id, batter_to_base=1) - - -def single_wellhit(this_play: StratPlay, comp_play: bool = True): - patch_play(this_play.id, locked=True) - advance_runners(this_play.id, num_bases=2) - patch_play(this_play.id, pa=1, ab=1, hit=1) - if comp_play: - complete_play(this_play.id, batter_to_base=1) - - -def double_twostar(this_play: StratPlay, comp_play: bool = True): - patch_play(this_play.id, locked=True) - advance_runners(this_play.id, num_bases=2) - patch_play(this_play.id, pa=1, ab=1, hit=1, double=1) - if comp_play: - complete_play(this_play.id, batter_to_base=2) - - -def double_threestar(this_play: StratPlay, comp_play: bool = True): - patch_play(this_play.id, locked=True) - advance_runners(this_play.id, num_bases=3) - patch_play(this_play.id, pa=1, ab=1, hit=1, double=1) - if comp_play: - complete_play(this_play.id, batter_to_base=2) - - -def triple(this_play: StratPlay, comp_play: bool = True): - patch_play(this_play.id, locked=True) - advance_runners(this_play.id, num_bases=3) - patch_play(this_play.id, pa=1, ab=1, hit=1, triple=1) - if comp_play: - complete_play(this_play.id, batter_to_base=3) - - -async def next_pitcher(this_play: StratPlay, ai_team, bot): - used_pitchers = await get_team_lineups( - game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, pitchers_only=True, as_string=False - ) - logging.debug(f'\n\nused_pitchers: {used_pitchers}') - - used_ids = [x.card_id for x in used_pitchers] - logging.debug(f'used_ids: {used_ids}') - bullpen = get_or_create_bullpen(ai_team, bot) - logging.debug(f'bullpen: {bullpen}') - - if this_play.game.short_game: - if this_play.inning_num == 1: - relievers = [bullpen.middle_two_id, bullpen.middle_one_id, bullpen.middle_three_id] - elif this_play.inning_num == 2: - relievers = [bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] - elif this_play.inning_num == 3: - relievers = [ - bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, - bullpen.middle_three_id - ] - else: - relievers = [ - bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id, - bullpen.middle_three_id, bullpen.middle_two_id, bullpen.middle_one_id, bullpen.setup_id, - bullpen.closer_id - ] - else: - if this_play.inning_num < 6: - relievers = [bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id] - elif this_play.inning_num == 6: - relievers = [bullpen.middle_two_id, bullpen.middle_one_id, bullpen.middle_three_id] - elif this_play.inning_num == 7: - relievers = [bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] - elif this_play.inning_num == 8: - relievers = [bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] - elif this_play.inning_num == 9: - relievers = [ - bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id - ] - else: - relievers = [ - bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id, - bullpen.middle_three_id, bullpen.middle_two_id, bullpen.middle_one_id, bullpen.setup_id, bullpen.closer_id - ] - - for arm in relievers: - if arm and arm not in used_ids: - return {'player': await get_player(this_play.game, {'card_id': arm}), 'card_id': arm} - - relievers = [ - bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id, - bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id - ] - - for arm in relievers: - if arm and arm not in used_ids: - return {'player': await get_player(this_play.game, {'card_id': arm}), 'card_id': arm} - - return {"p_name": "Backup Infielder"} - - -def starting_pitcher(ai_team, bot, is_home): - sheets = get_sheets(bot) - this_sheet = sheets.open_by_key(ai_team['gsheet']) - r_sheet = this_sheet.worksheet_by_title('My Rosters') - - rotation_range = f'I4:I8' - raw_cells = r_sheet.range(rotation_range) - d_100 = random.randint(1, 100) - logging.info(f'raw_cells: {raw_cells} / d_100: {d_100}') - - if is_home: - if d_100 <= 30: - return raw_cells[0][0].value - elif d_100 <= 55: - return raw_cells[1][0].value - elif d_100 <= 75: - return raw_cells[2][0].value - elif d_100 <= 90: - return raw_cells[3][0].value - else: - return raw_cells[4][0].value - else: - if d_100 <= 50: - return raw_cells[0][0].value - elif d_100 <= 75: - return raw_cells[1][0].value - elif d_100 <= 85: - return raw_cells[2][0].value - elif d_100 <= 95: - return raw_cells[3][0].value - else: - return raw_cells[4][0].value - - -def replace_pitcher(ai_team, this_play, pitcher_card_id): - this_card = db_get(f'cards', object_id=int(pitcher_card_id)) - new_lineup = { - 'game_id': this_play.game.id, - 'team_id': ai_team['id'], - 'player_id': this_card['player']['player_id'], - 'card_id': pitcher_card_id, - 'position': 'P', - 'batting_order': 10, - 'after_play': this_play.play_num - 1 - } - make_sub(new_lineup) - - -def get_pitcher(this_game: StratGame, this_play: StratPlay): - p_team_id = this_game.away_team_id - if this_play.inning_half == 'top': - p_team_id = this_game.home_team_id - return get_one_lineup(this_game.id, team_id=p_team_id, position='P', active=True) - - -async def legal_check(card_ids: list, difficulty_name: str): - all_ids = [str(x) for x in card_ids] - l_string = "&card_id=".join(all_ids) - legality = await db_post(f'cards/legal-check/{difficulty_name}?card_id={l_string}') - - r_val = {'legal': True, 'error_string': None} - if legality['count'] > 0: - r_val['legal'] = False - r_val['error_string'] = "\n- ".join(legality['bad_cards']) - - return r_val - - -async def show_outfield_cards(interaction: discord.Interaction, this_play: StratPlay): - lf_player = await get_player(this_play.game, as_dict=False, lineup_member=get_one_lineup( - this_play.game.id, team_id=this_play.pitcher.team_id, position='LF', active=True - )) - cf_player = await get_player(this_play.game, as_dict=False, lineup_member=get_one_lineup( - this_play.game.id, team_id=this_play.pitcher.team_id, position='CF', active=True - )) - rf_player = await get_player(this_play.game, as_dict=False, lineup_member=get_one_lineup( - this_play.game.id, team_id=this_play.pitcher.team_id, position='RF', active=True - )) - this_team = await db_get('teams', object_id=this_play.pitcher.team_id) - logging.debug(f'lf: {lf_player}\n\ncf: {cf_player}\n\nrf: {rf_player}\n\nteam: {this_team["lname"]}') - - view = Pagination([interaction.user], timeout=6) - view.left_button.label = f'Left Fielder' - view.left_button.style = discord.ButtonStyle.secondary - lf_embed = image_embed( - lf_player.image, f'{this_team["sname"]} LF', this_team['color'], lf_player.p_name, this_team['lname'], - this_team['logo']) - - view.cancel_button.label = f'Center Fielder' - view.cancel_button.style = discord.ButtonStyle.blurple - cf_embed = image_embed( - cf_player.image, f'{this_team["sname"]} CF', this_team['color'], cf_player.p_name, this_team['lname'], - this_team['logo']) - - view.right_button.label = f'Right Fielder' - view.right_button.style = discord.ButtonStyle.secondary - rf_embed = image_embed( - rf_player.image, f'{this_team["sname"]} RF', this_team['color'], rf_player.p_name, this_team['lname'], - this_team['logo']) - - page_num = 1 - embeds = [lf_embed, cf_embed, rf_embed] - - msg = await interaction.channel.send(embed=embeds[page_num], view=view) - - await view.wait() - - if view.value: - if view.value == 'left': - page_num = 0 - if view.value == 'cancel': - page_num = 1 - if view.value == 'right': - page_num = 2 - else: - await msg.edit(content=None, embed=embeds[page_num], view=None) - - view.value = None - - if page_num == 0: - view.left_button.style = discord.ButtonStyle.blurple - view.cancel_button.style = discord.ButtonStyle.secondary - view.right_button.style = discord.ButtonStyle.secondary - if page_num == 1: - view.left_button.style = discord.ButtonStyle.secondary - view.cancel_button.style = discord.ButtonStyle.blurple - view.right_button.style = discord.ButtonStyle.secondary - if page_num == 2: - view.left_button.style = discord.ButtonStyle.secondary - view.cancel_button.style = discord.ButtonStyle.secondary - view.right_button.style = discord.ButtonStyle.blurple - - view.left_button.disabled = True - view.cancel_button.disabled = True - view.right_button.disabled = True - - await msg.edit(content=None, embed=embeds[page_num], view=view) - return [lf_player, cf_player, rf_player][page_num] - - -def runner_on_first(this_play: StratPlay): - if this_play.on_base_code in [1, 4, 5, 7]: - return True - return False - - -def runner_on_second(this_play: StratPlay): - if this_play.on_base_code in [2, 4, 6, 7]: - return True - return False - - -def runner_on_third(this_play: StratPlay): - if this_play.on_base_code in [3, 5, 6, 7]: - return True - return False - - -def gb_result_1(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 1') - advance_runners(this_play.id, num_bases=0) - patch_play(this_play.id, pa=1, ab=1, outs=1) - return None - - -def gb_result_2(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 2') - num_outs = 2 if this_play.starting_outs <= 1 else 1 - - patch_play(this_play.id, pa=1, ab=1, outs=num_outs, on_first_final=False) - if num_outs + this_play.starting_outs < 3: - if runner_on_second(this_play): - advance_one_runner(this_play.id, from_base=2, num_bases=1) - if runner_on_third(this_play): - advance_one_runner(this_play.id, from_base=3, num_bases=1) - - return None - - -def gb_result_3(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 3') - if this_play.starting_outs < 2: - advance_runners(this_play.id, num_bases=1) - patch_play(this_play.id, pa=1, ab=1, outs=1) - return None - - -def gb_result_4(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 4') - patch_play(this_play.id, pa=1, ab=1, outs=1, on_first_final=False) - if this_play.starting_outs < 2: - if runner_on_second(this_play): - advance_one_runner(this_play.id, from_base=2, num_bases=1) - if runner_on_third(this_play): - advance_one_runner(this_play.id, from_base=3, num_bases=1) - - return 1 - - -def gb_result_5(this_play: StratPlay, to_mif: bool) -> Optional[int]: - logging.info(f'GB 5') - patch_play(this_play.id, pa=1, ab=1, outs=1) - if to_mif: - gb_result_3(this_play) - else: - gb_result_1(this_play) - - return None - - -def gb_result_6(this_play: StratPlay, to_right_side: bool) -> Optional[int]: - logging.info(f'GB 6') - patch_play(this_play.id, pa=1, ab=1, outs=1) - if to_right_side: - gb_result_3(this_play) - else: - gb_result_1(this_play) - - return None - - -def gb_result_7(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 7') - patch_play(this_play.id, pa=1, ab=1, outs=1) - advance_runners(this_play.id, num_bases=1, only_forced=True) - return None - - -def gb_result_8(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 8') - return gb_result_7(this_play) - - -def gb_result_9(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 9') - return gb_result_7(this_play) - - -def gb_result_10(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 10') - num_outs = 2 if this_play.starting_outs <= 1 else 1 - patch_play(this_play.id, pa=1, ab=1, outs=num_outs, on_third_final=False) - advance_one_runner(this_play.id, from_base=2, num_bases=1) - advance_one_runner(this_play.id, from_base=1, num_bases=1) - - return None - - -def gb_result_11(this_play: StratPlay) -> Optional[int]: - logging.info(f'GB 11') - patch_play(this_play.id, pa=1, ab=1, outs=1, on_first_final=2, on_second_final=3, on_third_final=False) - return 1 - - -async def gb_decide(this_play: StratPlay, interaction: discord.Interaction, runner: dict, from_base: int): - logging.info(f'GB Decide') - ai_is_batting = ai_batting(this_play.game, this_play) - ai_manager = get_manager(this_play.game) - bases = ['first', 'second', 'third'] - - ai_hint = '' - if this_play.inning_half == 'Top': - run_diff = this_play.away_score - this_play.home_score - else: - run_diff = this_play.home_score - this_play.away_score - - if this_play.game.ai_team and ai_is_batting: - ai_hint = f'*The runner will ' \ - f'{ai_manager.gb_decide_advance(this_play.starting_outs, run_lead=run_diff)}*' - - view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') - question = await interaction.channel.send( - f'Was {runner["p_name"]} sent from {bases[from_base - 1]} on the play?\n\n{ai_hint}', view=view - ) - await view.wait() - - if view.value: - await question.delete() - - ai_hint = '' - if not ai_is_batting: - ai_hint = f'*The defense will ' \ - f'{ai_manager.gb_decide_throw(this_play.starting_outs, run_lead=run_diff)}*' - - view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') - question = await interaction.channel.send( - f'Is the defense throwing for the lead runner?\n\n{ai_hint}', view=view - ) - await view.wait() - - if view.value: - await question.delete() - view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') - question = await interaction.channel.send( - content=f'Was {runner["p_name"]} thrown out?', view=view - ) - await view.wait() - - if view.value: - await question.delete() - if this_play.on_base_code == 2: - patch_play(this_play.id, on_second_final=False, pa=1, ab=1, outs=1) - elif this_play.on_base_code == 3: - patch_play(this_play.id, on_third_final=False, pa=1, ab=1, outs=1) - elif this_play.on_base_code == 6: - patch_play(this_play.id, on_third_final=False, on_second_final=3, pa=1, ab=1, outs=1) - - else: - await question.delete() - patch_play(this_play.id, pa=1, ab=1) - advance_runners(this_play.id, num_bases=1) - - return 1 - - else: - await question.delete() - advance_runners(this_play.id, num_bases=1) - patch_play(this_play.id, pa=1, ab=1, outs=1) - return None - - else: - await question.delete() - advance_runners(this_play.id, num_bases=0) - patch_play(this_play.id, pa=1, ab=1, outs=1) - return None - - -async def gb_result_12(this_play: StratPlay, defender: Literal['1b-2b', '3b', 'ss-p-c'], - interaction: discord.Interaction) -> Optional[int]: - logging.info(f'GB 12') - if defender == '1b-2b': - return gb_result_3(this_play) - elif defender == '3b': - return gb_result_1(this_play) - else: - return await gb_decide( - this_play, interaction, await get_player(this_play.game, this_play.on_second), from_base=2 - ) - - -def gb_result_13(this_play: StratPlay, defender: Literal['c-3b', 'else']) -> Optional[int]: - logging.info(f'GB 13') - if defender == 'c-3b': - num_outs = 2 if this_play.starting_outs <= 1 else 1 - patch_play(this_play.id, on_second_final=False, on_first_final=False, pa=1, ab=1, outs=num_outs) - return 1 - else: - return gb_result_2(this_play) - +import datetime +import logging +import random + +import discord + +from db_calls_gameplay import StratGame, StratPlay, StratLineup, StratManagerAi, patch_play, advance_runners, \ + complete_play, get_team_lineups, get_or_create_bullpen, get_player, get_sheets, make_sub, get_one_lineup, \ + advance_one_runner, get_one_lineup, ai_batting, get_manager +from db_calls import db_get, db_post +from helpers import Pagination, get_team_embed, image_embed, Confirm +from typing import Literal, Optional + + +def single_onestar(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=1) + patch_play(this_play.id, pa=1, ab=1, hit=1) + if comp_play: + complete_play(this_play.id, batter_to_base=1) + + +def single_wellhit(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=2) + patch_play(this_play.id, pa=1, ab=1, hit=1) + if comp_play: + complete_play(this_play.id, batter_to_base=1) + + +def double_twostar(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=2) + patch_play(this_play.id, pa=1, ab=1, hit=1, double=1) + if comp_play: + complete_play(this_play.id, batter_to_base=2) + + +def double_threestar(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=3) + patch_play(this_play.id, pa=1, ab=1, hit=1, double=1) + if comp_play: + complete_play(this_play.id, batter_to_base=2) + + +def triple(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=3) + patch_play(this_play.id, pa=1, ab=1, hit=1, triple=1) + if comp_play: + complete_play(this_play.id, batter_to_base=3) + + +async def next_pitcher(this_play: StratPlay, ai_team, bot): + used_pitchers = await get_team_lineups( + game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, pitchers_only=True, as_string=False + ) + logging.debug(f'\n\nused_pitchers: {used_pitchers}') + + used_ids = [x.card_id for x in used_pitchers] + logging.debug(f'used_ids: {used_ids}') + bullpen = get_or_create_bullpen(ai_team, bot) + logging.debug(f'bullpen: {bullpen}') + + if this_play.game.short_game: + if this_play.inning_num == 1: + relievers = [bullpen.middle_two_id, bullpen.middle_one_id, bullpen.middle_three_id] + elif this_play.inning_num == 2: + relievers = [bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] + elif this_play.inning_num == 3: + relievers = [ + bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, + bullpen.middle_three_id + ] + else: + relievers = [ + bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id, + bullpen.middle_three_id, bullpen.middle_two_id, bullpen.middle_one_id, bullpen.setup_id, + bullpen.closer_id + ] + else: + if this_play.inning_num < 6: + relievers = [bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id] + elif this_play.inning_num == 6: + relievers = [bullpen.middle_two_id, bullpen.middle_one_id, bullpen.middle_three_id] + elif this_play.inning_num == 7: + relievers = [bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] + elif this_play.inning_num == 8: + relievers = [bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] + elif this_play.inning_num == 9: + relievers = [ + bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id + ] + else: + relievers = [ + bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id, + bullpen.middle_three_id, bullpen.middle_two_id, bullpen.middle_one_id, bullpen.setup_id, bullpen.closer_id + ] + + for arm in relievers: + if arm and arm not in used_ids: + return {'player': await get_player(this_play.game, {'card_id': arm}), 'card_id': arm} + + relievers = [ + bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id, + bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id + ] + + for arm in relievers: + if arm and arm not in used_ids: + return {'player': await get_player(this_play.game, {'card_id': arm}), 'card_id': arm} + + return {"p_name": "Backup Infielder"} + + +def starting_pitcher(ai_team, bot, is_home): + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(ai_team['gsheet']) + r_sheet = this_sheet.worksheet_by_title('My Rosters') + + rotation_range = f'I4:I8' + raw_cells = r_sheet.range(rotation_range) + d_100 = random.randint(1, 100) + logging.info(f'raw_cells: {raw_cells} / d_100: {d_100}') + + if is_home: + if d_100 <= 30: + return raw_cells[0][0].value + elif d_100 <= 55: + return raw_cells[1][0].value + elif d_100 <= 75: + return raw_cells[2][0].value + elif d_100 <= 90: + return raw_cells[3][0].value + else: + return raw_cells[4][0].value + else: + if d_100 <= 50: + return raw_cells[0][0].value + elif d_100 <= 75: + return raw_cells[1][0].value + elif d_100 <= 85: + return raw_cells[2][0].value + elif d_100 <= 95: + return raw_cells[3][0].value + else: + return raw_cells[4][0].value + + +# def replace_pitcher(ai_team, this_play, pitcher_card_id): +# this_card = db_get(f'cards', object_id=int(pitcher_card_id)) +# new_lineup = { +# 'game_id': this_play.game.id, +# 'team_id': ai_team['id'], +# 'player_id': this_card['player']['player_id'], +# 'card_id': pitcher_card_id, +# 'position': 'P', +# 'batting_order': 10, +# 'after_play': this_play.play_num - 1 +# } +# make_sub(new_lineup) + + +def get_pitcher(this_game: StratGame, this_play: StratPlay): + p_team_id = this_game.away_team_id + if this_play.inning_half == 'top': + p_team_id = this_game.home_team_id + return get_one_lineup(this_game.id, team_id=p_team_id, position='P', active=True) + + +async def legal_check(card_ids: list, difficulty_name: str): + all_ids = [str(x) for x in card_ids] + l_string = "&card_id=".join(all_ids) + legality = await db_post(f'cards/legal-check/{difficulty_name}?card_id={l_string}') + + r_val = {'legal': True, 'error_string': None} + if legality['count'] > 0: + r_val['legal'] = False + r_val['error_string'] = "\n- ".join(legality['bad_cards']) + + return r_val + + +async def show_outfield_cards(interaction: discord.Interaction, this_play: StratPlay): + lf_player = await get_player(this_play.game, as_dict=False, lineup_member=get_one_lineup( + this_play.game.id, team_id=this_play.pitcher.team_id, position='LF', active=True + )) + cf_player = await get_player(this_play.game, as_dict=False, lineup_member=get_one_lineup( + this_play.game.id, team_id=this_play.pitcher.team_id, position='CF', active=True + )) + rf_player = await get_player(this_play.game, as_dict=False, lineup_member=get_one_lineup( + this_play.game.id, team_id=this_play.pitcher.team_id, position='RF', active=True + )) + this_team = await db_get('teams', object_id=this_play.pitcher.team_id) + logging.debug(f'lf: {lf_player}\n\ncf: {cf_player}\n\nrf: {rf_player}\n\nteam: {this_team["lname"]}') + + view = Pagination([interaction.user], timeout=6) + view.left_button.label = f'Left Fielder' + view.left_button.style = discord.ButtonStyle.secondary + lf_embed = image_embed( + lf_player.image, f'{this_team["sname"]} LF', this_team['color'], lf_player.p_name, this_team['lname'], + this_team['logo']) + + view.cancel_button.label = f'Center Fielder' + view.cancel_button.style = discord.ButtonStyle.blurple + cf_embed = image_embed( + cf_player.image, f'{this_team["sname"]} CF', this_team['color'], cf_player.p_name, this_team['lname'], + this_team['logo']) + + view.right_button.label = f'Right Fielder' + view.right_button.style = discord.ButtonStyle.secondary + rf_embed = image_embed( + rf_player.image, f'{this_team["sname"]} RF', this_team['color'], rf_player.p_name, this_team['lname'], + this_team['logo']) + + page_num = 1 + embeds = [lf_embed, cf_embed, rf_embed] + + msg = await interaction.channel.send(embed=embeds[page_num], view=view) + + await view.wait() + + if view.value: + if view.value == 'left': + page_num = 0 + if view.value == 'cancel': + page_num = 1 + if view.value == 'right': + page_num = 2 + else: + await msg.edit(content=None, embed=embeds[page_num], view=None) + + view.value = None + + if page_num == 0: + view.left_button.style = discord.ButtonStyle.blurple + view.cancel_button.style = discord.ButtonStyle.secondary + view.right_button.style = discord.ButtonStyle.secondary + if page_num == 1: + view.left_button.style = discord.ButtonStyle.secondary + view.cancel_button.style = discord.ButtonStyle.blurple + view.right_button.style = discord.ButtonStyle.secondary + if page_num == 2: + view.left_button.style = discord.ButtonStyle.secondary + view.cancel_button.style = discord.ButtonStyle.secondary + view.right_button.style = discord.ButtonStyle.blurple + + view.left_button.disabled = True + view.cancel_button.disabled = True + view.right_button.disabled = True + + await msg.edit(content=None, embed=embeds[page_num], view=view) + return [lf_player, cf_player, rf_player][page_num] + + +def runner_on_first(this_play: StratPlay): + if this_play.on_base_code in [1, 4, 5, 7]: + return True + return False + + +def runner_on_second(this_play: StratPlay): + if this_play.on_base_code in [2, 4, 6, 7]: + return True + return False + + +def runner_on_third(this_play: StratPlay): + if this_play.on_base_code in [3, 5, 6, 7]: + return True + return False + + +def gb_result_1(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 1') + advance_runners(this_play.id, num_bases=0) + patch_play(this_play.id, pa=1, ab=1, outs=1) + return None + + +def gb_result_2(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 2') + num_outs = 2 if this_play.starting_outs <= 1 else 1 + + patch_play(this_play.id, pa=1, ab=1, outs=num_outs, on_first_final=False) + if num_outs + this_play.starting_outs < 3: + if runner_on_second(this_play): + advance_one_runner(this_play.id, from_base=2, num_bases=1) + if runner_on_third(this_play): + advance_one_runner(this_play.id, from_base=3, num_bases=1) + + return None + + +def gb_result_3(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 3') + if this_play.starting_outs < 2: + advance_runners(this_play.id, num_bases=1) + patch_play(this_play.id, pa=1, ab=1, outs=1) + return None + + +def gb_result_4(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 4') + patch_play(this_play.id, pa=1, ab=1, outs=1, on_first_final=False) + if this_play.starting_outs < 2: + if runner_on_second(this_play): + advance_one_runner(this_play.id, from_base=2, num_bases=1) + if runner_on_third(this_play): + advance_one_runner(this_play.id, from_base=3, num_bases=1) + + return 1 + + +def gb_result_5(this_play: StratPlay, to_mif: bool) -> Optional[int]: + logging.info(f'GB 5') + patch_play(this_play.id, pa=1, ab=1, outs=1) + if to_mif: + gb_result_3(this_play) + else: + gb_result_1(this_play) + + return None + + +def gb_result_6(this_play: StratPlay, to_right_side: bool) -> Optional[int]: + logging.info(f'GB 6') + patch_play(this_play.id, pa=1, ab=1, outs=1) + if to_right_side: + gb_result_3(this_play) + else: + gb_result_1(this_play) + + return None + + +def gb_result_7(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 7') + patch_play(this_play.id, pa=1, ab=1, outs=1) + advance_runners(this_play.id, num_bases=1, only_forced=True) + return None + + +def gb_result_8(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 8') + return gb_result_7(this_play) + + +def gb_result_9(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 9') + return gb_result_7(this_play) + + +def gb_result_10(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 10') + num_outs = 2 if this_play.starting_outs <= 1 else 1 + patch_play(this_play.id, pa=1, ab=1, outs=num_outs, on_third_final=False) + advance_one_runner(this_play.id, from_base=2, num_bases=1) + advance_one_runner(this_play.id, from_base=1, num_bases=1) + + return None + + +def gb_result_11(this_play: StratPlay) -> Optional[int]: + logging.info(f'GB 11') + patch_play(this_play.id, pa=1, ab=1, outs=1, on_first_final=2, on_second_final=3, on_third_final=False) + return 1 + + +async def gb_decide(this_play: StratPlay, interaction: discord.Interaction, runner: dict, from_base: int): + logging.info(f'GB Decide') + ai_is_batting = ai_batting(this_play.game, this_play) + ai_manager = get_manager(this_play.game) + bases = ['first', 'second', 'third'] + + ai_hint = '' + if this_play.inning_half == 'Top': + run_diff = this_play.away_score - this_play.home_score + else: + run_diff = this_play.home_score - this_play.away_score + + if this_play.game.ai_team and ai_is_batting: + ai_hint = f'*The runner will ' \ + f'{ai_manager.gb_decide_advance(this_play.starting_outs, run_lead=run_diff)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["p_name"]} sent from {bases[from_base - 1]} on the play?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + + ai_hint = '' + if not ai_is_batting: + ai_hint = f'*The defense will ' \ + f'{ai_manager.gb_decide_throw(this_play.starting_outs, run_lead=run_diff)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is the defense throwing for the lead runner?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {runner["p_name"]} thrown out?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if this_play.on_base_code == 2: + patch_play(this_play.id, on_second_final=False, pa=1, ab=1, outs=1) + elif this_play.on_base_code == 3: + patch_play(this_play.id, on_third_final=False, pa=1, ab=1, outs=1) + elif this_play.on_base_code == 6: + patch_play(this_play.id, on_third_final=False, on_second_final=3, pa=1, ab=1, outs=1) + + else: + await question.delete() + patch_play(this_play.id, pa=1, ab=1) + advance_runners(this_play.id, num_bases=1) + + return 1 + + else: + await question.delete() + advance_runners(this_play.id, num_bases=1) + patch_play(this_play.id, pa=1, ab=1, outs=1) + return None + + else: + await question.delete() + advance_runners(this_play.id, num_bases=0) + patch_play(this_play.id, pa=1, ab=1, outs=1) + return None + + +async def gb_result_12(this_play: StratPlay, defender: Literal['1b-2b', '3b', 'ss-p-c'], + interaction: discord.Interaction) -> Optional[int]: + logging.info(f'GB 12') + if defender == '1b-2b': + return gb_result_3(this_play) + elif defender == '3b': + return gb_result_1(this_play) + else: + return await gb_decide( + this_play, interaction, await get_player(this_play.game, this_play.on_second), from_base=2 + ) + + +def gb_result_13(this_play: StratPlay, defender: Literal['c-3b', 'else']) -> Optional[int]: + logging.info(f'GB 13') + if defender == 'c-3b': + num_outs = 2 if this_play.starting_outs <= 1 else 1 + patch_play(this_play.id, on_second_final=False, on_first_final=False, pa=1, ab=1, outs=num_outs) + return 1 + else: + return gb_result_2(this_play) +