Project 9: Unique Trees and Shapes Assigned: Wed Apr 16 2014 Due: 11:59:59 PM on Tue Apr 22 2014 Team Size: 1 Language: Python Lab:
Inheritance
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.
Tasks
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.
-
Create a new working folder. Copy your lsystem.py and turtle_interpreter.py
files from your prior assignment (version 2). This week we are
writing version 3 of both files. Make sure a comment at the top of
the file indicates they are version 3.
-
The change to the Lsystem class is to enable it to use rules with
multiple possible replacements. During replacement, the particular
replacement string is chosen randomly. There are a number of methods
requiring modification: __init__, read, replace, and
addRule. In addition, you'll need to import the random package.
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.
-
Remove the getRule method. It no longer makes sense because rules are not stored via indices.
-
In the addRule method we need to redo the function. We no longer want
to append a 2-element list to self.rules. Instead, we want to make an
assignment into the dictionary with a new key. The value should be a
list of all the possible replacments for a symbol.
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.
-
In the applyRules function we need to make use of the dictionary to build
up the string. Using a dictionary is much easier than using a list of
rules because we don't have to search for a rule matching the key.
Instead, we just ask if the key exists in the dictionary. If it does,
we pick a random replacement from the list of replacements and add it
to the output string. Otherwise, we add the character itself to the
output string.
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
-
Because we are using mutators to update the information in the object,
the read method should not change, so long as you are passing to
self.addRule the argument words[1:], which passes everything but the
first item in the words list.
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.
-
Make three changes to the TurtleInterpreter class.
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.

-
We're now going to start building a system that makes use of our
turtle interpreter class to build shapes that are not necessarily the output
of L-systems. Just as we made functions in the second project to draw
shapes, now we're going to make classes that create shape objects.
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
-
Create the following mutator methods.
-
setColor(self, c) - set the color field to c
-
setDistance(self, d) - set the distance field to d
-
setAngle(self, a) - set the angle field to a
-
setString(self, s) - set the string field to s
-
Create a method draw that executes the following algorithm.
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
-
In the same file, but below the Shape class definition, begin a new
class Square that is derived from Shape.
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-'
-
In the same file, below the Square class, create a new class called
Triangle that is the same as the Square class, but sets the string to
'F-F-F-' and sets the angle to 120.
-
Once you have completed the Square and Triangle classes, download the
second test function and run it. You should
get the output below.

