diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 2d42c87..354efc0 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -3,23 +3,27 @@ from typing import Literal import discord from discord import app_commands +from discord import SelectOption from discord.app_commands import Choice from discord.ext import commands, tasks import pygsheets +from sqlmodel import or_ + from api_calls import db_get -from command_logic.logic_gameplay import advance_runners, bunts, chaos, complete_game, doubles, flyballs, get_lineups_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, hit_by_pitch, homeruns, is_game_over, manual_end_game, popouts, show_defense_cards, singles, strikeouts, triples, undo_play, update_game_settings, walks +from command_logic.logic_gameplay import advance_runners, bunts, chaos, complete_game, doubles, flyballs, get_full_roster_from_sheets, get_lineups_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, hit_by_pitch, homeruns, is_game_over, manual_end_game, popouts, read_lineup, show_defense_cards, singles, starting_pitcher_dropdown_view, strikeouts, triples, undo_play, update_game_settings, walks from dice import ab_roll -from exceptions import GameNotFoundException, TeamNotFoundException, PlayNotFoundException, GameException, log_exception +from exceptions import GameNotFoundException, GoogleSheetsException, TeamNotFoundException, PlayNotFoundException, GameException, log_exception from helpers import DEFENSE_LITERAL, PD_PLAYERS_ROLE_NAME, get_channel, team_role, user_has_role, random_gif, random_from_list # from in_game import ai_manager from in_game.ai_manager import get_starting_pitcher, get_starting_lineup from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check from in_game.gameplay_models import Lineup, Play, Session, engine, player_description, select, Game -from in_game.gameplay_queries import get_and_cache_position, get_channel_game_or_none, get_active_games_by_team, get_game_lineups, get_team_or_none, get_card_or_none +from in_game.gameplay_queries import get_and_cache_position, get_available_pitchers, get_channel_game_or_none, get_active_games_by_team, get_game_lineups, get_team_or_none, get_card_or_none from utilities.buttons import Confirm, ScorebugButtons, ask_confirm +from utilities.dropdown import DropdownView, SelectStartingPitcher CLASSIC_EMBED = True @@ -122,19 +126,22 @@ class Gameplay(commands.Cog): group_new_game = app_commands.Group(name='new-game', description='Start a new baseball game') @group_new_game.command(name='mlb-campaign', description='Start a new MLB campaign game against an AI') - @app_commands.describe( - sp_card_id='Light gray number to the left of the pitcher\'s name on your depth chart' + @app_commands.choices( + league=[ + Choice(value='minor-league', name='Minor League'), + Choice(value='flashback', name='Flashback'), + Choice(value='major-league', name='Major League'), + Choice(value='hall-of-fame', name='Hall of Fame') + ], + roster=[ + Choice(value='1', name='Primary'), + Choice(value='2', name='Secondary'), + Choice(value='3', name='Ranked') + ] ) - @app_commands.choices(league=[ - Choice(value='minor-league', name='Minor League'), - Choice(value='flashback', name='Flashback'), - Choice(value='major-league', name='Major League'), - Choice(value='hall-of-fame', name='Hall of Fame') - ]) @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) async def new_game_mlb_campaign_command( - self, interaction: discord.Interaction, league: Choice[str], away_team_abbrev: str, home_team_abbrev: str, sp_card_id: int - ): + self, interaction: discord.Interaction, league: Choice[str], away_team_abbrev: str, home_team_abbrev: str, roster: Choice[str]): await interaction.response.defer() with Session(engine) as session: @@ -212,6 +219,8 @@ class Gameplay(commands.Cog): this_game = Game( away_team_id=away_team.id, home_team_id=home_team.id, + away_roster_id=69 if away_team.is_ai else int(roster.value), + home_roster_id=69 if home_team.is_ai else int(roster.value), channel_id=interaction.channel_id, season=current['season'], week=week_num, @@ -223,44 +232,9 @@ class Gameplay(commands.Cog): game_info_log = f'{league.name} game between {away_team.description} and {home_team.description} / first message: {this_game.first_message}' logger.info(game_info_log) - # Get Human SP card - human_sp_card = await get_card_or_none(session, card_id=sp_card_id) - if human_sp_card is None: - await interaction.channel.send( - f'Uh oh. I can\'t find a card with ID {sp_card_id}. Will you double check that before we get started?' - ) - return - - if human_sp_card.team_id != human_team.id: - logger.error(f'Card_id {sp_card_id} does not belong to {human_team.abbrev} in Game {this_game.id}') - await interaction.channel.send( - f'Uh oh. Card ID {sp_card_id} is {human_sp_card.player.name} and belongs to {human_sp_card.team.sname}. Will you double check that before we get started?' - ) - return - - await get_and_cache_position(session, human_sp_card, 'P') - - legal_data = await legal_check([sp_card_id], difficulty_name=league.value) - if not legal_data['legal']: - await interaction.edit_original_response( - content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.name_with_desc} is not legal in {league.name} games. You can start a new game once you pick a new SP.' - ) - return - - human_sp_lineup = Lineup( - team_id=human_team.id, - player_id=human_sp_card.player.id, - card_id=sp_card_id, - position='P', - batting_order=10, - is_fatigued=False, - game=this_game - ) - # session.add(human_sp_lineup) - # Get AI SP await interaction.edit_original_response( - content=f'{ai_team.gmname} is looking for a SP to counter {human_sp_card.player.name}...' + content=f'{ai_team.gmname} is looking for a Starting Pitcher...' ) ai_sp_lineup = await get_starting_pitcher( session, @@ -282,9 +256,17 @@ class Gameplay(commands.Cog): team=ai_team, game=this_game, league_name=league.value, - sp_name=human_sp_card.player.name + sp_name=ai_sp_lineup.player.name ) + # Check for last game settings + g_query = session.exec(select(Game).where(or_(Game.home_team == human_team, Game.away_team == human_team)).order_by(Game.id.desc()).limit(1)).all() + + if len(g_query) > 0: + last_game = g_query[0] + this_game.auto_roll = last_game.auto_roll + this_game.roll_buttons = last_game.roll_buttons + # Commit game and lineups session.add(this_game) session.commit() @@ -306,6 +288,13 @@ class Gameplay(commands.Cog): value=this_game.team_lineup(session, ai_team) ) + # Get pitchers from rosterlinks + done = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, human_team, roster.value) + logger.info(f'done: {done}') + if done: + sp_view = starting_pitcher_dropdown_view(session, this_game, human_team) + await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view) + await final_message.edit( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', @@ -347,22 +336,16 @@ class Gameplay(commands.Cog): @app_commands.command(name='read-lineup', description='Import a saved lineup for this channel\'s PD game.') @app_commands.describe( - roster='Which roster to pull from your sheet?', lineup='Which handedness lineup are you using?' ) @app_commands.choices( - roster=[ - Choice(value='1', name='Primary'), - Choice(value='2', name='Secondary'), - Choice(value='3', name='Ranked') - ], lineup=[ Choice(value='1', name='v Right'), Choice(value='2', name='v Left') ] ) @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) - async def read_lineup_command(self, interaction: discord.Interaction, roster: Choice[str], lineup: Choice[str]): + async def read_lineup_command(self, interaction: discord.Interaction, lineup: Choice[str]): await interaction.response.defer() with Session(engine) as session: @@ -373,48 +356,23 @@ class Gameplay(commands.Cog): ) return - lineup_team = this_game.away_team if this_game.ai_team == 'home' else this_game.home_team - if interaction.user.id != lineup_team.gmid: + this_team = this_game.away_team if this_game.ai_team == 'home' else this_game.home_team + if interaction.user.id != this_team.gmid: logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.') return - existing_lineups = get_game_lineups( - session=session, + this_play = await read_lineup( + session, + interaction, this_game=this_game, - specific_team=lineup_team, - is_active=True + lineup_team=this_team, + sheets_auth=self.sheets, + lineup=lineup, + league_name=this_game.game_type ) - if len(existing_lineups) > 1: - await interaction.edit_original_response( - f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitution` to make changes.' - ) - return - - await interaction.edit_original_response(content='Okay, let\'s put this lineup card together...') - - if this_game.away_team == lineup_team: - this_game.away_roster_id = int(roster.value) - else: - this_game.home_roster_id = int(roster.value) - - session.add(this_game) - - human_lineups = await get_lineups_from_sheets(session, self.sheets, this_game, this_team=lineup_team, lineup_num=lineup.name, roster_num=int(roster.value)) - - await interaction.edit_original_response(content='Heard from sheets, pulling in scouting data...') - - for batter in human_lineups: - session.add(batter) - - session.commit() - - for batter in human_lineups: - if batter.position != 'DH': - await get_and_cache_position(session, batter.card, batter.position) - - this_play = this_game.initialize_play(session) - await self.post_play(session, interaction, this_play) + if this_play is not None: + await self.post_play(session, interaction, this_play) @app_commands.command(name='gamestate', description='Post the current game state') async def gamestate_command(self, interaction: discord.Interaction, include_lineups: bool = False): @@ -585,7 +543,6 @@ class Gameplay(commands.Cog): await self.complete_and_post_play(session, interaction, this_play) - @group_log.command(name='bunt', description='Bunts: sacrifice, bad, popout, double-play, defense') async def log_sac_bunt(self, interaction: discord.Interaction, bunt_type: Literal['sacrifice', 'bad', 'popout', 'double-play', 'defense']): with Session(engine) as session: diff --git a/cogs/owner.py b/cogs/owner.py index 850eea3..8e95a88 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -92,47 +92,46 @@ class Owner(commands.Cog): @commands.is_owner() async def sync(self, ctx: Context, guilds: Greedy[Object], spec: Optional[Literal['~', "*", '!']] = None) -> None: """ - !sync -> global sync - !sync ~ -> sync current guild - !sync * -> copies all global app commands to current guild and syncs - !sync id_1 id_2 -> syncs guilds with id 1 and 2 + !sync + This takes all global commands within the CommandTree and sends them to Discord. (see CommandTree for more info.) + !sync ~ + This will sync all guild commands for the current contexts guild. + !sync * + This command copies all global commands to the current guild (within the CommandTree) and syncs. + !sync ^ + This command will remove all guild commands from the CommandTree and syncs, which effectively removes all commands from the guild. + !sync 123 456 789 + This command will sync the 3 guild ids we passed: 123, 456 and 789. Only their guilds and guild-bound commands. """ logger.info(f'{ctx.author.name} has initiated a sync from guild ID {ctx.guild.id}') if not guilds: if spec == "~": - fmt = await ctx.bot.tree.sync(guild=ctx.guild) + synced = await ctx.bot.tree.sync(guild=ctx.guild) elif spec == "*": ctx.bot.tree.copy_global_to(guild=ctx.guild) - fmt = await ctx.bot.tree.sync(guild=ctx.guild) - elif spec == '!': - fmt = await ctx.bot.tree.sync() - await ctx.send(f'Synced {len(fmt)} commands globally.') - - if len(fmt) > 0: - ctx.bot.tree.copy_global_to(guild=ctx.guild) - fmt = await ctx.bot.tree.sync(guild=ctx.guild) - await ctx.send(f'Synced global commands to this guild.') - - ctx.bot.tree.clear_commands(guild=ctx.guild) - await ctx.send(f'Cleared all local commands.') + synced = await ctx.bot.tree.sync(guild=ctx.guild) + elif spec == "^": + ctx.bot.tree.clear_commands(guild=ctx.guild) + await ctx.bot.tree.sync(guild=ctx.guild) + synced = [] else: - fmt = await ctx.bot.tree.sync() + synced = await ctx.bot.tree.sync() await ctx.send( - f"Synced {len(fmt)} commands {'globally' if spec is None else 'to the current guild.'}" + f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}" ) return - fmt = 0 + ret = 0 for guild in guilds: try: await ctx.bot.tree.sync(guild=guild) except discord.HTTPException: pass else: - fmt += 1 + ret += 1 - await ctx.send(f"Synced the tree to {fmt}/{len(guilds)} guilds.") + await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.") async def setup(bot): diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index d1733f0..94af853 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -2,6 +2,8 @@ import asyncio import logging import discord +from discord import SelectOption +from discord.app_commands import Choice import pandas as pd from sqlmodel import Session, select, func from sqlalchemy import delete @@ -11,10 +13,10 @@ from api_calls import db_delete, db_get, db_post from exceptions import * from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel from in_game.game_helpers import legal_check -from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, Team, Play -from in_game.gameplay_queries import get_and_cache_position, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards +from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, Team, Play +from in_game.gameplay_queries import get_and_cache_position, get_available_pitchers, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_game_lineups, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards from utilities.buttons import ButtonOptions, Confirm, ask_confirm -from utilities.dropdown import DropdownView, SelectViewDefense +from utilities.dropdown import DropdownView, SelectStartingPitcher, SelectViewDefense from utilities.embeds import image_embed from utilities.pages import Pagination @@ -226,6 +228,63 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo return embed +def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team): + pitchers = get_available_pitchers(session, this_game, human_team, sort='starter-desc') + logger.info(f'sorted pitchers: {pitchers}') + sp_selection = SelectStartingPitcher( + this_game=this_game, + this_team=human_team, + session=session, + league_name=this_game.game_type, + options=[SelectOption(label=f'{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})', value=x.id) for x in pitchers], + placeholder='Select your starting pitcher' + ) + return DropdownView(dropdown_objects=[sp_selection]) + + +async def read_lineup(session: Session, interaction: discord.Interaction, this_game: Game, lineup_team: Team, sheets_auth, lineup: Choice[str], league_name: str): + """ + Commits lineups and rosterlinks + """ + existing_lineups = get_game_lineups( + session=session, + this_game=this_game, + specific_team=lineup_team, + is_active=True + ) + if len(existing_lineups) > 1: + await interaction.edit_original_response( + f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitution` to make changes.' + ) + return + + await interaction.edit_original_response(content='Okay, let\'s put this lineup card together...') + + session.add(this_game) + + human_lineups = await get_lineups_from_sheets(session, sheets_auth, this_game, this_team=lineup_team, lineup_num=lineup.name, roster_num=this_game.away_roster_id if this_game.is_ai else this_game.home_roster_id) + + await interaction.edit_original_response(content='Heard from sheets, pulling in scouting data...') + + legal_data = await legal_check([lineup.card.id for lineup in human_lineups], difficulty_name=league_name) + if not legal_data['legal']: + await interaction.edit_original_response( + content=f'It looks like this is a Ranked Legal game and {legal_data["error_string"]} is not legal in {league_name} games. You can start a new game once you update this lineup.' + ) + return None + + for batter in human_lineups: + session.add(batter) + + session.commit() + + for batter in human_lineups: + if batter.position != 'DH': + await get_and_cache_position(session, batter.card, batter.position) + + return this_game.initialize_play(session) + + def get_obc(on_first = None, on_second = None, on_third = None) -> int: if on_third is not None: if on_second is not None: @@ -415,7 +474,7 @@ def complete_play(session:Session, this_play: Play): async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, this_team: Team, lineup_num: int, roster_num: int) -> list[Lineup]: - logger.debug(f'sheets: {sheets}') + logger.debug(f'get_lineups_from_sheets - sheets: {sheets}') this_sheet = sheets.open_by_key(this_team.gsheet) logger.debug(f'this_sheet: {this_sheet}') @@ -446,7 +505,7 @@ async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, thi logger.debug(f'lineup_cells: {lineup_cells}') except ValueError as e: logger.error(f'Could not pull roster for {this_team.abbrev}: {e}') - raise ValueError(f'Uh oh. Looks like your roster might not be saved. I am reading blanks when I try to get the card IDs') + log_exception(GoogleSheetsException, f'Uh oh. Looks like your lineup might not be saved. I am reading blanks when I try to get the card IDs') all_lineups = [] all_pos = [] @@ -491,10 +550,60 @@ async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, thi return all_lineups +async def get_full_roster_from_sheets(session: Session, interaction: discord.Interaction, sheets, this_game: Game, this_team: Team, roster_num: int) -> list[RosterLink]: + """ + Commits roster links + """ + logger.debug(f'get_full_roster_from_sheets - sheets: {sheets}') + + this_sheet = sheets.open_by_key(this_team.gsheet) + + this_sheet = sheets.open_by_key(this_team.gsheet) + logger.debug(f'this_sheet: {this_sheet}') + + r_sheet = this_sheet.worksheet_by_title('My Rosters') + logger.debug(f'r_sheet: {r_sheet}') + + if roster_num == 1: + l_range = 'B3:B28' + elif roster_num == 2: + l_range = 'B29:B54' + else: + l_range = 'B55:B80' + + roster_message = await interaction.channel.send(content='I\'m diving into Sheets - wish me luck.') + + logger.info(f'l_range: {l_range}') + raw_cells = r_sheet.range(l_range) + logger.info(f'raw_cells: {raw_cells}') + + await roster_message.edit(content='Got your roster, now to find these cards in your collection...') + + try: + card_ids = [row[0].value for row in raw_cells] + logger.info(f'card_ids: {card_ids}') + except ValueError as e: + logger.error(f'Could not pull roster for {this_team.abbrev}: {e}') + log_exception(GoogleSheetsException, f'Uh oh. Looks like your roster might not be saved. I am reading blanks when I try to get the card IDs') + + for x in card_ids: + this_card = await get_card_or_none(session, card_id=x) + session.add(RosterLink( + game=this_game, + card=this_card, + team=this_team + )) + + session.commit() + await roster_message.edit(content='Your roster is logged and scouting data is available.') + return session.exec(select(RosterLink).where(RosterLink.game == this_game, RosterLink.team == this_team)).all() + + async def checks_log_interaction(session: Session, interaction: discord.Interaction, command_name: str) -> tuple[Game, Team, Play]: """ Commits this_play """ + logger.info(f'log interaction checks for {interaction.user.name} in channel {interaction.channel.name}') await interaction.response.defer(thinking=True) this_game = get_channel_game_or_none(session, interaction.channel_id) if this_game is None: diff --git a/exceptions.py b/exceptions.py index 845a886..1b62ab8 100644 --- a/exceptions.py +++ b/exceptions.py @@ -31,6 +31,10 @@ class CardLegalityException(GameException): pass +class CardNotFoundException(GameException): + pass + + class GameNotFoundException(GameException): pass @@ -65,3 +69,7 @@ class MultipleHumanTeamsException(GameException): class NoHumanTeamsException(GameException): pass + + +class GoogleSheetsException(GameException): + pass diff --git a/in_game/gameplay_models.py b/in_game/gameplay_models.py index 29a59c8..1df3ac0 100644 --- a/in_game/gameplay_models.py +++ b/in_game/gameplay_models.py @@ -47,6 +47,16 @@ class GameCardsetLink(SQLModel, table=True): cardset: 'Cardset' = Relationship(back_populates='game_links') +class RosterLink(SQLModel, table=True): + game_id: int | None = Field(default=None, foreign_key='game.id', primary_key=True) + card_id: int | None = Field(default=None, foreign_key='card.id', primary_key=True) + team_id: int = Field(index=True, foreign_key='team.id') + + game: 'Game' = Relationship(back_populates='roster_links') + card: 'Card' = Relationship() + team: 'Team' = Relationship() + + class TeamBase(SQLModel): id: int = Field(primary_key=True) abbrev: str = Field(index=True) @@ -110,6 +120,7 @@ class Game(SQLModel, table=True): auto_roll: bool | None = Field(default=False) cardset_links: list[GameCardsetLink] = Relationship(back_populates='game', cascade_delete=True) + roster_links: list[RosterLink] = Relationship(back_populates='game', cascade_delete=True) away_team: Team = Relationship( # back_populates='away_games', # sa_relationship_kwargs={ diff --git a/in_game/gameplay_queries.py b/in_game/gameplay_queries.py index 52bab79..e1ab093 100644 --- a/in_game/gameplay_queries.py +++ b/in_game/gameplay_queries.py @@ -6,7 +6,8 @@ from typing import Literal import pydantic from sqlalchemy import func from api_calls import db_get, db_post -from in_game.gameplay_models import CACHE_LIMIT, BatterScouting, BatterScoutingBase, BattingCard, BattingCardBase, BattingRatings, BattingRatingsBase, Card, CardBase, Lineup, PitcherScouting, PitchingCard, PitchingCardBase, PitchingRatings, PitchingRatingsBase, Player, PlayerBase, PositionRating, PositionRatingBase, Session, Team, TeamBase, select, or_, Game, Play +from sqlmodel import col +from in_game.gameplay_models import CACHE_LIMIT, BatterScouting, BatterScoutingBase, BattingCard, BattingCardBase, BattingRatings, BattingRatingsBase, Card, CardBase, Lineup, PitcherScouting, PitchingCard, PitchingCardBase, PitchingRatings, PitchingRatingsBase, Player, PlayerBase, PositionRating, PositionRatingBase, RosterLink, Session, Team, TeamBase, select, or_, Game, Play from exceptions import DatabaseError, PositionNotFoundException, log_exception, PlayNotFoundException @@ -797,3 +798,36 @@ async def post_game_rewards(session: Session, winning_team: Team, losing_team: T await db_post(f'teams/{losing_team.id}/money/{loss_reward["money"]}') return win_string, loss_string + + +def get_available_subs(session: Session, this_game: Game, this_team: Team) -> list[Card]: + team_lineups = session.exec(select(Lineup).where(Lineup.game == this_game, Lineup.team == this_team)).all() + used_card_ids = [x.card.id for x in team_lineups] + + all_roster_links = session.exec(select(RosterLink).where(RosterLink.game == this_game, RosterLink.team == this_team)).all() + + return [x.card for x in all_roster_links if x.card_id not in used_card_ids] + + +def get_available_pitchers(session: Session, this_game: Game, this_team: Team, sort: Literal['starter-desc', 'closer-desc'] = 'closer-desc') -> list[Card]: + logger.info(f'getting available pitchers for team {this_team.id} in game {this_game.id}') + all_subs = get_available_subs(session, this_game, this_team) + logger.info(f'all_subs: {all_subs}') + pitchers = [x for x in all_subs if x.pitcherscouting is not None] + logger.info(f'pitchers: {pitchers}') + + def sort_by_pow(this_card: Card): + s_pow = this_card.pitcherscouting.pitchingcard.starter_rating + r_pow = this_card.pitcherscouting.pitchingcard.relief_rating + c_pow = this_card.pitcherscouting.pitchingcard.closer_rating + + if sort == 'starter-desc': + r_val = (s_pow * 3) + r_pow + else: + r_val = (c_pow * 10) - (r_pow * 5) - (s_pow * 3) + + return r_val + + pitchers.sort(key=sort_by_pow, reverse=True) + + return pitchers diff --git a/tests/factory.py b/tests/factory.py index 85579ea..8a4f9b8 100644 --- a/tests/factory.py +++ b/tests/factory.py @@ -1,10 +1,10 @@ import datetime import pytest -from sqlmodel import Session, SQLModel, create_engine +from sqlmodel import Session, SQLModel, create_engine, select from sqlmodel.pool import StaticPool from typing import Literal -from in_game.gameplay_models import BatterScouting, BattingCard, BattingRatings, Card, Cardset, Game, GameCardsetLink, Lineup, ManagerAi, PitcherScouting, PitchingCard, PitchingRatings, Play, Team, Player +from in_game.gameplay_models import BatterScouting, BattingCard, BattingRatings, Card, Cardset, Game, GameCardsetLink, Lineup, ManagerAi, PitcherScouting, PitchingCard, PitchingRatings, Play, RosterLink, Team, Player @pytest.fixture(name='session') @@ -231,6 +231,30 @@ def session_fixture(): session.commit() + g1_t1_cards = session.exec(select(Card).where(Card.team_id == 31)).all() + g1_t2_cards = session.exec(select(Card).where(Card.team_id == 400)).all() + g2_t1_cards = session.exec(select(Card).where(Card.team_id == 69)).all() + g2_t2_cards = session.exec(select(Card).where(Card.team_id == 420)).all() + + for card in [*g1_t1_cards, *g1_t2_cards]: + session.add(RosterLink( + game_id=1, + card_id=card.id, + team_id=card.team_id + )) + for card in [*g2_t1_cards, *g2_t2_cards]: + session.add(RosterLink( + game_id=3, + card_id=card.id, + team_id=card.team_id + )) + # session.add(RosterLink( + # game_id=3, + # card_id=12, + # team_id=420 + # )) + + session.commit() all_ai = ManagerAi.create_ai(session) yield session diff --git a/tests/gameplay_models/test_game_model.py b/tests/gameplay_models/test_game_model.py index 2c8752c..c757651 100644 --- a/tests/gameplay_models/test_game_model.py +++ b/tests/gameplay_models/test_game_model.py @@ -109,19 +109,19 @@ def test_delete_game(session: Session): assert len(bad_links) == 0 -async def test_get_scorebug(session: Session): - game_1 = session.get(Game, 1) - # scorebug = game_1.get_scorebug_embed(session) - scorebug = await get_scorebug_embed(session, game_1) +# async def test_get_scorebug(session: Session): +# game_1 = session.get(Game, 1) +# # scorebug = game_1.get_scorebug_embed(session) +# scorebug = await get_scorebug_embed(session, game_1) - assert scorebug.title == 'CornBelters @ Black Bears - Minor League' - assert scorebug.color.value == int('a6ce39', 16) +# assert scorebug.title == 'CornBelters @ Black Bears - Minor League' +# assert scorebug.color.value == int('a6ce39', 16) - game_3 = session.get(Game, 3) - # scorebug = game_3.get_scorebug_embed(session) - scorebug = await get_scorebug_embed(session, game_3) +# game_3 = session.get(Game, 3) +# # scorebug = game_3.get_scorebug_embed(session) +# scorebug = await get_scorebug_embed(session, game_3) - assert '0 Outs' in scorebug.fields[0].value +# assert '0 Outs' in scorebug.fields[0].value def test_sum_function(session: Session): diff --git a/tests/gameplay_models/test_rosterlinks_model.py b/tests/gameplay_models/test_rosterlinks_model.py new file mode 100644 index 0000000..dc6236f --- /dev/null +++ b/tests/gameplay_models/test_rosterlinks_model.py @@ -0,0 +1,32 @@ +import pytest +from sqlmodel import Session, select, func + +from in_game.gameplay_models import Game, RosterLink +from in_game.gameplay_queries import get_available_subs +from tests.factory import session_fixture + +def test_get_rosterlinks(session: Session): + game_1 = session.get(Game, 1) + g1_links = session.exec(select(RosterLink).where(RosterLink.game == game_1)).all() + + assert len(g1_links) == 20 + + home_team = game_1.home_team + home_roster = session.exec(select(RosterLink).where(RosterLink.game == game_1, RosterLink.team == home_team)).all() + + assert len(home_roster) == 10 + + +def test_get_available_subs(session: Session): + this_game = session.get(Game, 3) + home_team = this_game.home_team + home_roster = session.exec(select(RosterLink).where(RosterLink.game == this_game, RosterLink.team == home_team)).all() + + assert len(home_roster) == 11 + + cards = get_available_subs(session, this_game, this_game.home_team) + + assert len(cards) == 1 + + + diff --git a/utilities/dropdown.py b/utilities/dropdown.py index 363c8be..0cb7cfa 100644 --- a/utilities/dropdown.py +++ b/utilities/dropdown.py @@ -1,9 +1,15 @@ +from typing import List import discord import logging +from discord import SelectOption +from discord.utils import MISSING from sqlmodel import Session -from in_game.gameplay_models import Lineup, Play +from exceptions import CardNotFoundException, log_exception +from in_game.game_helpers import legal_check +from in_game.gameplay_models import Game, Lineup, Play, Team +from in_game.gameplay_queries import get_and_cache_position, get_card_or_none logger = logging.getLogger('discord_app') @@ -84,3 +90,59 @@ class SelectViewDefense(discord.ui.Select): ) await interaction.response.edit_message(content=None, embed=self.embed, view=new_view) + + +class SelectStartingPitcher(discord.ui.Select): + def __init__(self, this_game: Game, this_team: Team, session: Session, league_name: str, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ...) -> None: + logger.info(f'Inside SelectStartingPitcher init function') + self.game = this_game + self.team = this_team + self.session = session + self.league_name = league_name + super().__init__(custom_id=custom_id, placeholder=placeholder, options=options) + + async def callback(self, interaction: discord.Interaction): + logger.info(f'SelectStartingPitcher - selection: {self.values[0]}') + + # Get Human SP card + human_sp_card = await get_card_or_none(self.session, card_id=self.values[0]) + if human_sp_card is None: + log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found') + + if human_sp_card.team_id != self.team.id: + logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}') + await interaction.channel.send( + f'Uh oh. Card ID {self.values[0]} is {human_sp_card.player.name} and belongs to {human_sp_card.team.sname}. Will you double check that before we get started?' + ) + return + + await get_and_cache_position(self.session, human_sp_card, 'P') + + legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name) + if not legal_data['legal']: + await interaction.edit_original_response( + content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.name_with_desc} is not legal in {self.league_name} games. You can start a new game once you pick a new SP.' + ) + return + + human_sp_lineup = Lineup( + team_id=self.team.id, + player_id=human_sp_card.player.id, + card_id=self.values[0], + position='P', + batting_order=10, + is_fatigued=False, + game=self.game + ) + self.session.add(human_sp_lineup) + self.session.commit() + + + await interaction.response.edit_message( + content=f'The {self.team.lname} are starting {human_sp_card.player.name_with_desc}', + view=None + ) + + + +