Check for AI fatigue

This commit is contained in:
Cal Corum 2025-01-26 02:20:29 -06:00
parent 92a56017a9
commit 4a76d59481
4 changed files with 166 additions and 13 deletions

View File

@ -13,7 +13,7 @@ import sqlalchemy
from sqlmodel import func, or_
from api_calls import db_get
from command_logic.logic_gameplay import bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play
from command_logic.logic_gameplay import bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play
from dice import ab_roll
from exceptions import *
import gauntlets
@ -69,9 +69,9 @@ class Gameplay(commands.Cog):
this_play = activate_last_play(session, this_game)
except Exception as e:
this_play = this_game.initialize_play(session)
if this_play is None:
log_exception(PlayNotFoundException, f'Attempting to display gamestate, but cannot find current play')
finally:
if this_play is None:
log_exception(PlayNotFoundException, f'Attempting to display gamestate, but cannot find current play')
if is_game_over(this_play):
logger.info(f'Game {this_play.game.id} seems to be over')
@ -98,6 +98,14 @@ class Gameplay(commands.Cog):
await self.post_play(session, interaction, this_play)
await interaction.channel.send(content=f'I let Cal know his bot is stupid')
if this_play.pitcher.is_fatigued and not this_play.ai_is_batting:
if this_play.managerai.replace_pitcher(session, this_play.game):
logger.info(f'Running a pitcher sub')
await interaction.edit_original_response(content='The AI is making a pitching change...')
new_pitcher_card = await select_ai_reliever(session, this_play.pitcher.team, this_play)
new_pitcher_lineup = substitute_player(session, this_play, this_play.pitcher, new_pitcher_card, 'P')
logger.info(f'Sub complete')
scorebug_buttons, this_ab_roll = None, None
scorebug_embed = await get_scorebug_embed(session, this_play.game, full_length=full_length, classic=CLASSIC_EMBED)

View File

