<p>The usual approach to robot behavior design relies on hierarchical state machines. Specifically, we might be in a “standing” state while the ball is far away; when the ball becomes close, we enter a “diving” state that persists for one second. Because of requirement 3, this solution will have a few warts: we need to keep track of how much time we’ve spent in the dive state. Every time we add a special case like this, we need to keep some extra state information around. Since robotics code is full of special cases, we tend to end up with a lot of bookkeeping cruft. In contrast, generators will let us clearly express the desired behavior.</p>
<p>On to the state-machine approach. First, we’ll have a class called Features that abstracts the robot’s raw sensor data. For this example, we only care whether the ball is near/far and left/right, so Features will just contain two boolean variables:</p>
<p>Next, we make the goalkeeper. The keeper’s behavior is specified by the <code>next()</code> function, which is called thirty times per second by the robot’s main event loop (every time the on-board camera produces a new image). The <code>next()</code> function returns one of three actions: <code>"stand"</code>, <code>"diveLeft"</code>, or <code>"diveRight"</code>, based on the current values of the Features object. For now, let’s pretend that requirement 3 doesn’t exist.</p>
<p>That was simple enough. The constructor takes in the <code>Features</code> object; the <code>next()</code> method checks the current <code>Features</code> values and returns the correct action. Now, how about satisfying requirement 3? When we choose to dive, we need to keep track of two things: how long we need to stay in the <code>"dive"</code> state and which direction we dove. We’ll do this by adding a couple of instance variables (<code>self.diveFramesRemaining</code> and <code>self.lastDiveCommand</code>) to the Goalkeeper class. These variables are set when we initiate the dive. At the top of the <code>next()</code> function, we check if <code>self.diveFramesRemaining</code> is positive; if so, we can immediately return <code>self.lastDiveCommand</code> without consulting the <code>Features</code>. Here’s the code:</p>
<p>This satisfies all the requirements, but it’s ugly. We’ve added a couple of bookkeeping variables to the Goalkeeper class. Code to properly maintain these variables is sprinkled all over the <code>next()</code> function. Even worse, the structure of the code no longer accurately represents the programmer’s intent: the top-level if-statement depends on the state of the robot rather than the state of the world. The intent of the original <code>next()</code> function is much easier to discern. (In real code, we could use a state-machine class to tidy things up a bit, but the end result would still be ugly when compared to our original <code>next()</code> function.)</p>
<p>With generators, we can preserve the form of the original <code>next()</code> function and keep the bookkeeping only where it’s needed. If you’re not familiar with generators, you can think of them as a special kind of function. The <code>yield</code> keyword is essentially equivalent to <code>return</code>, but the next time the generator is called, <em>execution continues from the point of the last <code>yield</code></em>, preserving the state of all local variables. With <code>yield</code>, we can use a <code>for</code> loop to “return” the same dive command the next 30 times the function is called! Lines 11-16 of the below code show the magic:</p>
<p>The usual approach to robot behavior design relies on hierarchical state machines. Specifically, we might be in a “standing” state while the ball is far away; when the ball becomes close, we enter a “diving” state that persists for one second. Because of requirement 3, this solution will have a few warts: we need to keep track of how much time we’ve spent in the dive state. Every time we add a special case like this, we need to keep some extra state information around. Since robotics code is full of special cases, we tend to end up with a lot of bookkeeping cruft. In contrast, generators will let us clearly express the desired behavior.</p>
<p>On to the state-machine approach. First, we’ll have a class called Features that abstracts the robot’s raw sensor data. For this example, we only care whether the ball is near/far and left/right, so Features will just contain two boolean variables:</p>
<p>Next, we make the goalkeeper. The keeper’s behavior is specified by the <code>next()</code> function, which is called thirty times per second by the robot’s main event loop (every time the on-board camera produces a new image). The <code>next()</code> function returns one of three actions: <code>"stand"</code>, <code>"diveLeft"</code>, or <code>"diveRight"</code>, based on the current values of the Features object. For now, let’s pretend that requirement 3 doesn’t exist.</p>
<p>That was simple enough. The constructor takes in the <code>Features</code> object; the <code>next()</code> method checks the current <code>Features</code> values and returns the correct action. Now, how about satisfying requirement 3? When we choose to dive, we need to keep track of two things: how long we need to stay in the <code>"dive"</code> state and which direction we dove. We’ll do this by adding a couple of instance variables (<code>self.diveFramesRemaining</code> and <code>self.lastDiveCommand</code>) to the Goalkeeper class. These variables are set when we initiate the dive. At the top of the <code>next()</code> function, we check if <code>self.diveFramesRemaining</code> is positive; if so, we can immediately return <code>self.lastDiveCommand</code> without consulting the <code>Features</code>. Here’s the code:</p>
<p>This satisfies all the requirements, but it’s ugly. We’ve added a couple of bookkeeping variables to the Goalkeeper class. Code to properly maintain these variables is sprinkled all over the <code>next()</code> function. Even worse, the structure of the code no longer accurately represents the programmer’s intent: the top-level if-statement depends on the state of the robot rather than the state of the world. The intent of the original <code>next()</code> function is much easier to discern. (In real code, we could use a state-machine class to tidy things up a bit, but the end result would still be ugly when compared to our original <code>next()</code> function.)</p>
<p>With generators, we can preserve the form of the original <code>next()</code> function and keep the bookkeeping only where it’s needed. If you’re not familiar with generators, you can think of them as a special kind of function. The <code>yield</code> keyword is essentially equivalent to <code>return</code>, but the next time the generator is called, <em>execution continues from the point of the last <code>yield</code></em>, preserving the state of all local variables. With <code>yield</code>, we can use a <code>for</code> loop to “return” the same dive command the next 30 times the function is called! Lines 11-16 of the below code show the magic:</p>