Added PO/FO to fielding roll

Complete /log xcheck and /log groundball
This commit is contained in:
Cal Corum 2024-12-06 00:46:49 -06:00
parent d6d3d7beb0
commit 8118b4a691
10 changed files with 1016 additions and 146 deletions

View File

@ -11,7 +11,7 @@ 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_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, steals, strikeouts, triples, undo_play, update_game_settings, walks, xchecks
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, groundballs, hit_by_pitch, homeruns, is_game_over, manual_end_game, popouts, read_lineup, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, triples, undo_play, update_game_settings, walks, xchecks
from dice import ab_roll
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
@ -88,7 +88,7 @@ class Gameplay(commands.Cog):
if this_play.game.roll_buttons and interaction.user.id in [this_play.game.away_team.gmid, this_play.game.home_team.gmid]:
scorebug_buttons = ScorebugButtons(this_play, scorebug_embed)
if this_play.on_base_code == 0 and this_play.game.auto_roll and not this_play.batter.team.is_ai:
if this_play.on_base_code == 0 and this_play.game.auto_roll and not this_play.batter.team.is_ai and not this_play.is_new_inning:
this_ab_roll = ab_roll(this_play.batter.team, this_play.game, allow_chaos=False)
scorebug_buttons = None
@ -243,6 +243,7 @@ class Gameplay(commands.Cog):
True if home_team.is_ai else False,
league.value
)
logger.info(f'Chosen SP in Game {this_game.id}: {ai_sp_lineup.player.name_with_desc}')
await interaction.edit_original_response(
content=f'The {ai_team.sname} are starting **{ai_sp_lineup.player.name_with_desc}**:\n\n{ai_sp_lineup.player.pitcher_card_url}'
)
@ -251,6 +252,7 @@ class Gameplay(commands.Cog):
final_message = await interaction.channel.send(
content=f'{ai_team.gmname} is filling out the {ai_team.sname} lineup card...'
)
logger.info(f'Pulling lineup in Game {this_game.id}')
batter_lineups = await get_starting_lineup(
session,
team=ai_team,
@ -260,12 +262,14 @@ class Gameplay(commands.Cog):
)
# Check for last game settings
logger.info(f'Checking human team\'s automation preferences...')
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
logger.info(f'Setting auto_roll to {last_game.auto_roll} and roll_buttons to {last_game.roll_buttons}')
# Commit game and lineups
session.add(this_game)
@ -517,6 +521,16 @@ class Gameplay(commands.Cog):
await self.complete_and_post_play(session, interaction, this_play)
@group_log.command(name='groundball', description='Groundballs: a, b, c')
async def log_groundball(self, interaction: discord.Interaction, groundball_type: Literal['a', 'b', 'c']):
with Session(engine) as session:
this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name=f'log groundball {groundball_type}')
logger.info(f'log groundball {groundball_type} - this_play: {this_play}')
this_play = await groundballs(session, interaction, this_play, groundball_type)
await self.complete_and_post_play(session, interaction, this_play)
@group_log.command(name='hit-by-pitch', description='Hit by pitch: batter to first; runners advance if forced')
async def log_hit_by_pitch(self, interaction: discord.Interaction):
with Session(engine) as session:

View File

@ -90,7 +90,7 @@ class Owner(commands.Cog):
@commands.command(name='sync', help='~ current guild, * global -> current, ! clear and sync current')
@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
This takes all global commands within the CommandTree and sends them to Discord. (see CommandTree for more info.)

View File