@ -16,8 +16,8 @@ from exceptions import *
from helpers import COLORS, DEFENSE_LITERAL, SBA_COLOR, get_channel, team_role
from in_game.ai_manager import get_starting_lineup
from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check
from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, Team, Play
from in_game.gameplay_queries import get_active_games_by_team, get_available_batters, get_batter_card, get_batting_statline, get_pitching_statline, 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_models import BattingCard, Card, Game, Lineup, PositionRating, RosterLink, Team, Play
from in_game.gameplay_queries import get_active_games_by_team, get_available_batters, get_batter_card, get_batting_statline, get_game_cardset_links, get_or_create_ai_card, get_pitcher_runs_by_innings, get_pitching_statline, get_plays_by_pitcher, 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, SelectBatterSub, SelectStartingPitcher, SelectViewDefense
@ -475,7 +475,6 @@ def complete_play(session:Session, this_play: Play):
on_first, on_second, on_third = None, None, None
logger.info(f'Running bulk checks')
is_go_ahead = False
if nso >= 3:
switch_sides = True
obc = 0
@ -557,12 +556,46 @@ def complete_play(session:Session, this_play: Play):
if outs is None:
outs = 0
pow_outs = new_pitcher.card.pitcherscouting.pitchingcard.starter_rating * 3
if outs >= pow_outs and not new_pitcher.is_fatigued:
new_pitcher.is_fatigued = True
if not new_pitcher.is_fatigued:
if outs >= pow_outs:
logger.info(f'Pitcher is beyond POW - adding fatigue')
new_pitcher.is_fatigued = True
elif new_pitcher.replacing_id is None:
total_runs = session.exec(select(func.count(Play.id)).where(
Play.game == this_play.game, Play.pitcher == new_pitcher, Play.run == 1
)).one()
logger.info(f'Runs allowed by {new_pitcher.player.name_with_desc}: {total_runs}')
if total_runs >= 5:
if total_runs >= 7:
logger.info(f'Starter has allowed 7+ runs - adding fatigue')
new_pitcher.is_fatigued = True
else:
last_two = [x for x in range(this_play.inning_num, this_play.inning_num - 2, -1) if x > 0]
runs_last_two = get_pitcher_runs_by_innings(session, this_play.game, new_pitcher, last_two)
logger.info(f'Runs allowed last two innings: {runs_last_two}')
if runs_last_two >= 6:
logger.info(f'Starter has allowed at least six in the last two - adding fatigue')
new_pitcher.is_fatigued = True
else:
runs_this_inning = get_pitcher_runs_by_innings(session, this_play.game, new_pitcher, [this_play.inning_num])
logger.info(f'Runs allowed this inning: {runs_this_inning}')
if runs_this_inning >= 5:
logger.info(f'Starter has allowed at least five this inning - adding fatigue')
new_pitcher.is_fatigued = True
session.add(new_pitcher)
if outs >= (pow_outs - 3):
in_pow = True
if not new_pitcher.is_fatigued:
logger.info(f'checking for runners in POW')
runners_in_pow = session.exec(select(func.count(Play.id)).where(
Play.game == this_play.game, Play.pitcher == new_pitcher, Play.in_pow == True, Play.batter_final != None
)).one()
logger.info(f'runners in pow: {runners_in_pow}')
if runners_in_pow >= 3:
new_pitcher.is_fatigued = True
else:
in_pow = False
@ -1385,6 +1418,7 @@ async def frame_checks(session: Session, interaction: discord.Interaction, this_
session.refresh(this_play)
return this_play
async def check_uncapped_advance(session: Session, interaction: discord.Interaction, this_play: Play, lead_runner: Lineup, lead_base: int, trail_runner: Lineup, trail_base: int):
this_game = this_play.game
outfielder = await show_outfield_cards(session, interaction, this_play)
@ -1857,7 +1891,7 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact
is_defense_throwing = await ask_confirm(
interaction=interaction,
question=f'{lead_runner.player.name} is advancing {TO_BASE[lead_base]} with a safe range of **1->{lead_safe_range if lead_base == 3 else {lead_safe_range - 1}}**! Is the defense throwing?',
question=f'{lead_runner.player.name} is advancing {TO_BASE[lead_base]} with a safe range of **1->{lead_safe_range if lead_base == 3 else lead_safe_range - 1}**! Is the defense throwing?',
label_type='yes'
)
@ -2409,7 +2443,7 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play:
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
# TODO: Full questionnaire
pass
if hit_result == 'SPD' and not is_rare_play:
@ -3620,3 +3654,77 @@ async def new_game_conflicts(session: Session, interaction: discord.Interaction)
content=f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything pops up?'
)
log_exception(GameException, f'{interaction.user} attempted to start a new game in {interaction.channel.name} so they were redirected to {PUBLIC_FIELDS_CATEGORY_NAME}')
async def select_ai_reliever(session: Session, ai_team: Team, this_play: Play) -> Card:
logger.info(f'Selecting an AI reliever')
ai_score = this_play.away_score if this_play.game.away_team_id == ai_team.id else this_play.home_score
human_score = this_play.home_score if this_play.game.away_team_id == ai_team.id else this_play.away_score
logger.info(f'scores - ai: {ai_score} / human: {human_score}')
if abs(ai_score - human_score) >= 7:
need = 'length'
elif this_play.inning_num >= 9 and abs(ai_score - human_score) <= 3:
need = 'closer'
elif this_play.inning_num in [7, 8] and abs(ai_score - human_score) <= 3:
need = 'setup'
elif abs(ai_score - human_score) <= 3:
need = 'middle'
else:
need = 'length'
logger.info(f'need: {need}')
used_pitchers = get_game_lineups(session, this_play.game, ai_team, is_active=False)
used_player_ids = [f'{this_play.pitcher.player_id}']
for x in used_pitchers:
used_player_ids.append(f'{x.player_id}')
logger.info(f'used ids: {used_player_ids}')
id_string = ','.join(used_player_ids)
all_links = get_game_cardset_links(session, this_play.game)
if len(all_links) > 0:
cardset_string = ''
for x in all_links:
cardset_string += f'&cardset_id={x.cardset_id}'
else:
cardset_string = ''
logger.info(f'cardset_string: {cardset_string}')
rp_json = await db_get(f'teams/{ai_team.id}/rp/{this_play.game.game_type.split("-run")[0]}?need={need}&used_pitcher_ids={id_string}{cardset_string}')
rp_player = await get_player_or_none(session, player_id=get_player_id_from_dict(rp_json))
if rp_player is None:
log_exception(PlayerNotFoundException, f'Reliever not found for the {ai_team.lname}')
logger.info(f'rp_player: {rp_player}')
rp_card = await get_or_create_ai_card(session, rp_player, ai_team)
logger.info(f'rp_card: {rp_card}')
return rp_card
def substitute_player(session, this_play: Play, old_player: Lineup, new_player: Card, position: str) -> Lineup:
logger.info(f'Substituting {new_player.player.name_with_desc} in for {old_player.card.player.name_with_desc} at {position}')
new_lineup = Lineup(
team=old_player.team,
player=new_player.player,
card=new_player,
position=position,
batting_order=old_player.batting_order,
game=this_play.game,
after_play=max(this_play.play_num - 1, 0),
replacing_id=old_player.id
)
logger.info(f'new_lineup: {new_lineup}')
session.add(new_lineup)
logger.info(f'De-activating last player')
old_player.active = False
session.add(old_player)
session.commit()
session.refresh(new_lineup)
return new_lineup

