fix: validate player positions in lineup before game start
Some checks failed
Build Docker Image / build (pull_request) Failing after 16s

Prevents PositionNotFoundException from crashing mlb-campaign when a
player is placed at a position they cannot play (e.g. an outfielder
listed at Catcher in the Google Sheet). Adds early validation in
get_lineups_from_sheets and proper error handling at all read_lineup
call sites so the user gets a clear message and the game is cleaned up.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-02-07 19:32:27 -06:00
parent caabf9c6d2
commit c4577ed46f
2 changed files with 44 additions and 7 deletions

View File

@ -436,19 +436,26 @@ class Gameplay(commands.Cog):
await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view)
try:
await asyncio.sleep(5)
await asyncio.sleep(5)
this_play = await read_lineup(
session,
interaction,
session,
interaction,
this_game=this_game,
lineup_team=human_team,
sheets_auth=self.sheets,
lineup_num=1 if roster_choice == 'vs Right' else 2,
league_name=this_game.game_type
)
except PositionNotFoundException as e:
logger.error(f'Position validation failed during lineup load: {e}')
this_game.active = False
session.add(this_game)
session.commit()
await interaction.channel.send(content=str(e))
return
except LineupsMissingException as e:
logger.error(f'Attempting to start game, pausing for 5 seconds: {e}')
await asyncio.sleep(5)
await asyncio.sleep(5)
try:
this_play = this_game.current_play_or_none(session)
@ -668,6 +675,13 @@ class Gameplay(commands.Cog):
lineup_num=1 if roster_choice == 'vs Right' else 2,
league_name=this_game.game_type
)
except PositionNotFoundException as e:
logger.error(f'Position validation failed during lineup load: {e}')
this_game.active = False
session.add(this_game)
session.commit()
await interaction.channel.send(content=str(e))
return
except LineupsMissingException as e:
# Expected - can't initialize play without SP yet
logger.info(f'Field player lineup read from sheets, waiting for SP selection: {e}')
@ -1044,14 +1058,18 @@ class Gameplay(commands.Cog):
logger.info(f'lineup: {lineup} / value: {lineup.value} / name: {lineup.name}')
try:
this_play = await read_lineup(
session,
interaction,
session,
interaction,
this_game=this_game,
lineup_team=this_team,
sheets_auth=self.sheets,
lineup_num=int(lineup.value),
league_name=this_game.game_type
)
except PositionNotFoundException as e:
logger.error(f'Position validation failed during lineup load: {e}')
await interaction.edit_original_response(content=str(e))
return
except LineupsMissingException as e:
await interaction.edit_original_response(content='Run `/set starting-pitcher` to select your SP')
return

View File

@ -628,7 +628,13 @@ async def read_lineup(
for batter in human_lineups:
if batter.position != "DH":
await get_position(session, batter.card, batter.position)
try:
await get_position(session, batter.card, batter.position)
except PositionNotFoundException:
raise PositionNotFoundException(
f"Could not find {batter.position} ratings for **{batter.player.name_with_desc}**. "
f"Please check your lineup in Google Sheets and make sure each player is at a valid position."
)
return this_game.initialize_play(session)
@ -1096,6 +1102,19 @@ async def get_lineups_from_sheets(
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."
)
position = row[0].upper()
if position != "DH":
player_positions = [
getattr(this_card.player, f"pos_{i}") for i in range(1, 9)
if getattr(this_card.player, f"pos_{i}") is not None
]
if position not in player_positions:
raise PositionNotFoundException(
f"**{this_card.player.name_with_desc}** (card {this_card.id}) is listed at **{position}** in your lineup, "
f"but can only play {', '.join(player_positions)}. Please fix your lineup in Google Sheets."
)
card_id = row[1]
card_ids.append(str(card_id))