/read-lineup logic is complete

This commit is contained in:
Cal Corum 2024-10-14 20:46:55 -05:00
parent 428afe048e
commit 46860fb3c0
9 changed files with 234 additions and 35 deletions

View File

@ -1,20 +1,20 @@
import asyncio
import enum
import logging import logging
from typing import Literal from typing import Literal
import discord import discord
from discord import app_commands from discord import app_commands
from discord.app_commands import Choice from discord.app_commands import Choice
from discord.ext import commands from discord.ext import commands, tasks
import pygsheets
from api_calls import db_get from api_calls import db_get
from command_logic.gameplay import get_lineups_from_sheets
from helpers import PD_PLAYERS_ROLE_NAME, team_role, user_has_role, random_gif, random_from_list from helpers import PD_PLAYERS_ROLE_NAME, 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.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, Session, engine, get_card_or_none, player_description, select, Game, get_team_or_none from in_game.gameplay_models import Lineup, Session, engine, player_description, select, Game
from in_game.gameplay_queries import get_channel_game_or_none, get_active_games_by_team from in_game.gameplay_queries import 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 from utilities.buttons import Confirm
@ -22,6 +22,19 @@ from utilities.buttons import Confirm
class Gameplay(commands.Cog): class Gameplay(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.sheets = None
self.get_sheets.start()
@tasks.loop(count=1)
async def get_sheets(self):
logging.info(f'Getting sheets')
self.sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json', retries=1)
@get_sheets.before_loop
async def before_get_sheets(self):
logging.info(f'Waiting to get sheets')
await self.bot.wait_until_ready()
async def cog_command_error(self, ctx, error): async def cog_command_error(self, ctx, error):
await ctx.send(f'{error}\n\nRun !help <command_name> to see the command requirements') await ctx.send(f'{error}\n\nRun !help <command_name> to see the command requirements')
@ -36,10 +49,10 @@ class Gameplay(commands.Cog):
sp_card_id='Light gray number to the left of the pitcher\'s name on your depth chart' sp_card_id='Light gray number to the left of the pitcher\'s name on your depth chart'
) )
@app_commands.choices(league=[ @app_commands.choices(league=[
Choice(name='minor-league', value='Minor League'), Choice(value='minor-league', name='Minor League'),
Choice(name='flashback', value='Flashback'), Choice(value='flashback', name='Flashback'),
Choice(name='major-league', value='Major League'), Choice(value='major-league', name='Major League'),
Choice(name='hall-of-fame', value='Hall of Fame') 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(
@ -58,8 +71,7 @@ class Gameplay(commands.Cog):
if interaction.channel.category is None or interaction.channel.category.name != PUBLIC_FIELDS_CATEGORY_NAME: if interaction.channel.category is None or interaction.channel.category.name != PUBLIC_FIELDS_CATEGORY_NAME:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' content=f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything pops up?'
f'pops up?'
) )
return return
@ -101,19 +113,19 @@ class Gameplay(commands.Cog):
def role_error(required_role: str, league_name: str, lower_league: str): def role_error(required_role: str, league_name: str, lower_league: str):
return f'Ope. Looks like you haven\'t received the **{required_role}** role, yet!\n\nTo play **{league_name}** games, you need to defeat all 30 MLB teams in the {lower_league} campaign. You can see your progress with `/record`.\n\nIf you have completed the {lower_league} campaign, go ping Cal to get your new role!' return f'Ope. Looks like you haven\'t received the **{required_role}** role, yet!\n\nTo play **{league_name}** games, you need to defeat all 30 MLB teams in the {lower_league} campaign. You can see your progress with `/record`.\n\nIf you have completed the {lower_league} campaign, go ping Cal to get your new role!'
if league.name == 'flashback': if league.value == 'flashback':
if not user_has_role(interaction.user, 'PD - Major League'): if not user_has_role(interaction.user, 'PD - Major League'):
await interaction.edit_original_response( await interaction.edit_original_response(
content=role_error('PD - Major League', league_name='Flashback', lower_league='Minor League') content=role_error('PD - Major League', league_name='Flashback', lower_league='Minor League')
) )
return return
elif league.name == 'major-league': elif league.value == 'major-league':
if not user_has_role(interaction.user, 'PD - Major League'): if not user_has_role(interaction.user, 'PD - Major League'):
await interaction.edit_original_response( await interaction.edit_original_response(
content=role_error('PD - Major League', league_name='Major League', lower_league='Minor League') content=role_error('PD - Major League', league_name='Major League', lower_league='Minor League')
) )
return return
elif league.name == 'hall-of-fame': elif league.value == 'hall-of-fame':
if not user_has_role(interaction.user, 'PD - Hall of Fame'): if not user_has_role(interaction.user, 'PD - Hall of Fame'):
await interaction.edit_original_response( await interaction.edit_original_response(
content=role_error('PD - Hall of Fame', league_name='Hall of Fame', lower_league='Major League') content=role_error('PD - Hall of Fame', league_name='Hall of Fame', lower_league='Major League')
@ -128,10 +140,10 @@ class Gameplay(commands.Cog):
week_num=week_num, week_num=week_num,
first_message=None if interaction.message is None else interaction.message.channel.id, first_message=None if interaction.message is None else interaction.message.channel.id,
ai_team='away' if away_team.is_ai else 'home', ai_team='away' if away_team.is_ai else 'home',
game_type=league.name game_type=league.value
) )
game_info_log = f'{league.value} 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}'
logging.info(game_info_log) logging.info(game_info_log)
# Get Human SP card # Get Human SP card
@ -149,10 +161,10 @@ class Gameplay(commands.Cog):
) )
return return
legal_data = await legal_check([sp_card_id], difficulty_name=league.name) legal_data = await legal_check([sp_card_id], difficulty_name=league.value)
if not legal_data['legal']: if not legal_data['legal']:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'It looks like this is a Ranked Legal game and {player_description(player=human_sp_card.player)} is not legal in {league.value} games. You can start a new game once you pick a new SP.' content=f'It looks like this is a Ranked Legal game and {player_description(player=human_sp_card.player)} is not legal in {league.name} games. You can start a new game once you pick a new SP.'
) )
return return
@ -176,7 +188,7 @@ class Gameplay(commands.Cog):
ai_team, ai_team,
this_game, this_game,
True if home_team.is_ai else False, True if home_team.is_ai else False,
league.name league.value
) )
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'The {ai_team.sname} are starting **{player_description(player=ai_sp_lineup.player)}**:\n\n{ai_sp_lineup.player.p_card_url}' content=f'The {ai_team.sname} are starting **{player_description(player=ai_sp_lineup.player)}**:\n\n{ai_sp_lineup.player.p_card_url}'
@ -190,7 +202,7 @@ class Gameplay(commands.Cog):
session, session,
team=ai_team, team=ai_team,
game=this_game, game=this_game,
league_name=league.name, league_name=league.value,
sp_name=human_sp_card.player.name sp_name=human_sp_card.player.name
) )
@ -248,17 +260,17 @@ class Gameplay(commands.Cog):
) )
@app_commands.choices( @app_commands.choices(
roster=[ roster=[
Choice(name=1, value='Primary'), Choice(value='1', name='Primary'),
Choice(name=2, value='Secondary'), Choice(value='2', name='Secondary'),
Choice(name=3, value='Ranked') Choice(value='3', name='Ranked')
], ],
lineup=[ lineup=[
Choice(name=1, value='v Right'), Choice(value='1', name='v Right'),
Choice(name=2, value='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[int], lineup: Choice[int]): async def read_lineup_command(self, interaction: discord.Interaction, roster: Choice[str], lineup: Choice[str]):
await interaction.response.defer() await interaction.response.defer()
with Session(engine) as session: with Session(engine) as session:
@ -269,6 +281,41 @@ class Gameplay(commands.Cog):
) )
return 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:
logging.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_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...')
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))
for batter in human_lineups:
session.add(batter)
session.commit()
await interaction.edit_original_response(content=f'Lineups are read and logged!')
async def setup(bot): async def setup(bot):

View File

@ -294,7 +294,7 @@ def get_record_embed(team: dict, results: dict, league: str):
class Players(commands.Cog): class Players(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json', retries=1) # self.sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json', retries=1)
self.player_list = [] self.player_list = []
self.cardset_list = [] self.cardset_list = []
self.freeze = False self.freeze = False

87
command_logic/gameplay.py Normal file
View File

@ -0,0 +1,87 @@
import logging
from sqlmodel import Session, select
from in_game.game_helpers import legal_check, CardLegalityException
from in_game.gameplay_models import Game, Lineup, Team
from in_game.gameplay_queries import get_card_or_none
async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, this_team: Team, lineup_num: int, roster_num: int) -> list[Lineup]:
logging.debug(f'sheets: {sheets}')
this_sheet = sheets.open_by_key(this_team.gsheet)
logging.debug(f'this_sheet: {this_sheet}')
r_sheet = this_sheet.worksheet_by_title('My Rosters')
logging.debug(f'r_sheet: {r_sheet}')
if lineup_num == 1:
row_start = 9
row_end = 17
else:
row_start = 18
row_end = 26
if roster_num == 1:
l_range = f'H{row_start}:I{row_end}'
elif roster_num == 2:
l_range = f'J{row_start}:K{row_end}'
else:
l_range = f'L{row_start}:M{row_end}'
logging.debug(f'l_range: {l_range}')
raw_cells = r_sheet.range(l_range)
logging.debug(f'raw_cells: {raw_cells}')
try:
lineup_cells = [(row[0].value, int(row[1].value)) for row in raw_cells]
logging.debug(f'lineup_cells: {lineup_cells}')
except ValueError as e:
logging.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')
all_lineups = []
all_pos = []
card_ids = []
for index, row in enumerate(lineup_cells):
if '' in row:
break
if row[0].upper() not in all_pos:
all_pos.append(row[0].upper())
else:
raise SyntaxError(f'You have more than one {row[0].upper()} in this lineup. Please update and set the lineup again.')
this_card = await get_card_or_none(session, card_id=int(row[1]))
if this_card is None:
raise LookupError(
f'Your {row[0].upper()} has a Card ID of {int(row[1])} and I cannot find that card. Did you sell it by chance? Or maybe you sold a duplicate and the bot sold the one you were using?'
)
if this_card.team_id != this_team.id:
raise SyntaxError(f'Easy there, champ. Looks like card ID {row[1]} belongs to the {this_card.team.lname}. Try again with only cards you own.')
card_id = row[1]
card_ids.append(str(card_id))
this_lineup = Lineup(
position=row[0].upper(),
batting_order=index + 1,
game=this_game,
team=this_team,
player=this_card.player,
card=this_card
)
all_lineups.append(this_lineup)
legal_data = await legal_check([card_ids], difficulty_name=this_game.game_type)
logging.debug(f'legal_data: {legal_data}')
if not legal_data['legal']:
raise CardLegalityException(f'The following cards appear to be illegal for this game mode:\n{legal_data["error_string"]}')
if len(all_lineups) != 9:
raise Exception(f'I was only able to pull in {len(all_lineups)} batters from Sheets. Please check your saved lineup and try again.')
return all_lineups