View File

@ -658,6 +658,15 @@ class ManagerAi(ManagerAiBase, table=True):
logger.info(f'gb_decide_throw response: {this_resp}')
return this_resp
def replace_pitcher(self, session: Session, this_game: Game):
this_play = this_game.current_play_or_none(session)
if this_play is None:
raise GameException(f'No game found while checking replace_pitcher')
# TODO: Add replace pitcher algorithms
return False
class CardsetBase(SQLModel):
id: int | None = Field(default=None, primary_key=True)

View File

@ -7,7 +7,7 @@ import pydantic
from sqlalchemy import func
from api_calls import db_get, db_post
from sqlmodel import col
from in_game.gameplay_models import CACHE_LIMIT, BatterScouting, BatterScoutingBase, BattingCard, BattingCardBase, BattingRatings, BattingRatingsBase, Card, CardBase, Cardset, CardsetBase, Lineup, PitcherScouting, PitchingCard, PitchingCardBase, PitchingRatings, PitchingRatingsBase, Player, PlayerBase, PositionRating, PositionRatingBase, RosterLink, Session, Team, TeamBase, select, or_, Game, Play
from in_game.gameplay_models import CACHE_LIMIT, BatterScouting, BatterScoutingBase, BattingCard, BattingCardBase, BattingRatings, BattingRatingsBase, Card, CardBase, Cardset, CardsetBase, GameCardsetLink, Lineup, PitcherScouting, PitchingCard, PitchingCardBase, PitchingRatings, PitchingRatingsBase, Player, PlayerBase, PositionRating, PositionRatingBase, RosterLink, Session, Team, TeamBase, select, or_, Game, Play
from exceptions import DatabaseError, PositionNotFoundException, log_exception, PlayNotFoundException
@ -1033,4 +1033,32 @@ def get_pitching_statline(session: Session, this_lineup: Lineup) -> str:
pit_string += f', {number_string}K'
return pit_string
def get_game_cardset_links(session: Session, this_game: Game) -> list[GameCardsetLink]:
logger.info(f'Getting game cardset links for game: {this_game}')
cardset_links = session.exec(select(GameCardsetLink).where(GameCardsetLink.game == this_game)).all()
logger.info(f'links: {cardset_links}')
return cardset_links
def get_plays_by_pitcher(session: Session, this_game: Game, this_lineup: Lineup, reversed: bool = False) -> list[Play]:
logger.info(f'Getting all pitching plays for {this_lineup.card.player.name_with_desc}')
statement = select(Play).where(Play.game == this_game, Play.pitcher == this_lineup)
if reversed:
statement = statement.order_by(Play.id.desc())
all_plays = session.exec(statement).all()
logger.info(f'all_plays: {all_plays}')
return all_plays
def get_pitcher_runs_by_innings(session: Session, this_game: Game, this_pitcher: Lineup, innings: list[int]) -> int:
logger.info(f'Checking runs for {this_pitcher.player.name_with_desc} in innings: {innings}')
runs = session.exec(select(func.count(Play.id)).where(
Play.game == this_game, Play.pitcher == this_pitcher, Play.run == 1, Play.inning_num.in_(innings)
)).one()
logger.info(f'runs: {runs}')
return runs