def _transpire(self): """Play out the at-bat.""" # TODO substitutions will change where this should be done assert not self.resolved, "Call to _transpire() of already resolved AtBat." while not self.resolved: self.playing_action = None # Don't retain prior playing action # Players get in position, pitcher decides his pitch PitchInterim(at_bat=self) # The pitch... pitch = self.pitcher.pitch(at_bat=self) if not pitch.bean: self.batter.decide_whether_to_swing(pitch) if not self.batter.will_swing: # Catcher attempts to receive pitch pitch.caught = self.catcher.receive_pitch(pitch) # TODO wild pitches, passed balls # Umpire makes his call pitch.call = pitch.would_be_call if pitch.call == "Strike": Strike(pitch=pitch, looking=True) elif pitch.call == "Ball": Ball(pitch=pitch) # ...the swing... elif self.batter.will_swing: self.batter.decide_swing(pitch) swing = self.batter.swing(pitch) if not swing.contact: # ...swing and a miss! Strike(pitch=pitch, looking=False) elif swing.foul_tip: # ...foul tip foul_tip = swing.result if self.catcher.receive_foul_tip(): Strike(pitch=pitch, looking=False, foul_tip=foul_tip) else: # ...foul ball FoulBall(batted_ball=foul_tip) elif swing.contact: # ...the ball is hit! self.playing_action = PlayingAction(batted_ball=swing.result) self.playing_action.enact() if self.batter.safely_on_base: self.resolved = True if self.playing_action: self._review()
class AtBat(object): """An at-bat in a frame.""" def __init__(self, frame): """Initialize an AtBat object.""" self.game = frame.game self.frame = frame frame.at_bats.append(self) # Set attributes for record keeping self.pitcher = frame.pitching_team.roster.pitcher self.catcher = frame.pitching_team.roster.catcher self.fielders = list(frame.pitching_team.roster.fielders) self.umpire = self.game.umpire # Summon the next batter to the plate self.batter = frame.batting_team.roster.next_batter() # Prepare attributes self.pitches = [] self.pitch_interims = [] self.balls = 0 self.strikes = 0 self.count = 00 # See playing_action.PitchInterim for info on how counts are represented self.playing_action = None # This gets set by _transpire(), as appropriate self.outs = [] # Keep track of this so that we can listen for double- and triple plays self.resolved = False self.result = None self.run_queue = [] # Potential runs; will be counted only if a third out isn't recorded during the play if self.game.trace: print "1B: {}, 2B: {}, 3B: {}, AB: {}".format(frame.on_first, frame.on_second, frame.on_third, self.batter) if not self.game.radio_announcer: self._transpire() if self.game.radio_announcer: self.game.radio_announcer.call_at_bat(at_bat=self) # This will _transpire the at-bat midway def _transpire(self): """Play out the at-bat.""" # TODO substitutions will change where this should be done assert not self.resolved, "Call to _transpire() of already resolved AtBat." while not self.resolved: self.playing_action = None # Don't retain prior playing action # Players get in position, pitcher decides his pitch PitchInterim(at_bat=self) # The pitch... pitch = self.pitcher.pitch(at_bat=self) if not pitch.bean: self.batter.decide_whether_to_swing(pitch) if not self.batter.will_swing: # Catcher attempts to receive pitch pitch.caught = self.catcher.receive_pitch(pitch) # TODO wild pitches, passed balls # Umpire makes his call pitch.call = pitch.would_be_call if pitch.call == "Strike": Strike(pitch=pitch, looking=True) elif pitch.call == "Ball": Ball(pitch=pitch) # ...the swing... elif self.batter.will_swing: self.batter.decide_swing(pitch) swing = self.batter.swing(pitch) if not swing.contact: # ...swing and a miss! Strike(pitch=pitch, looking=False) elif swing.foul_tip: # ...foul tip foul_tip = swing.result if self.catcher.receive_foul_tip(): Strike(pitch=pitch, looking=False, foul_tip=foul_tip) else: # ...foul ball FoulBall(batted_ball=foul_tip) elif swing.contact: # ...the ball is hit! self.playing_action = PlayingAction(batted_ball=swing.result) self.playing_action.enact() if self.batter.safely_on_base: self.resolved = True if self.playing_action: self._review() def _review(self): """Review the at-bat to effect its outcomes and record statistics.""" # Score any runs that remain in the run queue -- these are runs whose being scored # depended on the at bat not ending with a fly out or force out for run in self.run_queue: run.dequeue() # Check for whether a hit was made; if one was, instantiate the appropriate outcome object # [Note: if the batter-runner was part of a call at a base, PlayAtBaseCall.__init__() will # score the hit -- in those cases it is precluded here by self.result having already been # attributed by the scored hit] if not self.result and self.batter.base_reached_on_hit: if self.batter.base_reached_on_hit == "1B": Single(playing_action=self.playing_action, call=None) elif self.batter.base_reached_on_hit == "2B": Double(playing_action=self.playing_action, call=None) elif self.batter.base_reached_on_hit == "3B": Triple(playing_action=self.playing_action, call=None) elif self.batter.base_reached_on_hit == "H": HomeRun(batted_ball=self.playing_action.batted_ball, call=None, inside_the_park=True) # Next, check for whether a double- or triple play was turned -- if one was, instantiate # the appropriate outcome object if len(self.outs) == 2: DoublePlay(at_bat=self, outs=self.outs) elif len(self.outs) == 3: TriplePlay(at_bat=self, outs=self.outs) # Lastly, survey for which bases are now occupied and by whom if self.playing_action.running_to_third and self.playing_action.running_to_third.safely_on_base: self.frame.on_third = self.playing_action.running_to_third elif self.playing_action.retreating_to_third and self.playing_action.retreating_to_third.safely_on_base: self.frame.on_third = self.playing_action.retreating_to_third else: self.frame.on_third = None if self.playing_action.running_to_second and self.playing_action.running_to_second.safely_on_base: self.frame.on_second = self.playing_action.running_to_second elif self.playing_action.retreating_to_second and self.playing_action.retreating_to_second.safely_on_base: self.frame.on_second = self.playing_action.retreating_to_second else: self.frame.on_second = None if self.playing_action.running_to_first and self.playing_action.running_to_first.safely_on_base: self.frame.on_first = self.playing_action.running_to_first elif self.playing_action.retreating_to_first and self.playing_action.retreating_to_first.safely_on_base: self.frame.on_first = self.playing_action.retreating_to_first else: self.frame.on_first = None