Warmup

Part A: Making Repeaters

In this lab, you will be designing autonomous “animals” (called critters) that will compete in a simulation. You’ll be programming how they move and fight with each other. Often, the behavior of your critters will be repetitive: they’ll move north and then east, or in circles, or attack according to some pattern. In this exercise, you’ll design some data structures that will help you implement this behavior easily.

Your task is to program the Repeater class. A Repeater will store a sequence of values, and whenever asked, will return the next value in the sequence. When it gets to the end of its sequence, it loops back to the beginning and starts over. In other words, a Repeater object has two attributes:

  • self.sequence contains the list of values that the Repeater will go through
  • self.current contains the index of the element that the Repeater will return next time it is asked (this should start at 0)

A Repeater object has one method, next(), which returns the element of self.sequence at index self.current. If self.sequence is the empty list, next() should return None. When self.current reaches len(self.sequence), self.current should be reset to 0. Otherwise, self.current should increment every time next() is called. You can do this with the % operator or with a conditional statement. Be careful to make sure that you increment only after accessing the element to return - if you don’t, you could miss the first element of the sequence!

Finally, the constructor for a Repeater object takes in one parameter seq, so that the user can specify what they would like the sequence to be.

  1. In the file repeater.py, implement the Repeater interface.
  2. Write a function to test your code. Make sure to construct Repeater instances with a few list lengths, including the empty list, and to call next() enough times to go back to the beginning of self.sequence.

Part B: Turning It Around

Now it’s time to practice with inheritance. You’re going to program a subclass of Repeater, which we’ll call OppositeRepeater. An OppositeRepeater will have the same methods and attributes as a Repeater.

The only thing that will be different in OppositeRepeater is the constructor (__init__ method). An OppositeRepeater instance should move backwards through the list of values provided, starting at the last element and moving to the element before until the first element, and then looping back around. How can you modify the inputed list of elements to __init__ such that when you call next() from the Repeater class (which moves forward in self.sequence) it goes in the opposite order? (Hint: think about reversing seq!).

  1. In the file opposite_repeater.py, implement the OppositeRepeater class. Remember all you should do is add code for __init__. Think about how you can use inheritance to gain access to the next() method from the Repeater class. Then, assuming that next() will move forward in self.sequence, how can you set the value of self.sequence accordingly.
  2. Write a function to test your code. Feel free to reuse some of your test code for the Repeater class.

Part C: Getting Fancy

Repeaters and OppositeRepeaters will be helpful for several of the critters you construct on the latter portions of this lab. However, one animal (the Tiger) will benefit from having a slightly more sophisticated movement class. Let’s now design a new subclass of Repeater, called FancyRepeater, that will allow us to move in a random direction for a given number of steps before picking a new random direction to move.

As with all subclasses of Repeater, FancyRepeater will have self.sequence, self.current, and a next() method. Our new subclass will have two additional attributes: self.period and self.elt (short for “element”). The constructor for FancyRepeater should take the parameter seq as before, but also optionally take a second argument, period. On construction, self.elt is assigned to a randomly selected value from self.sequence. We would suggest using the random module and in particular the method random.choice. (Note that the default value for period can be whatever you like; by default we set period to be 1 in the interface).

Each call to next() should have the following behavior:

  • If the number of times next() has been called for the current direction is less than or equal to self.period, then return the current direction of self.elt.
  • Otherwise, pick a new random element of self.sequence to be self.elt, and returns this new direction.

The attribute self.current should keep track of how many calls since you last switched self.elt. So the code

fr = FancyRepeater(["a","b","c"], 4)
for i in range(13):
  print(fr.current,fr.next())

might produce the output below.

0 a
1 a
2 a
3 a
0 b
1 b
2 b
3 b
0 a
1 a
2 a
3 a
0 c

Your tasks are the following:

  1. In the file fancy_repeater.py, implement the FancyRepeater class
  2. Write a function (something a bit more complicated than the above) to test your class.

ReadMe

Feel free to make use of Repeater, OppositeRepeater, and/or FancyRepeater when completing the lab itself. However, if you run into issues or bugs with your implementations—don’t worry! You can complete the lab without relying on these classes.