1463 lines
55 KiB
Python
1463 lines
55 KiB
Python
|
|
import asyncio
|
|
import logging
|
|
import discord
|
|
import pandas as pd
|
|
from sqlmodel import Session, select, func
|
|
from sqlalchemy import delete
|
|
from typing import Literal
|
|
|
|
from api_calls import db_delete, db_get, db_post
|
|
from exceptions import *
|
|
from helpers import DEFENSE_LITERAL, get_channel
|
|
from in_game.game_helpers import legal_check
|
|
from in_game.gameplay_models import Game, Lineup, Team, Play
|
|
from in_game.gameplay_queries import get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, 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 utilities.buttons import ButtonOptions, Confirm, ask_confirm
|
|
from utilities.dropdown import DropdownOptions, DropdownView, SelectViewDefense
|
|
from utilities.embeds import image_embed
|
|
from utilities.pages import Pagination
|
|
|
|
|
|
logger = logging.getLogger('discord_app')
|
|
WPA_DF = pd.read_csv(f'storage/wpa_data.csv').set_index('index')
|
|
|
|
|
|
def get_obc(on_first = None, on_second = None, on_third = None) -> int:
|
|
if on_third is not None:
|
|
if on_second is not None:
|
|
if on_first is not None:
|
|
obc = 7
|
|
else:
|
|
obc = 6
|
|
elif on_first is not None:
|
|
obc = 5
|
|
else:
|
|
obc = 3
|
|
elif on_second is not None:
|
|
if on_first is not None:
|
|
obc = 4
|
|
else:
|
|
obc = 2
|
|
elif on_first is not None:
|
|
obc = 1
|
|
else:
|
|
obc = 0
|
|
|
|
return obc
|
|
|
|
|
|
def get_re24(this_play: Play, runs_scored: int, new_obc: int, new_starting_outs: int) -> float:
|
|
re_data = {
|
|
0: [0.457, 0.231, 0.077],
|
|
1: [0.793, 0.438, 0.171],
|
|
2: [1.064, 0.596, 0.259],
|
|
4: [1.373, 0.772, 0.351],
|
|
3: [1.340, 0.874, 0.287],
|
|
5: [1.687, 1.042, 0.406],
|
|
6: [1.973, 1.311, 0.448],
|
|
7: [2.295, 1.440, 0.618]
|
|
}
|
|
|
|
start_re24 = re_data[this_play.on_base_code][this_play.starting_outs]
|
|
end_re24 = 0 if this_play.starting_outs + this_play.outs > 2 else re_data[new_obc][new_starting_outs]
|
|
return round(end_re24 - start_re24 + runs_scored, 3)
|
|
|
|
|
|
def get_wpa(this_play: Play, next_play: Play):
|
|
"""
|
|
Returns wpa relative to batting team of this_play. Negative value if bad play, positive value if good play.
|
|
"""
|
|
new_rd = next_play.home_score - next_play.away_score
|
|
if new_rd > 6:
|
|
new_rd = 6
|
|
elif new_rd < -6:
|
|
new_rd = -6
|
|
|
|
old_rd = this_play.home_score - this_play.away_score
|
|
if old_rd > 6:
|
|
old_rd = 6
|
|
elif old_rd < -6:
|
|
old_rd = -6
|
|
|
|
# print(f'get_wpa: new_rd = {new_rd} / old_rd = {old_rd}')
|
|
if (next_play.inning_num >= 9 and new_rd > 0 and next_play.inning_half == 'bot') or (next_play.inning_num > 9 and new_rd > 0 and next_play.is_new_inning):
|
|
# print(f'manually setting new_win_ex to 1.0')
|
|
new_win_ex = 1.0
|
|
else:
|
|
new_win_ex = WPA_DF.loc[f'{next_play.inning_half}_{next_play.inning_num}_{next_play.starting_outs}_out_{next_play.on_base_code}_obc_{new_rd}_home_run_diff'].home_win_ex
|
|
# print(f'new_win_ex = {new_win_ex}')
|
|
|
|
old_win_ex = WPA_DF.loc[f'{this_play.inning_half}_{this_play.inning_num}_{this_play.starting_outs}_out_{this_play.on_base_code}_obc_{old_rd}_home_run_diff'].home_win_ex
|
|
# print(f'old_win_ex = {old_win_ex}')
|
|
|
|
wpa = float(round(new_win_ex - old_win_ex, 3))
|
|
# print(f'final wpa: {wpa}')
|
|
if this_play.inning_half == 'top':
|
|
return wpa * -1.0
|
|
|
|
return wpa
|
|
|
|
|
|
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
|
|
|
|
is_go_ahead = False
|
|
if nso >= 3:
|
|
switch_sides = True
|
|
obc = 0
|
|
nso = 0
|
|
nih = 'bot' if this_play.inning_half.lower() == 'top' else 'top'
|
|
away_score = this_play.away_score
|
|
home_score = this_play.home_score
|
|
|
|
try:
|
|
opponent_play = get_last_team_play(session, this_play.game, this_play.pitcher.team)
|
|
nbo = opponent_play.batting_order + 1
|
|
except PlayNotFoundException as e:
|
|
logger.info(f'logic_gameplay - complete_play - No last play found for {this_play.pitcher.team.sname}, setting upcoming batting order to 1')
|
|
nbo = 1
|
|
finally:
|
|
new_batter_team = this_play.game.away_team if nih == 'top' else this_play.game.home_team
|
|
new_pitcher_team = this_play.game.away_team if nih == 'bot' else this_play.game.home_team
|
|
inning = this_play.inning_num if nih == 'bot' else this_play.inning_num + 1
|
|
|
|
else:
|
|
switch_sides = False
|
|
nbo = this_play.batting_order + 1 if this_play.pa == 1 else this_play.batting_order
|
|
nih = this_play.inning_half
|
|
new_batter_team = this_play.batter.team
|
|
new_pitcher_team = this_play.pitcher.team
|
|
inning = this_play.inning_num
|
|
|
|
for this_runner, runner_dest in [
|
|
(this_play.batter, this_play.batter_final), (this_play.on_first, this_play.on_first_final), (this_play.on_second, this_play.on_second_final), (this_play.on_third, this_play.on_third_final)
|
|
]:
|
|
if runner_dest is not None:
|
|
if runner_dest == 1:
|
|
if on_first is not None:
|
|
log_exception(ValueError, f'Cannot place {this_runner.player.name} on first; {on_first.player.name} is already placed there')
|
|
on_first = this_runner
|
|
elif runner_dest == 2:
|
|
if on_second is not None:
|
|
log_exception(ValueError, f'Cannot place {this_runner.player.name} on second; {on_second.player.name} is already placed there')
|
|
on_second = this_runner
|
|
elif runner_dest == 3:
|
|
if on_third is not None:
|
|
log_exception(ValueError, f'Cannot place {this_runner.player.name} on third; {on_third.player.name} is already placed there')
|
|
on_third = this_runner
|
|
elif runner_dest == 4:
|
|
runs_scored += 1
|
|
|
|
if this_play.inning_half == 'top':
|
|
away_score = this_play.away_score + runs_scored
|
|
home_score = this_play.home_score
|
|
|
|
if runs_scored > 0 and this_play.away_score <= this_play.home_score and away_score > home_score:
|
|
this_play.is_go_ahead = True
|
|
|
|
else:
|
|
away_score = this_play.away_score
|
|
home_score = this_play.home_score + runs_scored
|
|
|
|
if runs_scored > 0 and this_play.home_score <= this_play.away_score and home_score > away_score:
|
|
this_play.is_go_ahead = True
|
|
|
|
obc = get_obc(on_first, on_second, on_third)
|
|
|
|
this_play.re24 = get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso)
|
|
|
|
if nbo > 9:
|
|
nbo = 1
|
|
|
|
new_batter = get_one_lineup(session, this_play.game, new_batter_team, batting_order=nbo)
|
|
|
|
new_play = Play(
|
|
game=this_play.game,
|
|
play_num=this_play.play_num + 1,
|
|
batting_order=nbo,
|
|
inning_half=nih,
|
|
inning_num=inning,
|
|
starting_outs=nso,
|
|
on_base_code=obc,
|
|
away_score=away_score,
|
|
home_score=home_score,
|
|
batter=new_batter,
|
|
batter_pos=new_batter.position,
|
|
pitcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='P'),
|
|
catcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='C'),
|
|
is_new_inning=switch_sides,
|
|
is_tied=away_score == home_score,
|
|
on_first=on_first,
|
|
on_second=on_second,
|
|
on_third=on_third,
|
|
managerai=this_play.managerai,
|
|
re24=get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso)
|
|
)
|
|
|
|
this_play.wpa = get_wpa(this_play, new_play)
|
|
this_play.locked = False
|
|
this_play.complete = True
|
|
session.add(this_play)
|
|
session.add(new_play)
|
|
session.commit()
|
|
session.refresh(new_play)
|
|
|
|
return new_play
|
|
|
|
|
|
async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, this_team: Team, lineup_num: int, roster_num: int) -> list[Lineup]:
|
|
logger.debug(f'sheets: {sheets}')
|
|
|
|
this_sheet = sheets.open_by_key(this_team.gsheet)
|
|
logger.debug(f'this_sheet: {this_sheet}')
|
|
|
|
r_sheet = this_sheet.worksheet_by_title('My Rosters')
|
|
logger.debug(f'r_sheet: {r_sheet}')
|
|
|
|
if lineup_num == 1:
|
|
row_start = 9
|
|
row_end = 17
|
|
else:
|
|
row_start = 18
|
|
row_end = 26
|
|
|
|
if roster_num == 1:
|
|
l_range = f'H{row_start}:I{row_end}'
|
|
elif roster_num == 2:
|
|
l_range = f'J{row_start}:K{row_end}'
|
|
else:
|
|
l_range = f'L{row_start}:M{row_end}'
|
|
|
|
logger.debug(f'l_range: {l_range}')
|
|
raw_cells = r_sheet.range(l_range)
|
|
logger.debug(f'raw_cells: {raw_cells}')
|
|
|
|
try:
|
|
lineup_cells = [(row[0].value, int(row[1].value)) for row in raw_cells]
|
|
logger.debug(f'lineup_cells: {lineup_cells}')
|
|
except ValueError as e:
|
|
logger.error(f'Could not pull roster for {this_team.abbrev}: {e}')
|
|
raise ValueError(f'Uh oh. Looks like your roster might not be saved. I am reading blanks when I try to get the card IDs')
|
|
|
|
all_lineups = []
|
|
all_pos = []
|
|
card_ids = []
|
|
for index, row in enumerate(lineup_cells):
|
|
if '' in row:
|
|
break
|
|
|
|
if row[0].upper() not in all_pos:
|
|
all_pos.append(row[0].upper())
|
|
else:
|
|
raise SyntaxError(f'You have more than one {row[0].upper()} in this lineup. Please update and set the lineup again.')
|
|
|
|
this_card = await get_card_or_none(session, card_id=int(row[1]))
|
|
if this_card is None:
|
|
raise LookupError(
|
|
f'Your {row[0].upper()} has a Card ID of {int(row[1])} and I cannot find that card. Did you sell it by chance? Or maybe you sold a duplicate and the bot sold the one you were using?'
|
|
)
|
|
if this_card.team_id != this_team.id:
|
|
raise SyntaxError(f'Easy there, champ. Looks like card ID {row[1]} belongs to the {this_card.team.lname}. Try again with only cards you own.')
|
|
card_id = row[1]
|
|
card_ids.append(str(card_id))
|
|
|
|
this_lineup = Lineup(
|
|
position=row[0].upper(),
|
|
batting_order=index + 1,
|
|
game=this_game,
|
|
team=this_team,
|
|
player=this_card.player,
|
|
card=this_card
|
|
)
|
|
all_lineups.append(this_lineup)
|
|
|
|
legal_data = await legal_check([card_ids], difficulty_name=this_game.game_type)
|
|
logger.debug(f'legal_data: {legal_data}')
|
|
if not legal_data['legal']:
|
|
raise CardLegalityException(f'The following cards appear to be illegal for this game mode:\n{legal_data["error_string"]}')
|
|
|
|
if len(all_lineups) != 9:
|
|
raise Exception(f'I was only able to pull in {len(all_lineups)} batters from Sheets. Please check your saved lineup and try again.')
|
|
|
|
return all_lineups
|
|
|
|
|
|
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:
|
|
raise GameNotFoundException('I don\'t see an active game in this channel.')
|
|
|
|
owner_team = await get_team_or_none(session, gm_id=interaction.user.id)
|
|
if owner_team is None:
|
|
logger.exception(f'{command_name} command: No team found for GM ID {interaction.user.id}')
|
|
raise TeamNotFoundException(f'Do I know you? I cannot find your team.')
|
|
|
|
if 'gauntlet' in this_game.game_type:
|
|
gauntlet_abbrev = f'Gauntlet-{owner_team.abbrev}'
|
|
owner_team = await get_team_or_none(session, team_abbrev=gauntlet_abbrev)
|
|
if owner_team is None:
|
|
logger.exception(f'{command_name} command: No gauntlet team found with abbrev {gauntlet_abbrev}')
|
|
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]:
|
|
if interaction.user.id != 258104532423147520:
|
|
logger.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:
|
|
logger.error(f'{command_name} command: No play found for Game ID {this_game.id} - attempting to initialize play')
|
|
this_play = activate_last_play(session, this_game)
|
|
|
|
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, 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) -> Play:
|
|
"""
|
|
No commits
|
|
"""
|
|
logger.info(f'Advancing runners {num_bases} bases in game {this_play.game.id}')
|
|
this_play.rbi = 0
|
|
|
|
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
|
|
if this_play.on_third:
|
|
this_play.on_third_final = 3
|
|
|
|
if this_play.on_second:
|
|
if this_play.on_third:
|
|
if num_bases > 0:
|
|
this_play.on_third_final = 4
|
|
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, this_play)
|
|
this_play.rbi += 1 if not is_error else 0
|
|
elif num_bases == 1:
|
|
this_play.on_second_final = 3
|
|
else:
|
|
this_play.on_second_final = 2
|
|
else:
|
|
if this_play.on_third:
|
|
this_play.on_third_final = 3
|
|
|
|
if num_bases > 2:
|
|
this_play.on_first_final = 4
|
|
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
|
|
elif num_bases == 1:
|
|
this_play.on_first_final = 2
|
|
else:
|
|
this_play.on_first_final = 1
|
|
|
|
else:
|
|
if this_play.on_third:
|
|
if num_bases > 0:
|
|
this_play.on_third_final = 4
|
|
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
|
|
|
|
if this_play.on_second:
|
|
if num_bases > 1:
|
|
this_play.on_second_final = 4
|
|
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
|
|
else:
|
|
this_play.on_second_final = 2
|
|
|
|
if this_play.on_first:
|
|
if num_bases > 2:
|
|
this_play.on_first_final = 4
|
|
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
|
|
elif num_bases == 1:
|
|
this_play.on_first_final = 2
|
|
else:
|
|
this_play.on_first_final = 1
|
|
|
|
return this_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')
|
|
this_team = this_play.pitcher.team
|
|
logger.debug(f'lf: {lf.player.name_with_desc}\n\ncf: {cf.player.name_with_desc}\n\nrf: {rf.player.name_with_desc}\n\nteam: {this_team.lname}')
|
|
|
|
view = Pagination([interaction.user], timeout=10)
|
|
view.left_button.label = f'Left Fielder'
|
|
view.left_button.style = discord.ButtonStyle.secondary
|
|
lf_embed = image_embed(
|
|
image_url=lf.player.image,
|
|
title=f'{this_team.sname} LF',
|
|
color=this_team.color,
|
|
desc=lf.player.name,
|
|
author_name=this_team.lname,
|
|
author_icon=this_team.logo
|
|
)
|
|
|
|
view.cancel_button.label = f'Center Fielder'
|
|
view.cancel_button.style = discord.ButtonStyle.blurple
|
|
cf_embed = image_embed(
|
|
image_url=cf.player.image,
|
|
title=f'{this_team.sname} CF',
|
|
color=this_team.color,
|
|
desc=cf.player.name,
|
|
author_name=this_team.lname,
|
|
author_icon=this_team.logo
|
|
)
|
|
|
|
view.right_button.label = f'Right Fielder'
|
|
view.right_button.style = discord.ButtonStyle.secondary
|
|
rf_embed = image_embed(
|
|
image_url=rf.player.image,
|
|
title=f'{this_team.sname} RF',
|
|
color=this_team.color,
|
|
desc=rf.player.name,
|
|
author_name=this_team.lname,
|
|
author_icon=this_team.logo
|
|
)
|
|
|
|
page_num = 1
|
|
embeds = [lf_embed, cf_embed, rf_embed]
|
|
|
|
msg = await interaction.channel.send(embed=embeds[page_num], view=view)
|
|
|
|
await view.wait()
|
|
|
|
if view.value:
|
|
if view.value == 'left':
|
|
page_num = 0
|
|
if view.value == 'cancel':
|
|
page_num = 1
|
|
if view.value == 'right':
|
|
page_num = 2
|
|
else:
|
|
await msg.edit(content=None, embed=embeds[page_num], view=None)
|
|
|
|
view.value = None
|
|
|
|
if page_num == 0:
|
|
view.left_button.style = discord.ButtonStyle.blurple
|
|
view.cancel_button.style = discord.ButtonStyle.secondary
|
|
view.right_button.style = discord.ButtonStyle.secondary
|
|
if page_num == 1:
|
|
view.left_button.style = discord.ButtonStyle.secondary
|
|
view.cancel_button.style = discord.ButtonStyle.blurple
|
|
view.right_button.style = discord.ButtonStyle.secondary
|
|
if page_num == 2:
|
|
view.left_button.style = discord.ButtonStyle.secondary
|
|
view.cancel_button.style = discord.ButtonStyle.secondary
|
|
view.right_button.style = discord.ButtonStyle.blurple
|
|
|
|
view.left_button.disabled = True
|
|
view.cancel_button.disabled = True
|
|
view.right_button.disabled = True
|
|
|
|
await msg.edit(content=None, embed=embeds[page_num], view=view)
|
|
return [lf, cf, rf][page_num]
|
|
|
|
|
|
async def flyballs(session: Session, interaction: discord.Interaction, this_play: Play, flyball_type: Literal['a', 'ballpark', 'b', 'b?', 'c']) -> Play:
|
|
"""
|
|
Commits this_play
|
|
"""
|
|
this_game = this_play.game
|
|
num_outs = 1
|
|
|
|
if flyball_type == 'a':
|
|
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
|
|
|
|
elif flyball_type == 'b' or flyball_type == 'ballpark':
|
|
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
|
|
this_play.bpfo = 1 if flyball_type == 'ballpark' else 0
|
|
advance_runners(session, this_play, num_bases=0)
|
|
|
|
if this_play.starting_outs < 2 and this_play.on_third:
|
|
this_play.ab = 0
|
|
this_play.rbi = 1
|
|
this_play.on_third_final = 4
|
|
log_run_scored(session, this_play.on_third, 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)
|
|
logger.debug(f'done with of embed')
|
|
|
|
runner = this_play.on_second.player
|
|
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
|
|
|
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
|
|
else:
|
|
await question.delete()
|
|
else:
|
|
await question.delete()
|
|
|
|
elif flyball_type == 'b?':
|
|
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
|
|
|
|
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)
|
|
logger.debug(f'done with of embed')
|
|
|
|
runner = this_play.on_second.player
|
|
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
|
|
|
|
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?'
|
|
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
|
|
)
|
|
await view.wait()
|
|
|
|
if view.value:
|
|
await question.delete()
|
|
num_outs += 1
|
|
this_play.on_third_final = 99
|
|
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
|
|
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_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)
|
|
logger.info(f'throw from {outfielder.player.name_with_desc}')
|
|
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:
|
|
is_lead_running = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Is **{lead_runner.player.name}** being sent {TO_BASE[lead_base]}?',
|
|
label_type='yes'
|
|
)
|
|
|
|
if is_lead_running:
|
|
throw_resp = None
|
|
if this_game.ai_team:
|
|
throw_resp = this_play.managerai.throw_at_uncapped(session, this_game)
|
|
logger.info(f'throw_resp: {throw_resp}')
|
|
|
|
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:
|
|
throw_for_lead = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Is the defense throwing {TO_BASE[lead_base]} for {lead_runner.player.name}?',
|
|
label_type='yes'
|
|
)
|
|
|
|
# Human defense is cutting off the throw
|
|
if not throw_for_lead:
|
|
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
|
|
trail_advancing = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Is **{trail_runner.player.name}** being sent {TO_BASE[trail_base]} as the trail runner?',
|
|
label_type='yes'
|
|
)
|
|
|
|
# Trail runner is advancing
|
|
if trail_advancing:
|
|
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'
|
|
|
|
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(
|
|
f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?',
|
|
view=view
|
|
)
|
|
|
|
if not ai_throw_lead:
|
|
await view.wait()
|
|
elif ai_throw_lead:
|
|
view.value = True
|
|
|
|
# Throw is going to lead runner
|
|
if view.value:
|
|
try:
|
|
await question.delete()
|
|
except (discord.NotFound, UnboundLocalError):
|
|
pass
|
|
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')
|
|
|
|
# Throw is going to trail runner
|
|
else:
|
|
try:
|
|
await question.delete()
|
|
except (discord.NotFound, UnboundLocalError):
|
|
pass
|
|
|
|
runner_thrown_out = await ask_confirm(
|
|
interaction=interaction,
|
|
question='Was **{trail_runner.player.name}** thrown out {AT_BASE[trail_base]}?',
|
|
label_type='yes'
|
|
)
|
|
|
|
# Trail runner is thrown out
|
|
if runner_thrown_out:
|
|
# 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
|
|
|
|
# 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, ask if safe
|
|
runner_thrown_out = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Was **{lead_runner.player.name}** thrown out {AT_BASE[lead_base]}?',
|
|
label_type='yes'
|
|
)
|
|
|
|
# Lead runner is thrown out
|
|
if runner_thrown_out:
|
|
logger.info(f'Lead runner is thrown out.')
|
|
this_play.outs += 1
|
|
|
|
# Lead runner is safe
|
|
else:
|
|
logger.info(f'Lead runner is safe.')
|
|
|
|
if this_play.on_second == lead_runner:
|
|
logger.info(f'setting lead runner on_second_final')
|
|
this_play.on_second_final = None if runner_thrown_out else lead_base
|
|
elif this_play.on_first == lead_runner:
|
|
logger.info(f'setting lead runner on_first')
|
|
this_play.on_first_final = None if runner_thrown_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:
|
|
return this_play
|
|
|
|
elif this_play.ai_is_batting:
|
|
run_resp = this_play.managerai.uncapped_advance(session, this_game, lead_base, trail_base)
|
|
|
|
is_lead_running = await ask_confirm(
|
|
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?',
|
|
label_type='yes'
|
|
)
|
|
|
|
if not is_lead_running:
|
|
return this_play
|
|
|
|
is_defense_throwing = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Is the defense throwing {TO_BASE[lead_base]} for {lead_runner.player.name}?',
|
|
label_type='yes'
|
|
)
|
|
|
|
# Human defense is throwing for lead runner
|
|
if not is_defense_throwing:
|
|
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 = 3
|
|
|
|
return this_play
|
|
|
|
# Human throw is not being cut off
|
|
if run_resp.send_trail:
|
|
await interaction.channel.send(
|
|
f'**{trail_runner.player.name}** is advancing {TO_BASE[trail_base]} as the trail runner!',
|
|
)
|
|
is_throwing_lead = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Is the throw going {TO_BASE[lead_base]} or {TO_BASE[trail_base]}?',
|
|
label_type='yes',
|
|
custom_confirm_label='Home Plate' if lead_base == 4 else 'Third Base',
|
|
custom_cancel_label='Third Base' if trail_base == 3 else 'Second Base'
|
|
)
|
|
|
|
# Trail runner advances, throwing for lead runner
|
|
if is_throwing_lead:
|
|
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')
|
|
|
|
# Throw is going to trail runner
|
|
else:
|
|
is_trail_out = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Was **{trail_runner.player.name}** thrown out {AT_BASE[trail_base]}?',
|
|
label_type='yes'
|
|
)
|
|
|
|
if is_trail_out:
|
|
# 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
|
|
|
|
# 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, ask if safe
|
|
is_lead_out = await ask_confirm(
|
|
interaction=interaction,
|
|
question=f'Was **{lead_runner.player.name}** thrown out {AT_BASE[lead_base]}?',
|
|
label_type='yes',
|
|
)
|
|
|
|
# Lead runner is thrown out
|
|
if is_lead_out:
|
|
logger.info(f'Lead runner is thrown out.')
|
|
this_play.outs += 1
|
|
|
|
if this_play.on_second == lead_runner:
|
|
logger.info(f'setting lead runner on_second_final')
|
|
this_play.on_second_final = None if is_lead_out else lead_base
|
|
elif this_play.on_first == lead_runner:
|
|
logger.info(f'setting lead runner on_first')
|
|
this_play.on_first_final = None if is_lead_out else lead_base
|
|
else:
|
|
log_exception(LineupsMissingException, f'Could not find lead runner to set final destination')
|
|
|
|
return this_play
|
|
|
|
|
|
async def singles(session: Session, interaction: discord.Interaction, this_play: Play, single_type: Literal['*', '**', 'ballpark', 'uncapped']) -> Play:
|
|
"""
|
|
Commits this_play
|
|
"""
|
|
this_play.hit, this_play.batter_final = 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(session, this_play, 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
|
|
lead_base = 4
|
|
|
|
if this_play.on_first:
|
|
trail_runner = this_play.on_first
|
|
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_play, lead_runner, lead_base, trail_runner, trail_base)
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
async def doubles(session: Session, interaction: discord.Interaction, this_play: Play, double_type: Literal['**', '***', 'uncapped']) -> Play:
|
|
"""
|
|
Commits this_play
|
|
"""
|
|
this_play.hit, this_play.double, this_play.batter_final = 1, 1, 2
|
|
|
|
if double_type == '**':
|
|
this_play = advance_runners(session, this_play, num_bases=2)
|
|
|
|
elif double_type == '***':
|
|
this_play = advance_runners(session, this_play, num_bases=3)
|
|
|
|
elif double_type == 'uncapped':
|
|
this_play = advance_runners(session, this_play, num_bases=2)
|
|
|
|
if this_play.on_first:
|
|
this_play = await check_uncapped_advance(session, interaction, this_play, lead_runner=this_play.on_first, lead_base=4, trail_runner=this_play.batter, trail_base=3)
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
async def triples(session: Session, interaction: discord.Interaction, this_play: Play):
|
|
"""
|
|
Commits this play
|
|
"""
|
|
this_play.hit, this_play.triple, this_play.batter_final = 1, 1, 3
|
|
this_play = advance_runners(session, this_play, num_bases=3)
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
async def homeruns(session: Session, interaction: discord.Interaction, this_play: Play, homerun_type: Literal['ballpark', 'no-doubt']):
|
|
this_play.hit, this_play.homerun, this_play.batter_final, this_play.run = 1, 1, 4, 1
|
|
this_play.bphr = 1 if homerun_type == 'ballpark' else 0
|
|
this_play = advance_runners(session, this_play, num_bases=4)
|
|
this_play.rbi += 1
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
async def walks(session: Session, interaction: discord.Interaction, this_play: Play, walk_type: Literal['unintentional', 'intentional'] = 'unintentional'):
|
|
this_play.ab, this_play.bb, this_play.batter_final = 0, 1, 1
|
|
this_play.ibb = 1 if walk_type == 'intentional' else 0
|
|
this_play = advance_runners(session, this_play, num_bases=1, only_forced=True)
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
async def strikeouts(session: Session, interaction: discord.Interaction, this_play: Play):
|
|
this_play.so, this_play.outs = 1, 1
|
|
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 popouts(session: Session, interaction: discord.Interaction, this_play: Play):
|
|
this_play.outs = 1
|
|
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 hit_by_pitch(session: Session, interaction: discord.Interaction, this_play: Play):
|
|
this_play.ab, this_play.hbp = 0, 1
|
|
this_play = advance_runners(session, this_play, num_bases=1, only_forced=True)
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
async def bunts(session: Session, interaction: discord.Interaction, this_play: Play, bunt_type: Literal['sacrifice', 'bad', 'popout', 'double-play', 'defense']):
|
|
this_play.ab = 1 if bunt_type != 'sacrifice' else 0
|
|
this_play.sac = 1 if bunt_type != 'sacrifice' else 0
|
|
|
|
if bunt_type == 'sacrifice':
|
|
this_play = advance_runners(session, this_play, num_bases=1)
|
|
elif bunt_type == 'popout':
|
|
this_play = advance_runners(session, this_play, num_bases=0)
|
|
else:
|
|
log_exception(KeyError, f'Bunt type {bunt_type} is not yet implemented')
|
|
|
|
session.add(this_play)
|
|
session.commit()
|
|
|
|
session.refresh(this_play)
|
|
return this_play
|
|
|
|
|
|
def activate_last_play(session: Session, this_game: Game) -> Play:
|
|
p_query = session.exec(select(Play).where(Play.game == this_game).order_by(Play.id.desc()).limit(1)).all()
|
|
|
|
this_play = complete_play(session, p_query[0])
|
|
|
|
return this_play
|
|
|
|
|
|
def undo_play(session: Session, this_play: Play):
|
|
this_game = this_play.game
|
|
|
|
last_two_plays = session.exec(select(Play).where(Play.game == this_game).order_by(Play.id.desc()).limit(2)).all()
|
|
|
|
for play in last_two_plays:
|
|
for runner, to_base in [(play.on_first, play.on_first_final), (play.on_second, play.on_second_final), (play.on_third, play.on_third_final)]:
|
|
if to_base == 4:
|
|
last_pa = get_players_last_pa(session, runner)
|
|
last_pa.run, last_pa.e_run = 0, 0
|
|
session.add(last_pa)
|
|
|
|
last_two_ids = [last_two_plays[0].id, last_two_plays[1].id]
|
|
logger.warning(f'Deleting plays: {last_two_ids}')
|
|
session.exec(delete(Play).where(Play.id.in_(last_two_ids)))
|
|
session.commit()
|
|
|
|
try:
|
|
this_play = this_game.initialize_play(session)
|
|
logger.info(f'Initialized play: {this_play.id}')
|
|
except PlayInitException:
|
|
this_play = activate_last_play(session, this_game)
|
|
logger.info(f'Re-activated play: {this_play.id}')
|
|
|
|
return this_play
|
|
|
|
|
|
async def show_defense_cards(session: Session, interaction: discord.Interaction, this_play: Play, first_position: DEFENSE_LITERAL):
|
|
position_map = {
|
|
'Pitcher': 'P',
|
|
'Catcher': 'C',
|
|
'First Base': '1B',
|
|
'Second Base': '2B',
|
|
'Third Base': '3B',
|
|
'Shortstop': 'SS',
|
|
'Left Field': 'LF',
|
|
'Center Field': 'CF',
|
|
'Right Field': 'RF'
|
|
}
|
|
this_position = position_map[first_position]
|
|
|
|
sorted_lineups = get_sorted_lineups(session, this_play.game, this_play.pitcher.team)
|
|
select_player_options = [
|
|
discord.SelectOption(label=f'{x.position} - {x.player.name}', value=f'{x.id}', default=this_position == x.position) for x in sorted_lineups
|
|
]
|
|
|
|
this_lineup = get_one_lineup(session, this_play.game, this_play.pitcher.team, position=this_position)
|
|
player_embed = image_embed(
|
|
image_url=this_lineup.player.image,
|
|
color=this_play.pitcher.team.color,
|
|
author_name=this_play.pitcher.team.lname,
|
|
author_icon=this_play.pitcher.team.logo
|
|
)
|
|
player_dropdown = SelectViewDefense(
|
|
options=select_player_options,
|
|
this_play=this_play,
|
|
base_embed=player_embed,
|
|
session=session,
|
|
sorted_lineups=sorted_lineups
|
|
)
|
|
dropdown_view = DropdownView(dropdown_objects=[player_dropdown], timeout=60)
|
|
|
|
await interaction.edit_original_response(content=None, embed=player_embed, view=dropdown_view)
|
|
|
|
|
|
def is_game_over(this_play: Play) -> bool:
|
|
print(f'1: ')
|
|
if this_play.inning_num < 9 and (abs(this_play.away_score - this_play.home_score) < 10):
|
|
return False
|
|
|
|
if abs(this_play.away_score - this_play.home_score) >= 10:
|
|
if ((this_play.home_score - this_play.away_score) >= 10) and this_play.inning_half == 'bot':
|
|
return True
|
|
|
|
elif ((this_play.away_score - this_play.home_score) >= 10) and this_play.is_new_inning and this_play.inning_half == 'top':
|
|
return True
|
|
|
|
if this_play.inning_num > 9 and this_play.inning_half == 'top' and this_play.is_new_inning and this_play.home_score != this_play.away_score:
|
|
return True
|
|
|
|
if this_play.inning_num >= 9 and this_play.inning_half == 'bot' and this_play.home_score > this_play.away_score:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
async def get_game_summary_embed(session: Session, interaction: discord.Interaction, this_play: Play, db_game_id: int, winning_team: Team, losing_team: Team, num_potg: int = 1, num_poop: int = 0):
|
|
game_summary = await db_get(f'plays/game-summary/{db_game_id}', params=[('tp_max', num_potg)])
|
|
this_game = this_play.game
|
|
|
|
game_embed = winning_team.embed
|
|
game_embed.title = f'{this_game.away_team.lname} {this_play.away_score} @ {this_play.home_score} {this_game.home_team.lname} - F/{this_play.inning_num}'
|
|
game_embed.add_field(
|
|
name='Location',
|
|
value=f'{interaction.guild.get_channel(this_game.channel_id).mention}'
|
|
)
|
|
game_embed.add_field(name='Game ID', value=f'{db_game_id}')
|
|
|
|
if this_game.game_type == 'major-league':
|
|
game_des = 'Major League'
|
|
elif this_game.game_type == 'minor-league':
|
|
game_des = 'Minor League'
|
|
elif this_game.game_type == 'hall-of-fame':
|
|
game_des = 'Hall of Fame'
|
|
elif this_game.game_type == 'flashback':
|
|
game_des = 'Flashback'
|
|
elif this_game.ranked:
|
|
game_des = 'Ranked'
|
|
elif 'gauntlet' in this_game.game_type:
|
|
game_des = 'Gauntlet'
|
|
else:
|
|
game_des = 'Unlimited'
|
|
|
|
game_embed.description = f'Score Report - {game_des}'
|
|
game_embed.add_field(
|
|
name='Box Score',
|
|
value=f'```\n'
|
|
f'Team | R | H | E |\n'
|
|
f'{this_game.away_team.abbrev.replace("Gauntlet-", ""): <4} | {game_summary["runs"]["away"]: >2} | '
|
|
f'{game_summary["hits"]["away"]: >2} | {game_summary["errors"]["away"]: >2} |\n'
|
|
f'{this_game.home_team.abbrev.replace("Gauntlet-", ""): <4} | {game_summary["runs"]["home"]: >2} | '
|
|
f'{game_summary["hits"]["home"]: >2} | {game_summary["errors"]["home"]: >2} |\n'
|
|
f'\n```',
|
|
inline=False
|
|
)
|
|
|
|
logger.info(f'getting top players string')
|
|
potg_string = ''
|
|
for tp in game_summary['top-players']:
|
|
player_name = f'{get_player_name_from_dict(tp['player'])}'
|
|
potg_line = f'{player_name} - '
|
|
if 'hr' in tp:
|
|
potg_line += f'{tp["hit"]}-{tp["ab"]}'
|
|
if tp['hr'] > 0:
|
|
num = f'{tp["hr"]} ' if tp["hr"] > 1 else ""
|
|
potg_line += f', {num}HR'
|
|
if tp['triple'] > 0:
|
|
num = f'{tp["triple"]} ' if tp["triple"] > 1 else ""
|
|
potg_line += f', {num}3B'
|
|
if tp['double'] > 0:
|
|
num = f'{tp["double"]} ' if tp["double"] > 1 else ""
|
|
potg_line += f', {num}2B'
|
|
if tp['run'] > 0:
|
|
potg_line += f', {tp["run"]} R'
|
|
if tp['rbi'] > 0:
|
|
potg_line += f', {tp["rbi"]} RBI'
|
|
else:
|
|
potg_line = f'{player_name} - {tp["ip"]} IP, {tp["run"]} R'
|
|
if tp['run'] != tp['e_run']:
|
|
potg_line += f' ({tp["e_run"]} ER)'
|
|
potg_line += f', {tp["hit"]} H, {tp["so"]} K'
|
|
potg_line += f', {tp["re24"]:.2f} re24\n'
|
|
potg_string += potg_line
|
|
|
|
game_embed.add_field(
|
|
name='Players of the Game',
|
|
value=potg_string,
|
|
inline=False
|
|
)
|
|
|
|
pit_string = f'Win: {game_summary["pitchers"]["win"]["p_name"]}\nLoss: {game_summary["pitchers"]["loss"]["p_name"]}\n'
|
|
|
|
hold_string = None
|
|
for player in game_summary['pitchers']['holds']:
|
|
player_name = f'{get_player_name_from_dict(player)}'
|
|
if hold_string is None:
|
|
hold_string = f'Holds: {player_name}'
|
|
else:
|
|
hold_string += f', {player_name}'
|
|
if hold_string is not None:
|
|
pit_string += f'{hold_string}\n'
|
|
|
|
if game_summary['pitchers']['save'] is not None:
|
|
player_name = f'{get_player_name_from_dict(game_summary["pitchers"]["save"])}'
|
|
pit_string += f'Save: {player_name}'
|
|
|
|
game_embed.add_field(
|
|
name=f'Pitching',
|
|
value=pit_string,
|
|
)
|
|
|
|
def name_list(raw_list: list) -> str:
|
|
logger.info(f'raw_list: {raw_list}')
|
|
player_dict = {}
|
|
for x in raw_list:
|
|
if x['player_id'] not in player_dict:
|
|
player_dict[x['player_id']] = x
|
|
|
|
data_dict = {}
|
|
for x in raw_list:
|
|
if x['player_id'] not in data_dict:
|
|
data_dict[x['player_id']] = 1
|
|
else:
|
|
data_dict[x['player_id']] += 1
|
|
|
|
r_string = ''
|
|
logger.info(f'players: {player_dict} / data: {data_dict}')
|
|
|
|
first = True
|
|
for p_id in data_dict:
|
|
r_string += f'{", " if not first else ""}{player_dict[p_id]["p_name"]}'
|
|
if data_dict[p_id] > 1:
|
|
r_string += f' {data_dict[p_id]}'
|
|
first = False
|
|
|
|
return r_string
|
|
|
|
logger.info(f'getting running string')
|
|
if len(game_summary['running']['sb']) + len(game_summary['running']['csc']) > 0:
|
|
run_string = ''
|
|
if len(game_summary['running']['sb']) > 0:
|
|
run_string += f'SB: {name_list(game_summary["running"]["sb"])}\n'
|
|
|
|
if len(game_summary['running']['csc']) > 0:
|
|
run_string += f'CSc: {name_list(game_summary["running"]["csc"])}'
|
|
|
|
game_embed.add_field(
|
|
name=f'Baserunning',
|
|
value=run_string
|
|
)
|
|
|
|
logger.info(f'getting xbh string')
|
|
if len(game_summary['xbh']['2b']) + len(game_summary['xbh']['3b']) + len(game_summary['xbh']['hr']) > 0:
|
|
bat_string = ''
|
|
if len(game_summary['xbh']['2b']) > 0:
|
|
bat_string += f'2B: {name_list(game_summary["xbh"]["2b"])}\n'
|
|
|
|
if len(game_summary['xbh']['3b']) > 0:
|
|
bat_string += f'3B: {name_list(game_summary["xbh"]["3b"])}\n'
|
|
|
|
if len(game_summary['xbh']['hr']) > 0:
|
|
bat_string += f'HR: {name_list(game_summary["xbh"]["hr"])}\n'
|
|
else:
|
|
bat_string = 'Oops! All bitches! No XBH from either team.'
|
|
|
|
game_embed.add_field(
|
|
name='Batting',
|
|
value=bat_string,
|
|
inline=False
|
|
)
|
|
|
|
return game_embed
|
|
|
|
|
|
async def complete_game(session: Session, interaction: discord.Interaction, this_play: Play):
|
|
# if interaction is not None:
|
|
# salutation = await interaction.channel.send('GGs, I\'ll tally this game up...')
|
|
# Add button with {winning_team} wins! and another with "Roll Back"
|
|
|
|
this_game = this_play.game
|
|
|
|
async def roll_back(db_game_id: int, game: bool = True, plays: bool = False, decisions: bool = False):
|
|
if decisions:
|
|
try:
|
|
await db_delete('decisions/game', object_id=db_game_id)
|
|
except DatabaseError as e:
|
|
logger.warning(f'Could not delete decisions for game {db_game_id}: {e}')
|
|
|
|
if plays:
|
|
try:
|
|
await db_delete('plays/game', object_id=db_game_id)
|
|
except DatabaseError as e:
|
|
logger.warning(f'Could not delete plays for game {db_game_id}: {e}')
|
|
|
|
if game:
|
|
try:
|
|
await db_delete('games', object_id=db_game_id)
|
|
except DatabaseError as e:
|
|
logger.warning(f'Could not delete game {db_game_id}: {e}')
|
|
|
|
# Post completed game to API
|
|
game_data = this_game.model_dump()
|
|
game_data['home_team_ranking'] = this_game.home_team.ranking
|
|
game_data['away_team_ranking'] = this_game.away_team.ranking
|
|
game_data['home_team_value'] = this_game.home_team.team_value
|
|
game_data['away_team_value'] = this_game.away_team.team_value
|
|
game_data['away_score'] = this_play.away_score
|
|
game_data['home_score'] = this_play.home_score
|
|
|
|
winning_team = this_game.home_team if this_play.home_score > this_play.away_score else this_game.away_team
|
|
losing_team = this_game.home_team if this_play.away_score > this_play.home_score else this_game.away_team
|
|
|
|
try:
|
|
db_game = await db_post('games', payload=game_data)
|
|
db_ready_plays = get_db_ready_plays(session, this_game, db_game['id'])
|
|
db_ready_decisions = get_db_ready_decisions(session, this_game, db_game['id'])
|
|
except Exception as e:
|
|
await roll_back(db_game['id'])
|
|
log_exception(e, msg='Unable to post game to API, rolling back')
|
|
|
|
# Post game stats to API
|
|
try:
|
|
resp = await db_post('plays', payload=db_ready_plays)
|
|
except Exception as e:
|
|
await roll_back(db_game['id'], plays=True)
|
|
log_exception(e, msg='Unable to post plays to API, rolling back')
|
|
|
|
if len(resp) > 0:
|
|
pass
|
|
|
|
try:
|
|
resp = await db_post('decisions', payload={'decisions': db_ready_decisions})
|
|
except Exception as e:
|
|
await roll_back(db_game['id'], plays=True, decisions=True)
|
|
log_exception(e, msg='Unable to post decisions to API, rolling back')
|
|
|
|
if len(resp) > 0:
|
|
pass
|
|
|
|
# Post game rewards (gauntlet and main team)
|
|
try:
|
|
win_reward, loss_reward = await post_game_rewards(
|
|
session,
|
|
winning_team=winning_team,
|
|
losing_team=losing_team,
|
|
this_game=this_game
|
|
)
|
|
except Exception as e:
|
|
await roll_back(db_game['id'], plays=True, decisions=True)
|
|
log_exception(e, msg='Error while posting game rewards')
|
|
|
|
session.delete(this_play)
|
|
session.commit()
|
|
|
|
# Pull game summary for embed
|
|
summary_embed = await get_game_summary_embed(
|
|
session,
|
|
interaction,
|
|
this_play,
|
|
db_game['id'],
|
|
winning_team=winning_team,
|
|
losing_team=losing_team,
|
|
num_potg=3,
|
|
num_poop=1
|
|
)
|
|
|
|
summary_embed.add_field(
|
|
name=f'{winning_team.abbrev} Rewards',
|
|
value=win_reward
|
|
)
|
|
summary_embed.add_field(
|
|
name=f'{losing_team.abbrev} Rewards',
|
|
value=loss_reward
|
|
)
|
|
summary_embed.add_field(
|
|
name='Highlights',
|
|
value=f'Please share the highlights in {get_channel(interaction, "pd-news-ticker").mention}!',
|
|
inline=False
|
|
)
|
|
|
|
# Create and post game summary to game channel and pd-network-news
|
|
|
|
news_ticker = get_channel(interaction, 'pd-network-news')
|
|
if news_ticker is not None:
|
|
await news_ticker.send(content=None, embed=summary_embed)
|
|
|
|
await interaction.channel.send(content=None, embed=summary_embed)
|
|
await interaction.edit_original_response(content=None, embed=summary_embed)
|
|
|
|
game_id = this_game.id
|
|
this_game.active = False
|
|
session.add(this_game)
|
|
session.commit()
|
|
|
|
logger.info(f'Just ended game {game_id}')
|