Add DTwentyRoll

Add /log lineout
Add ManagerAi.tag_from_third()
This commit is contained in:
Cal Corum 2024-12-06 20:41:38 -06:00
parent 1e9e79916f
commit 95ee071ef8
4 changed files with 378 additions and 64 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, frame_checks, 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 command_logic.logic_gameplay import advance_runners, bunts, chaos, complete_game, doubles, flyballs, frame_checks, 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, lineouts, 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
@ -433,7 +433,7 @@ class Gameplay(commands.Cog):
session,
interaction,
this_play,
buffer_message='Double logged' if this_play.starting_outs + this_play.outs < 3 and ((this_play.on_second and flyball_type == 'b') or (this_play.on_third and flyball_type == '?b')) else None
buffer_message='Double logged' if this_play.starting_outs + this_play.outs < 3 and ((this_play.on_second and flyball_type == 'b') or (this_play.on_third and flyball_type == 'b?')) else None
)
@group_log.command(name='frame-pitch', description=f'Walk/strikeout split; determined by home plate umpire')
@ -451,6 +451,16 @@ class Gameplay(commands.Cog):
buffer_message='Frame check logged'
)
@group_log.command(name='lineout', description='Lineouts: one out, ballpark, max outs')
async def log_lineout(self, interaction: discord.Interaction, lineout_type: Literal['one-out', 'ballpark', 'max-outs']):
with Session(engine) as session:
this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='log lineout')
logger.info(f'log lineout - this_play: {this_play}')
this_play = await lineouts(session, interaction, this_play, lineout_type)
await self.complete_and_post_play(session, interaction, this_play, buffer_message='Lineout logged' if this_play.on_base_code > 3 else None)
@group_log.command(name='single', description='Singles: *, **, ballpark, uncapped')
async def log_single(
self, interaction: discord.Interaction, single_type: Literal['*', '**', 'ballpark', 'uncapped']):

View File