@ -16,6 +16,7 @@ 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, RosterLink, Team, Play
from in_game.gameplay_queries import get_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 in_game.managerai_responses import DefenseResponse
from utilities.buttons import ButtonOptions, Confirm, ask_confirm
from utilities.dropdown import DropdownView, SelectStartingPitcher, SelectViewDefense
from utilities.embeds import image_embed
@ -263,7 +264,7 @@ async def read_lineup(session: Session, interaction: discord.Interaction, this_g
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)
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.home_team.is_ai else this_game.home_roster_id)
await interaction.edit_original_response(content='Heard from sheets, pulling in scouting data...')
@ -637,6 +638,7 @@ async def checks_log_interaction(session: Session, interaction: discord.Interact
this_play.locked = True
session.add(this_play)
session.commit()
session.refresh(this_play)
return this_game, owner_team, this_play
@ -664,12 +666,19 @@ def log_run_scored(session: Session, runner: Lineup, this_play: Play, is_earned:
return True
def advance_runners(session: Session, this_play: Play, num_bases: int, is_error: bool = False, only_forced: bool = False) -> Play:
def advance_runners(session: Session, this_play: Play, num_bases: int, only_forced: bool = False, earned_bases: int = None) -> Play:
"""
No commits
"""
logger.info(f'Advancing runners {num_bases} bases in game {this_play.game.id}')
if earned_bases is None:
earned_bases = num_bases
this_play.rbi = 0
er_from = {
3: True if earned_bases >= 1 else False,
2: True if earned_bases >= 2 else False,
1: True if earned_bases >= 3 else False
}
if num_bases == 0:
if this_play.on_first is not None:
@ -690,13 +699,13 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, is_error:
if this_play.on_third:
if num_bases > 0:
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third, this_play)
this_play.rbi += 1 if not is_error else 0
log_run_scored(session, this_play.on_third, this_play, is_earned=er_from[3])
this_play.rbi += 1 if er_from[3] else 0
if num_bases > 1:
this_play.on_second_final = 4
log_run_scored(session, this_play.on_second, this_play)
this_play.rbi += 1 if not is_error else 0
log_run_scored(session, this_play.on_second, this_play, is_earned=er_from[2])
this_play.rbi += 1 if er_from[2] else 0
elif num_bases == 1:
this_play.on_second_final = 3
else:
@ -707,8 +716,8 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, is_error:
if num_bases > 2:
this_play.on_first_final = 4
log_run_scored(session, this_play.on_first, this_play)
this_play.rbi += 1 if not is_error else 0
log_run_scored(session, this_play.on_first, this_play, is_earned=er_from[1])
this_play.rbi += 1 if er_from[1] else 0
elif num_bases == 2:
this_play.on_first_final = 3
elif num_bases == 1:
@ -720,16 +729,16 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, is_error:
if this_play.on_third:
if num_bases > 0:
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third, this_play)
this_play.rbi += 1 if not is_error else 0
log_run_scored(session, this_play.on_third, this_play, is_earned=er_from[3])
this_play.rbi += 1 if er_from[3] else 0
else:
this_play.on_third_final = 3
if this_play.on_second:
if num_bases > 1:
this_play.on_second_final = 4
log_run_scored(session, this_play.on_second, this_play)
this_play.rbi += 1 if not is_error else 0
log_run_scored(session, this_play.on_second, this_play, is_earned=er_from[2])
this_play.rbi += 1 if er_from[2] else 0
elif num_bases == 1:
this_play.on_second_final = 3
else:
@ -738,8 +747,8 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, is_error:
if this_play.on_first:
if num_bases > 2:
this_play.on_first_final = 4
log_run_scored(session, this_play.on_first, this_play)
this_play.rbi += 1 if not is_error else 0
log_run_scored(session, this_play.on_first, this_play, is_earned=er_from[1])
this_play.rbi += 1 if er_from[1] else 0
elif num_bases == 2:
this_play.on_first_final = 3
elif num_bases == 1:
@ -842,7 +851,7 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
if this_play.starting_outs < 2:
advance_runners(session, this_play, num_bases=1)
this_play = advance_runners(session, this_play, num_bases=1)
if this_play.on_third:
this_play.ab = 0
@ -850,7 +859,7 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play
elif flyball_type == 'b' or flyball_type == 'ballpark':
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
this_play.bpfo = 1 if flyball_type == 'ballpark' else 0
advance_runners(session, this_play, num_bases=0)
this_play = advance_runners(session, this_play, num_bases=0)
if this_play.starting_outs < 2 and this_play.on_third:
this_play.ab = 0
@ -951,7 +960,7 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play
elif flyball_type == 'c':
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
advance_runners(session, this_play, num_bases=0)
this_play = advance_runners(session, this_play, num_bases=0)
session.add(this_play)
session.commit()
@ -1238,14 +1247,14 @@ async def singles(session: Session, interaction: discord.Interaction, this_play:
this_play.hit, this_play.batter_final = 1, 1
if single_type == '**':
advance_runners(session, this_play, num_bases=2)
this_play = advance_runners(session, this_play, num_bases=2)
elif single_type in ['*', 'ballpark']:
advance_runners(session, this_play, num_bases=1)
this_play = advance_runners(session, this_play, num_bases=1)
this_play.bp1b = 1 if single_type == 'ballpark' else 0
elif single_type == 'uncapped':
advance_runners(session, this_play, 1)
this_play = advance_runners(session, this_play, 1)
if this_play.on_base_code in [1, 2, 4, 5, 6, 7]:
if this_play.on_second:
@ -1535,8 +1544,10 @@ async def chaos(session: Session, interaction: discord.Interaction, this_play: P
session.refresh(this_play)
return this_play
async def steals(session: Session, interaction: discord.Interaction, this_play: Play, steal_type: Literal['stolen-base', 'caught-stealing', 'steal-plus-overthrow'], to_base: Literal[2, 3, 4]) -> Play:
advance_runners(session, this_play, 0)
this_play = advance_runners(session, this_play, 0)
this_play.pa = 0
if steal_type in ['stolen-base', 'steal-plus-overthrow']:
this_play.sb = 1
@ -1602,7 +1613,8 @@ async def steals(session: Session, interaction: discord.Interaction, this_play:
session.refresh(this_play)
return this_play
async def xchecks(session: Session, interaction: discord.Interaction, this_play: Play, position: str) -> Play:
async def xchecks(session: Session, interaction: discord.Interaction, this_play: Play, position: str, debug: bool = False) -> Play:
defense_team = this_play.pitcher.team
this_defender = get_one_lineup(
session,
@ -1610,43 +1622,72 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play:
this_team=defense_team,
position=position
)
this_play.defender = this_defender
this_play.check_pos = position
def_alignment = this_play.managerai.defense_alignment(session, this_play.game)
defender_is_in = def_alignment.defender_in(position)
playing_in = False
defender_embed = defense_team.embed
defender_embed.title = f'{defense_team.sname} {position} Check'
defender_embed.description = f'{this_defender.player.name}'
defender_embed.set_image(url=this_defender.player.image)
embeds = [defender_embed]
logger.info(f'defender_embed: {defender_embed}')
# if not debug:
# await interaction.edit_original_response(content=None, embeds=embeds)
this_rating = await get_position(session, this_defender.card, position)
logger.info(f'position rating: {this_rating}')
if this_play.on_third is not None:
if not this_play.ai_is_batting and defender_is_in:
playing_in = True
elif this_play.ai_is_batting:
playing_in = await ask_confirm(
interaction,
question=f'Was {this_defender.card.player.name} playing in?',
label_type='yes'
)
if playing_in:
this_rating.range = min(this_rating.range + 1, 5)
this_roll = sa_fielding_roll(defense_team, this_play, position, this_rating)
logger.info(f'this_roll: {this_roll}')
question = f'Looks like this is a {this_roll.hit_result}'
if this_roll.is_chaos:
question += ' **rare play**.'
elif this_roll.error_result is not None:
question += f' plus {this_roll.error_result}-base error.'
question += f'Is that correct?'
if not debug:
question = f'Looks like this is a **{this_roll.hit_result}**'
if this_roll.is_chaos:
question += ' **rare play**'
elif this_roll.error_result is not None:
question += f' plus {this_roll.error_result}-base error'
question += f'. Is that correct?'
await interaction.edit_original_response(
content=None,
embeds=this_roll.embeds
)
is_correct = await ask_confirm(
interaction,
question,
label_type='yes',
timeout=30,
)
await interaction.edit_original_response(
content=None,
embeds=[defender_embed, *this_roll.embeds]
)
is_correct = await ask_confirm(
interaction,
question,
label_type='yes',
timeout=30,
)
else:
is_correct = True
hit_result = this_roll.hit_result
error_result = this_roll.error_result
is_rare_play = this_roll.is_chaos
logger.info(f'X-Check in Game #{this_play.game_id} at {this_play.check_pos} for {this_play.defender.card.player.name_with_desc} of the {this_play.pitcher.team.sname} / hit_result: {hit_result} / error_result: {error_result} / is_correct: {is_correct}')
if not is_correct:
# Full questionnaire
pass
if hit_result == 'SPD':
if hit_result == 'SPD' and not is_rare_play:
is_out = ask_confirm(
interaction,
f'Is {this_play.batter.player.name} thrown out at first?',
@ -1657,14 +1698,284 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play:
hit_result = 'G3'
else:
this_play = await singles(session, interaction, this_play, '*')
if '#' in hit_result:
logger.info(f'Checking if the # result becomes a hit')
if this_play.ai_is_batting:
if (position in ['1B', '3B', 'P', 'C'] and def_alignment.corners_in) or (position in ['1B, ''2B', '3B', 'SS', 'P', 'C'] and def_alignment.infield_in):
hit_result = 'SI2'
if hit_result not in ['SI1', 'SI2', 'DO2', 'DO3', 'TR'] and error_result is None:
elif this_play.on_base_code > 0:
is_holding = False
if not playing_in:
if position == '1B' and this_play.on_first is not None:
is_holding = await ask_confirm(
interaction,
question=f'Was {this_play.on_second.card.player.name} held at first base?',
label_type='yes'
)
elif position == '3B' and this_play.on_second is not None:
is_holding = await ask_confirm(
interaction,
question=f'Was {this_play.on_second.card.player.name} held at second base?',
label_type='yes'
)
elif position == '2B' and (this_play.on_first is not None or this_play.on_second is not None) and (this_play.batter.card.batterscouting.battingcard.hand == 'R' or (this_play.batter.card.batterscouting.battingcard.hand == 'S' and this_play.pitcher.card.pitcherscouting.pitchingcard.hand == 'L')):
if this_play.on_second is not None:
is_holding = await ask_confirm(
interaction,
question=f'Was {this_play.on_second.card.player.name} held at second base?',
label_type='yes'
)
elif this_play.on_first is not None:
is_holding = await ask_confirm(
interaction,
question=f'Was {this_play.on_first.card.player.name} held at first base?',
label_type='yes'
)
elif position == 'SS' and (this_play.on_first is not None or this_play.on_second is not None) and (this_play.batter.card.batterscouting.battingcard.hand == 'L' or (this_play.batter.card.batterscouting.battingcard.hand == 'S' and this_play.pitcher.card.pitcherscouting.pitchingcard.hand == 'R')):
if this_play.on_second is not None:
is_holding = await ask_confirm(
interaction,
question=f'Was {this_play.on_second.card.player.name} held at second base?',
label_type='yes'
)
elif this_play.on_first is not None:
is_holding = await ask_confirm(
interaction,
question=f'Was {this_play.on_first.card.player.name} held at first base?',
label_type='yes'
)
if is_holding or playing_in:
hit_result = 'SI2'
if is_rare_play:
logger.info(f'Is rare play')
if hit_result == 'SI1':
this_play = await singles(session, interaction, this_play, '*')
if this_play.on_first is None:
this_play.error = 1
this_play.batter_final = 2
elif hit_result == 'SI2':
this_play = await singles(session, interaction, this_play, '**')
this_play.batter_final = None
this_play.outs = 1
elif 'DO' in hit_result:
this_play = await doubles(session, interaction, this_play, '***')
this_play.batter_final = None
this_play.outs = 1
elif hit_result == 'TR':
this_play = await triples(session, interaction, this_play)
this_play.batter_final = 4
this_play.run = 1
this_play.error = 1
elif hit_result == 'PO':
this_play = advance_runners(session, this_play, 1, earned_bases=0)
this_play.ab, this_play.error, this_play.batter_final = 1, 1, 1
elif hit_result == 'FO':
this_play = advance_runners(session, this_play, 1, is_error=True, only_forced=True)
this_play.ab, this_play.error, this_play.batter_final = 1, 1, 1
elif hit_result == 'G1':
if this_play.on_first is not None and this_play.starting_outs < 2:
this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=defender_is_in)
else:
this_play = await gb_letter(session, interaction, this_play, 'A', position=this_play.check_pos, defender_is_in=defender_is_in)
elif hit_result == 'G2':
if this_play.on_base_code > 0:
this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=defender_is_in)
else:
this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=defender_is_in)
elif hit_result == 'G3':
if this_play.on_base_code > 0:
this_play = await singles(session, interaction, this_play, '*')
else:
this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=defender_is_in)
elif hit_result == 'SPD':
this_play = singles(session, interaction, this_play, '*')
elif hit_result == 'F1':
this_play.outs = 1
this_play.ab = 1 if this_play.on_third is None else 0
if this_play.on_base_code > 0 and this_play.starting_outs < 2:
this_play = advance_runners(session, this_play, 1)
if this_play.on_second is not None:
this_play.on_second_final = 4
log_run_scored(session, this_play.on_second, this_play, is_earned=False)
elif this_play.on_first is not None:
this_play.on_first_final = 3
elif hit_result == 'F2':
this_play.outs = 1
this_play.ab = 1 if this_play.on_third is None else 0
if this_play.on_base_code > 0 and this_play.starting_outs < 2:
this_play.on_third_final = None
this_play.outs = 2
else:
this_play.outs = 1
this_play.ab = 1
if this_play.on_third:
this_play.outs = 2
this_play.on_third_final = None
elif this_play.on_second:
this_play.outs = 2
this_play.on_second_final = None
elif this_play.on_first:
this_play.outs = 2
this_play.on_first_final = None
elif hit_result not in ['SI1', 'SI2', 'DO2', 'DO3', 'TR'] and error_result is None:
logger.info(f'Not a hit, not an error')
if this_play.on_base_code == 0:
this_play.ab, this_play.outs = 1, 1
this_play = await gb_result(session, interaction, this_play, 1)
# TODO: Continue working through results
# elif this_play
else:
to_mif = position in ['2B', 'SS']
to_right_side = position in ['1B', '2B']
if 'G3' in hit_result:
if this_play.on_base_code == 2 and not defender_is_in:
this_play = await gb_result(session, interaction, this_play, 12)
elif defender_is_in and this_play.on_base_code == 5:
this_play = await gb_result(session, interaction, this_play, 7, to_mif, to_right_side)
elif defender_is_in and this_play.on_base_code in [3, 6]:
this_play = await gb_decide(session, interaction=interaction, this_play=this_play)
elif defender_is_in and this_play.on_base_code == 7:
this_play = await gb_result(session, interaction, this_play, 11)
else:
this_play = await gb_result(session, interaction, this_play, 3)
elif 'G2' in hit_result:
if this_play.on_base_code == 7 and defender_is_in:
this_play = await gb_result(session, interaction, this_play, 11)
elif not defender_is_in and this_play.on_base_code in [3, 6]:
this_play = await gb_result(session, interaction, this_play, 5, to_mif=to_mif)
elif defender_is_in and this_play.on_base_code in [3, 5, 6]:
this_play = await gb_result(session, interaction, this_play, 1)
elif this_play.on_base_code == 2:
this_play = await gb_result(session, interaction, this_play, 12)
else:
this_play = await gb_result(session, interaction, this_play, 4)
elif 'G1' in hit_result:
if this_play.on_base_code == 7 and defender_is_in:
this_play = await gb_result(session, interaction, this_play, 10)
elif not defender_is_in and this_play.on_base_code == 4:
this_play = await gb_result(session, interaction, this_play, 13)
elif not defender_is_in and this_play.on_base_code in [3, 6]:
this_play = await gb_result(session, interaction, this_play, 3)
elif defender_is_in and this_play.on_base_code in [3, 5, 6]:
this_play = await gb_result(session, interaction, this_play, 1)
elif this_play.on_base_code == 2:
this_play = await gb_result(session, interaction, this_play, 12)
else:
this_play = await gb_result(session, interaction, this_play, 2)
elif 'F1' in hit_result:
this_play = await flyballs(session, interaction, this_play, 'a')
elif 'F2' in hit_result:
this_play = await flyballs(session, interaction, this_play, 'b')
elif 'F3' in hit_result:
this_play = await flyballs(session, interaction, this_play, 'c')
# FO and PO
else:
this_play.ab, this_play.outs = 1, 1
this_play = advance_runners(session, this_play, 0)
elif hit_result not in ['SI1', 'SI2', 'DO2', 'DO3', 'TR'] and error_result is not None:
logger.info(f'Not a hit, {error_result}-base error')
this_play = advance_runners(session, this_play, error_result, earned_bases=0)
this_play.ab, this_play.error, this_play.batter_final = 1, 1, error_result
else:
logger.info(f'Hit result: {hit_result}, Error: {error_result}')
if hit_result == 'SI1' and error_result is None:
this_play = await singles(session, interaction, this_play, '*')
elif hit_result == 'SI1':
this_play.ab, this_play.hit, this_play.error, this_play.batter_final = 1, 1, 1, 2
this_play = advance_runners(session, this_play, num_bases=error_result + 1, earned_bases=1)
elif hit_result == 'SI2' and error_result is None:
this_play = await singles(session, interaction, this_play, '**')
elif hit_result == 'SI2':
this_play.ab, this_play.hit, this_play.error = 1, 1, 1
if error_result > 1:
num_bases = 3
this_play.batter_final = 3
else:
num_bases = 2
this_play.batter_final = 2
this_play = advance_runners(session, this_play, num_bases=num_bases, earned_bases=2)
elif hit_result == 'DO2' and error_result is None:
this_play = await doubles(session, interaction, this_play, '**')
elif hit_result == 'DO2':
this_play.ab, this_play.hit, this_play.error, this_play.double = 1, 1, 1, 1
num_bases = 3
if error_result == 3:
this_play.batter_final = 4
else:
this_play.batter_final = 3
this_play = advance_runners(session, this_play, num_bases=num_bases, earned_bases=2)
elif hit_result == 'DO3' and error_result is None:
this_play = await doubles(session, interaction, this_play, '***')
elif hit_result == 'DO3':
this_play.ab, this_play.hit, this_play.error, this_play.double = 1, 1, 1, 1
if error_result == 1:
this_play.batter_final = 3
else:
this_play.batter_final = 4
this_play = advance_runners(session, this_play, num_bases=4, earned_bases=2)
elif hit_result == 'TR' and error_result is None:
this_play = await triples(session, interaction, this_play)
else:
this_play.ab, this_play.hit, this_play.error, this_play.triple = 1, 1, 1, 1
this_play = advance_runners(session, this_play, num_bases=4, earned_bases=3)
session.add(this_play)
session.commit()
@ -1672,6 +1983,7 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play:
session.refresh(this_play)
return this_play
def activate_last_play(session: Session, this_game: Game) -> Play:
p_query = session.exec(select(Play).where(Play.game == this_game).order_by(Play.id.desc()).limit(1)).all()
@ -2197,10 +2509,84 @@ async def manual_end_game(session: Session, interaction: discord.Interaction, th
await complete_game(session, interaction, current_play)
async def gb_result(session: Session, interaction: discord.Interaction, this_play: Play, groundball_result: int, to_mif: bool = None, to_right_side: bool = None):
async def groundballs(session: Session, interaction: discord.Interaction, this_play: Play, groundball_letter: Literal['a', 'b', 'c']):
if this_play.on_base_code == 2 and groundball_letter in ['a', 'b']:
to_right_side = await ask_confirm(
interaction,
question=f'Was that ball hit to either 1B or 2B?',
label_type='yes'
)
this_play = gb_result_6(session, this_play, to_right_side)
elif this_play.on_base_code in [3, 6] and groundball_letter in ['a', 'b']:
to_mif = await ask_confirm(
interaction,
question=f'Was that ball hit to either 2B or SS?',
label_type='yes'
)
this_play = gb_result_5(session, this_play, to_mif)
else:
this_play = await gb_letter(session, interaction, this_play, groundball_letter.upper(), 'None', False)
session.add(this_play)
session.commit()
session.refresh(this_play)
return this_play
async def gb_letter(session: Session, interaction: discord.Interaction, this_play: Play, groundball_letter: Literal['A', 'B', 'C'], position: str, defender_is_in: bool):
"""
Commits this_play
"""
if not defender_is_in:
if this_play.on_base_code == 0:
return await gb_result(session, interaction, this_play, 1)
elif groundball_letter == 'C':
return await gb_result(session, interaction, this_play, 3)
elif groundball_letter == 'A' and this_play.on_base_code in [1, 4, 5, 7]:
return await gb_result(session, interaction, this_play, 2)
elif groundball_letter == 'B' and this_play.on_base_code in [1, 4, 5, 7]:
return await gb_result(session, interaction, this_play, 4)
elif this_play.on_base_code in [3, 6]:
return await gb_result(session, interaction, this_play, 5, to_mif=position in ['2B', 'SS'])
else:
return await gb_result(session, interaction, this_play, 6, to_right_side=position in ['1B', '2B'])
else:
if groundball_letter == 'A' and this_play.on_base_code == 7:
return await gb_result(session, interaction, this_play, 10)
elif groundball_letter == 'B' and this_play.on_base_code == 5:
return await gb_result(session, interaction, this_play, 9)
elif this_play.on_base_code == 7:
return await gb_result(session, interaction, this_play, 11)
elif groundball_letter == 'A':
return await gb_result(session, interaction, this_play, 7)
elif groundball_letter == 'B':
return await gb_result(session, interaction, this_play, 1)
else:
return await gb_result(session, interaction, this_play, 8)
async def gb_result(session: Session, interaction: discord.Interaction, this_play: Play, groundball_result: int, to_mif: bool = None, to_right_side: bool = None):
"""
Commits this_play
Result 5 requires to_mif
Result 6 requires to_right_side
"""
logger.info(f'Starting a groundball result: GB #{groundball_result}, to_mif: {to_mif}, to_right_side: {to_right_side}')
if groundball_result == 1:
this_play = gb_result_1(session, this_play)
elif groundball_result == 2:
@ -2224,7 +2610,7 @@ async def gb_result(session: Session, interaction: discord.Interaction, this_pla
elif groundball_result == 11:
this_play = gb_result_11(session, this_play)
elif groundball_result == 12:
this_play = gb_result_12(session, this_play, interaction)
this_play = await gb_result_12(session, this_play, interaction)
elif groundball_result == 13:
this_play = gb_result_13(session, this_play)
@ -2238,7 +2624,7 @@ async def gb_result(session: Session, interaction: discord.Interaction, this_pla
def gb_result_1(session: Session, this_play: Play):
logger.info(f'GB 1')
this_play = advance_runners(session, this_play, 0)
this_play.ab, this_play.ab, this_play.outs = 1, 1, 1
this_play.ab, this_play.outs = 1, 1
return this_play
@ -2247,7 +2633,7 @@ def gb_result_2(session: Session, this_play: Play):
logger.info(f'GB 2')
num_outs = 2 if this_play.starting_outs <= 1 else 1
this_play.ab, this_play.outs = 1, 1
this_play.ab, this_play.outs = 1, num_outs
this_play.on_first_final = None
if num_outs + this_play.starting_outs < 3:
@ -2275,7 +2661,7 @@ def gb_result_4(session: Session, this_play: Play):
this_play = advance_runners(session, this_play, 1)
this_play.ab, this_play.outs = 1, 1
this_play.on_first_final = None
this_play.on_first_final = 1
return this_play
@ -2345,14 +2731,17 @@ def gb_result_11(session: Session, this_play: Play):
async def gb_decide(session: Session, this_play: Play, interaction: discord.Interaction):
logger.info(f'GB Decide')
runner = this_play.on_third if this_play.on_third is not None else this_play.on_second
logger.info(f'runner: {runner}')
pos_rating = await get_position(session, this_play.defender.card, this_play.check_pos)
safe_range = runner.card.batterscouting.battingcard.running - 4 + pos_rating.range
advance_base = 4 if this_play.on_third is not None else 3
logger.info(f'pos_rating: {pos_rating}\nsafe_range: {safe_range}\nadvance_base: {advance_base}')
if this_play.game.ai_team is not None and this_play.ai_is_batting:
run_resp = this_play.managerai.gb_decide_run(session, this_play.game)
if this_play.on_second is None:
log_exception(InvalidResultException, 'Cannot run GB Decide without a runner on second.')
if this_play.on_second is None and this_play.on_third is None:
log_exception(InvalidResultException, 'Cannot run GB Decide without a runner on base.')
if safe_range >= run_resp.min_safe:
is_lead_running = True
@ -2362,7 +2751,7 @@ async def gb_decide(session: Session, this_play: Play, interaction: discord.Inte
else:
is_lead_running = await ask_confirm(
interaction,
f'Is {runner.card.player.name} attempting to advance to third?',
f'Is {runner.card.player.name} attempting to advance {TO_BASE[advance_base]} with a 1-{safe_range} safe range?',
label_type='yes',
delete_question=False
)
@ -2381,7 +2770,7 @@ async def gb_decide(session: Session, this_play: Play, interaction: discord.Inte
)
throw_for_lead = throw_resp.at_lead_runner
await interaction.channel.send(
content=f'**{this_play.defender.player.name}** is {"not" if not throw_for_lead else ""} throwing for {runner.player.name}{"!" if throw_for_lead else "."}'
content=f'**{this_play.defender.player.name}** is {"not " if not throw_for_lead else ""}throwing for {runner.player.name}{"!" if throw_for_lead else "."}'
)
else:
throw_for_lead = await ask_confirm(

24
dice.py
View File

@ -32,7 +32,7 @@ class JumpRoll(DiceRoll):
class FieldingRoll(DiceRoll):
d_six_three: int | None = None
hit_result: Literal['SI1', 'SI2', 'DO2', 'DO3', 'TR', 'G1', 'G2', 'G3', 'G1#', 'G2#', 'G3#', 'F1', 'F2', 'F3', 'SPD'] | None = None
hit_result: Literal['SI1', 'SI2', 'DO2', 'DO3', 'TR', 'G1', 'G2', 'G3', 'G1#', 'G2#', 'G3#', 'F1', 'F2', 'F3', 'SPD', 'PO', 'FO'] | None = None
error_result: Literal[1, 2, 3] | None = None
@ -817,6 +817,9 @@ def sa_fielding_roll(this_team: Team, this_play: Play, pos_code: str, def_rating
d_twenty = random.randint(1, 20)
)
error_dice = this_roll.d_six_one + this_roll.d_six_two + this_roll.d_six_three
this_roll.roll_message = f'```md\n' \
f'# {str(this_roll.d_twenty)},{str(this_roll.d_six_one + this_roll.d_six_two + this_roll.d_six_three)}\n' \
f'Details:[1d20;3d6 ({this_roll.d_twenty} - {this_roll.d_six_one} {this_roll.d_six_two} {this_roll.d_six_three})]```'
if pos_code in ['1B', '2B', '3B', 'SS']:
x_chart = 'https://sombaseball.ddns.net/static/images/season04/range-infield.png'
@ -1632,7 +1635,7 @@ def sa_fielding_roll(this_team: Team, this_play: Play, pos_code: str, def_rating
elif def_rating.range == 3:
this_roll.hit_result = 'SI2'
elif def_rating.range == 4:
this_roll.hit_result = 'DO2'
this_roll.hit_result = 'SI2'
else:
this_roll.hit_result = 'DO2'
@ -2720,17 +2723,19 @@ def sa_fielding_roll(this_team: Team, this_play: Play, pos_code: str, def_rating
)
reference_string = f'[Range Chart]({x_chart}) / [Error Chart]({error_chart}) / [Result Reference]({symbol_link})'
chart_embed.add_field(name='References', value=reference_string, inline=False)
this_roll.embeds = [roll_embed, chart_embed]
error1_embed, error2_embed = None, None
if error_note1:
error1_embed = get_dice_embed(this_team)
error1_embed.add_field(name='Error Results', value=error_note1)
this_roll.embeds.append(error1_embed)
if error_note2:
error2_embed = get_dice_embed(this_team)
error2_embed.add_field(name='Error Results', value=error_note2)
this_roll.embeds = [roll_embed, chart_embed, error1_embed, error2_embed]
logger.info(f'Game {this_play.game.id} | Team {this_team.id} ({this_team.abbrev}): {this_roll.roll_message}')
this_roll.embeds.append(error1_embed)
logger.info(f'Game {this_play.game.id} | Team {this_team.id} ({this_team.abbrev}) Roll Message: {this_roll.roll_message}')
return this_roll
@ -2778,21 +2783,20 @@ def ab_roll(this_team: Team, this_game: Game, allow_chaos: bool = True) -> AbRol
if allow_chaos:
if d_twenty == 1:
logger.info(f'Game {this_game.id} - Wild Pitch Check')
flag = 'wild pitch'
elif d_twenty == 2:
if random.randint(1, 2) == 1:
logger.info(f'Game {this_game.id} - Balk Check')
flag = 'balk'
else:
logger.info(f'Game {this_game.id} - Passed Ball Check')
flag = 'passed ball'
if flag:
roll_message = f'```md\nCheck {flag}```\n' \
f'{flag.title()} roll```md\n# {d_twenty_two}\nDetails: [1d20 ({d_twenty_two})]```\n'
embed = get_dice_embed(this_team, f'Chaos roll for the {this_team.sname}', roll_message)
embed.set_footer(
text='If the chaos roll fails, ignore future chaos until a new batter comes to the plate.'
)
# return {'is_chaos': True, 'embeds': [embed]}
return AbRoll(
roll_message=roll_message,
is_chaos=True,

View File

@ -658,38 +658,55 @@ class ManagerAi(ManagerAiBase, table=True):
ai_rd = this_play.ai_run_diff
aggression = self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5
pitcher_hold = this_play.pitcher.card.pitcherscouting.pitchingcard.hold
catcher_defense = session.exec(select(PositionRating).where(PositionRating.player_id == this_play.catcher.player_id, PositionRating.position == 'C', PositionRating.variant == this_play.catcher.card.variant)).one()
catcher_hold = catcher_defense.arm
battery_hold = pitcher_hold + catcher_hold
if self.starting_outs == 2 and self.on_base_code > 0:
if self.on_base_code == 1:
if this_play.starting_outs == 2 and this_play.on_base_code > 0:
if this_play.on_base_code == 1:
this_resp.hold_first = True
elif self.on_base_code == 2:
this_resp.ai_note += f'- hold {this_play.on_first.player.name} on 1st\n'
elif this_play.on_base_code == 2:
this_resp.hold_second = True
elif self.on_base_code in [4, 5, 7]:
this_resp.ai_note += f'- hold {this_play.on_second.player.name} on 2nd\n'
elif this_play.on_base_code in [4, 5, 7]:
this_resp.hold_first = True
this_resp.hold_second = True
this_resp.ai_note += f'- hold {this_play.on_first.player.name} on 1st\n- hold {this_play.on_second.player.name} on 2nd\n'
# elif self.on_base_code == 5:
# ai_note += f'- hold the runner on first\n'
elif self.on_base_code == 6:
ai_note += f'- hold {self.on_second.player.name} on 2nd\n'
elif self.on_base_code in [1, 5]:
runner = self.on_first.player
if self.on_first.card.batterscouting.battingcard.steal_auto:
ai_note += f'- hold {runner.name} on 1st\n'
elif self.on_base_code in [2, 4]:
if self.on_second.card.batterscouting.battingcard.steal_low + max(self.pitcher.card.pitcherscouting.pitchingcard.hold, 5) >= 14:
ai_note += f'- hold {self.on_second.player.name} on 2nd\n'
# this_resp.ai_note += f'- hold the runner on first\n'
elif this_play.on_base_code == 6:
this_resp.hold_second = True
this_resp.ai_note += f'- hold {this_play.on_second.player.name} on 2nd\n'
elif this_play.on_base_code in [1, 5]:
runner = this_play.on_first.player
if this_play.on_first.card.batterscouting.battingcard.steal_auto and ((this_play.on_first.card.batterscouting.battingcard.steal_high + battery_hold) >= (12 - aggression)):
this_resp.hold_first = True
this_resp.ai_note += f'- hold {runner.name} on 1st\n'
elif this_play.on_base_code in [2, 4]:
if (this_play.on_second.card.batterscouting.battingcard.steal_low + max(battery_hold, 5)) >= (14 - aggression):
this_resp.hold_second = True
this_resp.ai_note += f'- hold {this_play.on_second.player.name} on 2nd\n'
# Defensive Alignment
if self.on_third and self.starting_outs < 2:
if self.could_walkoff:
ai_note += f'- play the outfield and infield in'
elif abs(self.away_score - self.home_score) <= 3:
ai_note += f'- play the whole infield in\n'
if this_play.on_third and this_play.starting_outs < 2:
if this_play.could_walkoff:
this_resp.outfield_in = True
this_resp.infield_in = True
this_resp.ai_note += f'- play the outfield and infield in'
elif abs(this_play.away_score - this_play.home_score) <= 3:
this_resp.infield_in = True
this_resp.ai_note += f'- play the whole infield in\n'
else:
ai_note += f'- play the corners in\n'
this_resp.corners_in = True
this_resp.ai_note += f'- play the corners in\n'
if len(ai_note) == 0 and self.on_base_code > 0:
ai_note += f'- play straight up\n'
if len(this_resp.ai_note) == 0 and this_play.on_base_code > 0:
this_resp.ai_note += f'- play straight up\n'
return this_resp
def gb_decide_run(self, session: Session, this_game: Game) -> RunResponse:
this_resp = RunResponse()
@ -881,21 +898,21 @@ class BattingRatings(BattingRatingsBase, table=True):
class BatterScoutingBase(SQLModel):
id: int | None = Field(default=None, primary_key=True)
battingcard_id: int | None = Field(default=None, foreign_key='battingcard.id')
ratings_vl_id: int | None = Field(default=None, foreign_key='battingratings.id')
ratings_vr_id: int | None = Field(default=None, foreign_key='battingratings.id')
battingcard_id: int | None = Field(default=None, foreign_key='battingcard.id', ondelete='CASCADE')
ratings_vl_id: int | None = Field(default=None, foreign_key='battingratings.id', ondelete='CASCADE')
ratings_vr_id: int | None = Field(default=None, foreign_key='battingratings.id', ondelete='CASCADE')
created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True)
class BatterScouting(BatterScoutingBase, table=True):
battingcard: BattingCard = Relationship() #back_populates='batterscouting')
ratings_vl: BattingRatings = Relationship(
sa_relationship_kwargs=dict(foreign_keys="[BatterScouting.ratings_vl_id]")
sa_relationship_kwargs=dict(foreign_keys="[BatterScouting.ratings_vl_id]",single_parent=True), cascade_delete=True
)
ratings_vr: BattingRatings = Relationship(
sa_relationship_kwargs=dict(foreign_keys="[BatterScouting.ratings_vr_id]")
sa_relationship_kwargs=dict(foreign_keys="[BatterScouting.ratings_vr_id]",single_parent=True), cascade_delete=True
)
cards: list['Card'] = Relationship(back_populates='batterscouting', cascade_delete=True)
cards: list['Card'] = Relationship(back_populates='batterscouting')
class PitchingCardBase(SQLModel):
@ -962,21 +979,21 @@ class PitchingRatings(PitchingRatingsBase, table=True):
class PitcherScoutingBase(SQLModel):
id: int | None = Field(default=None, primary_key=True)
pitchingcard_id: int | None = Field(default=None, foreign_key='pitchingcard.id',)
ratings_vl_id: int | None = Field(default=None, foreign_key='pitchingratings.id')
ratings_vr_id: int | None = Field(default=None, foreign_key='pitchingratings.id')
pitchingcard_id: int | None = Field(default=None, foreign_key='pitchingcard.id', ondelete='CASCADE')
ratings_vl_id: int | None = Field(default=None, foreign_key='pitchingratings.id', ondelete='CASCADE')
ratings_vr_id: int | None = Field(default=None, foreign_key='pitchingratings.id', ondelete='CASCADE')
created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True)
class PitcherScouting(PitcherScoutingBase, table=True):
pitchingcard: PitchingCard = Relationship()
ratings_vl: PitchingRatings = Relationship(
sa_relationship_kwargs=dict(foreign_keys="[PitcherScouting.ratings_vl_id]")
sa_relationship_kwargs=dict(foreign_keys="[PitcherScouting.ratings_vl_id]",single_parent=True), cascade_delete=True
)
ratings_vr: PitchingRatings = Relationship(
sa_relationship_kwargs=dict(foreign_keys="[PitcherScouting.ratings_vr_id]")
sa_relationship_kwargs=dict(foreign_keys="[PitcherScouting.ratings_vr_id]",single_parent=True), cascade_delete=True
)
cards: list['Card'] = Relationship(back_populates='pitcherscouting', cascade_delete=True)
cards: list['Card'] = Relationship(back_populates='pitcherscouting')
class CardBase(SQLModel):

View File

@ -155,18 +155,35 @@ async def get_player_or_none(session: Session, player_id: int, skip_cache: bool
async def get_batter_scouting_or_none(session: Session, card: Card, skip_cache: bool = False) -> BatterScouting | None:
logger.info(f'Getting batting scouting for card ID #{card.id}: {card.player.name_with_desc}')
if not skip_cache and card.batterscouting is not None:
this_scouting = session.get(BatterScouting, card.batterscouting.id)
if this_scouting is not None:
logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}')
tdelta = datetime.datetime.now() - this_scouting.created
logger.debug(f'tdelta: {tdelta}')
if tdelta.total_seconds() < CACHE_LIMIT:
return this_scouting
else:
session.delete(this_scouting)
session.commit()
s_query = await db_get(f'battingcardratings/player/{card.player.id}', none_okay=False)
if s_query['count'] != 2:
log_exception(DatabaseError, f'Scouting for {card.player.name_with_desc} was not found.')
this_scouting = session.get(BattingCard, s_query['ratings'][0]['battingcard']['id'])
if this_scouting is not None:
logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}')
tdelta = datetime.datetime.now() - this_scouting.created
logger.debug(f'tdelta: {tdelta}')
if tdelta.total_seconds() < CACHE_LIMIT:
return this_scouting
else:
session.delete(this_scouting)
session.commit()
# if not skip_cache and card.batterscouting is not None:
# this_scouting = session.get(BatterScouting, card.batterscouting.id)
# if this_scouting is not None:
# logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}')
# tdelta = datetime.datetime.now() - this_scouting.created
# logger.debug(f'tdelta: {tdelta}')
# if tdelta.total_seconds() < CACHE_LIMIT:
# return this_scouting
# else:
# session.delete(this_scouting)
# session.commit()
def cache_scouting(batting_card: dict, ratings_vr: dict, ratings_vl: dict) -> BatterScouting:
valid_bc = BattingCardBase.model_validate(batting_card, from_attributes=True)
@ -188,33 +205,45 @@ async def get_batter_scouting_or_none(session: Session, card: Card, skip_cache:
session.commit()
session.refresh(db_scouting)
return db_scouting
return cache_scouting(
batting_card=s_query['ratings'][0]['battingcard'],
ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1],
ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1]
)
s_query = await db_get(f'battingcardratings/player/{card.player.id}', none_okay=False)
if s_query['count'] == 2:
return cache_scouting(
batting_card=s_query['ratings'][0]['battingcard'],
ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1],
ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1]
)
# s_query = await db_get(f'battingcardratings/player/{card.player.id}', none_okay=False)
# if s_query['count'] == 2:
# return cache_scouting(
# batting_card=s_query['ratings'][0]['battingcard'],
# ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1],
# ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1]
# )
return None
# return None
async def get_pitcher_scouting_or_none(session: Session, card: Card, skip_cache: bool = False) -> PitcherScouting | None:
logger.info(f'Getting pitching scouting for card ID #{card.id}: {card.player.name_with_desc}')
if not skip_cache and card.pitcherscouting is not None:
this_scouting = session.get(PitcherScouting, card.pitcherscouting.id)
s_query = await db_get(f'pitchingcardratings/player/{card.player.id}', none_okay=False)
if s_query['count'] != 2:
log_exception(DatabaseError, f'Scouting for {card.player.name_with_desc} was not found.')
if this_scouting is not None:
logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}')
tdelta = datetime.datetime.now() - this_scouting.created
logger.debug(f'tdelta: {tdelta}')
if tdelta.total_seconds() < CACHE_LIMIT:
return this_scouting
else:
session.delete(this_scouting)
session.commit()
this_scouting = session.get(PitcherScouting, s_query['ratings'][0]['pitchingcard']['id'])
if this_scouting is not None:
logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}')
tdelta = datetime.datetime.now() - this_scouting.created
logger.debug(f'tdelta: {tdelta}')
if tdelta.total_seconds() < CACHE_LIMIT:
return this_scouting
else:
session.delete(this_scouting)
session.commit()
def cache_scouting(pitching_card: dict, ratings_vr: dict, ratings_vl: dict) -> PitcherScouting:
valid_bc = PitchingCardBase.model_validate(pitching_card, from_attributes=True)
db_bc = PitchingCard.model_validate(valid_bc)
@ -236,17 +265,60 @@ async def get_pitcher_scouting_or_none(session: Session, card: Card, skip_cache:
session.refresh(db_scouting)
return db_scouting
s_query = await db_get(f'pitchingcardratings/player/{card.player.id}', none_okay=False)
if s_query['count'] == 2:
scouting = cache_scouting(
pitching_card=s_query['ratings'][0]['pitchingcard'],
ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1],
ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1]
)
pos_rating = await get_position(session, card, 'P')
return scouting
scouting = cache_scouting(
pitching_card=s_query['ratings'][0]['pitchingcard'],
ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1],
ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1]
)
pos_rating = await get_position(session, card, 'P')
return scouting
return None
# if not skip_cache and card.pitcherscouting is not None:
# this_scouting = session.get(PitcherScouting, card.pitcherscouting.id)
# # s_query = session.exec(select(PitcherScouting).where(PitcherScouting.pitchingcard_id == card.))
# if this_scouting is not None:
# logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}')
# tdelta = datetime.datetime.now() - this_scouting.created
# logger.debug(f'tdelta: {tdelta}')
# if tdelta.total_seconds() < CACHE_LIMIT:
# return this_scouting
# else:
# session.delete(this_scouting)
# session.commit()
# def cache_scouting(pitching_card: dict, ratings_vr: dict, ratings_vl: dict) -> PitcherScouting:
# valid_bc = PitchingCardBase.model_validate(pitching_card, from_attributes=True)
# db_bc = PitchingCard.model_validate(valid_bc)
# valid_vl = PitchingRatingsBase.model_validate(ratings_vl, from_attributes=True)
# db_vl = PitchingRatings.model_validate(valid_vl)
# valid_vr = PitchingRatingsBase.model_validate(ratings_vr, from_attributes=True)
# db_vr = PitchingRatings.model_validate(valid_vr)
# db_scouting = PitcherScouting(
# pitchingcard=db_bc,
# ratings_vl=db_vl,
# ratings_vr=db_vr
# )
# session.add(db_scouting)
# session.commit()
# session.refresh(db_scouting)
# return db_scouting
# s_query = await db_get(f'pitchingcardratings/player/{card.player.id}', none_okay=False)
# if s_query['count'] == 2:
# scouting = cache_scouting(
# pitching_card=s_query['ratings'][0]['pitchingcard'],
# ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1],
# ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1]
# )
# pos_rating = await get_position(session, card, 'P')
# return scouting
# return None
# async def get_position_rating_or_none(session: Session, card: Card, position: str, skip_cache: bool = False) -> PositionRating | None:

