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 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') logging.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 = '○' logging.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 logging.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"]}' logging.info(f'getting pitcher and batter members') pbc_data = self.get_pbc(this_game) batter_member = pbc_data['batter'] pitcher_member = pbc_data['pitcher'] logging.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] logging.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]}...' logging.info(f'setting player stats') era = f'{6.9:.2f}' avg = f'{.420:.3f}' if avg[0] == '0': avg = avg[1:] logging.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 # logging.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']): logging.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 # logging.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: logging.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: logging.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']) # logging.info('pre-on_first') if this_state['on_first']: if num_bases > 2: logging.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: logging.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: logging.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 logging.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```' logging.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```' logging.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']: logging.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: logging.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: logging.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: logging.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: logging.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: logging.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 logging.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 logging.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 logging.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 ) logging.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: logging.info(f'No stats for {this_player["name"]} - moving on') else: dpos = None if len(ds['defense']) == 1: logging.info('one defense found') if ds['defense'][0]['pos'] == x['position']: logging.info('matches position') dpos = ds['defense'][0] else: logging.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: logging.info('many defenses found') for line in ds['defense']: if line['pos'] == line['pos']: logging.info('this one matches pos') dpos = line else: logging.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: logging.info('no defense found; adding blanks') dpos = { 'xch': '', 'xhit': '', 'errors': '', 'sba': '', 'csc': '', 'roba': '', 'robs': '', 'raa': '', 'rto': '', } logging.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']: logging.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 ') @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: --\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) logging.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']) logging.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']: logging.info('Both lineups valid - sending gamestate') content = None embed = await self.game_state_embed(this_game, full_length=False) else: logging.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 # # logging.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 ') @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) logging.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 ') @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 ') @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 ') @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 ') @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 ') @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] logging.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] logging.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() logging.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 ') @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 vs ') @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) logging.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) logging.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 --' f'```\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 ` 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))