Browse Source

reindent code

main
Colin McMillen 3 years ago
parent
commit
caf4c58da1
  1. 18
      content/blog/20070502-robot-behaviors-python.md

18
content/blog/20070502-robot-behaviors-python.md

@ -17,7 +17,7 @@ The usual approach to robot behavior design relies on hierarchical state machine
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:
```python
class Features(object):
class Features(object):
ballFar = True
ballOnLeft = True
```
@ -25,7 +25,7 @@ On to the state-machine approach. First, we'll have a class called Features that
Next, we make the goalkeeper. The keeper's behavior is specified by the `next()` 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 `next()` function returns one of three actions: `"stand"`, `"diveLeft"`, or `"diveRight"`, based on the current values of the Features object. For now, let's pretend that requirement 3 doesn't exist.
```python
class Goalkeeper(object):
class Goalkeeper(object):
def __init__(self, features):
self.features = features
@ -43,7 +43,7 @@ Next, we make the goalkeeper. The keeper's behavior is specified by the `next()`
That was simple enough. The constructor takes in the `Features` object; the `next()` method checks the current `Features` 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 `"dive"` state and which direction we dove. We'll do this by adding a couple of instance variables (`self.diveFramesRemaining` and `self.lastDiveCommand`) to the Goalkeeper class. These variables are set when we initiate the dive. At the top of the `next()` function, we check if `self.diveFramesRemaining` is positive; if so, we can immediately return `self.lastDiveCommand` without consulting the `Features`. Here's the code:
```python
class Goalkeeper(object):
class Goalkeeper(object):
def __init__(self, features):
self.features = features
self.diveFramesRemaining = 0
@ -72,7 +72,7 @@ That was simple enough. The constructor takes in the `Features` object; the `nex
With generators, we can preserve the form of the original `next()` 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 `yield` keyword is essentially equivalent to `return`, but the next time the generator is called, *execution continues from the point of the last `yield`*, preserving the state of all local variables. With `yield`, we can use a `for` 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:
```python
class GoalkeeperWithGenerator(object):
class GoalkeeperWithGenerator(object):
def __init__(self, features):
self.features = features
@ -93,13 +93,13 @@ With generators, we can preserve the form of the original `next()` function and
Here's a simple driver script that shows how to use our goalkeepers:
```python
import random
import random
f = Features()
g1 = Goalkeeper(f)
g2 = GoalkeeperWithGenerator(f).behavior()
f = Features()
g1 = Goalkeeper(f)
g2 = GoalkeeperWithGenerator(f).behavior()
for i in xrange(10000):
for i in xrange(10000):
f.ballFar = random.random() > 0.1
f.ballOnLeft = random.random() < 0.5
g1action = g1.next()

Loading…
Cancel
Save