@ -10,7 +10,7 @@ from sqlalchemy import delete
from typing import Literal
from api_calls import db_delete, db_get, db_post
from dice import frame_plate_check, sa_fielding_roll
from dice import d_twenty_roll, frame_plate_check, sa_fielding_roll
from exceptions import *
from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel
from in_game.game_helpers import legal_check
@ -870,94 +870,254 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play
if this_play.starting_outs < 2 and this_play.on_second:
logger.debug(f'calling of embed')
await show_outfield_cards(session, interaction, this_play)
this_of = await show_outfield_cards(session, interaction, this_play)
of_rating = await get_position(session, this_of.card, this_of.position)
of_mod = 0
if this_of.position == 'LF':
of_mod = -2
elif this_of.position == 'RF':
of_mod = 2
logger.debug(f'done with of embed')
runner = this_play.on_second.player
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
runner_lineup = this_play.on_second
runner = runner_lineup.player
max_safe = runner_lineup.card.batterscouting.battingcard.running + of_rating.arm + of_mod
min_out = 20 + of_rating.arm + of_mod
if (min_out >= 20 and max_safe >= 20) or min_out > 20:
min_out = 21
min_hold = max_safe + 1
safe_string = f'1{" - " if max_safe > 1 else ""}'
if max_safe > 1:
if max_safe <= 20:
safe_string += f'{max_safe}'
else:
safe_string += f'20'
if min_out > 20:
out_string = 'None'
else:
out_string = f'{min_out}{" - 20" if min_out < 20 else ""}'
hold_string = ''
if max_safe != min_out:
hold_string += f'{min_hold}'
if min_out - 1 > min_hold:
hold_string += f' - {min_out - 1}'
ranges_embed = this_of.team.embed
ranges_embed.title = f'Tag Play'
ranges_embed.description = f'{this_of.team.abbrev} {this_of.position} {this_of.card.player.name}\'s Throw vs {runner.name}'
ranges_embed.add_field(name=f'{this_of.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}')
ranges_embed.add_field(name=f'Runner Speed', value=runner_lineup.card.batterscouting.battingcard.running)
ranges_embed.add_field(name=f'{this_of.position} Mod', value=f'{of_mod}', inline=False)
ranges_embed.add_field(name='Safe Range', value=safe_string)
ranges_embed.add_field(name='Hold Range', value=hold_string)
ranges_embed.add_field(name='Out Range', value=out_string)
await interaction.channel.send(
content=None,
embed=ranges_embed
)
if this_play.ai_is_batting:
tag_resp = this_play.managerai.tag_from_second(session, this_game)
q_text = f'{runner.name} will attempt to advance to third if the safe range is **{tag_resp.min_safe}+**, are they going?'
else:
q_text = f'Is {runner.name} attempting to tag up to third?'
question = await interaction.channel.send(
content=q_text,
view=view
)
await view.wait()
if view.value:
await question.delete()
view = ButtonOptions(
responders=[interaction.user], timeout=60,
labels=['Tagged Up', 'Hold at 2nd', 'Out at 3rd', None, None]
)
question = await interaction.channel.send(
f'What was the result of {runner.name} tagging from second?', view=view
)
await view.wait()
if view.value:
await question.delete()
if view.value == 'Tagged Up':
this_play.on_second_final = 3
elif view.value == 'Out at 3rd':
num_outs += 1
this_play.on_second_final = None
this_play.outs = num_outs
logger.info(f'tag_resp: {tag_resp}')
tagging_from_second = tag_resp.min_safe >= max_safe
if tagging_from_second:
await interaction.channel.send(
content=f'**{runner.name}** is tagging from second!'
)
else:
await question.delete()
await interaction.channel.send(
content=f'**{runner.name}** is holding at second.'
)
else:
await question.delete()
tagging_from_second = await ask_confirm(
interaction,
question=f'Is {runner.name} attempting to tag up from second?',
label_type='yes',
)
if tagging_from_second:
this_roll = d_twenty_roll(this_play.pitcher.team, this_play.game)
if min_out is not None and this_roll.d_twenty >= min_out:
result = 'out'
elif this_roll.d_twenty <= max_safe:
result = 'safe'
else:
result = 'holds'
await interaction.channel.send(content=None, embeds=this_roll.embeds)
is_correct = await ask_confirm(
interaction,
question=f'Looks like {runner.name} {"is" if result != 'holds' else ""} **{result.upper()}** at {"third" if result != 'holds' else "second"}! Is that correct?',
label_type='yes',
delete_question=False
)
if not is_correct:
view = ButtonOptions(
responders=[interaction.user], timeout=60,
labels=['Safe at 3rd', 'Hold at 2nd', 'Out at 3rd', None, None]
)
question = await interaction.channel.send(
f'What was the result of {runner.name} tagging from second?', view=view
)
await view.wait()
if view.value:
await question.delete()
if view.value == 'Tagged Up':
result = 'safe'
elif view.value == 'Out at 3rd':
result = 'out'
else:
result = 'holds'
else:
await question.delete()
if result == 'safe':
this_play.on_second_final = 3
elif result == 'out':
num_outs += 1
this_play.on_second_final = None
this_play.outs = num_outs
elif flyball_type == 'b?':
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
this_play = advance_runners(session, this_play, 0)
if this_play.starting_outs < 2 and this_play.on_third:
logger.debug(f'calling of embed')
await show_outfield_cards(session, interaction, this_play)
this_of = await show_outfield_cards(session, interaction, this_play)
of_rating = await get_position(session, this_of.card, this_of.position)
logger.debug(f'done with of embed')
runner = this_play.on_second.player
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
runner_lineup = this_play.on_third
runner = runner_lineup.player
max_safe = runner_lineup.card.batterscouting.battingcard.running + of_rating.arm
safe_string = f'1{" - " if max_safe > 1 else ""}'
if max_safe > 1:
if max_safe <= 20:
safe_string += f'{max_safe - 1}'
else:
safe_string += f'20'
if max_safe == 20:
out_string = 'None'
catcher_string = '20'
elif max_safe > 20:
out_string = 'None'
catcher_string = 'None'
elif max_safe == 19:
out_string = 'None'
catcher_string = '19 - 20'
elif max_safe == 18:
out_string = f'20'
catcher_string = '18 - 19'
else:
out_string = f'{max_safe + 2} - 20'
catcher_string = f'{max_safe} - {max_safe + 1}'
true_max_safe = max_safe - 1
true_min_out = max_safe + 2
ranges_embed = this_play.batter.team.embed
ranges_embed.title = f'Play at the Plate'
ranges_embed.description = f'{runner.name} vs {this_of.card.player.name}\'s Throw'
ranges_embed.add_field(name=f'{this_of.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}')
ranges_embed.add_field(name=f'Runner Speed', value=runner_lineup.card.batterscouting.battingcard.running)
ranges_embed.add_field(name="", value="", inline=False)
ranges_embed.add_field(name='Safe Range', value=safe_string)
ranges_embed.add_field(name='Catcher Check', value=catcher_string)
ranges_embed.add_field(name='Out Range', value=out_string)
await interaction.channel.send(
content=None,
embed=ranges_embed
)
if this_play.ai_is_batting:
tag_resp = this_play.managerai.tag_from_second(session, this_game)
q_text = f'{runner.name} will attempt to advance home if the safe range is **{tag_resp.min_safe}+**, are they going?'
tag_resp = this_play.managerai.tag_from_third(session, this_game)
logger.info(f'tag_resp: {tag_resp}')
tagging_from_third = tag_resp.min_safe <= max_safe
if tagging_from_third:
await interaction.channel.send(
content=f'**{runner.name}** is tagging from third!'
)
else:
await interaction.channel.send(
content=f'**{runner.name}** is holding at third.'
)
else:
q_text = f'Is {runner.name} attempting to tag up and go home?'
question = await interaction.channel.send(
content=q_text,
view=view
)
await view.wait()
if view.value:
await question.delete()
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
question = await interaction.channel.send(
f'Was {runner.name} thrown out?', view=view
tagging_from_third = await ask_confirm(
interaction,
question=f'Is {runner.name} attempting to tag up from third?',
label_type='yes',
)
await view.wait()
if tagging_from_third:
this_roll = d_twenty_roll(this_play.batter.team, this_play.game)
if this_roll.d_twenty <= true_max_safe:
result = 'safe'
q_text = f'Looks like {runner.name} is SAFE at home!'
out_at_home = False
elif this_roll.d_twenty >= true_min_out:
result = 'out'
q_text = f'Looks like {runner.name} is OUT at home!'
out_at_home = True
else:
result = 'catcher'
q_text = f'Looks like this is a check for {this_play.catcher.player.name} to block the plate!'
await interaction.channel.send(content=None, embeds=this_roll.embeds)
if view.value:
await question.delete()
is_correct = await ask_confirm(
interaction,
question=f'{q_text} Is that correct?',
label_type='yes',
delete_question=False
)
if not is_correct:
out_at_home = await ask_confirm(
interaction,
question=f'Was {runner.name} thrown out?',
label_type='yes'
)
elif result == 'catcher':
catcher_rating = await get_position(session, this_play.catcher.card, 'C')
this_roll = d_twenty_roll(this_play.catcher.team, this_play.game)
if catcher_rating.range == 1:
safe_range = 3
elif catcher_rating.range == 2:
safe_range = 7
elif catcher_rating.range == 3:
safe_range = 11
elif catcher_rating.range == 4:
safe_range = 15
elif catcher_rating.range == 5:
safe_range = 19
out_at_home = True
if this_roll.d_twenty <= safe_range:
out_at_home = False
if out_at_home:
num_outs += 1
this_play.on_third_final = 99
this_play.on_third_final = None
this_play.outs = num_outs
else:
await question.delete()
this_play.ab = 0
this_play.rbi = 1
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third, this_play)
else:
await question.delete()
elif flyball_type == 'c':
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
@ -970,6 +1130,100 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play
return this_play
async def lineouts(session: Session, interaction: discord.Interaction, this_play: Play, lineout_type: Literal['one-out', 'ballpark', 'max-outs']) -> Play:
"""
Commits this_play
"""
num_outs = 1
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
this_play.bplo = 1 if lineout_type == 'ballpark' else 0
this_play = advance_runners(session, this_play, num_bases=0)
if lineout_type == 'max-outs' and this_play.on_base_code > 0 and this_play.starting_outs < 2:
logger.info(f'Lomax going in')
if this_play.on_base_code <= 3 or this_play.starting_outs == 1:
logger.info(f'Lead runner is out')
this_play.outs = 2
if this_play.on_third is not None:
this_play.on_third_final = None
elif this_play.on_second is not None:
this_play.on_second_final = None
elif this_play.on_first is not None:
this_play.on_first_final = None
else:
logger.info(f'Potential triple play')
this_roll = d_twenty_roll(this_play.pitcher.team, this_play.game)
ranges_embed = this_play.pitcher.team.embed
ranges_embed.title = f'Potential Triple Play'
ranges_embed.description = f'{this_play.pitcher.team.lname}'
ranges_embed.add_field(name=f'Double Play Range', value='1 - 13')
ranges_embed.add_field(name=f'Triple Play Range', value='14 - 20')
await interaction.edit_original_response(
content=None,
embeds=[ranges_embed, *this_roll.embeds]
)
if this_roll.d_twenty > 13:
logger.info(f'Roll of {this_roll.d_twenty} is a triple play!')
num_outs = 3
else:
logger.info(f'Roll of {this_roll.d_twenty} is a double play!')
num_outs = 2
is_correct = await ask_confirm(
interaction,
question=f'Looks like this is a {"triple" if num_outs == 3 else "double"} play! Is that correct?'
)
if not is_correct:
logger.warning(f'{interaction.user.name} marked this result incorrect')
num_outs = 2 if num_outs == 3 else 3
if num_outs == 2:
logger.info(f'Lead baserunner is out')
this_play.outs = 2
out_marked = False
if this_play.on_third is not None:
this_play.on_third_final = None
out_marked = True
elif this_play.on_second and not out_marked:
this_play.on_second_final = None
out_marked = True
elif this_play.on_first and not out_marked:
this_play.on_first_final = None
out_marked = True
else:
logger.info(f'Two baserunners are out')
this_play.outs = 3
outs_marked = 1
if this_play.on_third is not None:
this_play.on_third_final = None
outs_marked += 1
elif this_play.on_second is not None:
this_play.on_second_final = None
outs_marked += 1
elif this_play.on_first is not None and outs_marked < 3:
this_play.on_first_final = None
outs_marked += 1
session.add(this_play)
session.commit()
session.refresh(this_play)
return this_play
async def frame_checks(session: Session, interaction: discord.Interaction, this_play: Play):
"""
Commits this_play

24
dice.py
View File

@ -40,6 +40,10 @@ class FrameRoll(DiceRoll):
is_walk: bool = False
class DTwentyRoll(DiceRoll):
pass
def get_dice_embed(team: Team = None, embed_title: str = None):
if team:
embed = discord.Embed(
@ -2837,6 +2841,26 @@ def ab_roll(this_team: Team, this_game: Game, allow_chaos: bool = True) -> AbRol
return this_roll
def d_twenty_roll(this_team: Team, this_game: Game) -> DTwentyRoll:
logger.info(f'Rolling a d20 for {this_team.sname} in Game {this_game.id}')
this_roll = DTwentyRoll(
d_twenty=random.randint(1, 20)
)
this_roll.roll_message = f'```md\n# {this_roll.d_twenty}\nDetails:[1d20 ({this_roll.d_twenty})]\n```'
logger.info(f'D20 roll with message: {this_roll}')
embed = get_dice_embed(this_team)
embed.add_field(
name=f'D20 roll for the {this_team.sname}',
value=this_roll.roll_message
)
this_roll.embeds = [embed]
logger.info(f'Game {this_game.id} | Team {this_team.id} ({this_team.abbrev}): {this_roll.roll_message}')
return this_roll
def jump_roll(this_team: Team, this_game: Game) -> JumpRoll:
"""
Check for a baserunner's jump before stealing

View File

@ -563,6 +563,32 @@ class ManagerAi(ManagerAiBase, table=True):
return this_resp
def tag_from_third(self, session: Session, this_game: Game) -> TagResponse:
this_resp = TagResponse()
this_play = this_game.current_play_or_none(session)
if this_play is None:
raise GameException(f'No game found while checking tag_from_third')
ai_rd = this_play.ai_run_diff
aggression_mod = abs(self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5)
adjusted_running = self.running + aggression_mod
if adjusted_running >= 8:
this_resp.min_safe = 7
elif adjusted_running >= 5:
this_resp.min_safe = 10
else:
this_resp.min_safe = 12
if ai_rd in [-1, 0]:
this_resp.min_safe -= 2
if this_play.starting_outs == 1:
this_resp.min_safe -= 2
return this_resp
def throw_at_uncapped(self, session: Session, this_game: Game) -> ThrowResponse:
this_resp = ThrowResponse()
this_play = this_game.current_play_or_none(session)