Added RosterLinks to remove card_id from setup process

Add SelectStartingPitcher dropdown
New .sync function
This commit is contained in:
Cal Corum 2024-11-23 19:53:48 -06:00
parent a4af7652fc
commit 073bd04b4b
10 changed files with 371 additions and 135 deletions

View File

@ -3,23 +3,27 @@ from typing import Literal
import discord import discord
from discord import app_commands from discord import app_commands
from discord import SelectOption
from discord.app_commands import Choice from discord.app_commands import Choice
from discord.ext import commands, tasks from discord.ext import commands, tasks
import pygsheets import pygsheets
from sqlmodel import or_
from api_calls import db_get 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 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 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 import ai_manager
from in_game.ai_manager import get_starting_pitcher, get_starting_lineup 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.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_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.buttons import Confirm, ScorebugButtons, ask_confirm
from utilities.dropdown import DropdownView, SelectStartingPitcher
CLASSIC_EMBED = True 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 = 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') @group_new_game.command(name='mlb-campaign', description='Start a new MLB campaign game against an AI')
@app_commands.describe( @app_commands.choices(
sp_card_id='Light gray number to the left of the pitcher\'s name on your depth chart' 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) @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME)
async def new_game_mlb_campaign_command( 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() await interaction.response.defer()
with Session(engine) as session: with Session(engine) as session:
@ -212,6 +219,8 @@ class Gameplay(commands.Cog):
this_game = Game( this_game = Game(
away_team_id=away_team.id, away_team_id=away_team.id,
home_team_id=home_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, channel_id=interaction.channel_id,
season=current['season'], season=current['season'],
week=week_num, 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}' 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) 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 # Get AI SP
await interaction.edit_original_response( 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( ai_sp_lineup = await get_starting_pitcher(
session, session,
@ -282,9 +256,17 @@ class Gameplay(commands.Cog):
team=ai_team, team=ai_team,
game=this_game, game=this_game,
league_name=league.value, 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 # Commit game and lineups
session.add(this_game) session.add(this_game)
session.commit() session.commit()
@ -306,6 +288,13 @@ class Gameplay(commands.Cog):
value=this_game.team_lineup(session, ai_team) 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( await final_message.edit(
content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' content=f'{away_role.mention} @ {home_role.mention} is set!\n\n'
f'Go ahead and set lineups with the `/read-lineup` command!', 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.command(name='read-lineup', description='Import a saved lineup for this channel\'s PD game.')
@app_commands.describe( @app_commands.describe(
roster='Which roster to pull from your sheet?',
lineup='Which handedness lineup are you using?' lineup='Which handedness lineup are you using?'
) )
@app_commands.choices( @app_commands.choices(
roster=[
Choice(value='1', name='Primary'),
Choice(value='2', name='Secondary'),
Choice(value='3', name='Ranked')
],
lineup=[ lineup=[
Choice(value='1', name='v Right'), Choice(value='1', name='v Right'),
Choice(value='2', name='v Left') Choice(value='2', name='v Left')
] ]
) )
@app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) @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() await interaction.response.defer()
with Session(engine) as session: with Session(engine) as session:
@ -373,48 +356,23 @@ class Gameplay(commands.Cog):
) )
return return
lineup_team = this_game.away_team if this_game.ai_team == 'home' else this_game.home_team this_team = this_game.away_team if this_game.ai_team == 'home' else this_game.home_team
if interaction.user.id != lineup_team.gmid: 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.') 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.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.')
return return
existing_lineups = get_game_lineups( this_play = await read_lineup(
session=session, session,
interaction,
this_game=this_game, this_game=this_game,
specific_team=lineup_team, lineup_team=this_team,
is_active=True sheets_auth=self.sheets,
lineup=lineup,
league_name=this_game.game_type
) )
if len(existing_lineups) > 1: if this_play is not None:
await interaction.edit_original_response( await self.post_play(session, interaction, this_play)
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)
@app_commands.command(name='gamestate', description='Post the current game state') @app_commands.command(name='gamestate', description='Post the current game state')
async def gamestate_command(self, interaction: discord.Interaction, include_lineups: bool = False): 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) await self.complete_and_post_play(session, interaction, this_play)
@group_log.command(name='bunt', description='Bunts: sacrifice, bad, popout, double-play, defense') @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']): async def log_sac_bunt(self, interaction: discord.Interaction, bunt_type: Literal['sacrifice', 'bad', 'popout', 'double-play', 'defense']):
with Session(engine) as session: with Session(engine) as session:

View File

@ -92,47 +92,46 @@ class Owner(commands.Cog):
@commands.is_owner() @commands.is_owner()
async def sync(self, ctx: Context, guilds: Greedy[Object], spec: Optional[Literal['~', "*", '!']] = None) -> None: async def sync(self, ctx: Context, guilds: Greedy[Object], spec: Optional[Literal['~', "*", '!']] = None) -> None:
""" """
!sync -> global sync !sync
!sync ~ -> sync current guild This takes all global commands within the CommandTree and sends them to Discord. (see CommandTree for more info.)
!sync * -> copies all global app commands to current guild and syncs !sync ~
!sync id_1 id_2 -> syncs guilds with id 1 and 2 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}') logger.info(f'{ctx.author.name} has initiated a sync from guild ID {ctx.guild.id}')
if not guilds: if not guilds:
if spec == "~": if spec == "~":
fmt = await ctx.bot.tree.sync(guild=ctx.guild) synced = await ctx.bot.tree.sync(guild=ctx.guild)
elif spec == "*": elif spec == "*":
ctx.bot.tree.copy_global_to(guild=ctx.guild) ctx.bot.tree.copy_global_to(guild=ctx.guild)
fmt = await ctx.bot.tree.sync(guild=ctx.guild) synced = await ctx.bot.tree.sync(guild=ctx.guild)
elif spec == '!': elif spec == "^":
fmt = await ctx.bot.tree.sync() ctx.bot.tree.clear_commands(guild=ctx.guild)
await ctx.send(f'Synced {len(fmt)} commands globally.') await ctx.bot.tree.sync(guild=ctx.guild)
synced = []
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.')
else: else:
fmt = await ctx.bot.tree.sync() synced = await ctx.bot.tree.sync()
await ctx.send( 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 return
fmt = 0 ret = 0
for guild in guilds: for guild in guilds:
try: try:
await ctx.bot.tree.sync(guild=guild) await ctx.bot.tree.sync(guild=guild)
except discord.HTTPException: except discord.HTTPException:
pass pass
else: 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): async def setup(bot):

View File

@ -2,6 +2,8 @@
import asyncio import asyncio
import logging import logging
import discord import discord
from discord import SelectOption
from discord.app_commands import Choice
import pandas as pd import pandas as pd
from sqlmodel import Session, select, func from sqlmodel import Session, select, func
from sqlalchemy import delete from sqlalchemy import delete
@ -11,10 +13,10 @@ from api_calls import db_delete, db_get, db_post
from exceptions import * from exceptions import *
from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel
from in_game.game_helpers import legal_check from in_game.game_helpers import legal_check
from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, Team, Play from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, 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_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.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.embeds import image_embed
from utilities.pages import Pagination from utilities.pages import Pagination
@ -226,6 +228,63 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo
return embed 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: def get_obc(on_first = None, on_second = None, on_third = None) -> int:
if on_third is not None: if on_third is not None:
if on_second 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]: 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) this_sheet = sheets.open_by_key(this_team.gsheet)
logger.debug(f'this_sheet: {this_sheet}') 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}') logger.debug(f'lineup_cells: {lineup_cells}')
except ValueError as e: except ValueError as e:
logger.error(f'Could not pull roster for {this_team.abbrev}: {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_lineups = []
all_pos = [] all_pos = []
@ -491,10 +550,60 @@ async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, thi
return all_lineups 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]: async def checks_log_interaction(session: Session, interaction: discord.Interaction, command_name: str) -> tuple[Game, Team, Play]:
""" """
Commits this_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) await interaction.response.defer(thinking=True)
this_game = get_channel_game_or_none(session, interaction.channel_id) this_game = get_channel_game_or_none(session, interaction.channel_id)
if this_game is None: if this_game is None:

