Added baserunning ai notes

Added get_batter_card to gameplay_queries
This commit is contained in:
Cal Corum 2024-12-24 21:08:00 -06:00
parent c7b45aecf4
commit 7015fb3125
4 changed files with 220 additions and 96 deletions

View File

@ -438,6 +438,8 @@ class Gameplay(commands.Cog):
content=f'Creating this game for {t_role.mention}:\n{this_game}' content=f'Creating this game for {t_role.mention}:\n{this_game}'
) )
# TODO: add new-game exhibition, unlimited, and ranked
@commands.command(name='force-endgame', help='Mod: Force a game to end without stats') @commands.command(name='force-endgame', help='Mod: Force a game to end without stats')
async def force_end_game_command(self, ctx: commands.Context): async def force_end_game_command(self, ctx: commands.Context):
with Session(engine) as session: with Session(engine) as session:

View File

@ -15,7 +15,7 @@ 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, RosterLink, Team, Play from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, Team, Play
from in_game.gameplay_queries import get_available_batters, 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.gameplay_queries import get_available_batters, get_batter_card, 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 in_game.managerai_responses import DefenseResponse
from utilities.buttons import ButtonOptions, Confirm, ask_confirm from utilities.buttons import ButtonOptions, Confirm, ask_confirm
from utilities.dropdown import DropdownView, SelectBatterSub, SelectStartingPitcher, SelectViewDefense from utilities.dropdown import DropdownView, SelectBatterSub, SelectStartingPitcher, SelectViewDefense
@ -151,7 +151,7 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo
good_jump = '2-11' good_jump = '2-11'
else: else:
good_jump = '2-12' good_jump = '2-12'
steal_string = f'{"*" if batting_card.steal_auto else ""}{good_jump}/- ({batting_card.steal_high}-{batting_card.steal_low})' steal_string = f'{"`*`" if batting_card.steal_auto else ""}{good_jump}/- ({batting_card.steal_high}-{batting_card.steal_low})'
return steal_string return steal_string
baserunner_string = '' baserunner_string = ''
@ -201,8 +201,21 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo
cat_string = f'{curr_play.catcher.player.name_card_link('batter')}\nArm: {catcher_rating.arm}' cat_string = f'{curr_play.catcher.player.name_card_link('batter')}\nArm: {catcher_rating.arm}'
embed.add_field(name='Catcher', value=cat_string) embed.add_field(name='Catcher', value=cat_string)
ai_note = curr_play.ai_note if curr_play.ai_is_batting and curr_play.on_base_code > 0:
logger.info(f'gameplay_models - Game.get_scorebug_embed - ai_note: {ai_note}') if curr_play.on_base_code in [2, 4]:
to_base = 3
elif curr_play.on_base_code in [1, 5]:
to_base = 2
else:
to_base = 4
jump_resp = curr_play.managerai.check_jump(session, this_game, to_base=to_base)
ai_note = jump_resp.ai_note
else:
def_align = curr_play.managerai.defense_alignment(session, this_game)
ai_note = def_align.ai_note
logger.info(f'gameplay_models - get_scorebug_embed - ai_note: {ai_note}')
if len(ai_note) > 0: if len(ai_note) > 0:
gm_name = this_game.home_team.gmname if this_game.ai_team == 'home' else this_game.away_team.gmname gm_name = this_game.home_team.gmname if this_game.ai_team == 'home' else this_game.away_team.gmname
embed.add_field(name=f'{gm_name} will...', value=ai_note, inline=False) embed.add_field(name=f'{gm_name} will...', value=ai_note, inline=False)
@ -1276,6 +1289,13 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
outfielder = await show_outfield_cards(session, interaction, this_play) outfielder = await show_outfield_cards(session, interaction, this_play)
logger.info(f'throw from {outfielder.player.name_with_desc}') logger.info(f'throw from {outfielder.player.name_with_desc}')
def_team = this_play.pitcher.team def_team = this_play.pitcher.team
runner_bc = get_batter_card(this_lineup=lead_runner)
of_rating = await get_position(session, this_card=outfielder.card, position=outfielder.position)
defense_embed = def_team.embed
defense_embed.description = f'{outfielder.player.name}\'s Throw'
trail_bc = get_batter_card(this_lineup=trail_runner)
logger.info(f'trail runner batting card: {trail_bc}')
safe_range = None
# Either there is no AI team or the AI is pitching # Either there is no AI team or the AI is pitching
if not this_game.ai_team or not this_play.ai_is_batting: if not this_game.ai_team or not this_play.ai_is_batting:
@ -1287,6 +1307,8 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
if is_lead_running: if is_lead_running:
throw_resp = None throw_resp = None
def_alignment = this_play.managerai.defense_alignment(session, this_play.game)
if this_game.ai_team: if this_game.ai_team:
throw_resp = this_play.managerai.throw_at_uncapped(session, this_game) throw_resp = this_play.managerai.throw_at_uncapped(session, this_game)
logger.info(f'throw_resp: {throw_resp}') logger.info(f'throw_resp: {throw_resp}')
@ -1303,6 +1325,9 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
await asyncio.sleep(1) await asyncio.sleep(1)
return this_play return this_play
else:
await interaction.channel.send(content=f'{outfielder.player.name} is throwing {TO_BASE[lead_base]}!')
else: else:
throw_for_lead = await ask_confirm( throw_for_lead = await ask_confirm(
interaction=interaction, interaction=interaction,
@ -1326,36 +1351,44 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
label_type='yes' label_type='yes'
) )
lead_bc = get_batter_card(this_lineup=lead_runner)
logger.info(f'lead runner batting card: {lead_bc}')
lead_safe_range = lead_bc.running + of_rating.arm + 1
if lead_runner == this_play.on_second:
lead_safe_range -= 2 if def_alignment.hold_second else 0
elif lead_runner == this_play.on_first:
lead_safe_range -= 2 if def_alignment.hold_first else 0
logger.info(f'lead_safe_range: {lead_safe_range}')
# Trail runner is advancing # Trail runner is advancing
if trail_advancing: if trail_advancing:
trail_safe = trail_bc.running - 5 + of_rating.arm
logger.info(f'trail_safe: {trail_safe}')
throw_lead = False
if this_game.ai_team:
if throw_resp.at_trail_runner and trail_safe <= throw_resp.trail_max_safe and trail_safe <= throw_resp.trail_max_safe_delta - lead_safe_range:
logger.info(f'defense throwing at trail runner {AT_BASE[trail_base]}')
await interaction.channel.send(f'**{outfielder.player.name}** will throw {TO_BASE[trail_base]}!')
throw_lead = False
else:
logger.info(f'defense throwing at lead runner {AT_BASE[lead_base]}')
await interaction.channel.send(f'**{outfielder.player.name}** will throw {TO_BASE[lead_base]}!')
throw_lead = True
else:
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
view.confirm.label = 'Home Plate' if lead_base == 4 else 'Third Base' view.confirm.label = 'Home Plate' if lead_base == 4 else 'Third Base'
view.cancel.label = 'Third Base' if trail_base == 3 else 'Second Base' view.cancel.label = 'Third Base' if trail_base == 3 else 'Second Base'
ai_throw_lead = False
if this_game.ai_team:
if throw_resp.at_trail_runner:
question = await interaction.channel.send(
f'The {def_team.sname} will throw for the trail runner if both:\n- {trail_runner.player.name}\'s safe range is {throw_resp.trail_max_safe} or lower\n- {trail_runner.player.name}\'s safe range is lower than {lead_runner.player.name}\'s by at least {abs(throw_resp.trail_max_safe_delta)}.\n\nIs the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?',
view=view
)
else:
await interaction.channel.send(f'**{outfielder.player.name}** will throw {TO_BASE[lead_base]}!')
ai_throw_lead = True
else:
question = await interaction.channel.send( question = await interaction.channel.send(
f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?', f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?',
view=view view=view
) )
throw_lead = await view.wait()
if not ai_throw_lead:
await view.wait()
elif ai_throw_lead:
view.value = True
# Throw is going to lead runner # Throw is going to lead runner
if view.value: if throw_lead:
try: try:
await question.delete() await question.delete()
except (discord.NotFound, UnboundLocalError): except (discord.NotFound, UnboundLocalError):
@ -1376,8 +1409,10 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
runner_thrown_out = await ask_confirm( runner_thrown_out = await ask_confirm(
interaction=interaction, interaction=interaction,
question='Was **{trail_runner.player.name}** thrown out {AT_BASE[trail_base]}?', question=f'**{trail_runner.player.name}**\'s safe range is 1 -> {trail_safe} - were they thrown out {AT_BASE[trail_base]}?',
label_type='yes' label_type='yes',
custom_confirm_label=f'Out {AT_BASE[trail_base]}',
custom_cancel_label=f'Safe {AT_BASE[trail_base]}'
) )
# Trail runner is thrown out # Trail runner is thrown out
@ -1408,8 +1443,10 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
# Ball is going to lead base, ask if safe # Ball is going to lead base, ask if safe
runner_thrown_out = await ask_confirm( runner_thrown_out = await ask_confirm(
interaction=interaction, interaction=interaction,
question=f'Was **{lead_runner.player.name}** thrown out {AT_BASE[lead_base]}?', question=f'**{lead_runner.player.name}**\'s safe range is 1 -> {lead_safe_range} - were they thrown out {AT_BASE[lead_base]}?',
label_type='yes' label_type='yes',
custom_confirm_label=f'Out {AT_BASE[lead_base]}',
custom_cancel_label=f'Safe {AT_BASE[lead_base]}'
) )
# Lead runner is thrown out # Lead runner is thrown out
@ -1437,18 +1474,32 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
elif this_play.ai_is_batting: elif this_play.ai_is_batting:
run_resp = this_play.managerai.uncapped_advance(session, this_game, lead_base, trail_base) run_resp = this_play.managerai.uncapped_advance(session, this_game, lead_base, trail_base)
is_lead_running = await ask_confirm( runner_held = await ask_confirm(
interaction=interaction, interaction=interaction,
question=f'**{lead_runner.player.name}** will advance {TO_BASE[lead_base]} if the safe range is {run_resp.min_safe} or higher.\n\nIs **{lead_runner.player.name}** attempting to advance?', question=f'Was **{lead_runner.player.name}** held before the pitch?',
label_type='yes' label_type='yes'
) )
safe_range = runner_bc.running + of_rating.arm - 1
if runner_held:
safe_range -= 1
else:
safe_range += 1
if not is_lead_running: if this_play.starting_outs == 2:
safe_range += 2
if lead_base == 3:
if outfielder.position == 'RF':
safe_range += 2
elif outfielder.position == 'LF':
safe_range -= 2
if safe_range > run_resp.min_safe:
return this_play return this_play
is_defense_throwing = await ask_confirm( is_defense_throwing = await ask_confirm(
interaction=interaction, interaction=interaction,
question=f'Is the defense throwing {TO_BASE[lead_base]} for {lead_runner.player.name}?', question=f'{lead_runner.player.name} is advancing {TO_BASE[lead_base]} with a safe range of **1->{safe_range}**! Is the defense throwing?',
label_type='yes' label_type='yes'
) )
@ -1467,7 +1518,7 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
# Human throw is not being cut off # Human throw is not being cut off
if run_resp.send_trail: if run_resp.send_trail:
await interaction.channel.send( await interaction.channel.send(
f'**{trail_runner.player.name}** is advancing {TO_BASE[trail_base]} as the trail runner!', f'**{trail_runner.player.name}** is advancing {TO_BASE[trail_base]} as the trail runner with a safe range of 1->{trail_bc.running - 5 + of_rating.arm}!',
) )
is_throwing_lead = await ask_confirm( is_throwing_lead = await ask_confirm(
interaction=interaction, interaction=interaction,
@ -1518,6 +1569,9 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
return this_play return this_play
else:
await interaction.channel.send(content=f'**{trail_runner.player.name}** is NOT trailing to {TO_BASE[trail_base]}.')
# Ball is going to lead base, ask if safe # Ball is going to lead base, ask if safe
is_lead_out = await ask_confirm( is_lead_out = await ask_confirm(
interaction=interaction, interaction=interaction,
@ -1776,7 +1830,7 @@ async def bunts(session: Session, interaction: discord.Interaction, this_play: P
lead_runner_out = await ask_confirm( lead_runner_out = await ask_confirm(
interaction=interaction, interaction=interaction,
question=f'{runner.player.name}\'s safe range is **1 -> {runner.card.batterscouting.battingcard.running - 4 + def_pos.range}**. Is the runner out {AT_BASE[lead_base]}?', question=f'{runner.player.name}\'s safe range is **1->{runner.card.batterscouting.battingcard.running - 4 + def_pos.range}**. Is the runner out {AT_BASE[lead_base]}?',
custom_confirm_label=f'Out {AT_BASE[lead_base]}', custom_confirm_label=f'Out {AT_BASE[lead_base]}',
custom_cancel_label=f'Safe {AT_BASE[lead_base]}' custom_cancel_label=f'Safe {AT_BASE[lead_base]}'
) )
@ -2981,7 +3035,8 @@ def gb_result_4(session: Session, this_play: Play):
this_play = advance_runners(session, this_play, 1) this_play = advance_runners(session, this_play, 1)
this_play.ab, this_play.outs = 1, 1 this_play.ab, this_play.outs = 1, 1
this_play.on_first_final = 1 this_play.on_first_final = None
this_play.batter_final = 1
return this_play return this_play
@ -3071,7 +3126,7 @@ async def gb_decide(session: Session, this_play: Play, interaction: discord.Inte
else: else:
is_lead_running = await ask_confirm( is_lead_running = await ask_confirm(
interaction, interaction,
f'Is {runner.card.player.name} attempting to advance {TO_BASE[advance_base]} with a 1-{safe_range} safe range?', f'Is **{runner.card.player.name}** attempting to advance {TO_BASE[advance_base]} with a **1-{safe_range}** safe range?',
label_type='yes', label_type='yes',
delete_question=False delete_question=False
) )

View File

@ -298,7 +298,8 @@ class ManagerAi(ManagerAiBase, table=True):
return True return True
def check_jump(self, session: Session, this_game: Game, to_base: Literal[2, 3, 4]) -> JumpResponse | None: def check_jump(self, session: Session, this_game: Game, to_base: Literal[2, 3, 4]) -> JumpResponse:
logger.info(f'Checking jump to {to_base} in Game {this_game.id}')
this_resp = JumpResponse() this_resp = JumpResponse()
this_play = this_game.current_play_or_none(session) this_play = this_game.current_play_or_none(session)
if this_play is None: if this_play is None:
@ -309,7 +310,16 @@ class ManagerAi(ManagerAiBase, table=True):
if this_game.ai_team == 'home': if this_game.ai_team == 'home':
run_diff = run_diff * -1 run_diff = run_diff * -1
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 to_base == 2: if to_base == 2:
runner = this_play.on_first
if runner is None:
log_exception(CardNotFoundException, f'Attempted to check a jump to 2nd base, but no runner found on first.')
match self.steal: match self.steal:
case 10: case 10:
this_resp.min_safe = 12 + num_outs this_resp.min_safe = 12 + num_outs
@ -322,14 +332,36 @@ class ManagerAi(ManagerAiBase, table=True):
case self.steal if self.steal > 2 and num_outs < 2 and run_diff <= 5: case self.steal if self.steal > 2 and num_outs < 2 and run_diff <= 5:
this_resp.min_safe = 16 + num_outs this_resp.min_safe = 16 + num_outs
case _: case _:
this_resp = 17 + num_outs this_resp.min_safe = 17 + num_outs
if self.steal > 7 and num_outs < 2 and run_diff <= 5: if self.steal > 7 and num_outs < 2 and run_diff <= 5:
this_resp.run_if_auto_jump = True this_resp.run_if_auto_jump = True
elif self.steal < 5: elif self.steal < 5:
this_resp.must_auto_jump = True this_resp.must_auto_jump = True
runner_card = runner.card.batterscouting.battingcard
if this_resp.run_if_auto_jump and runner_card.steal_auto:
this_resp.ai_note = f'- WILL SEND **{runner.player.name}** to second!'
elif this_resp.must_auto_jump and not runner_card.steal_auto:
this_resp.ai_note = f''
else:
jump_safe_range = runner_card.steal_high + battery_hold
nojump_safe_range = runner_card.steal_low + battery_hold
logger.info(f'jump_safe_range: {jump_safe_range} / nojump_safe_range: {nojump_safe_range}')
if this_resp.min_safe <= nojump_safe_range:
this_resp.ai_note = f'- SEND **{runner.player.name}** to second!'
elif this_resp.min_safe <= jump_safe_range:
this_resp.ai_note = f'- SEND **{runner.player.name}** to second if they get the jump'
elif to_base == 3: elif to_base == 3:
runner = this_play.on_second
if runner is None:
log_exception(CardNotFoundException, f'Attempted to check a jump to 3rd base, but no runner found on second.')
match self.steal: match self.steal:
case 10: case 10:
this_resp.min_safe = 12 + num_outs this_resp.min_safe = 12 + num_outs
@ -343,14 +375,39 @@ class ManagerAi(ManagerAiBase, table=True):
elif self.steal <= 5: elif self.steal <= 5:
this_resp.must_auto_jump = True this_resp.must_auto_jump = True
runner_card = runner.card.batterscouting.battingcard
if this_resp.run_if_auto_jump and runner_card.steal_auto:
this_resp.ai_note = f'- SEND **{runner.player.name}** to third!'
elif this_resp.must_auto_jump and not runner_card.steal_auto:
pass
else:
jump_safe_range = runner_card.steal_low + battery_hold
logger.info(f'jump_safe_range: {jump_safe_range}')
if this_resp.min_safe <= jump_safe_range:
this_resp.ai_note = f'- SEND **{runner.player.name}** to third!'
elif run_diff in [-1, 0]: elif run_diff in [-1, 0]:
runner = this_play.on_third
if runner is None:
log_exception(CardNotFoundException, f'Attempted to check a jump to home, but no runner found on third.')
if self.steal == 10: if self.steal == 10:
this_resp.min_safe = 5 this_resp.min_safe = 5
elif self.steal > 5:
this_resp.min_safe = 7
elif this_play.inning_num > 7 and self.steal >= 5: elif this_play.inning_num > 7 and self.steal >= 5:
this_resp.min_safe = 6 this_resp.min_safe = 6
elif self.steal > 5:
this_resp.min_safe = 7
runner_card = runner.card.batterscouting.battingcard
jump_safe_range = runner_card.steal_low - 9
if this_resp.min_safe <= jump_safe_range:
this_resp.ai_note = f'- SEND **{runner.player.name}** to third!'
logger.info(f'Returning jump resp to game {this_game.id}: {this_resp}')
return this_resp return this_resp
def tag_from_second(self, session: Session, this_game: Game) -> TagResponse: def tag_from_second(self, session: Session, this_game: Game) -> TagResponse:
@ -402,7 +459,6 @@ class ManagerAi(ManagerAiBase, table=True):
return this_resp return this_resp
def throw_at_uncapped(self, session: Session, this_game: Game) -> ThrowResponse: def throw_at_uncapped(self, session: Session, this_game: Game) -> ThrowResponse:
this_resp = ThrowResponse() this_resp = ThrowResponse()
this_play = this_game.current_play_or_none(session) this_play = this_game.current_play_or_none(session)
@ -577,6 +633,63 @@ class ManagerAi(ManagerAiBase, table=True):
return this_resp return this_resp
# @property
# def ai_note(self) -> str: # TODO: test these three functions with specific OBCs
# if self.inning_half == 'top':
# if self.game.ai_team == 'away':
# return self.batting_ai_note
# else:
# return self.pitching_ai_note
# else:
# if self.game.ai_team == 'away':
# return self.pitching_ai_note
# else:
# return self.batting_ai_note
# @property
# def batting_ai_note(self) -> str:
# ai_note = '' # TODO: migrate Manager AI to their own local model
# return ai_note
# @property
# def pitching_ai_note(self) -> str:
# def_alignment = self.defense_alignment(session)
# ai_note = ''
# # Holding Baserunners
# if self.starting_outs == 2 and self.on_base_code > 0:
# if self.on_base_code == 1:
# ai_note += f'- hold {self.on_first.player.name}\n'
# elif self.on_base_code == 2:
# ai_note += f'- hold {self.on_second.player.name}\n'
# elif self.on_base_code in [4, 5, 7]:
# ai_note += f'- hold {self.on_first.player.name} on first\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'
# # 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'
# else:
# 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'
# return ai_note
class CardsetBase(SQLModel): class CardsetBase(SQLModel):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
@ -1048,62 +1161,6 @@ class Play(PlayBase, table=True):
return game_string return game_string
@property
def pitching_ai_note(self) -> str:
ai_note = ''
# Holding Baserunners
if self.starting_outs == 2 and self.on_base_code > 0:
if self.on_base_code == 1:
ai_note += f'- hold {self.on_first.player.name}\n'
elif self.on_base_code == 2:
ai_note += f'- hold {self.on_second.player.name}\n'
elif self.on_base_code in [4, 5, 7]:
ai_note += f'- hold {self.on_first.player.name} on first\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'
# 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'
else:
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'
return ai_note
@property
def batting_ai_note(self) -> str:
ai_note = '' # TODO: migrate Manager AI to their own local model
return ai_note
@property
def ai_note(self) -> str: # TODO: test these three functions with specific OBCs
if self.inning_half == 'top':
if self.game.ai_team == 'away':
return self.batting_ai_note
else:
return self.pitching_ai_note
else:
if self.game.ai_team == 'away':
return self.pitching_ai_note
else:
return self.batting_ai_note
@property @property
def ai_is_batting(self) -> bool: def ai_is_batting(self) -> bool:
if self.game.ai_team is None: if self.game.ai_team is None:

View File

@ -854,3 +854,13 @@ def get_available_batters(session: Session, this_game: Game, this_team: Team) ->
logger.info(f'batters: {batters}') logger.info(f'batters: {batters}')
return batters return batters
def get_batter_card(this_card: Card = None, this_lineup: Lineup = None) -> BattingCard:
if this_card is not None:
logger.info(f'Getting batter card for {this_card.player.name}')
return this_card.batterscouting.battingcard
if this_lineup is not None:
logger.info(f'Getting batter card for {this_lineup.player.name}')
return this_lineup.card.batterscouting.battingcard
log_exception(KeyError, 'Either a Card or Lineup must be provided to get_batter_card')