Esempio n. 1
0
 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)
Esempio n. 2
0
 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)
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
 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)
Esempio n. 6
0
 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)
Esempio n. 7
0
    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)
Esempio n. 8
0
    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)
Esempio n. 9
0
    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