View File

@ -1,7 +1,11 @@
import pydantic
class RunResponse(pydantic.BaseModel):
class AiResponse(pydantic.BaseModel):
ai_note: str = ''
class RunResponse(AiResponse):
min_safe: int | None = None
@ -20,7 +24,7 @@ class UncappedRunResponse(RunResponse):
trail_min_safe_delta: int = 0
class ThrowResponse(pydantic.BaseModel):
class ThrowResponse(AiResponse):
cutoff: bool = False # Stops on True
at_lead_runner: bool = True
at_trail_runner: bool = False # Stops on False
@ -28,10 +32,20 @@ class ThrowResponse(pydantic.BaseModel):
trail_max_safe_delta: int = -6
class DefenseResponse(pydantic.BaseModel):
class DefenseResponse(AiResponse):
hold_first: bool = False
hold_second: bool = False
hold_third: bool = False
outfield_in: bool = False
infield_in: bool = False
corners_in: bool = False
def defender_in(self, position: str):
if self.infield_in and position in ['C', '1B', '2B', '3B', 'SS', 'P']:
return True
elif self.corners_in and position in ['C', '1B', '3B', 'P']:
return True
elif self.outfield_in and position in ['LF', 'CF', 'RF']:
return True
return False

View File

@ -1,7 +1,7 @@
import pytest
from sqlmodel import Session, select, func
from command_logic.logic_gameplay import advance_runners, doubles, get_obc, get_re24, get_wpa, complete_play, log_run_scored, strikeouts, steals
from command_logic.logic_gameplay import advance_runners, doubles, gb_result_1, get_obc, get_re24, get_wpa, complete_play, log_run_scored, strikeouts, steals, xchecks
from in_game.gameplay_models import Lineup, Play
from tests.factory import session_fixture, Game
@ -22,7 +22,7 @@ def test_advance_runners(session: Session):
assert play_2.on_second_id == play_1.batter_id
play_2.pa, play_2.ab, play_2.hit, play_2.batter_final = 1, 1, 1, 1
advance_runners(session, play_2, 1)
play_2 = advance_runners(session, play_2, 1)
session.add(play_2)
session.commit()
@ -35,6 +35,17 @@ def test_advance_runners(session: Session):
assert play_3.on_second is None
assert play_3.on_first is not None
play_3.pa, play_3.ab, play_3.hit, play_3.batter_final = 1, 1, 1, 2
play_3 = advance_runners(session, play_3, 3, earned_bases=1)
session.add(play_3)
session.commit()
assert play_3.rbi == 1
assert play_2.run == 1
assert play_2.e_run == 0
assert play_1.run == 1
assert play_1.e_run == 1
def test_get_obc():
assert get_obc() == 0
@ -207,4 +218,3 @@ async def test_stealing(session: Session):
assert play_2.on_first_final == 2
assert play_2.sb == 1
assert play_2.runner == play_2.on_first