View File

@ -13,7 +13,8 @@ from typing import Optional, Literal
from in_game import data_cache from in_game import data_cache
import in_game.gameplay_models as iggm import in_game.gameplay_models as iggm
from in_game.gameplay_models import Session, Game, Team, get_or_create_ai_card, get_player_id_from_dict, get_player_or_none from in_game.gameplay_models import Session, Game, Team
from in_game.gameplay_queries import get_or_create_ai_card, get_player_id_from_dict, get_player_or_none
db = SqliteDatabase( db = SqliteDatabase(
'storage/ai-database.db', 'storage/ai-database.db',
@ -352,7 +353,7 @@ async def get_starting_pitcher(
f'teams/{this_team.id}/sp/{league_name}?sp_rank={sp_rank}{this_game.cardset_param_string}' f'teams/{this_team.id}/sp/{league_name}?sp_rank={sp_rank}{this_game.cardset_param_string}'
) )
this_player = await get_player_or_none(session, get_player_id_from_dict(sp_query)) this_player = await get_player_or_none(session, get_player_id_from_dict(sp_query))
sp_card = await iggm.get_or_create_ai_card(session, this_player, this_team) sp_card = await get_or_create_ai_card(session, this_player, this_team)
return iggm.Lineup( return iggm.Lineup(
team=this_team, team=this_team,

View File

@ -14,6 +14,10 @@ from typing import Literal, Optional
PUBLIC_FIELDS_CATEGORY_NAME = 'Public Fields' PUBLIC_FIELDS_CATEGORY_NAME = 'Public Fields'
class CardLegalityException(Exception):
pass
def single_onestar(this_play: StratPlay, comp_play: bool = True): def single_onestar(this_play: StratPlay, comp_play: bool = True):
patch_play(this_play.id, locked=True) patch_play(this_play.id, locked=True)
advance_runners(this_play.id, num_bases=1) advance_runners(this_play.id, num_bases=1)

View File

@ -50,12 +50,15 @@ class TeamBase(SQLModel):
class Team(TeamBase, table=True): class Team(TeamBase, table=True):
cards: list['Card'] = Relationship(back_populates='team', cascade_delete=True) cards: list['Card'] = Relationship(back_populates='team', cascade_delete=True)
lineups: list['Lineup'] = Relationship(back_populates='team', cascade_delete=True) lineups: list['Lineup'] = Relationship(back_populates='team', cascade_delete=True)
# away_games: list['Game'] = Relationship(back_populates='away_team')
# home_games: list['Game'] = Relationship(back_populates='home_team')
class Game(SQLModel, table=True): class Game(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
away_team_id: int away_team_id: int = Field(foreign_key='team.id')
home_team_id: int home_team_id: int = Field(foreign_key='team.id')
channel_id: int = Field(index=True) channel_id: int = Field(index=True)
season: int season: int
active: bool | None = Field(default=True) active: bool | None = Field(default=True)
@ -71,6 +74,24 @@ class Game(SQLModel, table=True):
game_type: str | None = Field(default=None) game_type: str | None = Field(default=None)
cardset_links: list[GameCardsetLink] = Relationship(back_populates='game', cascade_delete=True) cardset_links: list[GameCardsetLink] = Relationship(back_populates='game', cascade_delete=True)
away_team: Team = Relationship(
# back_populates='away_games',
# sa_relationship_kwargs={
# 'primaryjoin': 'Game.away_team_id==Team.id',
# 'foreign_keys': '[Game.away_team.id]',
# 'lazy': 'joined'
# }
sa_relationship_kwargs=dict(foreign_keys="[Game.away_team_id]")
)
home_team: Team = Relationship(
# back_populates='home_games',
# sa_relationship_kwargs={
# 'primaryjoin': 'Game.home_team_id==Team.id',
# 'foreign_keys': '[Game.home_team.id]',
# 'lazy': 'joined'
# }
sa_relationship_kwargs=dict(foreign_keys="[Game.home_team_id]")
)
lineups: list['Lineup'] = Relationship(back_populates='game', cascade_delete=True) lineups: list['Lineup'] = Relationship(back_populates='game', cascade_delete=True)
@property @property

View File

@ -3,7 +3,7 @@ import logging
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, Card, CardBase, Player, PlayerBase, Session, Team, TeamBase, select, or_, Game from in_game.gameplay_models import CACHE_LIMIT, Card, CardBase, Lineup, Player, PlayerBase, Session, Team, TeamBase, select, or_, Game
def get_games_by_channel(session: Session, channel_id: int) -> list[Game]: def get_games_by_channel(session: Session, channel_id: int) -> list[Game]:
@ -15,7 +15,7 @@ def get_channel_game_or_none(session: Session, channel_id: int) -> Game | None:
if len(all_games) > 1: if len(all_games) > 1:
err = 'Too many games found in get_channel_game_or_none' err = 'Too many games found in get_channel_game_or_none'
logging.error(f'cogs.gameplay - get_channel_game_or_none - channel_id: {channel_id} / {err}') logging.error(f'cogs.gameplay - get_channel_game_or_none - channel_id: {channel_id} / {err}')
raise LookupError(err) raise Exception(err)
elif len(all_games) == 0: elif len(all_games) == 0:
return None return None
return all_games[0] return all_games[0]
@ -232,3 +232,15 @@ async def get_card_or_none(session: Session, card_id: int, skip_cache: bool = Fa
return cache_card(c_query) return cache_card(c_query)
return None return None
def get_game_lineups(session: Session, this_game: Game, specific_team: Team = None, is_active: bool = None) -> list[Lineup]:
st = select(Lineup).where(Lineup.game == this_game)
if specific_team is not None:
st = st.where(Lineup.team == specific_team)
if is_active is not None:
st = st.where(Lineup.active == is_active)
return session.exec(st).all()

View File

@ -1,7 +1,8 @@
import datetime import datetime
from sqlmodel import Session from sqlmodel import Session
from in_game.gameplay_models import CACHE_LIMIT, Card, Player, Team, select, get_card_or_none, get_or_create_ai_card from in_game.gameplay_models import CACHE_LIMIT, Card, Player, Team, select, get_card_or_none
from in_game.gameplay_queries import get_or_create_ai_card
from factory import session_fixture from factory import session_fixture

View File

@ -1,6 +1,7 @@
from sqlmodel import Session, select from sqlmodel import Session, select
from in_game.gameplay_models import Lineup from in_game.gameplay_models import Game, Lineup
from in_game.gameplay_queries import get_game_lineups
from factory import session_fixture from factory import session_fixture
@ -18,6 +19,31 @@ def test_create_lineup(session: Session):
assert lineup_id_21.position == 'C' assert lineup_id_21.position == 'C'
def test_get_game_lineups(session: Session):
this_game = session.get(Game, 1)
all_lineups = get_game_lineups(session, this_game=this_game)
away_lineups = get_game_lineups(session, this_game=this_game, specific_team=this_game.away_team)
assert len(all_lineups) == 20
assert len(away_lineups) == 10
for count in range(5):
away_lineups[count].active = False
session.add(away_lineups[count])
session.commit()
all_lineups = get_game_lineups(session, this_game=this_game)
away_lineups = get_game_lineups(session, this_game=this_game, specific_team=this_game.away_team)
active_away_lineups = get_game_lineups(session, this_game=this_game, specific_team=this_game.away_team, is_active=True)
inactive_home_lineups = get_game_lineups(session, this_game=this_game, specific_team=this_game.home_team, is_active=False)
assert len(all_lineups) == 20
assert len(away_lineups) == 10
assert len(active_away_lineups) == 5
assert len(inactive_home_lineups) == 0
# def test_lineup_substitution(session: Session, new_games_with_lineups: list[Game]): # def test_lineup_substitution(session: Session, new_games_with_lineups: list[Game]):
# game_1 = new_games_with_lineups[0] # game_1 = new_games_with_lineups[0]
# game_2 = new_games_with_lineups[1] # game_2 = new_games_with_lineups[1]