View File

@ -31,6 +31,10 @@ class CardLegalityException(GameException):
pass pass
class CardNotFoundException(GameException):
pass
class GameNotFoundException(GameException): class GameNotFoundException(GameException):
pass pass
@ -65,3 +69,7 @@ class MultipleHumanTeamsException(GameException):
class NoHumanTeamsException(GameException): class NoHumanTeamsException(GameException):
pass pass
class GoogleSheetsException(GameException):
pass

View File

@ -47,6 +47,16 @@ class GameCardsetLink(SQLModel, table=True):
cardset: 'Cardset' = Relationship(back_populates='game_links') 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): class TeamBase(SQLModel):
id: int = Field(primary_key=True) id: int = Field(primary_key=True)
abbrev: str = Field(index=True) abbrev: str = Field(index=True)
@ -110,6 +120,7 @@ class Game(SQLModel, table=True):
auto_roll: bool | None = Field(default=False) auto_roll: bool | None = Field(default=False)
cardset_links: list[GameCardsetLink] = Relationship(back_populates='game', cascade_delete=True) 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( away_team: Team = Relationship(
# back_populates='away_games', # back_populates='away_games',
# sa_relationship_kwargs={ # sa_relationship_kwargs={

View File

@ -6,7 +6,8 @@ from typing import Literal
import pydantic import pydantic
from sqlalchemy import func from sqlalchemy import func
from api_calls import db_get, db_post 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 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"]}') await db_post(f'teams/{losing_team.id}/money/{loss_reward["money"]}')
return win_string, loss_string 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

View File

@ -1,10 +1,10 @@
import datetime import datetime
import pytest import pytest
from sqlmodel import Session, SQLModel, create_engine from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool from sqlmodel.pool import StaticPool
from typing import Literal 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') @pytest.fixture(name='session')
@ -231,6 +231,30 @@ def session_fixture():
session.commit() 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) all_ai = ManagerAi.create_ai(session)
yield session yield session

View File

@ -109,19 +109,19 @@ def test_delete_game(session: Session):
assert len(bad_links) == 0 assert len(bad_links) == 0
async def test_get_scorebug(session: Session): # async def test_get_scorebug(session: Session):
game_1 = session.get(Game, 1) # game_1 = session.get(Game, 1)
# scorebug = game_1.get_scorebug_embed(session) # # scorebug = game_1.get_scorebug_embed(session)
scorebug = await get_scorebug_embed(session, game_1) # scorebug = await get_scorebug_embed(session, game_1)
assert scorebug.title == 'CornBelters @ Black Bears - Minor League' # assert scorebug.title == 'CornBelters @ Black Bears - Minor League'
assert scorebug.color.value == int('a6ce39', 16) # assert scorebug.color.value == int('a6ce39', 16)
game_3 = session.get(Game, 3) # game_3 = session.get(Game, 3)
# scorebug = game_3.get_scorebug_embed(session) # # scorebug = game_3.get_scorebug_embed(session)
scorebug = await get_scorebug_embed(session, game_3) # 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): def test_sum_function(session: Session):

View File

@ -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

View File

@ -1,9 +1,15 @@
from typing import List
import discord import discord
import logging import logging
from discord import SelectOption
from discord.utils import MISSING
from sqlmodel import Session 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') 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) 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
)