View File

@ -0,0 +1,348 @@
import pytest
from sqlmodel import Session, select, func
from command_logic.logic_gameplay import advance_runners, gb_result_10, gb_result_11, gb_result_13, gb_result_2, gb_result_3, gb_result_4, gb_result_5, gb_result_6, gb_result_7, gb_result_8, gb_result_9, singles, doubles, triples, gb_result_1, complete_play, log_run_scored, strikeouts, steals, undo_play, xchecks
from in_game.gameplay_models import Lineup, Play
from tests.factory import session_fixture, Game
def test_groundball_1(session: Session):
game_1 = session.get(Game, 2)
play_1 = session.get(Play, 1)
play_1.hit, play_1.batter_final = 1, 2
play_2 = complete_play(session, play_1)
play_2 = gb_result_1(session, play_2)
ending = complete_play(session, play_2)
assert ending.on_second == play_2.on_second
assert play_2.batter_final == None
async def test_groundball_2_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = gb_result_2(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 0
assert ending.on_base_code == 0
async def test_groundball_2_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await doubles(session, None, play_1, '**')
play_1.starting_outs = 0
play_2 = complete_play(session, play_1)
play_2 = gb_result_2(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_third == play_2.on_second
async def test_groundball_2_3(session: Session):
play_1 = session.get(Play, 2)
play_1 = await triples(session, None, play_1)
play_1.starting_outs = 0
play_2 = complete_play(session, play_1)
play_2 = gb_result_2(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 0
assert ending.away_score == 1
async def test_groundball_3_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = gb_result_3(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 2
assert ending.away_score == 0
async def test_groundball_3_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await doubles(session, None, play_1, '**')
play_2 = complete_play(session, play_1)
play_2 = gb_result_3(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 3
assert ending.away_score == 0
async def test_groundball_3_3(session: Session):
play_1 = session.get(Play, 2)
play_1 = await triples(session, None, play_1,)
play_2 = complete_play(session, play_1)
play_2 = gb_result_3(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 0
assert ending.away_score == 1
async def test_groundball_4_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = gb_result_4(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 1
assert ending.on_first == play_2.batter
async def test_groundball_4_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '**')
play_3 = complete_play(session, play_2)
play_3 = gb_result_4(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 2
assert ending.on_base_code == 1
assert ending.on_first == play_2.batter
assert ending.away_score == 1
async def test_groundball_5_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await triples(session, None, play_1)
play_2 = complete_play(session, play_1)
play_2 = gb_result_5(session, play_2, True)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 0
assert ending.away_score == 1
async def test_groundball_5_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await triples(session, None, play_1)
play_2 = complete_play(session, play_1)
play_2 = gb_result_5(session, play_2, False)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 3
assert ending.away_score == 0
async def test_groundball_6_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await doubles(session, None, play_1, '**')
play_2 = complete_play(session, play_1)
play_2 = gb_result_6(session, play_2, True)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 3
async def test_groundball_6_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await doubles(session, None, play_1, '**')
play_2 = complete_play(session, play_1)
play_2 = gb_result_6(session, play_2, False)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 2
async def test_groundball_7_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await triples(session, None, play_1)
play_2 = complete_play(session, play_1)
play_2 = gb_result_7(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 3
assert ending.away_score == 0
async def test_groundball_7_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '**')
play_3 = complete_play(session, play_2)
play_3 = gb_result_7(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 2
assert ending.on_base_code == 6
assert ending.away_score == 0
async def test_groundball_8_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await triples(session, None, play_1)
play_2 = complete_play(session, play_1)
play_2 = gb_result_8(session, play_2)
ending = complete_play(session, play_2)
assert ending.starting_outs == 2
assert ending.on_base_code == 3
assert ending.away_score == 0
async def test_groundball_8_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '**')
play_3 = complete_play(session, play_2)
play_3 = gb_result_8(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 2
assert ending.on_base_code == 6
assert ending.away_score == 0
async def test_groundball_9_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '**')
play_3 = complete_play(session, play_2)
play_3 = gb_result_9(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 2
assert ending.on_base_code == 6
assert ending.away_score == 0
async def test_groundball_10_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '*')
play_3 = complete_play(session, play_2)
play_3 = await singles(session, None, play_3, '*')
play_4 = complete_play(session, play_3)
play_4 = gb_result_10(session, play_4)
ending = complete_play(session, play_4)
assert ending.starting_outs == 0
assert ending.on_base_code == 0
assert ending.away_score == 0
async def test_groundball_13_1_1(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '*')
play_3 = complete_play(session, play_2)
play_3.check_pos == '3B'
play_3 = gb_result_13(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 0
assert ending.on_base_code == 0
assert ending.away_score == 0
async def test_groundball_13_1_2(session: Session):
play_1 = session.get(Play, 2)
play_1.starting_outs = 0
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '*')
play_3 = complete_play(session, play_2)
play_3.check_pos = '3B'
play_3 = gb_result_13(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 2
assert ending.on_base_code == 1
assert ending.away_score == 0
async def test_groundball_13_1_3(session: Session):
play_1 = session.get(Play, 2)
play_1.starting_outs = 0
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '*')
play_3 = complete_play(session, play_2)
play_3.check_pos == '1B'
play_3 = gb_result_13(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 2
assert ending.on_base_code == 3
assert ending.away_score == 0
async def test_groundball_13_2(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '*')
play_3 = complete_play(session, play_2)
play_3.check_pos == 'C'
play_3 = gb_result_13(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 0
assert ending.on_base_code == 0
assert ending.away_score == 0
async def test_groundball_13_3(session: Session):
play_1 = session.get(Play, 2)
play_1 = await singles(session, None, play_1, '*')
play_2 = complete_play(session, play_1)
play_2 = await singles(session, None, play_2, '*')
play_3 = complete_play(session, play_2)
play_3.check_pos == '1B'
play_3 = gb_result_13(session, play_3)
ending = complete_play(session, play_3)
assert ending.starting_outs == 0
assert ending.on_base_code == 0
assert ending.away_score == 0

View File

@ -218,7 +218,9 @@ class ScorebugButtons(discord.ui.View):
logger.info(f'User {interaction.user.id} rolling AB in Game {self.play.game.id}')
this_roll = ab_roll(self.team, self.play.game, allow_chaos=not self.had_chaos)
logger.info(f'this_roll: {this_roll}')
if this_roll.is_chaos:
logger.info('AB Roll Is Chaos')
self.had_chaos = True
else:
button.disabled = True