def _perform_action(self): self._happened = True outcome = self.choice() fielder = self.action.subjects.player record = outcome_records['catch'][outcome] complete_record = record.format(fielder.pos) details = dict(fielder=fielder) self['outcome'] = Outcome(outcome, complete_record, details)
def _perform_action(self): self._happend = True outcome = self.choice() fielder = self.action.subjects.player target = self.action.subjects.target record = outcome_records['throw'][outcome] complete_record = record.format(fielder.pos, target.pos) details = dict(player=fielder, target=target) self['outcome'] = Outcome(outcome, complete_record, details)
def __init__(self, event_tag='<START>'): assert isinstance(event_tag, str) start_action = self._build_action_context( 'start', build_subjects('start') ) start_ref_class = self._build_game_context(None, None) super().__init__(start_action, start_ref_class) self._happened = True self['outcome'] = Outcome(event_tag, event_tag, None)
def _perform_action(self): self._happened = True outcome = self.choice() tagger = self.action.subjects.tagger tagged = self.action.subjects.tagged record = outcome_records['tag'][outcome] complete_record = record.format(tagger.num, tagged.num) details = dict(tagger=tagger, tagged=tagged) self['outcome'] = Outcome(outcome, complete_record, details)
def make_happen(self, outcome='move'): self._happened = True assert outcome in outcome_records['move'] to_base = self.action.subjects.to_base from_base = self.action.subjects.from_base player = self.action.subjects.player record = outcome_records['move'][outcome] complete_record = record.format(from_base, to_base) details = dict(from_base=from_base, to_base=to_base, player=player) self['outcome'] = Outcome(outcome, complete_record, details)
def make_happen(self, shift_option): details = {} playerstack = self.action.subjects.playerstack varstack = self.action.subjects.varstack record = outcome_records['shift'][shift_option] assert all(isinstance(v, list) for v in [playerstack, varstack]) if shift_option in ['sub', 'swap']: complete_record = record.format( playerstack[0], playerstack[1] ) details['new_player'] = playerstack[0] details['old_player'] = playerstack[1] elif shift_option in ['ASCORE', 'HSCORE', 'out', 'iwalk']: complete_record = record elif shift_option in ['lead']: complete_record = record.format(playerstack[0]) details['leadoff'] = playerstack[0] else: raise NotImplemented self['outcome'] = Outcome(shift_option, complete_record, details)
def build_outcome_from_hit_result( self, result, details=None, *players): """We don't have enough information, in all cases, to return a fully completed outcome, with a filled in record. These details need to be supplied explicitly using this function. Example: Triple or double plays. We have to know, after the batter gets a hit/bunt whatever, who is out, who runs, ect. """ if not self.happened: raise AttributeError('Unable to get pitch record. ' \ + 'Pitch has not yet happened.') record = self.possible_outcomes[result] complete_record = record.format(*players) # Updating Outcome self['outcome'] = Outcome(result, complete_record, details)
def _perform_action(self): self._happened = True batter = self.get('action').subjects.batter pitcher = self.get('action').subjects.pitcher batter_guess = pitcher.make_decision('pitch') pitcher_decision = pitcher.make_decision('pitch') batter.make_decision('swing') if batter.swung: probs = self.probs._asdict() batter_decision = batter.make_decision('hit') temp_dict = cond_dampen(probs, ('balk','ball', 'wild')) self.probs = self.prior(**temp_dict) else: probs = self.probs._asdict() temp_dict = cond_dampen(probs, ('balk', 'contact')) self.probs = self.prior(**temp_dict) """Batter doesn't swing'""" batter_decision = 'hold' """This next piece of code is here to see how well the batter hit the ball. Right now, it is fairly simple, and is NOT meant to model the actual physics of a batting/pitching event. I have been working on a module for simulating just that, but it isn't being used here, mainly for simplicity sake. """ unit = .1 batter_mod = 0 pitcher_mod = 0 """Checks if batter guessed pitcher's decision'""" if pitcher_decision[0] == batter_guess[0]: batter_mod+=unit else: pitcher_mod+=unit """checks to see if the batter guessed the right location on a grid. This is a super simple way of doing it, and can easily be changed to make the batting swing event more realistic. """ for x in [0, 1]: if pitcher_decision[1][x] == pitcher_decision[1][x]: batter_mod+=unit else: pitcher_mod+=unit new_ball = 0.0 if batter.swung else self.probs.ball+pitcher_mod n = sum((self.probs.wild, self.probs.balk, self.probs.hbp, self.probs.strike+pitcher_mod, new_ball, self.probs.contact+batter_mod)) self.probs = self.prior( wild=self.probs.wild/n, balk=self.probs.balk/n, hbp=self.probs.hbp/n, strike=(self.probs.strike+pitcher_mod)/n, ball=new_ball/n, contact=(self.probs.contact+batter_mod)/n) """For later use: This is especially useful for record keeping, e.g., if we want to keep track of pitcher pitch types, AND for pitcher/batter learning! """ self.player_choices = ( batter_decision, pitcher_decision, batter_guess ) # print('Current pitch probs: {}'.format(self.probs)) """constants""" gs = self.ref_class.state strikes = gs.count.strikes balls = gs.count.balls outs = gs.count.outs runners_on = sum(gs.bases) locs = self.ref_class.environment.locations prior = self.prior """priors""" hit_type_priors = prior(hit=.4, foul=.45, oop=.1, bunt=.05) wild_priors = prior(wp=1) balk_priors = prior(blk=1) hbp_priors = prior(hbp=1) hit_foul_priors = prior(foul=1) hit_bunt_priors = prior(bunt=1) hit_oop_priors = prior(gdb=.05, hr=.95) hit_ball_priors = prior( single=.5, double=.35, triple=.149, four=.001 ) base_prior_map = dict( bunt=hit_bunt_priors, oop=hit_oop_priors, foul=hit_foul_priors, hit=hit_ball_priors, wild=wild_priors, balk=balk_priors, hbp=hbp_priors ) record_type = self.choice() precs = outcome_records['pitch'] # pitch records mappings record = None if record_type == 'strike': if batter.swung: if strikes >= 2: record = precs['strikeout']['swing'] record_type = 'strikeout' else: record = precs['strike']['swing'] else: if strikes >= 2: record = precs['strikeout']['look'] record_type = 'strikeout' else: record = precs['strike']['look'] fielder = locs.home elif record_type == 'ball': if balls >= 3: record = precs['walk']['w'] record_type = 'walk' else: record = precs['ball']['b'] fielder = locs.home elif record_type == 'contact': contact_type = self.choice(hit_type_priors) record_type = self.choice(base_prior_map[contact_type]) record = precs['contact'][contact_type][record_type] """quick hack to get fielder""" if record in precs['contact']['foul'].values(): if pitcher_decision[0] == 'fastball' \ and batter_decision[0] == 'power': fielder = choice([locs.center, locs.left, locs.right]) elif batter_decision[0] == 'contact': fielder = choice([locs.first, locs.gap, locs.third]) else: fielder = locs.home elif record in precs['contact']['bunt'].values(): fielder = choice([locs.home, locs.mound, locs.third]) elif record in precs['contact']['hit'].values(): if batter_decision[0] == 'power': fielder = choice((locs.left, locs.center, locs.right)) else: fielder = choice((locs.third, locs.gap, locs.second, locs.first)) elif record in precs['contact']['oop'].values(): fielder = None else: raise NotImplementedError elif record_type == 'wild': """need to check if catcher caught the pitch for a passed ball to be possible! """ if balls >= 3: self._batter_done = True record = precs['walk']['w'] else: record_sub_type = self.choice(wild_priors) record = precs['wild'][record_sub_type] fielder = locs.home elif record_type == 'hbp': record_sub_type = self.choice(hbp_priors) record = precs['hbp'][record_sub_type] fielder = None elif record_type == 'balk': record_sub_type = self.choice(balk_priors) record = precs['balk'][record_sub_type] fielder = None else: raise NotImplementedError details = dict(ball=None, fielder=fielder) self['outcome'] = Outcome(record_type, record, details)
def play_next_state(self, action_cls, shift_option, *subjects): """Main method for transitioning between gamestates action_cls : BayesEvent class shift_option : str subjects : list """ assert action_cls.isaction(action_cls.__name__) basenames = ['firstbase', 'secondbase', 'thirdbase'] global records """Helper functions.""" def play_hit(N, batter_move=True): """Most conservative movement of batter to base N. Paramaters: ========== N : int batter_move : bool (default: True) """ assert isinstance(N, int) if self.locations.thirdbase: self.action_move(self.locations.thirdbase, 3, 4) if self.locations.secondbase: self.action_move(self.locations.secondbase, 2, min(4, N + 2)) if self.locations.firstbase: self.action_move(self.locations.firstbase, 1, min(4, N + 1)) if batter_move: self.action_move(self.batter, 0, N) self._batter_done = True def throw_arc(thrower, target, tagged, *second_throw_args): """Event sequence: hrow -> Tag -> [Throw -> Tag]""" global records throw = self.action_throw(thrower, target) if throw.result == 'good': catch = self.action_catch(None, target) if catch.result == 'yes': self.action_tag(target, tagged) if second_throw_args: thrower = second_throw_args[0] target = second_throw_args[1] tagged = second_throw_args[2] throw_arc(thrower, target, tagged) def catcher_catch_pitch(pitch_result): """Main sequence for Catcher catching a pitch""" c_catch = self.action_catch(None, catcher) homesteal = False stealers = [] walk = pitch_result == 'walk' for n in basenames: if getattr(self.locations, n): player = getattr(self.locations, n) if player.stealing: stealers.append((n, player)) if c_catch.result == 'yes' and walk: pass elif c_catch.result == 'yes' and not walk: if stealers: runner = None base = None n = -1 m = -1 for basename, player in stealers: if 'thirdbase' == basename: runner = player n = 3 m = 4 homesteal = True break if 'secondbase' == basename: runner = player n = 2 m = 3 base = 'third' if runner == None: runner = stealers[0][1] n = 1 m = 2 base = 'second' if homesteal: tag = self.action_tag(catcher, runner) else: if base == 'second': target = self.locations.second else: target = self.locations.third throw = self.action_throw(catcher, target) if throw.result == 'yes': tag = self.action_tag(target, runner) else: tag = None if tag: if tag.result == 'safe': self.action_move(runner, n, m, option='steal') if tag.result == 'out': self.action_move(runner, n, m, option='caught') else: self.action_move(runner, n, m, option='steal') """Action instantiation.""" action = action_cls( self.gamestate, Environment(weather=None, locations=self.locations, importance=0, batter=self.batter, pitcher=self.pitcher), *subjects) if action.has_name('PitchEvent'): pitch = action """First: check if runners will leadoff/steal""" bases = [] for n in basenames: if getattr(self.locations, n): player = getattr(self.locations, n) player.make_decision('leadoff') player.make_decision('steal') bases.append((n, player)) self.pitcher.make_decision('pick-off', bases) if self.pitcher.pick_off: """If there is a pitch-out, after the throw/catch/tag sequence is over, the play ends and a new pitch event has to start. """ loc = self.pitcher.pickoff_location base = loc.split('base')[0] fielder = getattr(self.locations, base) runner = getattr(self.locations, loc) throw_arc(self.pitcher, fielder, runner) record = 'pitchout:{}'.format(fielder.pos) pitch['outcome'] = Outcome('pitch_out', record, {}) return pitch pitch.make_happen contacts = records['pitch']['contact'] catcher = self.locations.home if pitch.result in ['balk', 'hbp']: """Pitcher balks or batter is hit by pitch""" if self.locations.thirdbase: self.action_move(self.locations.thirdbase, 3, 4) if self.locations.secondbase: self.action_move(self.locations.secondbase, 2, 3) if self.locations.firstbase: self.action_move(self.locations.firstbase, 1, 2) if pitch.result == 'hbp': self.action_move(self.batter, 0, 1) self._batter_done = True self.add_record(pitch.record) elif pitch.result == 'strike': self.add_record(pitch.record) catcher_catch_pitch(pitch.record) elif pitch.result == 'strikeout': self._batter_done = True self.add_record(pitch.record) self.action_shift('out') catcher_catch_pitch(pitch.record) elif pitch.result == 'ball': self.add_record(pitch.record) catcher_catch_pitch(pitch.record) elif pitch.result == 'walk': self._batter_done = True if self.locations.firstbase: if self.locations.secondbase: if self.locations.thirdbase: self.action_move(self.locations.thirdbase, 3, 4) self.action_move(self.locations.secondbase, 2, 3) self.action_move(self.locations.firstbase, 1, 2) self.action_move(self.batter, 0, 1) self.add_record(pitch.record) elif pitch.result == 'wild': """wild pitch""" if self.locations.firstbase: if self.locations.secondbase: if self.locations.thirdbase: self.action_move(self.locations.thirdbase, 3, 4) self.action_move(self.locations.secondbase, 2, 3) self.action_move(self.locations.firstbase, 1, 2) self.add_record(pitch.record) if self.gamestate.count.balls == 4: self._batter_done = True elif pitch.result in contacts['hit']: hit = pitch fielder = hit.outcome.details['fielder'] ball = hit.outcome.details['ball'] catch = self.action_catch(ball, fielder) if catch.result == 'yes': """NOT REALISTIC YET Also, what about attempts at double/triple plays? ALSO: if the ball is GROUNDBALL, runners can still run! ... so sacflys, etc. are not possible at the moment!!! """ rec_dict = records['pitch']['outs'] record = choice(list(rec_dict.values())) self.add_record(record.format(fielder.pos)) self.action_shift('out') self._batter_done = True else: """WARNING! I'm ignoring miss/drop distinction. ALSO: if dropped, the fielder should get an error! ALSO: runners should be able to attempt to keep running! """ N = int(hit.record.split(':')[1]) play_hit(N) self._batter_done = True self.add_record(hit.record) elif pitch.result == 'gdb': hit = pitch """Ground hit double""" if self.locations.thirdbase: self.action_move(self.locations.thirdbase, 3, 4) if self.locations.secondbase: self.action_move(self.locations.secondbase, 2, 4) if self.locations.firstbase: self.action_move(self.locations.firstbase, 1, 3) self.action_move(self.batter, 0, 2) self.add_record(hit.record) self._batter_done = True elif pitch.result == 'hr': hit = pitch """Homerun""" play_hit(4) self.add_record(hit.record) self._batter_done = True elif pitch.result == 'bunt': hit = pitch fielder = hit.outcome.details['fielder'] ball = hit.outcome.details['ball'] catch = self.action_catch(ball, fielder) if catch.result == 'yes': record = records['pitch']['outs']['go'] self.add_record(record.format(fielder.pos)) self.action_shift('out') self._batter_done = True else: play_hit(1) self.add_record(hit.record) elif pitch.result == 'foul': hit = pitch fielder = hit.outcome.details['fielder'] ball = hit.outcome.details['ball'] catch = self.action_catch(ball, fielder) if catch.result == 'yes': record = records['pitch']['outs']['fo'] self.add_record(record.format(fielder.pos)) self.action_shift('out') self._batter_done = True else: self.add_record(hit.record) self.add_record(hit.record) else: err_msg = '\nUnknown {}\n\t result: {}\n\t record: {}\n' raise ValueError( err_msg.format(pitch.name, pitch.result, pitch.record)) """Tail end of PitchEvent: Now we have to check if a triple/double play occured. THIS PART IS BROKEN. """ initial_outs = self.gamestate.count.outs initial_runners = sum(self.gamestate.bases) FLYO = -1 SCORE = False GIDP = False out_events = [] triple_possible = (initial_outs < 1) \ and (initial_runners > 1) double_possible = (initial_outs < 2) \ and (initial_runners > 0) for x, grec in enumerate(self): splitrec = grec.outcome.split(':') if splitrec[0] == 'FO': FLYO = x out_events.append((grec.time, grec.outcome.split(':')[1])) elif grec.outcome in ['ASCORE', 'HSCORE']: SCORE = True elif grec.outcome in ['PB', 'Ks', 'Kc', 'FT']: out_events.append((grec.time, self.locations.home.pos)) elif len(splitrec) > 1 and splitrec[1] == 'out': firstplayernum = splitrec[0].split('<')[1] secondplayernum = splitrec[2].split('>')[0] out_events.append( (grec.time, firstplayernum, secondplayernum)) elif splitrec[0] in ['LO', 'FC', 'GO']: out_events.append((grec.time, splitrec[1])) if splitrec[0] == 'FC': GDIP = True if (FLYO > -1) and SCORE: self.add_record('SF', self[FLYO].time) """In baseball, triple/double plays are recorded like: 3-6*-5, where 6* is a position that is a transition between positions 3 and 5, but player 6 did NOT make an out. Below, I just record the players who make outs. Good enough for now ... BUT STILL BROKEN """ if triple_possible and len(out_events) == 3: out_events.sort() playernums = [] max_time = 0 for t, p in out_events: playernums.extend(p) max_time = max(t, max_time) num = len(playernums) assert num > 0 and 5 > num records = { 1: 'TP:{}', 2: 'TP:{}-{}', 3: 'TP:{}-{}-{}', 4: 'TP:{}-{}-{}-{}' } rec = records[num] self.add_record(rec.format(*playernums), max_time) if double_possible and len(out_events) == 2: out_events.sort() playernums = [] max_time = 0 for t, p in out_events: playernums.extend(p) max_time = max(t, max_time) num = len(playernums) assert num > 0 and 4 > num records = {1: 'DP:{}', 2: 'DP:{}-{}', 3: 'DP:{}-{}-{}'} rec = records[num] self.add_record(rec.format(*playernums), max_time) if GIDP: self.add_record(rec.format('GIDP'), max_time) return pitch elif action.has_name('StartEvent'): start = action self.add_record(start.record) return start elif action.has_name('CatchEvent'): catch = action try: catch.make_happen self.add_record(catch.record) if catch.result == 'error': raise GameError('catch', catch) return catch except GameError as ge: """BROKEN, needs to be fixed. Originally, the below code was going to be used for when a catch is missed, but fielders still need to throw out runners. """ # fresh copy of locations! locs = self.locations """Update where the runners are heading next right now: they automatically try to get to the next base when there is an error, which is NOT entirely realistic. """ runners = [] if self.locations.firstbase: runners.append('first') self.action_move(self.locations.firstbase, 1, 2) end_base = 'second' if self.locations.secondbase: runners.append('second') self.action_move(self.locations.secondbase, 2, 3) end_base = 'third' if self.locations.thirdbase: runners.append('third') self.action_move(self.locations.thirdbase, 3, 4) end_base = 'home' if 'third' in runners: relevant_base = 'third' else: relevant_base = choice(runners) tagged = getattr(loc, relevant_base + 'base') target = getattr(loc, relevant_base) if ge.parent_name == 'pitch': if ge.event.action.action == 'catch': """ Fielder dropped the ball! """ fielder = ge.event.outcome.details['fielder'] outfield = ['LF', 'CF', 'RF'] infield = ['3B', 'SS', '2B', '1B'] of_nums = [Postions.index(s) for s in outfield] in_nums = [Postions.index(s) for s in infield] left_side_outfield = [ self.locations.left, self.locations.center ] right_side_outfield = [ self.locations.center, self.locations.right ] # probabilities for which new fielder # picks up the ball that the old fielder # missed/dropped due to an error. # to do: REWRITE when a proper baseball # physics / hit detection is added. pr_cf_of = 16.66667 pr_lf_of = pr_cf_of * 2 pr_rf_of = 1 - pr_cf_of - pr_lf_of """ These are cases where an infielder, e.g. SS missed a ball, and then an outfielder has to pick it up. Reminder: super important to add a time penalty for passed balls! also: fielder is the player who errored! """ if fielder.pos in in_nums[:2]: thrower = categorical_dist(left_side_outfield, *[pr_lf_of, pr_cf_of], predict=True) elif fielder.pos in in_nums[1:]: thrower = categorical_dist(right_side_outfield, *[pr_cf_of, pr_rf_of], predict=True) elif fielder.pos in of_nums[:2]: thrower = self.locations.gap throw_arc(thrower, target, tagged) elif fielder.pos in of_nums[1:]: thrower = self.locations.second throw_arc(thrower, target, tagged) else: # to do: check if fielder is injured ... # if so: runner gets the base w/o tag thrower = fielder if target == fielder: self.action_tag(target, tagged) else: throw_arc(thrower, target, tagged) for base_name, runner in runners: if base_name == 'third': tagged = runner if target == None: target = choice(runners) catch['outcome'] = Outcome(catch.outcome.result, 'E:{}'.format(fielder), catch.outcome.details) if ge.parent_name == 'throw': if ge.event.action.action == 'catch': fielder = ge.event.outcome.details['fielder'] if target == fielder: # to do: add time penalty! self.action_tag(fielder, tagged) else: throw_arc(fielder, target, tagged) catch['outcome'] = Outcome(catch.outcome.result, 'E:{}'.format(fielder), catch.outcome.details) """End of except case for CatchEvent.""" elif action.has_name('TagEvent'): tag = action tag.make_happen tagger = tag.outcome.details['tagger'], tagged = tag.outcome.details['tagged'] if tag.result in ['out']: self.action_shift('out') self.add_record(tag.record) return tag elif action.has_name('ShiftEvent'): shift = action shift.make_happen(shift_option) if shift.result == 'lead': player = shift.outcome.details['leadoff'] player._leadoff = True elif shift.result == 'sub': old_player = shift.outcome.details['old_player'] new_player = shift.outcome.details['new_player'] self.sub_players(old_player, new_player) elif shift.name == 'swap': old_player = shift.outcome.details['old_player'] new_player = shift.outcome.details['new_player'] self.swap_players(old_player, new_player) else: pass self.add_record(shift.record) return shift elif action.has_name('ThrowEvent'): throw = action throw.make_happen self.add_record(throw.record) return throw elif action.has_name('MoveEvent'): move = action move.make_happen(shift_option) base_dict = { 1: 'firstbase', 2: 'secondbase', 3: 'thirdbase', 0: None, 4: None } fromb = base_dict[move.outcome.details['from_base']] tob = base_dict[move.outcome.details['to_base']] runner = move.outcome.details['player'] loc = self.locations._asdict() if move.result in ['move', 'steal']: if fromb: if fromb == base_dict[3]: if self.gamestate.inning.order == 'top': self.action_shift('ASCORE') else: self.action_shift('HSCORE') loc[fromb] = None if tob: loc[tob] = runner elif move.result == 'caught': if tob: loc[tob] = None loc[fromb] = None else: raise NotImplementedError self.locations = Locations(**loc) self.add_record(move.record) return move