CIT 591 Addendum: Small Town Traffic
Fall 2009, David Matuszek

Additional requirements

Sorry, these turn out to be all but essential for unit testing.

Location

As mentioned in lab, but I don't think it's in the writeup, a location is a (row, column) tuple.

Town class

I've previously asked for the method getPrimaryArray(self); now, for testing purposes, I also need to ask for a method getSecondaryArray(self).

Here's what these arrays are all about. The "primary" array holds the vehicles, and is what is displayed as the program runs. However, in order to allow the vehicles to move "simultaneously," a "secondary" array is used. At each step, the program figures out where each vehicle will be after moving (or not moving, if it has to yield the right-of-way). Since at the next turn it may be where some other vehicle is now, we need to keep the results of all movements in a second array. Then, when we have figured out where everything will be on the next turn, we can copy this secondary array into the primary array.

Vehicle class

I've asked for the method putVehicleAt(self, vehicle, location) to put the given vehicle in the given location in the primary array. This method should be able to put a vehicle anywhere in the array, even in a corner location. (As the program runs, I've said that vehicles should not enter in a corner location, but this should be enforced elsewhere, otherwise testing becomes much more difficult.)

The getRandomEntryLocationAndDirection(self) should not choose a corner location.

Provide a method getDirection(self) to tell in which direction the vehicle is currently facing. The return value should be an integer in the range 1 to 4, with meanings as listed in the provided skeleton code.

You should have a method getRightOfWayLocation(self) that a vehicle uses to look "ahead and to the right," relative to the direction it is facing, and return the (row, column) location it sees (or None, if off the edge of the town). It turns out that you also need to provide a method getOtherWayLocation(self) to look "ahead and to the left." I didn't realize it earlier, but a Car may need to yield right-of-way to an Ambulance in this position.

Car and Ambulance classes

Add the method __str__(self) to both the Car and the Ambulance class. In the Car class, it should return one of the strings ' ^ ', ' > ', ' v ', or ' < '; in the Ambulance class, one of the strings ' ^!', ' >!', ' v!', or ' <!'. For convenience in printing, these are all three-character strings.

More Python

Optional parameters

You can give default values to parameters; parameters without default values must come first. If you call the method with fewer parameters than expected, the defaults will be used for the remaining parameters.

For example, if you define a constructor method as:
    def makeMove(self, town, fromLoc=(0, 0), toLoc=None)
then it can be called with any of:
    self.makeMove(myTown)         # fromLoc is (0, 0), toLoc is None
    self.makeMove(myTown, (2, 2)) # toLoc is None
    self.makeMove(myTown, (2, 0), (3, 0)

str(value) and object.__str__()

The function str(value) can be used to convert any Python value to a string. You can use this to, for instance, convert a value to a string so that you can concatenate it with another string.

The object.__str__() method is used by the print statement when you try to print something. However, for objects of a class that you define, the result is likely to be something like '<__main__.Town object at 0x17b5e50>'. If you define, in your class, your own __str__(self) method, the print statement will use that instead, and you'll be able to control how things look when printed. Note that this method should return a string, not print one.

__init__(self, other variables)

You create an object of a class by giving the name of the class, and (in parentheses) whatever parameters are required. The required parameters are those after the self in the constructor method, which is named __init__.

For example, if you have a Town class with the constructor __init__(self, size), then you must create the object with an appropriate value for size. For instance,

smallTown = Town(10)

Here's something I had wrong before. If a class is a subclass of another class, its constructor should call the superclass constructor. For example,

class Vehicle(object):
    def __init__(self, town, startingLocation=None, destination=None):
        ...
class Car(Vehicle):
    def __init__(self, town, currentLocation=None, destination=None):
         Vehicle.__init__(self, town, currentLocation, destination) # Required!
         ...

self

The variable self refers to the object executing the method. (If you know Java, it's the same as the Java keyword this.) If a Car object wants to refer to the direction in which it is going, it can say self.direction. I sometimes find it helpful to think of self as meaning "my", as in "my.direction." If, inside a method, the object wants to call another of its own methods, it has to say something like self.getNextLocation().

Sometimes these have to be "chained." For example, if a Car object has a town variable (of type Town), and the Town class defines a method isLegalLocation(location), then the Car object can call this method by saying self.town.isLegalLocation(location).

isinstance(value, Class)

You can use the isinstance function to test what class an object belongs to. For example, isinstance(item, Car) or isinstance(item, int).

The DRY principle

A very important style rule that we have not yet discussed in class is the DRY principle: Don't Repeat Yourself.This principle has two main applications:

  1. Don't duplicate code. If you find yourself writing the same code in two or more places, or if you are tempted to copy and paste code, don't do it. Write a method instead, and call the method. If you are writing "almost" the same code, but with minor variations, try to capture those differences in parameters that you can pass to a method.
  2. Don't duplicate data. Don't have the same information in more than one place, or in more than one form.

I mention the DRY principle here because can cause serious problems in the Traffic assignment. You need arrays to hold the vehicles; in addition, each vehicle needs to know its location in the array. That's the same information in two different places. When you "move" a vehicle, you have to change where it is in the array (which array?), and you also have to change where the vehicle "thinks" it is.

I often work with arrays of objects, where the individual objects need to know their location in the array. In this situation, I have yet to find a good way to satisfy the DRY principle, and that's always a problem.

Other information

Error messages

Global name variable not defined
Often, this means you forgot to use the prefix self. on the variable, so Python thinks you meant it to be global.
Sometimes, it means you used == instead of = in an assignment statement.
Syntax error at the beginning of a line
Sometimes this means you have unbalanced parentheses on the line above.
variable != None
When you see this message in a failed test, it may mean that you forgot to return a value from the method being tested.

Overall skeleton

up = 1
right = 2
down = 3
left = 4

class Town(object):
    def __init__(self, size):
    def getPrimaryArray(self):                ## ADDED
    def getSecondaryArray(self):
    def isLegalLocation(self, location):
    def getVehicleAt(self, location):
    def putVehicleAt(self, vehicle, location):
    def getRandomEntryLocationAndDirection(self):
    def getRandomExitLocation(self, exitDirection):
    def moveVehicle(self, fromLocation, toLocation):
    def makeOneStep(self, numberOfNewVehicles):
    def display(self, message=None):
    def makeOneStep(self, newVehicles):

class Vehicle(object):
    def __init__(self, town, startingLocation=None, destination=None):
    def findStartingDirection(self, startingLocation):
    def getDirection(self):                   ## ADDED
    def getLocation(self):
    def getDesiredNextLocation(self):
    def getRightOfWayLocation(self):
    def getOtherWayLocation(self):            ## ADDED
    def getDestination(self):

class Car(Vehicle):
    def __init__(self, town, currentLocation=None, destination=None):
    def canMove(self):
    def move(self):
    def __str__(self):                        ## ADDED

class Ambulance(Vehicle):

    def __init__(self, town, currentLocation=None, destination=None):
    def canMove(self):
    def move(self):
    def __str__(self):                        ## ADDED

def main():

if __name__ == '__main__':
    main()