Assignment:
The project this week is to continue to make shape classes, making use
of inheritance, starting with a tree class.
Tasks
-
The first task is to make a Tree shape class, similar to the Square
and Triangle classes. The difference between a Tree and a Square,
though, is that the Tree generates its string dynamically using an
L-system. The string for a Square is always the same, so it can set
the string field once and then use the parent Shape class' draw
method. However, every time we draw a tree, we first must build a
string using an L-system. Then it can use the Shape draw method.
Because we use an L-system to generate the string to draw, a Tree
object must contain an L-system, which means it must have a
field that holds an L-system object.
Because a Tree is a Shape, it must be a child of the Shape
class. That lets it use the parent functions for setting color,
distance, and angle, among other things.
To make our Tree class, start by creating a file called tree.py.
Import your lsystem and shape modules. The Tree class should be derived from
the Shape class, but you'll need to override some of the methods
because of the special nature of a Tree: it needs more fields than a
simple Shape, and it has to dynamically create the string it will draw
using an L-system.
The methods you'll need to override or create for the Tree class include:
-
def __init__(self, distance=5, angle=22.5, color=(0.5, 0.4, 0.3),
iterations=3, filename=None): - The init function should call the
parent init with distance, angle, and color, store the iterations
number in an iterations field, then create an Lsystem object and store
it in a field.
-
Create a setIterations modifier for the iterations field of the Tree
object. Then create a read(self, filename) method that calls the
lsystem's read method with the specified filename.
-
Override the draw method--but keep the same parameter list--so it uses
the Lsystem to build the string, assigns the string to the string
field of self, and then calls the parent draw function. You may want
to change the default orientation to 90 so the trees grow up.
Once you've written the tree class, make a test function for the class
and try it out. This function should take in an L-System filename,
create a Tree object, and then use the Tree object's draw method to
draw at least 3 trees. Use an L-system with multiple
replacements for at least one rule and show the three trees are
different.
The output of your tree.py test function is required image 1.
-
In shapes.py, create at least three classes--other than Square and Triangle--that
are derived from the Shape class and define different shapes using strings.
One of them should make a filled shape using curly brackets { and } to
turn on and off the fill. Make a test function for your shapes.py file
that generates an image that incorporates all of the shapes you
created. The function should test all of the capabilities of the
different shape classes.
The output of your shapes.py test method is required image 2.
-
In a file named indoorscene.py, Create a new indoor scene where part of your scene is a set of shapes
and at least one tree in something that looks like a painting. You
may use only the Tree and various shape classes from this assignment
to create the scene. You may not use your turtle code from first
three assignments. Only the TurtleInterpreter class can execute turtle
commands.
The indoor scene with a painting is required image 3.
-
In a file named mosaic.py, create a function tile(x, y, scale) that draws a
set of shapes inside a square that is scale by scale in size with the
lower left corner of the tile at location (x, y). If scale is 10,
then the tile would be 10x10.
Then make a function mosaic(x, y, scale, Nx, Ny) that draws
a 2D array of tiles Nx by Ny, where each tile is of size scale by
scale, and the lower left corner of the mosaic is at (x, y). So if
scale is 10, Nx is 3 and Ny is 4, the function should draw twelve
10x10 tiles three across and four down.
An image of at least 20 tiles (5 x 4) is required image 4.
Extensions
Each assignment will have a set of suggested extensions. The required tasks constitute about 85% of the assignment, and if you do only the required tasks and do them well you will earn a B+. To earn a higher grade, you need to undertake one or more extensions. The difficulty and quality of the extension or extensions will determine your final grade for the assignment. One complex extension, done well, or 2-3 simple extensions are typical.
-
Make non-square tiles. Rectangles are easy, hexagons or triangles are
a real extension.
-
Make new L-systems and add characters to the vocabulary that do
interesting things.
-
Modify drawString so that when drawing a tree the branches droop down
like gravity is pulling at them.
-
Create a sequence of images to build an animation.
-
Make more tile functions and mix them around in the mosaic function.
-
Make more shape classes that do interesting things. Making a fixed
sequence of characters is easy. Make a shape class where the strings
are the result of executing a function. L-systems are one example of
a dynamically created string, but there are many other ways to do
that.
-
Use a Python language feature new to you (not just a new library feature or function)
Writeup and Hand-In
Before handing in your code, double check that it is well-styled:
- All variable names and function names use either camelCase or snake_case.
- All files and functions have docstrings.
- Comments are added to explain complicated code blocks.
- All variable and function names are appropriately descriptive.
- Functions are defined before any other code is added at the top level of each file.
- In a file with any functions defined, top level code is wrapped so that it won't execute if that file is imported by another.
Make a new wiki page for your assignment. Put the label cs151s14project9
on the page. Each of you needs to make your own writeup.
In addition to making the wiki page writeup, put the python files you
wrote on the Academics server in your private handin directory.
Colby Wiki
In general, your writeup should follow the outline below.
-
A brief summary of the task, in your own words. This should be no
more than a few sentences. Give the reader context and identify the
key purpose of the assignment.
-
A description of your solution to the tasks, including any images
you created. (Make sure your images are appropriately sized to fit onto the wiki page.) This should be a description of the form and
functionality of your final code. You may want to incorporate code
snippets in your description to point out relevant features. Note
any unique computational solutions you developed.
-
A description of any extensions you undertook, including images
demonstrating those extensions. If you added any modules,
functions, or other design components, note their structure and the
algorithms you used.
-
A brief description (1-3 sentences) of what you learned.
-
A list of people you worked with, including students who took the course in previous semesters, TAs, and professors. Include in this list anyone whose code you may have seen. If you didn't work with anyone, please say so.
-
Don't forget to label your writeup so that it is easy for others to find. For this lab, use cs151s14project9
|