Added ThrowResponse as managerai response
Added /log single, all but uncapped complete Added check_uncapped_advance, ai on defense complete
This commit is contained in:
parent
6e4904282e
commit
0327b6af62
@ -8,7 +8,7 @@ from discord.ext import commands, tasks
|
||||
import pygsheets
|
||||
|
||||
from api_calls import db_get
|
||||
from command_logic.logic_gameplay import flyballs, get_lineups_from_sheets, checks_log_interaction
|
||||
from command_logic.logic_gameplay import flyballs, get_lineups_from_sheets, checks_log_interaction, complete_play, singles
|
||||
from exceptions import GameNotFoundException, TeamNotFoundException, PlayNotFoundException, GameException
|
||||
from helpers import PD_PLAYERS_ROLE_NAME, team_role, user_has_role, random_gif, random_from_list
|
||||
|
||||
@ -21,6 +21,9 @@ from in_game.gameplay_queries import get_channel_game_or_none, get_active_games_
|
||||
from utilities.buttons import Confirm
|
||||
|
||||
|
||||
CLASSIC_EMBED = True
|
||||
|
||||
|
||||
class Gameplay(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
@ -343,18 +346,49 @@ class Gameplay(commands.Cog):
|
||||
)
|
||||
|
||||
group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game')
|
||||
|
||||
@group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c')
|
||||
async def log_flyball(self, interaction: discord.Interaction, flyball_type: Literal['a', 'b', 'ballpark', 'b?', 'c']):
|
||||
with Session(engine) as session:
|
||||
this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='flyball')
|
||||
|
||||
this_play.locked = True
|
||||
session.add(this_play)
|
||||
session.commit()
|
||||
this_play = await flyballs(session, interaction, this_game, this_play, flyball_type)
|
||||
logging.info(f'log flyball {flyball_type} - this_play: {this_play}')
|
||||
|
||||
this_play = await flyballs(session, interaction, this_game, this_play, flyball_type, comp_play=False)
|
||||
complete_play(session, this_play)
|
||||
|
||||
await interaction.edit_original_response(content=f'Pow goes teh fly ball!\n\n{this_play}')
|
||||
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')):
|
||||
await interaction.edit_original_response(content='Flyball logged')
|
||||
await interaction.channel.send(
|
||||
content=None,
|
||||
embed=this_play.game.get_scorebug_embed(session, full_length=False, classic=CLASSIC_EMBED)
|
||||
)
|
||||
else:
|
||||
await interaction.edit_original_response(
|
||||
content=None,
|
||||
embed=this_play.game.get_scorebug_embed(session, full_length=False, classic=CLASSIC_EMBED)
|
||||
)
|
||||
|
||||
@group_log.command(name='single', description='Singles: *, **, ballpark, uncapped')
|
||||
async def log_single(
|
||||
self, interaction: discord.Interaction, single_type: Literal['*', '**', 'ballpark', 'uncapped']):
|
||||
with Session(engine) as session:
|
||||
this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='single')
|
||||
|
||||
this_play = await singles(session, interaction, this_game, this_play, single_type)
|
||||
logging.info(f'log single {single_type} - this_play: {this_play}')
|
||||
|
||||
if this_play.starting_outs + this_play.outs < 3 and ((this_play.on_first or this_play.on_second) and single_type == 'uncapped'):
|
||||
await interaction.edit_original_response(content='Single logged')
|
||||
await interaction.channel.send(
|
||||
content=None,
|
||||
embed=this_play.game.get_scorebug_embed(session, full_length=False, classic=CLASSIC_EMBED)
|
||||
)
|
||||
else:
|
||||
await interaction.edit_original_response(
|
||||
content=None,
|
||||
embed=this_play.game.get_scorebug_embed(session, full_length=False, classic=CLASSIC_EMBED)
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import discord
|
||||
import pandas as pd
|
||||
from sqlmodel import Session, select
|
||||
from sqlmodel import Session, select, func
|
||||
from typing import Literal
|
||||
|
||||
from exceptions import *
|
||||
@ -86,6 +87,9 @@ def get_wpa(this_play: Play, next_play: Play):
|
||||
|
||||
|
||||
def complete_play(session:Session, this_play: Play):
|
||||
"""
|
||||
Commits this_play and new_play
|
||||
"""
|
||||
nso = this_play.starting_outs + this_play.outs
|
||||
runs_scored = 0
|
||||
on_first, on_second, on_third = None, None, None
|
||||
@ -273,6 +277,9 @@ async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, thi
|
||||
|
||||
|
||||
async def checks_log_interaction(session: Session, interaction: discord.Interaction, command_name: str) -> tuple[Game, Team, Play]:
|
||||
"""
|
||||
Commits this_play
|
||||
"""
|
||||
await interaction.response.defer(thinking=True)
|
||||
this_game = get_channel_game_or_none(session, interaction.channel_id)
|
||||
if this_game is None:
|
||||
@ -291,30 +298,62 @@ async def checks_log_interaction(session: Session, interaction: discord.Interact
|
||||
raise TeamNotFoundException(f'Hm, I was not able to find a gauntlet team for you.')
|
||||
|
||||
if not owner_team.id in [this_game.away_team_id, this_game.home_team_id]:
|
||||
logging.exception(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.')
|
||||
raise TeamNotFoundException('Bruh. Only GMs of the active teams can log plays.')
|
||||
if interaction.user.id != 258104532423147520:
|
||||
logging.exception(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.')
|
||||
raise TeamNotFoundException('Bruh. Only GMs of the active teams can log plays.')
|
||||
else:
|
||||
await interaction.channel.send(f'Cal is bypassing the GM check to run the {command_name} command')
|
||||
|
||||
this_play = this_game.current_play_or_none(session)
|
||||
if this_play is None:
|
||||
logging.error(f'{command_name} command: No play found for Game ID {this_game.id} - attempting to initialize play')
|
||||
this_play = this_game.initialize_play(session)
|
||||
|
||||
this_play.locked = True
|
||||
session.add(this_play)
|
||||
session.commit()
|
||||
|
||||
return this_game, owner_team, this_play
|
||||
|
||||
|
||||
def log_run_scored(session: Session, runner: Lineup, is_earned: bool = True):
|
||||
def log_run_scored(session: Session, runner: Lineup, this_play: Play, is_earned: bool = True):
|
||||
"""
|
||||
Commits last_ab
|
||||
"""
|
||||
last_ab = get_players_last_pa(session, lineup_member=runner)
|
||||
last_ab.run = 1
|
||||
|
||||
errors = session.exec(select(func.count(Play.id)).where(
|
||||
Play.inning_num == last_ab.inning_num, Play.inning_half == last_ab.inning_half, Play.error == 1
|
||||
)).one()
|
||||
outs = session.exec(select(func.sum(Play.outs)).where(
|
||||
Play.inning_num == last_ab.inning_num, Play.inning_half == last_ab.inning_half
|
||||
)).one()
|
||||
|
||||
if errors + outs + this_play.error >= 3:
|
||||
is_earned = False
|
||||
|
||||
last_ab.e_run = 1 if is_earned else 0
|
||||
session.add(last_ab)
|
||||
session.commit()
|
||||
return True
|
||||
|
||||
|
||||
def advance_runners(session: Session, this_play: Play, num_bases: int, is_error: bool = False, only_forced: bool = False):
|
||||
def advance_runners(session: Session, this_play: Play, num_bases: int, is_error: bool = False, only_forced: bool = False) -> Play:
|
||||
"""
|
||||
No commits
|
||||
"""
|
||||
this_play.rbi = 0
|
||||
|
||||
if only_forced:
|
||||
if num_bases == 0:
|
||||
if this_play.on_first is not None:
|
||||
this_play.on_first_final = 1
|
||||
if this_play.on_second_id is not None:
|
||||
this_play.on_second_final = 2
|
||||
if this_play.on_third_id is not None:
|
||||
this_play.on_third_final = 3
|
||||
|
||||
elif only_forced:
|
||||
if not this_play.on_first:
|
||||
if this_play.on_second:
|
||||
this_play.on_second_final = 2
|
||||
@ -326,12 +365,12 @@ 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)
|
||||
log_run_scored(session, this_play.on_third, this_play)
|
||||
this_play.rbi += 1 if not is_error else 0
|
||||
|
||||
if num_bases > 1:
|
||||
this_play.on_second_final = 4
|
||||
log_run_scored(session, this_play.on_second)
|
||||
log_run_scored(session, this_play.on_second, this_play)
|
||||
this_play.rbi += 1 if not is_error else 0
|
||||
elif num_bases == 1:
|
||||
this_play.on_second_final = 3
|
||||
@ -343,7 +382,7 @@ 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)
|
||||
log_run_scored(session, this_play.on_first, this_play)
|
||||
this_play.rbi += 1 if not is_error else 0
|
||||
elif num_bases == 2:
|
||||
this_play.on_first_final = 3
|
||||
@ -356,7 +395,7 @@ 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)
|
||||
log_run_scored(session, this_play.on_third, this_play)
|
||||
this_play.rbi += 1 if not is_error else 0
|
||||
else:
|
||||
this_play.on_third_final = 3
|
||||
@ -364,7 +403,7 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, is_error:
|
||||
if this_play.on_second:
|
||||
if num_bases > 1:
|
||||
this_play.on_second_final = 4
|
||||
log_run_scored(session, this_play.on_second)
|
||||
log_run_scored(session, this_play.on_second, this_play)
|
||||
this_play.rbi += 1 if not is_error else 0
|
||||
elif num_bases == 1:
|
||||
this_play.on_second_final = 3
|
||||
@ -374,7 +413,7 @@ 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)
|
||||
log_run_scored(session, this_play.on_first, this_play)
|
||||
this_play.rbi += 1 if not is_error else 0
|
||||
elif num_bases == 2:
|
||||
this_play.on_first_final = 3
|
||||
@ -387,9 +426,11 @@ def advance_runners(session: Session, this_play: Play, num_bases: int, is_error:
|
||||
this_play.batter_final = 4
|
||||
this_play.rbi += 1
|
||||
this_play.run = 1
|
||||
|
||||
return this_play
|
||||
|
||||
|
||||
async def show_outfield_cards(session: Session, interaction: discord.Interaction, this_play: Play):
|
||||
async def show_outfield_cards(session: Session, interaction: discord.Interaction, this_play: Play) -> Lineup:
|
||||
lf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='LF')
|
||||
cf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='CF')
|
||||
rf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='RF')
|
||||
@ -470,22 +511,20 @@ async def show_outfield_cards(session: Session, interaction: discord.Interaction
|
||||
return [lf, cf, rf][page_num]
|
||||
|
||||
|
||||
async def flyballs(session: Session, interaction: discord.Interaction, this_game: Game, this_play: Play, flyball_type: Literal['a', 'ballpark', 'b', 'b?', 'c'], comp_play: bool = True) -> Play:
|
||||
async def flyballs(session: Session, interaction: discord.Interaction, this_game: Game, this_play: Play, flyball_type: Literal['a', 'ballpark', 'b', 'b?', 'c']) -> Play:
|
||||
"""
|
||||
Commits this_play
|
||||
"""
|
||||
num_outs = 1
|
||||
|
||||
if flyball_type == 'a':
|
||||
this_play.pa = 1
|
||||
this_play.ab = 1
|
||||
this_play.outs = 1
|
||||
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)
|
||||
|
||||
if this_play.on_third:
|
||||
this_play.ab = 0
|
||||
|
||||
session.add(this_play)
|
||||
session.commit()
|
||||
|
||||
elif flyball_type == 'b' or flyball_type == 'ballpark':
|
||||
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
|
||||
@ -496,7 +535,7 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_game
|
||||
this_play.ab = 0
|
||||
this_play.rbi = 1
|
||||
this_play.on_third_final = 4
|
||||
log_run_scored(session, this_play.on_third)
|
||||
log_run_scored(session, this_play.on_third, this_play)
|
||||
|
||||
if this_play.starting_outs < 2 and this_play.on_second:
|
||||
logging.debug(f'calling of embed')
|
||||
@ -542,9 +581,6 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_game
|
||||
await question.delete()
|
||||
else:
|
||||
await question.delete()
|
||||
|
||||
session.add(this_play)
|
||||
session.commit()
|
||||
|
||||
elif flyball_type == 'b?':
|
||||
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
|
||||
@ -588,19 +624,236 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_game
|
||||
this_play.ab = 0
|
||||
this_play.rbi = 1
|
||||
this_play.on_third_final = 4
|
||||
log_run_scored(session, this_play.on_third)
|
||||
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
|
||||
|
||||
if comp_play:
|
||||
complete_play(session, this_play)
|
||||
advance_runners(session, this_play, num_bases=0)
|
||||
|
||||
session.add(this_play)
|
||||
session.commit()
|
||||
|
||||
session.refresh(this_play)
|
||||
return this_play
|
||||
|
||||
|
||||
|
||||
async def check_uncapped_advance(session: Session, interaction: discord.Interaction, this_game: Game, this_play: Play, lead_runner: Lineup, lead_base: int, trail_runner: Lineup, trail_base: int):
|
||||
outfielder = await show_outfield_cards(interaction, this_play)
|
||||
def_team = this_play.pitcher.team
|
||||
TO_BASE = {
|
||||
2: 'to second',
|
||||
3: 'to third',
|
||||
4: 'home'
|
||||
}
|
||||
AT_BASE = {
|
||||
2: 'at second',
|
||||
3: 'at third',
|
||||
4: 'at home'
|
||||
}
|
||||
|
||||
# Either there is no AI team or the AI is pitching
|
||||
if not this_game.ai_team or not this_play.ai_is_batting:
|
||||
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
||||
question = await interaction.channel.send(
|
||||
f'Is **{lead_runner.name}** being sent {TO_BASE[lead_base]}?', view=view
|
||||
)
|
||||
await view.wait()
|
||||
|
||||
# Human runner is attempting uncapped advance
|
||||
if view.value:
|
||||
await question.delete()
|
||||
|
||||
throw_resp = None
|
||||
if this_game.ai_team:
|
||||
throw_resp = this_play.managerai.throw_at_uncapped(session, this_game)
|
||||
|
||||
if throw_resp.cutoff:
|
||||
await interaction.channel.send(f'The {def_team.sname} will cut off the throw {TO_BASE[lead_base]}')
|
||||
if this_play.on_second == lead_runner:
|
||||
this_play.rbi += 1
|
||||
this_play.on_second_final = 4
|
||||
log_run_scored(session, lead_runner, this_play)
|
||||
else:
|
||||
this_play.on_first_final = 3
|
||||
|
||||
await asyncio.sleep(1)
|
||||
return this_play
|
||||
|
||||
else:
|
||||
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
||||
question = await interaction.channel.send(
|
||||
f'Is the defense throwing {TO_BASE[lead_base]} for {lead_runner.player.name}?', view=view
|
||||
)
|
||||
await view.wait()
|
||||
|
||||
# Human defense is throwing for lead runner
|
||||
if view.value:
|
||||
await question.delete()
|
||||
|
||||
# Human defense is cutting off the throw
|
||||
else:
|
||||
await question.delete()
|
||||
if this_play.on_second == lead_runner:
|
||||
this_play.rbi += 1
|
||||
this_play.on_second_final = 4
|
||||
log_run_scored(session, lead_runner, this_play)
|
||||
return this_play
|
||||
|
||||
# Human runner is advancing, defense is throwing
|
||||
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
||||
question = await interaction.channel.send(
|
||||
f'Will {trail_runner.player.name} be sent {TO_BASE[trail_base]} as the trail runner?', view=view
|
||||
)
|
||||
await view.wait()
|
||||
|
||||
# Trail runner is advancing
|
||||
if view.value:
|
||||
await question.delete()
|
||||
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
||||
view.confirm.label = 'Home Plate' if lead_base == 4 else 'Third Base'
|
||||
view.cancel.label = 'Third Base' if trail_base == 3 else 'Second Base'
|
||||
|
||||
play_at_trail = False
|
||||
if this_game.ai_team and 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 safe range is {throw_resp.trail_max_safe} or lower\n- Trail runner\'s safe range lower by at least {abs(throw_resp.trail_max_safe_delta)}.\n\nIs the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?'
|
||||
)
|
||||
|
||||
else:
|
||||
question = await interaction.channel.send(
|
||||
f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?',
|
||||
view=view
|
||||
)
|
||||
|
||||
await view.wait()
|
||||
|
||||
# Throw is going to lead runner
|
||||
if view.value:
|
||||
await question.delete()
|
||||
|
||||
# Throw is going to trail runner
|
||||
else:
|
||||
play_at_trail = True
|
||||
await question.delete()
|
||||
|
||||
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
||||
question = await interaction.channel.send(
|
||||
content=f'Was {trail_runner.player.name} thrown out {AT_BASE[trail_base]}?', view=view
|
||||
)
|
||||
await view.wait()
|
||||
|
||||
# Trail runner is thrown out
|
||||
if view.value:
|
||||
# Log out on play
|
||||
this_play.outs += 1
|
||||
|
||||
# Remove trail runner
|
||||
if this_play.on_first == trail_runner:
|
||||
this_play.on_first_final = None
|
||||
else:
|
||||
this_play.batter_final = None
|
||||
|
||||
await question.delete()
|
||||
|
||||
# Advance lead runner extra base
|
||||
if this_play.on_second == lead_runner:
|
||||
this_play.rbi += 1
|
||||
this_play.on_second_final = 4
|
||||
log_run_scored(session, lead_runner, this_play)
|
||||
|
||||
elif this_play.on_first == lead_runner:
|
||||
this_play.on_first_final += 1
|
||||
if this_play.on_first_final > 3:
|
||||
this_play.rbi += 1
|
||||
log_run_scored(session, lead_runner, this_play)
|
||||
|
||||
return this_play
|
||||
|
||||
# Ball is going to lead base, advance trail runner
|
||||
if this_play.on_first == trail_runner:
|
||||
this_play.on_first_final += 1
|
||||
|
||||
elif this_play.batter == trail_runner:
|
||||
this_play.batter_final += 1
|
||||
|
||||
else:
|
||||
log_exception(LineupsMissingException, f'Could not find trail runner to advance')
|
||||
|
||||
# Ball is going to lead base, ask if safe
|
||||
Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
||||
question = await interaction.channel.send(
|
||||
content=f'Was {lead_runner.player.name} thrown out {AT_BASE[lead_base]}?', view=view
|
||||
)
|
||||
await view.wait()
|
||||
|
||||
# Lead runner is thrown out
|
||||
if view.value:
|
||||
await question.delete()
|
||||
runner_out = True
|
||||
this_play.outs += 1
|
||||
|
||||
# Lead runner is safe
|
||||
else:
|
||||
runner_out = False
|
||||
|
||||
if this_play.on_second == lead_runner:
|
||||
this_play.on_second_final = None if runner_out else lead_base
|
||||
elif this_play.on_first == lead_runner:
|
||||
this_play.on_first_final = None if runner_out else lead_base
|
||||
else:
|
||||
log_exception(LineupsMissingException, f'Could not find lead runner to set final destination')
|
||||
|
||||
# Human lead runner is not advancing
|
||||
else:
|
||||
await question.delete()
|
||||
return this_play
|
||||
|
||||
elif this_play.ai_is_batting:
|
||||
pass
|
||||
|
||||
|
||||
async def singles(session: Session, interaction: discord.Interaction, this_game: Game, this_play: Play, single_type: Literal['*', '**', 'ballpark', 'uncapped']) -> Play:
|
||||
"""
|
||||
Commits this_play
|
||||
"""
|
||||
this_play.pa, this_play.ab, this_play.hit, this_play.batter_final = 1, 1, 1, 1
|
||||
|
||||
if single_type == '**':
|
||||
advance_runners(session, this_play, num_bases=2)
|
||||
|
||||
elif single_type in ['*', 'ballpark']:
|
||||
advance_runners(session, this_play, num_bases=1)
|
||||
this_play.bp1b = 1 if single_type == 'ballpark' else 0
|
||||
|
||||
elif single_type == 'uncapped':
|
||||
advance_runners(this_play.id, 1)
|
||||
|
||||
if this_play.on_base_code in [1, 2, 4, 5, 6, 7]:
|
||||
if this_play.on_second:
|
||||
lead_runner = this_play.on_second.player
|
||||
lead_base = 4
|
||||
|
||||
if this_play.on_first:
|
||||
trail_runner = this_play.on_first.player
|
||||
trail_base = 3
|
||||
|
||||
else:
|
||||
trail_runner = this_play.batter
|
||||
trail_base = 2
|
||||
|
||||
else:
|
||||
lead_runner = this_play.on_first
|
||||
lead_base = 3
|
||||
trail_runner = this_play.batter
|
||||
trail_base = 2
|
||||
|
||||
this_play = await check_uncapped_advance(session, interaction, this_game, this_play, lead_runner, lead_base, trail_runner, trail_base)
|
||||
|
||||
session.add(this_play)
|
||||
session.commit()
|
||||
|
||||
session.refresh(this_play)
|
||||
return this_play
|
||||
|
||||
@ -10,7 +10,7 @@ from sqlalchemy import func, desc
|
||||
|
||||
from api_calls import db_get, db_post
|
||||
from exceptions import *
|
||||
from in_game.managerai_responses import JumpResponse, TagResponse
|
||||
from in_game.managerai_responses import JumpResponse, TagResponse, ThrowResponse
|
||||
|
||||
|
||||
sqlite_url = 'sqlite:///storage/gameplay.db'
|
||||
@ -224,6 +224,9 @@ class Game(SQLModel, table=True):
|
||||
return embed
|
||||
|
||||
def initialize_play(self, session: Session):
|
||||
"""
|
||||
Commits new_play
|
||||
"""
|
||||
existing_play = self.current_play_or_none(session)
|
||||
if existing_play is not None:
|
||||
return existing_play
|
||||
@ -418,6 +421,46 @@ class ManagerAi(ManagerAiBase, table=True):
|
||||
|
||||
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)
|
||||
if this_play is None:
|
||||
raise KeyError(f'No game found while checking throw_at_uncapped')
|
||||
|
||||
ai_rd = this_play.ai_run_diff()
|
||||
aggression = self.ahead_aggression if ai_rd > 0 else self.behind_aggression
|
||||
current_outs = this_play.starting_outs + this_play.outs
|
||||
|
||||
if ai_rd > 5:
|
||||
if self.ahead_aggression > 5:
|
||||
this_resp.at_trail_runner = True
|
||||
this_resp.trail_max_safe_delta = -4 + current_outs
|
||||
else:
|
||||
this_resp.cutoff = True
|
||||
elif ai_rd > 2:
|
||||
if self.ahead_aggression > 8:
|
||||
this_resp.at_trail_runner = True
|
||||
this_resp.trail_max_safe_delta = -4 + current_outs
|
||||
elif ai_rd > 0:
|
||||
if self.ahead_aggression > 8:
|
||||
this_resp.at_trail_runner = True
|
||||
this_resp.trail_max_safe_delta = -6 + current_outs
|
||||
elif ai_rd > -3:
|
||||
if self.behind_aggression < 5:
|
||||
this_resp.at_trail_runner = True
|
||||
this_resp.trail_max_safe_delta = -6 + current_outs
|
||||
elif ai_rd > -6:
|
||||
if self.behind_aggression < 5:
|
||||
this_resp.at_trail_runner = True
|
||||
this_resp.trail_max_safe_delta = -4 + current_outs
|
||||
else:
|
||||
if self.behind_aggression < 5:
|
||||
this_resp.at_trail_runner = True
|
||||
this_resp.trail_max_safe_delta = -4
|
||||
|
||||
return this_resp
|
||||
|
||||
|
||||
|
||||
class CardsetBase(SQLModel):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
@ -568,12 +611,12 @@ class PlayBase(SQLModel):
|
||||
in_pow: bool = Field(default=False)
|
||||
|
||||
on_first_id: int | None = Field(default=None, foreign_key='lineup.id')
|
||||
on_first_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = no change
|
||||
on_first_final: int | None = Field(default=None) # None = out, 1-4 = base
|
||||
on_second_id: int | None = Field(default=None, foreign_key='lineup.id')
|
||||
on_second_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = no change
|
||||
on_second_final: int | None = Field(default=None) # None = out, 1-4 = base
|
||||
on_third_id: int | None = Field(default=None, foreign_key='lineup.id')
|
||||
on_third_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = no change
|
||||
batter_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = out
|
||||
on_third_final: int | None = Field(default=None) # None = out, 1-4 = base
|
||||
batter_final: int | None = Field(default=None) # None = out, 1-4 = base
|
||||
|
||||
pa: int = Field(default=0, ge=0, le=1)
|
||||
ab: int = Field(default=0, ge=0, le=1)
|
||||
@ -736,6 +779,9 @@ class Play(PlayBase, table=True):
|
||||
|
||||
@property
|
||||
def ai_is_batting(self) -> bool:
|
||||
if self.game.ai_team is None:
|
||||
return False
|
||||
|
||||
if (self.game.ai_team.lower() == 'away' and self.inning_half.lower() == 'top') or (self.game.ai_team.lower() == 'home' and self.inning_half.lower() == 'bot'):
|
||||
return True
|
||||
else:
|
||||
@ -815,10 +861,17 @@ def select_all_testing():
|
||||
print(f'Game: {game}')
|
||||
|
||||
|
||||
# def select_specic_fields():
|
||||
# with Session(engine) as session:
|
||||
# games = session.exec(select(Game.id, Game.away_team, Game.home_team))
|
||||
# print(f'Games: {games}')
|
||||
# print(f'.all(): {games.all()}')
|
||||
|
||||
|
||||
def main():
|
||||
# create_db_and_tables()
|
||||
# create_test_games()
|
||||
select_speed_testing()
|
||||
create_db_and_tables()
|
||||
create_test_games()
|
||||
# select_speed_testing()
|
||||
# select_all_testing()
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,23 @@
|
||||
import pydantic
|
||||
|
||||
|
||||
class JumpResponse(pydantic.BaseModel):
|
||||
class RunResponse(pydantic.BaseModel):
|
||||
min_safe: int | None = None
|
||||
|
||||
|
||||
class JumpResponse(RunResponse):
|
||||
must_auto_jump: bool = False
|
||||
run_if_auto_jump: bool = False
|
||||
|
||||
|
||||
class TagResponse(pydantic.BaseModel):
|
||||
min_safe: int | None = None
|
||||
class TagResponse(RunResponse):
|
||||
pass
|
||||
|
||||
|
||||
class ThrowResponse(pydantic.BaseModel):
|
||||
cutoff: bool = False # Stops on True
|
||||
at_lead_runner: bool = True
|
||||
at_trail_runner: bool = False # Stops on False
|
||||
trail_max_safe: int = 10
|
||||
trail_max_safe_delta: int = -6
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import pytest
|
||||
from sqlmodel import Session
|
||||
from sqlmodel import Session, select, func
|
||||
|
||||
from command_logic.logic_gameplay import advance_runners, get_obc, get_re24, get_wpa, complete_play
|
||||
from command_logic.logic_gameplay import advance_runners, get_obc, get_re24, get_wpa, complete_play, log_run_scored
|
||||
from in_game.gameplay_models import Lineup, Play
|
||||
from tests.factory import session_fixture, Game
|
||||
|
||||
|
||||
@ -14,7 +15,25 @@ def test_advance_runners(session: Session):
|
||||
assert play_1.on_second_id is None
|
||||
assert play_1.on_third_id is None
|
||||
|
||||
# TODO: Test advance runners once "advance play" function is ready
|
||||
play_1.pa, play_1.ab, play_1.hit, play_1.double, play_1.batter_final = 1, 1, 1, 1, 2
|
||||
|
||||
play_2 = complete_play(session, play_1)
|
||||
assert play_2.inning_half == play_1.inning_half
|
||||
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)
|
||||
session.add(play_2)
|
||||
session.commit()
|
||||
|
||||
assert play_2.on_second_final == 3
|
||||
assert play_2.batter_final == 1
|
||||
|
||||
play_3 = complete_play(session, play_2)
|
||||
|
||||
assert play_3.on_third is not None
|
||||
assert play_3.on_second is None
|
||||
assert play_3.on_first is not None
|
||||
|
||||
def test_get_obc():
|
||||
assert get_obc() == 0
|
||||
@ -75,5 +94,72 @@ def test_complete_play(session: Session):
|
||||
|
||||
assert third_play.on_base_code == 2
|
||||
assert next_play.re24 == 0.182
|
||||
assert third_play.on_second == next_play.batter
|
||||
|
||||
third_play.pa, third_play.ab, third_play.hit
|
||||
|
||||
|
||||
def test_complete_play_scratch(session: Session):
|
||||
this_game = session.get(Game, 3)
|
||||
|
||||
assert len(this_game.plays) == 0
|
||||
|
||||
play_1 = this_game.initialize_play(session)
|
||||
|
||||
assert play_1.starting_outs == 0
|
||||
assert play_1.on_first_id is None
|
||||
assert play_1.on_second_id is None
|
||||
assert play_1.on_third_id is None
|
||||
|
||||
play_1.hit = 1
|
||||
|
||||
assert play_1.hit == 1
|
||||
|
||||
play_1.pa, play_1.ab, play_1.hit, play_1.double, play_1.batter_final = 1, 1, 1, 1, 2
|
||||
print(f'play_1: {play_1}')
|
||||
|
||||
play_2 = complete_play(session, play_1)
|
||||
|
||||
assert play_2.on_second is not None
|
||||
|
||||
|
||||
def test_log_run_scored(session: Session):
|
||||
game_1 = session.get(Game, 1)
|
||||
lineup_1 = session.get(Lineup, 1)
|
||||
play_1 = session.get(Play, 1)
|
||||
|
||||
assert play_1.run == 0
|
||||
|
||||
log_run_scored(session, lineup_1, play_1)
|
||||
|
||||
assert play_1.run == 1
|
||||
|
||||
play_2 = session.get(Play, 2)
|
||||
play_2.pa, play_2.ab, play_2.double, play_2.batter_final = 1, 1, 1, 2
|
||||
play_3 = complete_play(session, play_2)
|
||||
|
||||
assert play_3.on_second is not None
|
||||
assert play_3.on_second.game == game_1
|
||||
|
||||
play_3.pa, play_3.ab, play_3.so, play_3.outs = 1, 1, 1, 1
|
||||
play_3 = advance_runners(session, play_3, num_bases=0)
|
||||
|
||||
assert play_3.on_second_final == 2
|
||||
|
||||
play_4 = complete_play(session, play_3)
|
||||
|
||||
assert play_4.starting_outs == 2
|
||||
assert play_4.on_second is not None
|
||||
|
||||
play_4.pa, play_4.ab, play_4.error = 1, 1, 1
|
||||
log_run_scored(session, play_4.on_second, play_4)
|
||||
|
||||
runs = session.exec(select(func.sum(Play.run)).where(Play.game == game_1)).one()
|
||||
e_runs = session.exec(select(func.sum(Play.e_run)).where(Play.game == game_1)).one()
|
||||
|
||||
assert runs == 2
|
||||
assert e_runs == 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -158,7 +158,8 @@ def session_fixture():
|
||||
pitcher=all_lineups[19],
|
||||
catcher=all_lineups[10],
|
||||
pa=1,
|
||||
so=1
|
||||
so=1,
|
||||
outs=1
|
||||
)
|
||||
game_1_play_2 = Play(
|
||||
game=game_1,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
from sqlmodel import Session, select
|
||||
import pytest
|
||||
from sqlmodel import Session, select, func
|
||||
|
||||
from in_game.gameplay_models import Lineup, Play, Game
|
||||
from in_game.gameplay_queries import get_last_team_play
|
||||
@ -54,4 +55,25 @@ def test_last_team_play(session: Session):
|
||||
assert get_last_team_play(session, this_game, this_team, none_okay=True) is None
|
||||
|
||||
|
||||
def test_query_scalars(session: Session):
|
||||
this_game = session.get(Game, 1)
|
||||
all_plays = session.exec(select(Play.id, Play.error, Play.outs).where(Play.game == this_game)).all()
|
||||
|
||||
assert len(all_plays) == 2
|
||||
assert all_plays[0].error == 0
|
||||
|
||||
with pytest.raises(AttributeError) as exc_info:
|
||||
all_plays[0].batter_id == None
|
||||
|
||||
assert str(exc_info) == "<ExceptionInfo AttributeError('batter_id') tblen=4>"
|
||||
|
||||
count_plays = session.exec(select(func.count(Play.id)).where(Play.game == this_game)).one()
|
||||
|
||||
assert count_plays == 2
|
||||
|
||||
outs = session.exec(select(func.sum(Play.outs)).where(Play.game == this_game)).one()
|
||||
|
||||
assert outs == 1
|
||||
|
||||
|
||||
# TODO: test get_ai_note
|
||||
Loading…
Reference in New Issue
Block a user