The purpose of this lab is to give you practice in creating a base class and many child (derived) classes that inherit the methods and fields of a base class. In addition, we'll be modifying the L-system class to enable it to use rules that include multiple possible replacement strings and choose them stochastically (randomly).
Adding the capability to incorporate different replacements will enable you to draw yet more complex L-system structures where each tree is unique. We'll continue to use material from the ABOP book.
This week we'll modify the lsystem.py and turtle_interpreter.py files and create a new shapes.py file that implements shapes other than trees. Please follow the naming conventions for classes and methods. We'll provide some test code that expects particular classes, methods, and parameter orderings.
An example of an L-system with multiple possible replacements is given below. Note that the rule has the symbol F followed by three separate replacement strings, separated by spaces.
base F rule F F[+F]F[-F]F F[+F]F F[-F]F
To make handling multiple rules easier, we're going to make use of Python dictionaries. A dictionary is an implementation of a lookup table. Each entry in a dictionary has a key (like a word) and a value (like a definition). To create an empty dictionary we use curly brackets instead of square brackets like a list.
myDict = {}
To add a new entry to a dictionary, we index the dictionary with the key and make an assignment. The following creates an entry in the dictionary for the string 'F'. The value of the entry is a list containing the string 'FF'.
myDict['F'] = [ 'FF' ]
In the __init__ method, change the initialization of the self.rules field to be an empty dictionary instead of an empty list.
Consider the process from file to dictionary. For example, the line below is a possible rule for a tree.
rule F F[+F] F[-F] F[+F][-F]
After reading the line and breaking it into strings using split, the words variable in the read method would be the following.
[ 'rule', 'F', 'F[+F]', 'F[-F]', 'F[+F][-F]' ]
If we pass words[1:] into addRule as the rule argument, then the rule variable will be:
[ 'F', 'F[+F]', 'F[-F]', 'F[+F][-F]' ]
The symbol is the first item in the list (rule[0]) and the set of replacements is the remainder of the list (rule[1:]). Modify the addRule method so that it assigns the list of replacements (rule[1:]) to the self.rules dictionary with the symbol (rule[0]) as the key.
def applyRules( self, inputString ): # assign to a local variable (e.g. resultString) the empty string # for each character, char, in the input # if char is in the self.rules dictionary # add to resultString a random choice from the dictionary entry self.rules[char] # else # add to resultString the character char # return resultString
When you're done with these changes, try running one of the following L-systems with just one or two iterations. Do it twice and see if you get the same string both times.
First, just after the class TurtleInterpreter: line, and inside the class, assign False to a variable called initialized. This variable is what is called a class global. Only one copy of it exists for all of the objects in the class. We're going to use it to keep track of whether the turtle window has been initialized.
After created the class variable, add the following three lines to the beginning of the __init__ method. Note that we have to use the class name to access a class variable.
if TurtleInterpreter.initialized: return TurtleInterpreter.initialized = True
Now the init function will not recreate the turtle window if it already exists.
Second, add two cases to draw string. For the character '{' call turtle.fill(True), and for the character '}' call turtle.fill(False).
Third, if you don't already have it, create a method color(self, c) that sets the turtle's color to the value in c.
When you're done, download and run the following test function. You should get something like the following, but with randomly selected colors.
Create a new file shapes.py. In it, create a class called Shape. It is a base class, from which other classes will be derived. Then define an init method with the following definition.
def __init__(self, distance = 100, angle = 90, color = (0, 0, 0), istring = '' ): # create a field of self called distance and assign it distance # create a field of self called angle and assign it angle # create a field of self called color and assign it color # create a field of self called string and assign it istring
def draw(self, xpos, ypos, scale=1.0, orientation=0): # create a TurtleInterpreter object # have the TurtleInterpreter object place the turtle at (xpos, ypos, orientation) # have the TurtleInterpreter object set the turtle color to self.color # have the TurtleInterpreter object draw the string # Note: use the distance, angle, and string fields of self # Note: multiply the distance by the scale parameter of the function
class Square(Shape):
Have the __init__() function of the Square class take two optional arguments: distance and color. The __init__() function should do the following.def __init__(self, distance=100, color=(0, 0, 0) ): # call the parent's __init__ function with distance, # an angle of 90, color, and the string 'F-F-F-F-'