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:
Cal Corum 2024-11-07 11:38:45 -06:00
parent 6e4904282e
commit 0327b6af62
7 changed files with 511 additions and 51 deletions

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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