major-domo-legacy/cogs/gameday.py
2025-07-12 23:15:00 -05:00

2385 lines
101 KiB
Python

import re
from pandas import DataFrame
from helpers import *
from db_calls import *
from db_calls_scouting import *
from db_calls_gameday import *
import discord
from discord.ext import commands
logger = logging.getLogger('discord_app')
class Gameday(commands.Cog):
def __init__(self, bot):
self.bot = bot
async def cog_command_error(self, ctx, error):
await ctx.send(f'{error}')
@staticmethod
def lineup_check(this_game, home=True):
if home:
team_id = this_game['home_team_id']
team_name = 'home team'
else:
team_id = this_game['away_team_id']
team_name = 'away team'
full_lineup = get_lineup_members(this_game['id'], active=1, team_id=team_id)
roster_check = {
'C': 0,
'1B': 0,
'2B': 0,
'3B': 0,
'SS': 0,
'LF': 0,
'CF': 0,
'RF': 0,
'P': 0,
'DH': 0,
'batting_count': 0,
}
for x in full_lineup['members']:
if x['position'] in roster_check.keys():
roster_check[x["position"]] += 1
if 0 < x['batting_order'] < 10:
roster_check['batting_count'] += 1
errors = []
for key in roster_check:
if roster_check[key] > 1 and key != 'batting_count':
errors.append(f'{team_name} has {roster_check[key]}x {key}')
if roster_check[key] < 1 and key != 'DH':
errors.append(f'{team_name} has {roster_check[key]}x {key}')
if roster_check['batting_count'] != 9:
errors.append(f'{team_name} has {roster_check["batting_count"]} players in the batting order')
logger.info(f'{"home" if home else "away"} error count: {len(errors)}')
return {
'valid': True if len(errors) == 0 else False,
'errors': errors
}
async def game_state_ascii(self, this_game):
away_team = await get_one_team(this_game['away_team_id'])
home_team = await get_one_team(this_game['home_team_id'])
gs = get_game_state(this_game['id'])['states'][0]
occupied = ''
unoccupied = ''
logger.info(f'checking baserunners')
first_base = unoccupied if not gs['on_first'] else occupied
second_base = unoccupied if not gs['on_second'] else occupied
third_base = unoccupied if not gs['on_third'] else occupied
logger.info(f'checking inning and score')
if gs['final']:
outs = ''
inning = 'FINAL'
else:
outs = f'{gs["outs"]} Out{"s" if gs["outs"] != 1 else ""}'
half = '' if gs['top_half'] else ''
inning = f'{half} {gs["inning"]}'
logger.info(f'getting pitcher and batter members')
pbc_data = self.get_pbc(this_game)
batter_member = pbc_data['batter']
pitcher_member = pbc_data['pitcher']
logger.info(f'getting pitcher and batter players')
if not batter_member:
this_batter = {'name': 'BATTER NOT FOUND', 'image': LOGO}
else:
this_batter = await get_one_player(batter_member['player_id'])
if not pitcher_member:
this_pitcher = {'name': 'PITCHER NOT FOUND', 'image': LOGO}
else:
this_pitcher = await get_one_player(pitcher_member['player_id'])
# pitcher_name = this_pitcher['name'].split(' ', 1)[1]
# batter_name = this_batter['name'].split(' ', 1)[1]
logger.info(f'setting player names')
# pitcher_name = this_pitcher['name'] if len(this_pitcher['name']) <= 19 else f'{this_pitcher["name"][:16]}...'
# batter_name = this_batter['name'] if len(this_batter['name']) <= 19 else f'{this_batter["name"][:16]}...'
logger.info(f'setting player stats')
era = f'{6.9:.2f}'
avg = f'{.420:.3f}'
if avg[0] == '0':
avg = avg[1:]
logger.info(f'generating game string')
game_string = f'```\n' \
f'{away_team["abbrev"]: ^4}{gs["away_score"]: ^3} {second_base}' \
f'{inning: >12}\n' \
f'{home_team["abbrev"]: ^4}{gs["home_score"]: ^3} {third_base} {first_base}' \
f'{outs: >10}\n```'
return {'gs': game_string, 'batter': this_batter, 'pitcher': this_pitcher, 'away_team': away_team,
'home_team': home_team, 'batter_member': batter_member, 'pitcher_member': pitcher_member}
@staticmethod
def on_base_code(this_state):
if this_state['on_first'] and this_state['on_second'] and this_state['on_third']:
obc = 7
elif this_state['on_second'] and this_state['on_third']:
obc = 6
elif this_state['on_first'] and this_state['on_third']:
obc = 5
elif this_state['on_first'] and this_state['on_second']:
obc = 4
elif this_state['on_third']:
obc = 3
elif this_state['on_second']:
obc = 2
elif this_state['on_first']:
obc = 1
else:
obc = 0
return obc
@staticmethod
def increment_score(this_game, runs: int):
this_state = get_game_state(game_id=this_game['id'])['states'][0]
if this_state['top_half']:
new_state = patch_game_state(
this_state['id'],
away_score=this_state['away_score'] + runs
)
else:
new_state = patch_game_state(
this_state['id'],
home_score=this_state['home_score'] + runs
)
return new_state
@staticmethod
def increment_outs(this_game, outs: int):
this_state = get_game_state(game_id=this_game['id'])['states'][0]
if this_state['outs'] + outs < 3:
new_outs = this_state['outs'] + outs
new_state = patch_game_state(
this_state['id'],
outs=new_outs
)
elif this_state['top_half']:
new_state = patch_game_state(
this_state['id'],
outs=0,
top_half=False,
on_first_id=False,
on_second_id=False,
on_third_id=False
)
else:
new_state = patch_game_state(
this_state['id'],
outs=0,
top_half=True,
inning=this_state['inning'] + 1,
on_first_id=False,
on_second_id=False,
on_third_id=False
)
return new_state
def advance_runners(self, this_state, num_bases: int, error: int, force_only=False):
"""
Moves runners and gives them a run scored if necessary - does not update game state
:param error:
:param this_state:
:param num_bases:
:param force_only:
:return: int: runs scored on play
"""
rbi = 0
# logger.info(f'this_state:\n{this_state}\n')
if this_state['on_third']:
if not force_only or (force_only and this_state['on_second'] and this_state['on_first']):
logger.info(f'runner {this_state["on_third"]["id"]} scores')
on_third = get_one_atbat(
game_id=this_state['game']['id'], batter_id=this_state['on_third']['player_id']
)
patch_atbat(on_third['id'], run=1, error=error)
patch_game_state(this_state['id'], on_third_id=False)
rbi += 1
# logger.info('pre-on_second')
if this_state['on_second']:
if not force_only or (force_only and this_state['on_first']):
if num_bases > 1:
logger.info(f'runner {this_state["on_second"]["id"]} scores')
on_second = get_one_atbat(
game_id=this_state['game']['id'], batter_id=this_state['on_second']['player_id']
)
patch_atbat(on_second['id'], run=1, error=error)
patch_game_state(this_state['id'], on_second_id=False)
rbi += 1
else:
logger.info(f'runner {this_state["on_second"]["id"]} from second to third')
patch_game_state(this_state['id'], on_second_id=False, on_third_id=this_state['on_second']['id'])
# logger.info('pre-on_first')
if this_state['on_first']:
if num_bases > 2:
logger.info(f'runner {this_state["on_first"]["id"]} scores')
on_first = get_one_atbat(
game_id=this_state['game']['id'], batter_id=this_state['on_first']['player_id']
)
patch_atbat(on_first['id'], run=1, error=error)
patch_game_state(this_state['id'], on_first_id=False)
rbi += 1
elif num_bases == 2:
logger.info(f'runner {this_state["on_first"]["id"]} from first to third')
patch_game_state(this_state['id'], on_first_id=False, on_third_id=this_state['on_first']['id'])
else:
logger.info(f'runner {this_state["on_first"]["id"]} from first to second')
patch_game_state(this_state['id'], on_first_id=False, on_second_id=this_state['on_first']['id'])
return rbi
@staticmethod
def get_pbc(this_game):
"""
Return a dict including the pitcher, batter, and catcher
:param this_game:
:return:
"""
this_state = get_game_state(game_id=this_game['id'])['states'][0]
# Helpers for finding pitcher and batter
if this_state['top_half']:
bat_team_id = this_game['away_team_id']
pit_team_id = this_game['home_team_id']
batting_order = this_state['away_batter_up']
else:
bat_team_id = this_game['home_team_id']
pit_team_id = this_game['away_team_id']
batting_order = this_state['home_batter_up']
# Get pitcher and batter
logger.info('get pitcher and batter')
try:
this_batter = get_one_member(
game_id=this_game['id'], team_id=bat_team_id, batting_order=batting_order, active=1
)
except:
this_batter = None
try:
this_pitcher = get_one_member(
game_id=this_game['id'], team_id=pit_team_id, position='P', active=1
)
except:
this_pitcher = None
try:
this_catcher = get_one_member(
game_id=this_game['id'], team_id=pit_team_id, position='C', active=1
)
except:
this_catcher = None
return {'batter': this_batter, 'pitcher': this_pitcher, 'catcher': this_catcher}
@staticmethod
def get_defender(this_game, pos):
this_state = get_game_state(game_id=this_game['id'])['states'][0]
if this_state['top_half']:
this_member = get_one_member(
game_id=this_game['id'],
team_id=this_game['home_team_id'],
position=pos.upper(),
active=1
)
else:
this_member = get_one_member(
game_id=this_game['id'],
team_id=this_game['away_team_id'],
position=pos.upper(),
active=1
)
return this_member
@staticmethod
async def get_batter_statline(data):
batter_string = ''
bs = get_atbat_totals(data['batter_member']['id'])
if not bs['empty']:
batter_string += f' - {bs["hit"] if bs["hit"] else 0}-{bs["ab"] if bs["ab"] else 0}'
if bs["homerun"]:
batter_string += f', '
if bs["homerun"] > 1:
batter_string += f'{bs["homerun"]} '
batter_string += 'HR'
if bs["triple"]:
batter_string += f', '
if bs["triple"] > 1:
batter_string += f'{bs["triple"]} '
batter_string += '3B'
if bs["double"]:
batter_string += f', '
if bs["double"] > 1:
batter_string += f'{bs["double"]} '
batter_string += '2B'
if bs["bb"]:
batter_string += f', '
if bs["bb"] > 1:
batter_string += f'{bs["bb"]} '
batter_string += 'BB'
if bs["hbp"]:
batter_string += f', '
if bs["hbp"] > 1:
batter_string += f'{bs["hbp"]} '
batter_string += 'HBP'
if bs["sac"]:
batter_string += f', '
if bs["sac"] > 1:
batter_string += f'{bs["sac"]} '
batter_string += 'SAC'
if bs["so"]:
batter_string += f', '
if bs["so"] > 1:
batter_string += f'{bs["so"]} '
batter_string += 'K'
else:
b = await get_one_battingseason(data['batter_member']['player_id'])
batter_string = '1st PA'
if len(b) > 0:
if b['ab'] > 0:
singles = b['hit'] - b['hr'] - b['triple'] - b['double']
avg = b['hit'] / b['ab']
obp = (b['hit'] + b['bb'] + b['ibb'] + b['hbp']) / b['pa']
slg = ((b['hr'] * 4) + (b['triple'] * 3) + (b['double'] * 2) + singles) / b['ab']
ops = obp + slg
woba = ((b['bb'] * .69) + (b['hbp'] * .72) + (singles * .89) + (b['double'] * 1.27) +
(b['triple'] * 1.62) + (b['hr'] * 2.1)) / (b['pa'] - b['hbp'] - b['sac'])
ab = f'{b["ab"]:.0f}'
run = f'{b["run"]:.0f}'
hit = f'{b["hit"]:.0f}'
double = f'{b["double"]:.0f}'
top_stat = {'name': '2B', 'value': double, 'int': b['double']}
triple = f'{b["triple"]:.0f}'
if b["triple"] > top_stat['int']:
top_stat['name'] = '3B'
top_stat['value'] = triple
top_stat['int'] = b['triple']
hr = f'{b["hr"]:.0f}'
if b["hr"] > top_stat['int']:
top_stat['name'] = 'HR'
top_stat['value'] = hr
top_stat['int'] = b['hr']
rbi = f'{b["rbi"]:.0f}'
sb = f'{b["sb"]:.0f}'
if b["sb"] > top_stat['int']:
top_stat['name'] = 'SB'
top_stat['value'] = sb
top_stat['int'] = b['sb']
so = f'{b["so"]:.0f}'
batter_string = f' - {avg:.3f} / {obp:.3f} / {slg:.3f}, {top_stat["value"]} {top_stat["name"]}'
# batting_string = f'```\n' \
# f' AVG OBP SLG OPS\n' \
# f' {avg:.3f} {obp:.3f} {slg:.3f} {ops:.3f}\n``````\n' \
# f' AB R H 2B 3B HR RBI SB SO\n' \
# f'{ab: >3} {run: >2} {hit: ^3} {double: >2} {triple: >2} {hr: >2} {rbi: >3} ' \
# f'{sb: >2} {so: ^3}\n```'
logger.info(f'batter_string: {batter_string}')
return batter_string
@staticmethod
async def get_pitcher_statline(data):
pitcher_string = ''
bs = get_atbat_pitching_totals(data['pitcher_member']['id'])
if not bs['empty']:
pitcher_string += f'{bs["ip"]:.1f} IP'
if bs["run"]:
pitcher_string += f', {bs["run"]} R'
if bs["run"] != bs["erun"]:
pitcher_string += f' ({bs["erun"]} ER)'
if bs["so"]:
pitcher_string += f', {bs["so"]} K'
if bs["hit"]:
pitcher_string += f', {bs["hit"]} H'
if bs["bb"]:
pitcher_string += f', {bs["bb"]} BB'
if bs["hbp"]:
pitcher_string += f', {bs["hbp"]} HBP'
if bs["hr"]:
pitcher_string += f', {bs["hbp"]} HR'
else:
p = await get_one_pitchingseason(data['pitcher_member']['player_id'])
if len(p) > 0:
if p['ip'] > 0:
win = f'{p["win"]:.0f}'
loss = f'{p["loss"]:.0f}'
save = f'{p["sv"]:.0f}'
era = f'{(p["erun"] * 9) / p["ip"]:.2f}'
game = f'{p["game"]:.0f}'
gs = f'{p["gs"]:.0f}'
ip = f'{p["ip"]:.0f}'
if p["ip"] % 1 == 0:
ip += '.0'
elif str(p["ip"] % 1)[2] == '3':
ip += '.1'
else:
ip += '.2'
so = f'{p["so"]:.0f}'
whip = f'{(p["bb"] + p["hit"]) / p["ip"]:.2f}'
g_string = f'{gs} GS' if p['game'] == p['gs'] else f'{game} G'
pitcher_string = f' - {g_string}, ({win}-{loss})'
if p["sv"]:
pitcher_string += f', {save} SV'
pitcher_string += f', {era} ERA'
# pitching_string = f'```\n' \
# f' W L SV ERA IP SO WHIP\n' \
# f'{win: >2} {loss: >2} {save: >2} {era: >5} {ip: >5} ' \
# f'{so: >3} {whip: >4}\n```'
logger.info(f'pitcher_string: {pitcher_string}')
return pitcher_string
@staticmethod
def adv_specific_runner(this_game, from_base, num_bases):
"""
Moves a single runner and gives them a run scored if necessary - does not update game state
:param this_game:
:param from_base:
:param num_bases:
:return:
"""
this_state = get_game_state(game_id=this_game['id'])['states'][0]
rbi = 0
if from_base == 3 and this_state['on_third']:
logger.info(f'runner {this_state["on_third"]["id"]} scores')
this_runner = get_atbat(
game_id=this_state['game']['id'], batter_id=this_state['on_third']['player_id']
)['atbats']
on_third = this_runner[len(this_runner) - 1]
patch_atbat(on_third['id'], run=1)
patch_game_state(this_state['id'], on_third_id=False)
rbi += 1
elif from_base == 2 and this_state['on_second']:
if num_bases > 1:
logger.info(f'runner {this_state["on_second"]["id"]} scores')
this_runner = get_atbat(
game_id=this_state['game']['id'], batter_id=this_state['on_second']['player_id']
)['atbats']
on_second = this_runner[len(this_runner) - 1]
patch_atbat(on_second['id'], run=1)
patch_game_state(this_state['id'], on_second_id=False)
rbi += 1
else:
logger.info(f'runner {this_state["on_second"]["id"]} from second to third')
patch_game_state(this_state['id'], on_second_id=False, on_third_id=this_state['on_second']['id'])
elif from_base == 1 and this_state['on_first']:
if num_bases > 2:
logger.info(f'runner {this_state["on_first"]["id"]} scores')
this_runner = get_atbat(
game_id=this_state['game']['id'], batter_id=this_state['on_first']['player_id']
)['atbats']
on_first = this_runner[len(this_runner) - 1]
patch_atbat(on_first['id'], run=1)
patch_game_state(this_state['id'], on_first_id=False)
rbi += 1
elif num_bases == 2:
logger.info(f'runner {this_state["on_first"]["id"]} from first to third')
patch_game_state(this_state['id'], on_first_id=False, on_third_id=this_state['on_first']['id'])
else:
logger.info(f'runner {this_state["on_first"]["id"]} from first to second')
patch_game_state(this_state['id'], on_first_id=False, on_second_id=this_state['on_first']['id'])
return rbi
async def game_state_embed(self, this_game, full_length=True):
data = await self.game_state_ascii(this_game)
embed = discord.Embed(title=f'{data["away_team"]["sname"]} @ {data["home_team"]["sname"]}')
embed.add_field(name='Game State', value=data['gs'], inline=False)
pitcher_string = f'[{data["pitcher"]["name"]}]({get_player_url(data["pitcher"])}) ' \
f'{await self.get_pitcher_statline(data)}'
# embed.add_field(name='Pitcher', value=pitcher_string)
batter_string = f'[{data["batter"]["name"]}]({get_player_url(data["batter"])}) ' \
f'{await self.get_batter_statline(data)}'
# embed.add_field(name='Batter', value=batter_string)
embed.add_field(name='Matchup', value=f'Pitcher: {pitcher_string}\nvs\nBatter: {batter_string}', inline=False)
embed.set_thumbnail(url=f'{data["pitcher"]["image"]}')
embed.set_image(url=f'{data["batter"]["image"]}')
this_state = get_game_state(game_id=this_game['id'])['states'][0]
baserunner_string = ''
if this_state['on_first']:
runner = await get_one_player(this_state['on_first']['player_id'])
# embed.add_field(name='On First', value=f'[{runner["name"]}]({get_player_url(runner)})')
baserunner_string += f'On First: [{runner["name"]}]({get_player_url(runner)})\n'
if this_state['on_second']:
runner = await get_one_player(this_state['on_second']['player_id'])
# embed.add_field(name='On Second', value=f'[{runner["name"]}]({get_player_url(runner)})')
baserunner_string += f'On Second: [{runner["name"]}]({get_player_url(runner)})\n'
if this_state['on_third']:
runner = await get_one_player(this_state['on_third']['player_id'])
# embed.add_field(name='On Third', value=f'[{runner["name"]}]({get_player_url(runner)})')
baserunner_string += f'On Third: [{runner["name"]}]({get_player_url(runner)})\n'
if len(baserunner_string) > 0:
embed.add_field(name='Baserunners', value=baserunner_string, inline=False)
if not full_length:
return embed
async def lineup_string(team):
full_lineup = get_lineup_members(this_game['id'], active=1, team_id=team['id'])
sorted_lineup = sorted(full_lineup['members'], key=lambda item: item['batting_order'])
l_string = ''
for x in sorted_lineup:
player = await get_one_player(x["player_id"])
l_string += f'{x["batting_order"]}. [{player["name"]}]({get_player_url(player)}) - ' \
f'{x["position"]}\n'
if len(l_string) == 0:
l_string = 'None, yet\n\'!sl\' to set'
return l_string
embed.add_field(
name=f'{data["away_team"]["abbrev"]} Lineup',
value=await lineup_string(data["away_team"])
)
embed.add_field(
name=f'{data["home_team"]["abbrev"]} Lineup',
value=await lineup_string(data["home_team"])
)
return embed
def log_play_core(self, this_game, bases=0, outs=0, ab=1, hit=0, error=0, force_only=False):
"""
Logs an AtBat, taking the optional parameters into account; does not place batter runner
:param error:
:param this_game:
:param bases:
:param outs:
:param ab:
:param hit:
:param force_only:
:return: {'batter': LineupMember, 'ab': AtBat}
"""
this_state = get_game_state(game_id=this_game['id'])['states'][0]
data = self.get_pbc(this_game)
this_batter = data['batter']
this_pitcher = data['pitcher']
# Get OBC and patch GameState score if necessary
logger.info('Get OBC and patch GameState score if necessary')
obc = self.on_base_code(this_state)
rbi = 0
next_batters = {
'away': this_state['away_batter_up'] + 1 if this_state['away_batter_up'] < 9 else 1,
'home': this_state['home_batter_up'] + 1 if this_state['home_batter_up'] < 9 else 1,
}
new_half = this_state['top_half']
new_inning = this_state['inning']
if this_state['outs'] + outs < 3:
new_outs = this_state['outs'] + outs
# Advance runners
logger.info('Advance runners')
if obc > 0 and bases > 0:
rbi = self.advance_runners(this_state, bases, error=error, force_only=force_only)
if bases == 4:
rbi += 1
else:
# Ends half inning
new_outs = 0
new_half = not this_state['top_half']
if new_half:
new_inning += 1
patch_game_state(
this_state['id'],
on_first_id=False,
on_second_id=False,
on_third_id=False
)
if this_state['top_half']:
# Update score, batter up, and runner on first
logger.info(f'current batter: {this_state["away_batter_up"]} / next: {next_batters["away"]}')
patch_game_state(
this_state['id'],
away_score=this_state['away_score'] + rbi,
away_batter_up=next_batters['away'],
outs=new_outs,
top_half=new_half,
inning=new_inning
)
else:
patch_game_state(
this_state['id'],
home_score=this_state['home_score'] + rbi,
home_batter_up=next_batters['home'],
outs=new_outs,
top_half=new_half,
inning=new_inning
)
logger.info(f'post AB:\ngame_id: {this_game["id"]}\nbatter_id: {this_batter["player_id"]}\n'
f'pitcher_id: {this_pitcher["player_id"]}\nobc: {obc}\ninning: {this_state["inning"]}\n'
f'hit: {hit}\nrbi: {rbi}\nab: {ab}\nerror: {error}')
this_ab = post_atbat({
'game_id': this_game['id'],
'batter_id': this_batter['player_id'],
'pitcher_id': this_pitcher['player_id'],
'on_base_code': obc,
'inning': this_state['inning'],
'hit': hit,
'rbi': rbi if not error else 0,
'ab': ab,
'error': error
})
return {'batter': this_batter, 'ab': this_ab}
async def get_csv_batters(self, this_game, team_id):
bats = []
for x in get_lineup_members(this_game['id'], team_id=team_id)['members']:
this_player = await get_one_player(x['player_id'])
bs = get_atbat_totals(x['id'])
rs = get_running_totals(x['id'])
ch = get_chaos_totals(x['id'])
ds = get_defense_totals(x['id'])
if bs['empty'] and rs['empty'] and ch['empty'] and len(ds['defense']) == 0:
logger.info(f'No stats for {this_player["name"]} - moving on')
else:
dpos = None
if len(ds['defense']) == 1:
logger.info('one defense found')
if ds['defense'][0]['pos'] == x['position']:
logger.info('matches position')
dpos = ds['defense'][0]
else:
logger.info('doesn\'t match position')
line = ds['defense'][0]
bats.append([
this_player['name'], this_player['team']['abbrev'], x['position'], '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', line['xch'],
line['xch'],
line['xch'], line['xhit'], line['errors'], '', line['sba'], line['csc'], line['roba'],
line['robs'], line['raa'], line['rto']
])
else:
logger.info('many defenses found')
for line in ds['defense']:
if line['pos'] == line['pos']:
logger.info('this one matches pos')
dpos = line
else:
logger.info('this one does not matche pos')
bats.append([
this_player['name'], this_player['team']['abbrev'], x['position'], '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', line['xch'],
line['xch'],
line['xch'], line['xhit'], line['errors'], '', line['sba'], line['csc'], line['roba'],
line['robs'], line['raa'], line['rto']
])
if not dpos:
logger.info('no defense found; adding blanks')
dpos = {
'xch': '',
'xhit': '',
'errors': '',
'sba': '',
'csc': '',
'roba': '',
'robs': '',
'raa': '',
'rto': '',
}
logger.info('done with defense')
bats.append([
this_player['name'], this_player['team']['abbrev'], x['position'], bs['pa'], bs['ab'], bs['run'],
bs['hit'], bs['rbi'], bs['double'], bs['triple'], bs['homerun'], bs['bb'], bs['so'],
bs['hbp'], bs['sac'], bs['ibb'], bs['gidp'], rs['sb'], rs['cs'], bs['bphr'], bs['bpfo'], bs['bp1b'],
bs['bplo'], '', '', dpos['xch'], dpos['xhit'], dpos['errors'],
ch['pb'] if x['position'] == 'C' else '', dpos['sba'], dpos['csc'], '', '', dpos['raa'], dpos['rto']
])
return bats
async def get_csv_pitchers(self, this_game, team_id):
arms = []
for x in get_lineup_members(this_game['id'], team_id=team_id, position='P')['members']:
this_player = await get_one_player(x['player_id'])
bs = get_atbat_pitching_totals(x['id'])
cs = get_chaos_totals(x['id'])
if bs['empty'] and cs['empty']:
logger.info(f'No stats for {this_player["name"]} - moving on')
else:
arms.append([
this_player['name'], this_player['team']['abbrev'], bs['ip'], bs['hit'], bs['run'], bs['erun'],
bs['so'], bs['bb'], bs['hbp'], cs['wp'], cs['bk'], bs['hr']
])
return arms
async def get_game_results(self, this_game, combined=False):
away_bats = await self.get_csv_batters(this_game, this_game['away_team_id'])
away_arms = await self.get_csv_pitchers(this_game, this_game['away_team_id'])
home_bats = await self.get_csv_batters(this_game, this_game['home_team_id'])
home_arms = await self.get_csv_pitchers(this_game, this_game['home_team_id'])
if combined:
batters = [['player', 'team', 'pos', 'pa', 'ab', 'run', 'hit', 'rbi', '2b', '3b', 'hr', 'bb', 'so', 'hbp',
'sac', 'ibb', 'gidp', 'sb', 'cs', 'bphr', 'bpfo', 'bp1b', 'bplo', 'xba', 'xbt', 'xch', 'xhit', 'e',
'pb', 'sba', 'csc', 'roba', 'robs', 'raa', 'rto']]
for x in away_bats:
batters.append(x)
for x in home_bats:
batters.append(x)
pitchers = [['pitcher', 'team', 'ip', 'h', 'r', 'er', 'so', 'bb', 'hbp', 'wp', 'bk', 'hr', 'ir', 'irs',
'gs', 'win', 'loss', 'hold', 'save', 'bs']]
for x in away_arms:
pitchers.append(x)
for x in home_arms:
pitchers.append(x)
return {'batters': batters, 'pitchers': pitchers}
else:
return {'ab': away_bats, 'aa': away_arms, 'hb': home_bats, 'ha': home_arms}
async def get_game_sheet(self, home_team, away_team, results, player_name):
sheets = pygsheets.authorize(service_file='storage/major-domo-service-creds.json')
results_sheet = sheets.drive.copy_file(
'1e_o5Fg0-Q9sp09btesBpXA_4lUtbNkRngQ32_Ie9_iI',
f'{away_team["abbrev"]} @ {home_team["abbrev"]} for {player_name}',
'1G0XJvlUb2cIGjD3b2-XT89TSwAOZmzaz')
sheet_id = results_sheet['id']
results_tab = sheets.open_by_key(sheet_id).worksheet_by_title('Results')
results_tab.update_values(
crange='A3', values=results['ab']
)
results_tab.update_values(
crange='A33', values=results['hb']
)
results_tab.update_values(
crange='A22', values=results['aa']
)
results_tab.update_values(
crange='A52', values=results['ha']
)
return sheet_id
@commands.command(name='newgame', aliases=['ng'], help='Start a new game')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def new_game_command(self, ctx):
current = await db_get('current')
this_team = await get_team_by_owner(current.season, ctx.author.id)
await ctx.send('**Note:** Make sure this is the channel where you will be playing commands. Once this is set, '
'I will only take gameplay commands here.')
async def get_team(which='home'):
prompt = f'Please enter the abbrev of the {which} team.'
this_q = Question(self.bot, ctx.channel, prompt, qtype='text', timeout=15)
resp = await this_q.ask([ctx.author])
if not resp:
await ctx.send('You keep thinking about it and hit me up later if you figure it out.')
return None
else:
other_team = await get_one_team(resp)
if not other_team:
await ctx.send(f'What\'s a **{resp}**? If you could go ahead and run this command again, that\'d '
f'be great.')
return None
else:
return other_team
if not this_team:
await ctx.send('Hmm...I can\'t find your team. Are you from around here?')
return
try:
this_matchup = await get_one_schedule(
season=current.season,
team_abbrev1=this_team['abbrev'],
week=current.week
)
except ValueError as e:
home_team = await get_team('home')
away_team = await get_team('away')
if not home_team or not away_team:
return
else:
away_team = this_matchup['awayteam']
home_team = this_matchup['hometeam']
prompt = f'Would you like to start {away_team["abbrev"]} @ {home_team["abbrev"]} now?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 15)
resp = await this_q.ask([ctx.author])
if not resp:
home_team = await get_team('home')
away_team = await get_team('away')
if not home_team or not away_team:
return
this_game = post_game({
'away_team_id': away_team['id'],
'home_team_id': home_team['id'],
'channel_id': ctx.channel.id
})
await ctx.send(random_conf_gif())
await ctx.send('Go ahead and set lineups with the !setlineup command')
@commands.command(name='endgame', aliases=['eg'], help='End game in channel')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def end_game_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs_embed = await self.game_state_embed(this_game)
prompt = 'Is this the game you would like to end?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 15, gs_embed)
resp = await this_q.ask([ctx.author])
end_proper = True
if not resp:
await ctx.send('Okay, we can leave it active for now.')
return
else:
away_team = await get_one_team(this_game['away_team_id'])
home_team = await get_one_team(this_game['home_team_id'])
try:
async with ctx.typing():
results = await self.get_game_results(this_game)
sheet_id = await self.get_game_sheet(home_team, away_team, results, ctx.author.display_name)
except pygsheets.InvalidArgumentValue as e:
prompt = 'There was an error creating the scorecard. Should I just nuke this game?'
this_q.prompt = prompt
resp = await this_q.ask([ctx.author])
if not resp:
await ctx.send('Okay, we can leave it active for now.')
return
else:
end_proper = False
if end_proper:
await ctx.send(f'Here is the results sheet for this game: https://docs.google.com/spreadsheets/d/'
f'{sheet_id}')
else:
await ctx.send(random_conf_gif())
patch_game(game_id=this_game['id'], active=False)
@commands.command(name='setlineup', aliases=['sub', 'sl'], help='!sl <formatted_subs>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def set_lineup_command(self, ctx, team_abbrev, *, lineup):
ERROR_MESSAGE = 'The format for a lineup is: <batting order # (10 for pitcher)>-<Player Name>-<position>\n' \
'Examples:\n2-Trea Turner-SS\n10-Rich Hill-P\n\n' \
'You may set multiple spots by adding a comma at the end of the position and, without ' \
'spaces, entering the next spot.\n' \
'Examples:\n1-Ronald Acuna Jr-RF,2-Trea Turner-SS,3-Robinson Cano-2B\n' \
'7-Travis Jankowski-LF,10-Anthony Bass-P,5-Erik Gonzalez-3B'
# current = await db_get('current')
this_team = await get_one_team(team_abbrev)
this_game = get_game(channel_id=ctx.channel.id, active=1)
logger.info(f'this_game: {this_game}')
if this_team['id'] not in [this_game['away_team_id'], this_game['home_team_id']]:
away_team = await get_one_team(this_game['away_team_id'])
home_team = await get_one_team(this_game['home_team_id'])
await ctx.send(f'This channel is for {away_team["abbrev"]} @ {home_team["abbrev"]} - I can\'t set a lineup '
f'for **{team_abbrev.upper()}**.')
return
async with ctx.typing():
for member in re.split(',', lineup):
data = re.split('-', member)
this_player = await get_one_player(data[1])
# Check if player is already in game
try:
this_member = get_one_member(game_id=this_game['id'], player_id=this_player['id'], active=1)
except:
this_member = None
# If already in game, just change position
if this_member:
# Stop sub if changing batting order - unless pitcher is entering the lineup
if this_member['batting_order'] != int(data[0]) and this_member['batting_order'] != 10:
await ctx.send(f'{this_player["name"]} is already in the game batting '
f'#{this_member["batting_order"]} so they cannot move in the order.')
return
new_member = patch_lineup_member(
member_id=this_member['id'],
position=data[2].upper(),
batting_order=data[0]
)
# Else add new lineup member
else:
new_member = post_lineup_member({
'game_id': this_game['id'],
'team_id': this_team['id'],
'player_id': this_player['id'],
'position': data[2].upper(),
'batting_order': data[0]
})
members = get_lineup_members(this_game['id'], this_team['id'], batting_order=data[0], active=1)
if len(members['members']) > 1:
for x in range(len(members['members'])):
if members['members'][x]['id'] != new_member['id']:
old_member = patch_lineup_member(members['members'][x]['id'], active=False)
this_state = get_one_game_state(game_id=this_game['id'])
logger.info(f'old_member_id: {old_member["id"]} / on_first: {this_state["on_first"]} / '
f'on_second: {this_state["on_second"]} / on_third: {this_state["on_third"]}')
if old_member in [this_state['on_first'], this_state['on_second'], this_state['on_third']]:
if old_member == this_state['on_first']:
this_state = patch_game_state(
this_state['id'],
on_first_id=new_member['id']
)
elif old_member == this_state['on_second']:
this_state = patch_game_state(
this_state['id'],
on_second_id=new_member['id']
)
elif old_member == this_state['on_third']:
this_state = patch_game_state(
this_state['id'],
on_third_id=new_member['id']
)
sub_ab = post_atbat({
'game_id': this_game['id'],
'batter_id': new_member['player_id'],
'pitcher_id': self.get_pbc(this_game)['pitcher']['player_id'],
'on_base_code': 0,
'inning': this_state['inning'],
'pa': 0
})
# If player appears elsewhere in lineup, deactive old members
dupes = get_lineup_members(
this_game['id'], team_id=this_team['id'], player_id=this_player['id'], active=1
)
if len(dupes['members']) > 1:
for x in range(len(dupes['members']) - 1):
patch_lineup_member(dupes['members'][x]['id'], active=False)
home_check = self.lineup_check(this_game, True)
away_check = self.lineup_check(this_game, False)
check_string = f'Away lineup valid: {away_check["valid"]}\n'
if len(away_check['errors']) > 0:
error_string = "\n- ".join(away_check["errors"])
check_string += f'- {error_string}\n\n'
check_string += f'Home lineup valid: {home_check["valid"]}\n'
if len(home_check['errors']) > 0:
error_string = "\n- ".join(home_check["errors"])
check_string += f'- {error_string}\n\n'
if away_check['valid'] and home_check['valid']:
logger.info('Both lineups valid - sending gamestate')
content = None
embed = await self.game_state_embed(this_game, full_length=False)
else:
logger.info('Both lineups are not valid - sending check_string')
content = check_string
embed = None
await ctx.send(content=content, embed=embed)
@commands.command(name='gamestate', aliases=['gs'], help='Show game state')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def game_state_command(self, ctx):
# current = await db_get('current')
this_game = get_game(channel_id=ctx.channel.id, active=1)
await ctx.send(content=None, embed=await self.game_state_embed(this_game))
@commands.group(name='log', help='`!help log` for all commands')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_play(self, ctx):
if ctx.invoked_subcommand is None:
await ctx.send('No play details listed. Run `!help log` for available commands.')
@log_play.command(
name='single',
aliases=['si', 'si*', 'si**', 'siwh', '1b', '1b*', 'bp1b', 'bpsi', '1b**', '1bwh', 'single*', 'single**',
'singlewh'],
help='si, 1b, bp1b, bpsi, single, *, **, wh'
)
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_single_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
cmd = ctx.message.content.lower()
# Get baserunner advancement
if 'wh' in cmd or '**' in cmd:
bases = 2
else:
bases = 1
data = self.log_play_core(this_game, bases=bases, hit=1)
patch_game_state(
this_game['id'],
on_first_id=data['batter']['id']
)
if 'bp' in cmd:
patch_atbat(
data['ab']['id'],
bp1b=1
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(
name='double',
aliases=['do', 'do**', 'do***', 'dowh', '2b', '2b**', '2b***', '2bwh', 'double**', 'double***', 'doublewh'],
help='do, 2b, double, **, ***, wh'
)
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_double_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
# Get baserunner advancement
if 'wh' in ctx.message.content.lower() or '***' in ctx.message.content:
bases = 3
else:
bases = 2
data = self.log_play_core(this_game, bases=bases, hit=1)
patch_atbat(
data['ab']['id'],
double=1,
)
patch_game_state(
this_game['id'],
on_second_id=data['batter']['id']
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='triple', aliases=['tr', '3b'], help='tr, 3b')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_triple_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
# Get baserunner advancement
bases = 3
data = self.log_play_core(this_game, bases=bases, hit=1)
patch_atbat(
data['ab']['id'],
triple=1,
)
patch_game_state(
this_game['id'],
on_third_id=data['batter']['id']
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='homerun', aliases=['hr', 'bphr'], help='hr, bphr')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_homerun_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
# Get baserunner advancement
ballpark = 1 if 'bphr' in ctx.message.content.lower() else 0
data = self.log_play_core(this_game, bases=4, hit=1)
patch_atbat(
data['ab']['id'],
run=1,
rbi=data['ab']['rbi'],
homerun=1,
bphr=ballpark
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='sacrifice', aliases=['sacf', 'sacb'], help='sacf, sacb')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_sac_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
rbi = 0
sac = 0
bases = 0
ab = 0
if gs['outs'] < 2:
cmd = ctx.message.content.lower()
sac = 1
ab = 0
if 'sacf' in cmd:
rbi = self.adv_specific_runner(this_game, 3, 1)
elif 'sacb' in cmd:
bases = 1
data = self.log_play_core(this_game, bases=bases, outs=1, ab=ab)
patch_atbat(
atbat_id=data['ab']['id'],
rbi=rbi,
sac=sac
)
if rbi:
self.increment_score(this_game, rbi)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='walk', aliases=['bb', 'hbp', 'ibb'], help='bb, hbp, ibb')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_walk_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
data = self.log_play_core(this_game, bases=1, ab=0, force_only=True)
patch_game_state(
this_game['id'],
on_first_id=data['batter']['id']
)
cmd = ctx.message.content.lower()
if 'ibb' in cmd:
patch_atbat(
data['ab']['id'],
ibb=1,
)
elif 'walk' in cmd or 'bb' in cmd:
patch_atbat(
data['ab']['id'],
bb=1,
)
elif 'hbp' in cmd:
patch_atbat(
data['ab']['id'],
hbp=1,
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='sb_attempt', aliases=['sb', 'cs'], help='sb, cs')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_sb_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
if not gs['on_first'] and not gs['on_second'] and not gs['on_third']:
await ctx.send('I don\'t see any baserunners aboard - maybe this isn\'t a stolen base.')
return
runner_found = None
this_runner = None
count = 4
for runner in [gs['on_third'], gs['on_second'], gs['on_first']]:
count -= 1
if runner:
a_runner = await get_one_player(runner['player_id'])
prompt = f'Did {a_runner["name"]} attempt the steal?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
resp = await this_q.ask([ctx.author])
if resp:
runner_found = count
this_runner = runner
break
if not runner_found:
await ctx.send('If nobody was forced out - it must not have been a fielder\'s choice.')
return
cmd = ctx.message.content.upper()
stolen_base = 1 if 'SB' in cmd else 0
battery = self.get_pbc(this_game)
post_running({
'game_id': this_game['id'],
'runner_id': this_runner['player_id'],
'stolen_base': stolen_base,
'caught_stealing': not stolen_base
})
post_defense({
'game_id': this_game['id'],
'player_id': battery['catcher']['player_id'],
'position': 'C',
'stolen_base_attempt': 1,
'caught_stealing': not stolen_base,
})
if stolen_base:
if runner_found == 1:
patch_game_state(
state_id=gs['id'],
on_second_id=runner['id'],
on_first_id=False
)
elif runner_found == 2:
patch_game_state(
state_id=gs['id'],
on_third_id=runner['id'],
on_second_id=False
)
else:
patch_game_state(
state_id=gs['id'],
on_third_id=False
)
self.increment_score(this_game, 1)
else:
if runner_found == 1:
patch_game_state(
state_id=gs['id'],
on_first_id=False
)
elif runner_found == 2:
patch_game_state(
state_id=gs['id'],
on_second_id=False
)
else:
patch_game_state(
state_id=gs['id'],
on_third_id=False
)
self.increment_outs(this_game, 1)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
# @log_play.command(name='extra_base', aliases=['xba', 'xbt'], help='xba, xbt')
# @commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
# async def log_xba_command(self, ctx):
# this_game = get_game(channel_id=ctx.channel.id, active=1)
# gs = get_game_state(game_id=this_game['id'])['states'][0]
#
# if not gs['on_first'] and not gs['on_second'] and not gs['on_third']:
# await ctx.send('I don\'t see any baserunners aboard - maybe there wasn\'t an advance.')
# return
#
# runner_found = None
# runner_player = None
# this_runner = None
# base_taken = True
# count = 4
# for runner in [gs['on_third'], gs['on_second'], gs['on_first']]:
# count -= 1
# if runner:
# runner_player = await get_one_player(runner['player_id'])
# prompt = f'Did {runner_player["name"]} attempt the advance?'
# this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
# resp = await this_q.ask([ctx.author])
# if resp:
# runner_found = count
# this_runner = runner
# break
#
# if not runner_found:
# await ctx.send('If nobody made an attempt - it must not have been an xba.')
# return
#
# prompt = 'Was there a throw from an outfielder? (Was a d20 rolled?)'
# this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
# defender_found = None
# this_defender = None
# resp = await this_q.ask([ctx.author])
# if resp:
# count = -1
# for defender in [self.get_defender(this_game, 'CF'), self.get_defender(this_game, 'RF'),
# self.get_defender(this_game, 'LF')]:
# count += 1
# if defender:
# a_defender = await get_one_player(defender['player_id'])
# prompt = f'Did {a_defender["name"]} make the throw?'
# this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
# resp = await this_q.ask([ctx.author])
# if resp:
# defender_found = ['CF', 'RF', 'LF'][count]
# this_defender = defender
# break
#
# if not defender_found:
# await ctx.send('So I guess there wasn\'t an attempt.')
# return
#
# this_q.prompt = f'Did {runner_player["name"]} reach safely?'
# resp = await this_q.ask([ctx.author])
# if resp is not None:
# base_taken = resp
#
# logger.info(f'defender: {defender}')
# post_defense({
# 'game_id': this_game['id'],
# 'player_id': this_defender['player_id'],
# 'position': this_defender['position'],
# 'runner_adv_att': 1,
# 'runner_throw_out': not base_taken
# })
#
# post_running({
# 'game_id': this_game['id'],
# 'runner_id': this_runner['player_id'],
# 'extra_base_attempt': 1,
# 'extra_base_taken': base_taken
# })
#
# if base_taken:
# if runner_found == 1:
# self.adv_specific_runner(this_game, 1, 1)
# elif runner_found == 2:
# self.adv_specific_runner(this_game, 2, 1)
# else:
# self.adv_specific_runner(this_game, 3, 1)
# self.increment_score(this_game, 1)
# await ctx.send('**Take a note to log this RBI - it\'s one of the things '
# 'I can\'t do automatically, yet.**')
# else:
# if runner_found == 1:
# patch_game_state(
# state_id=gs['id'],
# on_first_id=False
# )
# elif runner_found == 2:
# patch_game_state(
# state_id=gs['id'],
# on_second_id=False
# )
# else:
# patch_game_state(
# state_id=gs['id'],
# on_third_id=False
# )
# self.increment_outs(this_game, 1)
#
# await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
# @log_play.command(name='robs', aliases=['roba'], help='RobA, RobS')
# @commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
# async def log_rob_command(self, ctx):
# this_game = get_game(channel_id=ctx.channel.id, active=1)
#
# defender_found = None
# this_defender = None
# count = 0
# for defender in [self.get_defender(this_game, 'CF'), self.get_defender(this_game, 'LF'),
# self.get_defender(this_game, 'RF')]:
# count += 1
# if defender:
# a_defender = await get_one_player(defender['player_id'])
# prompt = f'Did {a_defender["name"]} attempt the rob?'
# this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
# resp = await this_q.ask([ctx.author])
# if resp:
# defender_found = True
# this_defender = defender
# break
#
# if not defender_found:
# await ctx.send('So I guess there wasn\'t an attempt.')
# return
#
# prompt = f'Did {a_defender["name"]} successfully rob it?'
# this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
# resp = await this_q.ask([ctx.author])
# if resp is not None:
# base_taken = resp
#
# post_defense({
# 'game_id': this_game['id'],
# 'player_id': this_defender['player_id'],
# 'position': this_defender['position'],
# 'rob_attempt': 1,
# 'rob_success': resp
# })
#
# await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(
name='out',
aliases=['flyout', 'groundout', 'popout', 'lineout', 'strikeout', 'fo', 'bpfo', 'go', 'po', 'lo', 'bplo',
'so', 'k'],
help='fo, bpfo, go, po, lo, bplo, so, k'
)
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME)
async def log_out_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
cmd = ctx.message.content.lower()
if 'bpfo' in cmd:
data = self.log_play_core(this_game, outs=1, ab=1)
sac_rbi = self.adv_specific_runner(this_game, 3, 1)
patch_atbat(
atbat_id=data['ab']['id'],
ab=1 - sac_rbi,
bpfo=1,
rbi=sac_rbi,
sac=sac_rbi
)
else:
data = self.log_play_core(this_game, outs=1)
if 'so' in cmd or 'k' in cmd or 'strikeout' in cmd:
patch_atbat(
atbat_id=data['ab']['id'],
so=1
)
elif 'bplo' in cmd:
patch_atbat(
atbat_id=data['ab']['id'],
bplo=1
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='xcheck', aliases=['x'], help='x <pos>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_xcheck_command(self, ctx, position):
this_game = get_game(channel_id=ctx.channel.id, active=1)
if position.upper() not in ['C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'P']:
await ctx.send(f'Ope, **{position.upper()}** is not a valid position.')
return
defender = self.get_defender(this_game, position)
this_state = get_game_state(game_id=this_game['id'])['states'][0]
prompt = f'Please enter the hit/out result (\'1B(\\*\\*)\', \'2B(\\*\\*\\*)\', \'3B\', or \'out\')'
this_q = Question(self.bot, ctx.channel, prompt, 'text', 15)
resp = await this_q.ask([ctx.author])
if not resp:
await ctx.send('You think on it and get back to me.')
return
elif resp.upper() not in ['1B', '1BWH', '1B**', '2B', '2BWH', '2B***', '3B', 'OUT']:
await ctx.send('I can only take 1B, 2B, 3B, or OUT')
return
else:
if resp.upper() == 'OUT':
hit = 0
else:
hit_bases = int(resp[0])
if len(resp) > 2:
bases = int(resp[0]) + 1
else:
bases = hit_bases
hit = 1
data = self.log_play_core(this_game, bases=bases, hit=1)
if hit_bases == 1:
patch_game_state(
state_id=this_state['id'],
on_first_id=data['batter']['id']
)
elif hit_bases == 2:
patch_atbat(
data['ab']['id'],
double=1,
)
patch_game_state(
state_id=this_state['id'],
on_second_id=data['batter']['id']
)
elif hit_bases == 3:
patch_atbat(
data['ab']['id'],
triple=1,
)
patch_game_state(
state_id=this_state['id'],
on_third_id=data['batter']['id']
)
prompt = f'For an error, please enter the number of bases. Otherwise, enter 0.'
this_q.prompt = prompt
this_q.qtype = 'int'
resp = await this_q.ask([ctx.author])
if resp is None:
await ctx.send('You think on it and get back to me.')
return
elif 0 > resp > 3:
await ctx.send('I can only take 0 -> 3')
return
else:
if resp > 0:
error = 1
if hit == 0:
data = self.log_play_core(this_game, bases=resp, error=1)
logger.info(f'error: {resp}')
if resp == 1:
patch_game_state(
state_id=this_state['id'],
on_first_id=data['batter']['id']
)
elif resp == 2:
patch_game_state(
state_id=this_state['id'],
on_second_id=data['batter']['id']
)
elif resp == 3:
patch_game_state(
state_id=this_state['id'],
on_third_id=data['batter']['id']
)
else:
self.advance_runners(this_state=this_state, num_bases=resp, error=1)
else:
error = 0
post_defense({
'game_id': this_game['id'],
'player_id': defender['player_id'],
'x_check': 1,
'position': defender['position'],
'hit': hit,
'error': error
})
if hit == 0 and error == 0:
await ctx.send('**The defense check has been logged - please log the specific out now. (e.g. !log '
'fo, !log gora, !log sacf)**')
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='doubleplay', aliases=['gidp', 'gba'], help='gidp, gbA')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_gidp_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
if not gs['on_first'] and not gs['on_second'] and not gs['on_third']:
await ctx.send('I don\'t see any baserunners aboard - maybe this should just be a groundout.')
return
runner_found = None
count = 0
for runner in [gs['on_first'], gs['on_second'], gs['on_third']]:
count += 1
if runner:
this_runner = await get_one_player(runner['player_id'])
prompt = f'Was {this_runner["name"]} doubled off?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
resp = await this_q.ask([ctx.author])
if resp:
runner_found = count
break
if not runner_found:
await ctx.send('If nobody was double off - it must not have been a GIDP.')
return
# If both outs of gidp are recorded, remove baserunner and record gidp
if gs['outs'] < 2:
gidp = 1
if runner_found == 1:
patch_game_state(
state_id=gs['id'],
on_first_id=False
)
elif runner_found == 2:
patch_game_state(
state_id=gs['id'],
on_second_id=False
)
else:
patch_game_state(
state_id=gs['id'],
on_third_id=False
)
else:
gidp = 0
data = self.log_play_core(this_game, outs=2)
patch_atbat(
atbat_id=data['ab']['id'],
gidp=gidp
)
gs = get_game_state(game_id=this_game['id'])['states'][0]
rbi = 0
count = 1
for runner in [gs['on_second'], gs['on_third']]:
count += 1
if runner:
this_runner = await get_one_player(runner['player_id'])
prompt = f'Did {this_runner["name"]} advance?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
resp = await this_q.ask([ctx.author])
if resp:
rbi += self.adv_specific_runner(this_game, count, 1)
if rbi:
patch_atbat(
data['ab']['id'],
rbi=rbi,
)
patch_game_state(
gs['id'],
home_score=gs['home_score'] + rbi
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='gora', aliases=['gbc'], help='gora, gbC')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_gora_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
data = self.log_play_core(this_game, outs=1, bases=1)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='fc', aliases=['gbb'], help='fc, gbB')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_fc_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
if not gs['on_first'] and not gs['on_second'] and not gs['on_third']:
await ctx.send('I don\'t see any baserunners aboard - maybe this should just be a groundout.')
return
runner_found = None
count = 0
for runner in [gs['on_first'], gs['on_second'], gs['on_third']]:
count += 1
if runner:
this_runner = await get_one_player(runner['player_id'])
prompt = f'Was {this_runner["name"]} the forced out runner?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
resp = await this_q.ask([ctx.author])
if resp:
runner_found = count
break
if not runner_found:
await ctx.send('If nobody was forced out - it must not have been a fielder\'s choice.')
return
# If fewer than 2 outs, remove forced out runner first
if gs['outs'] < 2:
if runner_found == 1:
patch_game_state(
state_id=gs['id'],
on_first_id=False
)
elif runner_found == 2:
patch_game_state(
state_id=gs['id'],
on_second_id=False
)
else:
patch_game_state(
state_id=gs['id'],
on_third_id=False
)
data = self.log_play_core(this_game, outs=1)
patch_game_state(
gs['id'],
on_first_id=data['batter']['id']
)
gs = get_game_state(game_id=this_game['id'])['states'][0]
rbi = 0
count = 1
for runner in [gs['on_second'], gs['on_third']]:
count += 1
if runner:
this_runner = await get_one_player(runner['player_id'])
prompt = f'Did {this_runner["name"]} advance?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
resp = await this_q.ask([ctx.author])
if resp:
rbi += self.adv_specific_runner(this_game, count, 1)
if rbi:
patch_atbat(
data['ab']['id'],
rbi=rbi,
)
self.increment_score(this_game, rbi)
# patch_game_state(
# gs['id'],
# home_score=gs['home_score'] + rbi
# )
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@log_play.command(name='chaos', aliases=['wp', 'pb', 'pick', 'balk'], help='wp, pb, pick, balk')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def log_chaos_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
cmd = ctx.message.content.lower()
pick = 0
wp = 0
pb = 0
balk = 0
if 'pick' in cmd:
if not gs['on_first'] and not gs['on_second'] and not gs['on_third']:
await ctx.send('I don\'t see any baserunners aboard.')
return
runner_found = None
count = 0
for runner in [gs['on_first'], gs['on_second'], gs['on_third']]:
count += 1
if runner:
this_runner = await get_one_player(runner['player_id'])
prompt = f'Was {this_runner["name"]} picked off?'
this_q = Question(self.bot, ctx.channel, prompt, 'yesno', 10)
resp = await this_q.ask([ctx.author])
if resp:
runner_found = count
break
if not runner_found:
await ctx.send('If nobody was picked off - maybe try another play.')
return
elif runner_found == 1:
patch_game_state(
gs['id'],
on_first_id=False
)
elif runner_found == 2:
patch_game_state(
gs['id'],
on_second_id=False
)
elif runner_found == 3:
patch_game_state(
gs['id'],
on_third_id=False
)
pick = 1
self.increment_outs(this_game, 1)
else:
if 'wp' in cmd:
wp = 1
elif 'balk' in cmd:
balk = 1
else:
pb = 1
runs = self.advance_runners(gs, num_bases=1, error=True)
if runs:
self.increment_score(this_game, runs)
data = self.get_pbc(this_game)
post_chaos({
'game_id': this_game['id'],
'pitcher_id': data['pitcher']['player_id'],
'catcher_id': data['catcher']['player_id'],
'wild_pitch': wp,
'passed_ball': pb,
'pick_off': pick,
'balk': balk
})
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@commands.command(name='undo', help='Roll back last entered play', hidden=True)
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def roll_back_command(self, ctx):
await ctx.send('This command will do something soon, I promise.')
@commands.command(name='advance', aliases=['adv'], help='!adv <from_base> <num_bases>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def advance_runners_command(self, ctx, from_base: int, num_bases: int):
this_game = get_game(channel_id=ctx.channel.id, active=1)
if 0 > from_base > 3:
await ctx.send('from_base parameter must be 1, 2, or 3')
return
if 0 > num_bases > 3:
await ctx.send('num_bases parameter must be 1, 2, or 3')
return
self.adv_specific_runner(this_game, from_base, num_bases)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@commands.command(name='place', help='!place <base_num> <player_to_place>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def place_runner_command(self, ctx, base: int, *, player_name):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
if 0 > base > 3:
await ctx.send('from_base parameter must be 1, 2, or 3')
return
player_cog = self.bot.get_cog('Players')
player_name = await fuzzy_player_search(ctx, ctx.channel, self.bot, player_name, player_cog.player_list.keys())
player = await get_one_player(player_name)
this_member = get_one_member(
game_id=this_game['id'],
player_id=player['id'],
active=1
)
if base == 1:
patch_game_state(
state_id=gs['id'],
on_first_id=this_member['id']
)
elif base == 2:
patch_game_state(
state_id=gs['id'],
on_second_id=this_member['id']
)
else:
patch_game_state(
state_id=gs['id'],
on_third_id=this_member['id']
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@commands.command(name='clear', help='!clear <base_to_clear>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def clear_runner_command(self, ctx, base: int):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
if 0 > base > 3:
await ctx.send('from_base parameter must be 1, 2, or 3')
return
if base == 1:
patch_game_state(
state_id=gs['id'],
on_first_id=False
)
elif base == 2:
patch_game_state(
state_id=gs['id'],
on_second_id=False
)
else:
patch_game_state(
state_id=gs['id'],
on_third_id=False
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@commands.command(name='outs', help='!outs <modifier>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def outs_command(self, ctx, modifier: int):
this_game = get_game(channel_id=ctx.channel.id, active=1)
this_state = get_game_state(game_id=this_game['id'])['states'][0]
if -2 <= modifier <= 3:
outs = modifier
else:
await ctx.send('I can only change the outs from -2 to +3')
return
if outs == 0:
await ctx.message.add_reaction('👍')
return
if outs > 0:
self.increment_outs(this_game, outs)
else:
if this_state['outs'] + outs < 0:
new_outs = 0
else:
new_outs = this_state['outs'] + outs
patch_game_state(
this_state['id'],
outs=0
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@commands.command(name='score', help='!score <home_or_away> <modifier>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def score_command(self, ctx, home_or_away: str, modifier: int):
this_game = get_game(channel_id=ctx.channel.id, active=1)
this_state = get_game_state(game_id=this_game['id'])['states'][0]
home_score = this_state['home_score']
away_score = this_state['away_score']
if home_or_away.lower() == 'home':
new_home_score = home_score + modifier
new_away_score = away_score
elif home_or_away.lower() == 'away':
new_away_score = away_score + modifier
new_home_score = home_score
else:
await ctx.send('I only take \'home\' or \'away\'. Try again.')
return
new_state = patch_game_state(
this_state['id'],
home_score=new_home_score,
away_score=new_away_score
)
await ctx.send(content=None, embed=await self.game_state_embed(this_game, full_length=False))
@commands.group(name='show', help='`!help show` for all options')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def show_data(self, ctx):
if ctx.invoked_subcommand is None:
await ctx.send('No play details listed. Run `!help show` for available commands.')
@show_data.command(name='pitcher', help='Show pitcher card')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def show_pitcher_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
logger.info(f'gs: {gs}')
if gs['top_half']:
this_member = get_one_member(
game_id=this_game['id'],
team_id=this_game['home_team_id'],
batting_order=10,
active=1
)
else:
this_member = get_one_member(
game_id=this_game['id'],
team_id=this_game['away_team_id'],
batting_order=10,
active=1
)
this_player = await get_one_player(this_member['player_id'])
await ctx.send(content=None, embed=await get_player_embed(this_player, await db_get('current')))
@show_data.command(name='batter', help='Show batter card')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def show_batter_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
logger.info(f'gs: {gs}')
if gs['top_half']:
this_member = get_one_member(
game_id=this_game['id'],
team_id=this_game['away_team_id'],
batting_order=gs['away_batter_up'],
active=1
)
else:
this_member = get_one_member(
game_id=this_game['id'],
team_id=this_game['home_team_id'],
batting_order=gs['home_batter_up'],
active=1
)
this_player = await get_one_player(this_member['player_id'])
await ctx.send(content=None, embed=await get_player_embed(this_player, await db_get('current')))
@show_data.command(
name='position',
aliases=['c', '1b', '2b', '3b', 'ss', 'lf', 'cf', 'rf'],
help='!show 1b, !show rf, etc'
)
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def show_defense_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
pos = ctx.message.content.split(' ')[1].upper()
logger.info(f'pos: {pos}')
this_player = await get_one_player(self.get_defender(this_game, pos)['player_id'])
await ctx.send(content=None, embed=await get_player_embed(this_player, await db_get('current')))
@show_data.command(name='runner', help='!show runner <base_num>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def show_runner_command(self, ctx, base_num: int):
this_game = get_game(channel_id=ctx.channel.id, active=1)
gs = get_game_state(game_id=this_game['id'])['states'][0]
if base_num == 1:
runner_id = gs['on_first']['player_id']
elif base_num == 2:
runner_id = gs['on_second']['player_id']
elif base_num == 3:
runner_id = gs['on_third']['player_id']
else:
await ctx.send('I can only check for runners on base 1, 2, or 3. Try again maybe?')
return
this_player = await get_one_player(runner_id)
await ctx.send(content=None, embed=await get_player_embed(this_player, await db_get('current')))
@commands.command(name='stats', aliases=['tocsv'], help='Get current stats')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def export_csv_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
async with ctx.typing():
results = await self.get_game_results(this_game, combined=True)
csv = DataFrame(results['batters']).to_csv(header=False, index=False)
file_name = f'storage/csv/{ctx.author.display_name}{random.randint(1000000000,9999999999)}.csv'
file = open(file_name, 'w')
file.write(csv)
file.close()
await ctx.send(content='Here is your batter csv:', file=discord.File(file_name))
csv = DataFrame(results['pitchers']).to_csv(header=False, index=False)
file_name = f'storage/csv/{ctx.author.display_name}{random.randint(1000000000,9999999999)}.csv'
file = open(file_name, 'w')
file.write(csv)
file.close()
await ctx.send(content='Here is your pitcher csv:', file=discord.File(file_name))
@commands.command(name='tosheets', help='Get results Sheet')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def export_sheets_command(self, ctx):
this_game = get_game(channel_id=ctx.channel.id, active=1)
away_team = await get_one_team(this_game['away_team_id'])
home_team = await get_one_team(this_game['home_team_id'])
results = await self.get_game_results(this_game)
await ctx.send('You got it - just pulled stats and posting them to a spreadsheet for ya.')
async with ctx.typing():
sheet_id = await self.get_game_sheet(home_team, away_team, results, ctx.author.display_name)
await ctx.send(f'Here is the results sheet for this game: https://docs.google.com/spreadsheets/d/'
f'{sheet_id}')
@commands.command(name='pull', hidden=True)
@commands.is_owner()
async def pull_command(self, ctx, player_type):
if player_type.lower() not in ['pitchers', 'batters']:
await ctx.send('Can only import pitcher or batter')
return
# Get data from Sheets
async with ctx.typing():
sheets = pygsheets.authorize(service_file='storage/major-domo-service-creds.json')
if player_type == 'pitchers':
data_sheet = sheets.open_by_key('1tAKj1PXTJbVyEXzB8YPcGOdydS24N12qoT6n7bSSk_w').worksheet_by_title(
'2020 Pitchers')
raw_data = data_sheet.get_values('A3', 'AP547')
else:
data_sheet = sheets.open_by_key('1tAKj1PXTJbVyEXzB8YPcGOdydS24N12qoT6n7bSSk_w').worksheet_by_title(
'2020 Batters')
raw_data = data_sheet.get_values('A3', 'AZ509')
await ctx.send('Alrighty, got the import data. Now to sift through...')
all_data = []
async with ctx.typing():
if player_type == 'pitchers':
for line in raw_data:
this_player = await get_one_player(line[0])
this_line = {
'player_id': this_player['id'],
'name': line[0],
'hand': line[2],
'innings': line[3],
'so_vl': line[4],
'so_vr': line[5],
'bb_vl': line[6],
'bb_vr': line[7],
'hit_vl': line[8],
'hit_vr': line[9],
'ob_vl': line[10],
'ob_vr': line[11],
'tb_vl': line[12],
'tb_vr': line[13],
'tb123_vl': line[16],
'tb123_vr': line[17],
'hit123_vl': line[14],
'hit123_vr': line[15],
'hr_vl': line[18],
'hr_vr': line[19],
'bphr_vl': line[22],
'bphr_vr': line[23],
'bp1b_vl': line[24],
'bp1b_vr': line[25],
'dp_vl': line[26],
'dp_vr': line[27],
'hold': line[29],
'st_rating': line[30] if line[30] != '' else None,
're_rating': line[31] if line[31] != '' else None,
'cl_rating': line[32] if line[32] != '' else None,
'range': line[33],
'error': line[34],
'balk': line[35],
'wild_pitch': line[36],
'speed': line[39],
'bunt': line[40],
'injury': line[41],
}
all_data.append(this_line)
posts = post_pitchers(all_data)
else:
for line in raw_data:
this_player = await get_one_player(line[0])
this_line = {
'player_id': this_player['id'],
'name': line[0],
'hand': line[3],
'atbats': line[2],
'so_vl': line[4],
'so_vr': line[5],
'bb_vl': line[6],
'bb_vr': line[7],
'hit_vl': line[8],
'hit_vr': line[9],
'ob_vl': line[10],
'ob_vr': line[11],
'tb_vl': line[12],
'tb_vr': line[13],
'hr_vl': line[14],
'hr_vr': line[15],
'bphr_vl': line[18],
'bphr_vr': line[19],
'bp1b_vl': line[20],
'bp1b_vr': line[21],
'dp_vl': line[24],
'dp_vr': line[25],
'steal': line[27],
'speed': line[28],
'bunt': line[29],
'hit_and_run': line[30],
'injury': line[31],
'rating_c': line[32] if line[32] != '' else None,
'rating_1b': line[33] if line[33] != '' else None,
'rating_2b': line[34] if line[34] != '' else None,
'rating_3b': line[35] if line[35] != '' else None,
'rating_ss': line[36] if line[36] != '' else None,
'rating_lf': line[37] if line[37] != '' else None,
'rating_cf': line[38] if line[38] != '' else None,
'rating_rf': line[39] if line[39] != '' else None,
'error_c': line[40] if line[40] != '' else None,
'error_1b': line[41] if line[41] != '' else None,
'error_2b': line[42] if line[42] != '' else None,
'error_3b': line[43] if line[43] != '' else None,
'error_ss': line[44] if line[44] != '' else None,
'error_lf': line[45] if line[45] != '' else None,
'error_cf': line[46] if line[46] != '' else None,
'error_rf': line[47] if line[47] != '' else None,
'arm_c': line[49] if line[49] != '' else None,
'arm_of': line[48] if line[48] != '' else None,
'overthrow': line[50] if line[50] != '' else None,
'passed_ball': line[51] if line[51] != '' else None,
}
all_data.append(this_line)
posts = post_batters(all_data)
await ctx.send(f'Posted {posts} {player_type.lower()}!')
@commands.command(name='vs', help='!vs <Pitcher Name> vs <Batter Name>')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def versus_command(self, ctx, *, args):
# Split names out
names = re.split(' vs ', args)
if len(names) == 1:
names = re.split(' vs. ', args)
if len(names) == 1:
await ctx.send('I could not find player names in that message. Make sure the format is '
'`!vs Gerrit Cole vs Salvador Perez')
return
# Perform fuzzy search to get player objects
player_cog = self.bot.get_cog('Players')
player_name = await fuzzy_player_search(ctx, ctx.channel, self.bot, names[0], player_cog.player_list.keys())
player1 = await get_one_player(player_name)
player_name = await fuzzy_player_search(ctx, ctx.channel, self.bot, names[1], player_cog.player_list.keys())
player2 = await get_one_player(player_name)
pitcher_player = None
batter_player = None
pitcher_pos = ['SP', 'RP', 'CP']
if player1['pos_1'] in pitcher_pos:
pitcher_player = player1
if player2['pos_1'] not in pitcher_pos:
batter_player = player2
else:
await ctx.send(f'I could not find batting info for {player2["name"]}.')
return
else:
pitcher_player = player2
if player1['pos_1'] not in pitcher_pos:
batter_player = player1
else:
await ctx.send(f'I could not find batting info for {player1["name"]}.')
return
# Get Pitcher and Batter objects
pitcher = get_one_pitcher(pitcher_player['id'])
batter = get_one_batter(batter_player['id'])
da_roll = random.randint(1, 1000)
embed = get_team_embed(f'{pitcher_player["name"]} vs {batter_player["name"]}', thumbnail=False)
# Calculate matchup OBP
batter_ob = batter['ob_vr'] if pitcher['hand'] == 'R' else batter['ob_vl']
if batter['hand'] == 'L':
pitcher_ob = pitcher['ob_vl']
elif batter['hand'] == 'R':
pitcher_ob = pitcher['ob_vr']
else:
pitcher_ob = pitcher['ob_vr'] if pitcher['hand'] == 'L' else pitcher['ob_vl']
obp = ((batter_ob + pitcher_ob) / 216)
# Calculate matchup AVG
batter_hit = batter['hit_vr'] if pitcher['hand'] == 'R' else batter['hit_vl']
batter_hr = batter['hr_vr'] if pitcher['hand'] == 'R' else batter['hr_vl']
batter_bb = batter['bb_vr'] if pitcher['hand'] == 'R' else batter['bb_vl']
batter_so = batter['so_vr'] if pitcher['hand'] == 'R' else batter['so_vl']
if batter['hand'] == 'L':
pitcher_hit = pitcher['hit_vl']
pitcher_hr = pitcher['hr_vl']
pitcher_bb = pitcher['bb_vl']
pitcher_so = pitcher['so_vl']
elif batter['hand'] == 'R':
pitcher_hit = pitcher['hit_vr']
pitcher_hr = pitcher['hr_vr']
pitcher_bb = pitcher['bb_vr']
pitcher_so = pitcher['so_vr']
else:
pitcher_hit = pitcher['hit_vr'] if pitcher['hand'] == 'L' else pitcher['hit_vl']
pitcher_hr = pitcher['hr_vr'] if pitcher['hand'] == 'L' else pitcher['hr_vl']
pitcher_bb = pitcher['bb_vr'] if pitcher['hand'] == 'L' else pitcher['bb_vl']
pitcher_so = pitcher['so_vr'] if pitcher['hand'] == 'L' else pitcher['so_vl']
avg = ((batter_hit + pitcher_hit) / (216 - batter_ob - pitcher_ob + batter_hit + pitcher_hit))
# Calculate matchup SLG
batter_tb = batter['tb_vr'] if pitcher['hand'] == 'R' else batter['tb_vl']
if batter['hand'] == 'L':
pitcher_tb = pitcher['tb_vl']
elif batter['hand'] == 'R':
pitcher_tb = pitcher['tb_vr']
else:
pitcher_tb = pitcher['tb_vr'] if pitcher['hand'] == 'L' else pitcher['tb_vl']
slg = ((batter_tb + pitcher_tb) / (216 - batter_ob - pitcher_ob + batter_hit + pitcher_hit))
embed.add_field(name='Average', value=f'{avg:.3f}')
embed.add_field(name='On Base', value=f'{obp:.3f}')
embed.add_field(name='Slugging', value=f'{slg:.3f}')
# Check for hit
if da_roll <= round(avg * 1000):
hit_roll = random.randint(1, round((batter_hit + pitcher_hit) * 1000))
if hit_roll <= round((batter_hr + pitcher_hr) * 1000):
result = 'Home Run!'
else:
tb123 = batter_tb + pitcher_tb - ((batter_hr + pitcher_hr) * 4)
si_chance = tb123 / (batter_hit + pitcher_hit)
if random.random() <= si_chance:
result = 'Single!'
else:
result = 'Double!'
# Check for on base
elif da_roll <= round(obp * 1000):
bb_chance = (batter_bb + pitcher_bb) / (batter_ob + pitcher_ob - batter_hit - pitcher_hit)
logger.info(f'bb_chance: {bb_chance}')
if random.random() <= bb_chance:
result = 'Walk!'
else:
result = 'HBP!'
# Check for out
else:
out_chances = 216 - round(216 * obp)
out_roll = random.randint(1, out_chances)
if out_roll <= batter_so + pitcher_so:
result = 'Strikeout!'
elif out_roll <= out_chances - ((out_chances - (batter_so + pitcher_so)) / 2):
result = 'Groundout!'
else:
result = 'Flyout!'
embed.add_field(name='Result', value=result)
logger.info(f'{pitcher_player["name"]} vs {batter_player["name"]}\n'
f'batter OB: {batter_ob} / pitcher OB: {pitcher_ob}\n'
f'batter BB: {batter_bb} / pitcher BB: {pitcher_bb}\n'
f'batter hit: {batter_hit} / pitcher hit: {pitcher_hit}\n'
f'batter hr: {batter_hr} / pitcher hr: {pitcher_hr}')
await ctx.send(content=None, embed=embed)
@commands.command(name='tutorial', help='Quick how-to guide')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def tutorial_command(self, ctx):
embed = get_team_embed(f'Discord Scorebot Tutorial')
embed.add_field(
name='Start & End Game',
value=f'The `!startgame` command will initiate a game in the channel it is run. Other commands will only '
f'be accepted in this channel. The `!endgame` command will ask for confirmation and then send game '
f'stats to a google sheet (just like running `!tosheets`) before closing out the game.'
)
embed.add_field(
name='Set Lineups',
value=f'The `!setlineups` command or `!sl` is used to set the initial lineups as well as make mid-game '
f'substitutions. The format is as follows:\n```!sl <team_abbrev> <lineup_order>-<player_name>-'
f'<position>```\nIf there are multiple subs (or this is the initial lineup), enter a comma after the '
f'position and, without any spaces, begin the next player. Below are two examples:\n```!sl WV 1-'
f'Ronald Acuna Jr-RF```and```!sl WV 2-Trea Turner-SS,4-Eloy Jimenez-RF,7-Cody Bellinger-CF,10-'
f'Anthony Bass-P```\nNote on pitchers: if the pitcher is not batting, they are entered with 10 for '
f'their lineup_order; if they enter the game to bat, they are then entered in their batting order.',
inline=False
)
embed.add_field(
name='Logging Plays',
value=f'Plays are logged with the `!log <play_details>` command. You can run `!help log` to see a full '
f'list. For help with a specific play log, run `!help log x` for help logging an x-chance.',
inline=False
)
embed.add_field(
name='Game State Modifications',
value=f'The score, the number of ours, and the placement of baserunners can all be modified manually in '
f'cases of either bad automation (please let Cal know) or a mistake was made.\nThe `!advance` '
f'command manually advanced baserunners; the `!clear` command removes a baserunner; the `!outs` '
f'command can add or subtract outs (never past 0 or 3); the `!place` command manually places '
f'baserunners; the `!score` command adjusts either the home or away team\'s score.',
inline=False
)
embed.add_field(
name='Results',
value=f'The `!tosheets` command will compile the game\'s stats and upload them into a google sheets page '
f'formatted for the !submit command. The `!stats` command will display the game\'s stats in a simple '
f'csv format and post them to discord. Both of these commands can be run at any time __before__ the '
f'`!endgame` command is run.',
inline=False
)
await ctx.send(content=None, embed=embed)
async def setup(bot):
await bot.add_cog(Gameday(bot))