
In this week's lab, you will practice designing your own classes for representing and drawing L-system strings. This will give you more practice manipulating strings, dictionaries, and file IO.
TurtleInterpreter class
There is a division of labor between the two classes that you will create this week:
Lsystem: creates the L-system string that represents the shape that will be drawn (e.g. 'F--F--F').TurtleInterpreter: takes the characters in the L-system string and interprets them as turtle movement and drawing commands. For example, when 'F' is encountered, the turtle should move forward.Important: The Lsystem class handles ONLY the string operations — it does do anything not directly related with the turtle. In others words, do NOT import turtle in lsystem.py!
All of these will be handled by your Lsystem class:
You will read in this information from a text file which looks like:
base F--F--F rule F F+F--F+F
In this example, the base string is 'F--F--F' and there is one replacement rule: each F ("find string") is replaced with 'F+F--F+F' ("replace string"). Additional rules would appear on their own lines, in the same format as the rule on the second line above.
Here is the starting set of characters in the L-system alphabet that the TurtleInterpreter can draw and what they do:
F is go forward by a certain distance+ is turn left by an angle- is turn right by an angleCreate a Lab07b folder on your computer where you store CS151 projects and labs, hopefully somewhere that is backed up (e.g. in Dropbox, Google Drive, Microsoft One Drive, etc.)
Open VS Code. Create a new Python source code file called lsystem.py.
Open up a Terminal (e.g. Control+` in VS Code) and change the directory (cd) to the Lab07b folder.
You will store the L-system (in the Lsystem class) in the following format:
'F-F-F-F'{rule 1 find string: rule 1 replace string, rule 2 find string: rule 2 replace string, ...}
{'F': 'FF-F+F-F-FF'} if there is only one replacement rule.Create the Lsystem class and inside write the class constructor with the signature, following the "todo" items in the comments:
def __init__(self, filename=None): '''L-system class constructor Parameters: ----------- filename: str. Filename of the L-system text file with the base string and 1+ replacement rules ''' # Create an instance variable for the base string # (initialize as the empty string) # Create an instance variable for your replacement rules # (initialize as an empty dictionary) # Check if filename passed in (i.e. parameter is not None) # If so, read in the L-system from the file called # filename. Do this by calling the read method: # self.read(filename) # (you will implement this shortly)
Implement the following set, get, and other "one liner" methods:
def getBase(self):def setBase(self, newBase):key: def getRule(self, key):newRuleKey and newRuleValue to the current dictionary of replacement rules self.rules: def addRule(self, newRuleKey, newRuleValue):def getKeys(self):Download the test file test_set_get.py to your lab folder and run it to check your work. Make sure that all the test code works as expected before moving on.
Write a method to read in the L-system from a text file that has the format described above. For example:
base F--F--F rule F F+F--F+F
Here is the signature and see the code comments below to guide you through the method:
def read(self, filename): '''Reads the L-system base string and 1+ rules from a text file. Stores the data in the instance variables in the constructor in the format: base string: str. e.g. `'F-F-F-F'` replacement rules: dictionary. e.g. `{'F': 'FF-F+F-F-FF'}` for one rule Parameters: ----------- filename: str. Filename of the L-system text file with the base string and 1+ replacement rules ''' # Open the file called filename # Read in the file line-by-line. # For each line, split it into a 2 item list. # If the first list item is the string 'base', set the L-system base string # to the second item in the list. # If the first list item is the string 'rule', add a rule to your replacement # rules dictionary consisting of the find and replace strings # (2nd and 3rd items in the list). # Remember: We add a rule as a dictionary key-value pair: {find str: replace str} # Close the file
Remember, the L-system files have a standard format described above.
Guidelines:
self. (e.g. self.getBase() instead of getBase()). Pay attention to what kind of data they expect as parameters and what they return (e.g. a list, a string).\n) in your base and/or rule strings, the easiest way to remove them is to split the line by calling the split string method without any parameters (e.g. line.split()). Without a parameter, split splits by whitespace and automatically removes extra whitespace at the beginning and end (including new line characters!). Another approach is to use the strip string method. Note that strip is a string method and can only be called on a string, NOT a list.Download
Run the test file with the two L-system file present in your lab working folder. Make sure that all the test code passes before moving on.
Here is the docstring with a summary of the instructions and method signature:
def replace(self, currString): '''Applies the full set of replacement rules to current 'base' L-system string `currString`. Overall strategy: - Scan the L-system string left to right, char by char - Apply AT MOST ONE replacement rule to a matching character. Example: If the current char is 'F' and that matches a rule's find string 'F', apply that rule then move onto the next character in the L-system string (don't try to match more rules to the current char). - If no rule matches a rule find string, we just add the char as-is to the new string. Parameters: ----------- currString: str. The current L-system base string. Returns: ----------- newString: str. The base string `currString` with replacement rules applied to it. '''
Here is the method signature and docstring:
def buildString(self, n): '''Starting with the base string, apply the L-system replacement rules for `n` iterations. You should NOT change your base string instance variable here! Parameters: ----------- n: int. Number of times you go through the L-system string to apply the replacement rules. Returns: ----------- str. The L-system string after apply the replacement rules `n` times. '''
Download the test file test_buildString.py It assumes that systemKoch.txt is in your lab folder. Make sure that all the test code passes before moving on.
Tip: If the length of the 3 iteration L-system is 449, you may have a new line character in your base string. To correct this, revise your read method.
TurtleInterpreter classAs mentioned above, the purpose of the TurtleInterpreter class is to translate a L-system string into turtle movement and drawing commands.
Create a new file called turtle_interpreter.py and define a class in it called TurtleInterpreter. Inside, define the constructor with the following signature and docstring. You may want to turn off all turtle animations in the constructor (remember that tracer is a Screen object method).
def __init__(self, width=800, height=800, bgColor='white'): '''TurtleInterpreter constructor. Creates instance variables for a Turtle object and a Screen object with a particular window `width`, `height`, and background color `bgColor`. ''' # Create a Turtle object, set it as an instance variable # Create a Screen object, set it as an instance variable. # Set the screen's height, width, and color based on the parameters # Turn the screen's tracer off.
Write the following methods for TurtleInterpreter that modify your Turtle object instance variable:
c: def setColor(self, c)w: def setWidth(self, w)x, y) and sets the heading to heading if the value of the heading parameter passed in is not None: def goto(self, x, y, heading=None)def getScreenWidth(self)def getScreenHeight(self)hold method that you can call on TurtleInterpreter that keeps the window open until the user clicks (like usual) or presses the 'q' key. Note: it assumes your Screen object instance variable is called screen and the one for Turtle object is called turt — modify these to accommodate your own naming scheme.def hold(self): '''Holds the screen open until user clicks or presses 'q' key''' # Hide the turtle cursor and update the screen self.turt.hideturtle() self.screen.update() # Close the window when users presses the 'q' key self.screen.onkey(self.screen.bye, 'q') # Listen for the q button press event self.screen.listen() # Have the turtle listen for a click self.screen.exitonclick()
drawString methodThis is the method that translates an L-system string (e.g. 'F-F-F-F') into turtle drawing commands. It should process the L-system string one character at a time from left-to-right.
At the moment, you should add support for the following drawing commands:
F is forward by a certain distance+ is left by an angle- is right by an angleHere is the method signature and docstring:
def drawString(self, lsysString, distance, angle): '''Interpret each character in an L-system string as a turtle command. Here is the starting L-system alphabet: F is forward by a certain distance + is left by an angle - is right by an angle Parameters: ----------- lsysString: str. The L-system string with characters that will be interpreted as drawing commands. distance: distance to travel with F command. angle: turning angle (in deg) for each right/left command. ''' # Walk through the lsysString character-by-character and # have the turtle object (instance variable) carry out the # appropriate commands # Call the update method on the screen object to make sure # everything drawn shows up at the very end of the method # (remember: you turned off animations in the constructor)
TurtleInterpreterDownload and run the test code file test_triangle.py. You should get the following:

Above, take note of the position, thickness, and orientation of the triangle in the screen. Also note the test code print out showing the screen width and height.
Download and run the test code file test_drawString.py. You can test any L-system file that you'd like with it. Here is the usage statement:
Usage: python3 test_drawString.py <L-system filename> <distance> <angle>
Here are some test cases that you should try along with the expected results:
| L-system | distance (pixels) | angle (deg) | Output |
|---|---|---|---|
| systemKoch.txt | 40 | 60 |
|
| systemA1.txt | 40 | 90 |
|
| systemA2.txt | 40 | 90 |
|
| systemB.txt | 10 | 40 |
|
You are encouraged to play around with the angles to see how that affects the shapes that are drawn!
Once you are done with each of the lab tasks, you may start on the project.
© 2021 Oliver Layton