"""
A module to draw cool shapes with the Tk Turtle.
The module can be run as a script to show off the various functions. Unimplemented
functions will do nothing.
YOUR NAME(S) AND NETID(S) HERE
DATE COMPLETED HERE
"""
import cornell
import math
################# Helpers for Precondition Verification #################
def is_number(x):
"""
Returns: True if value x is a number; False otherwise.
Parameter x: the value to check
Precondition: NONE (x can be any value)
"""
return type(x) in [float, int]
def is_window(w):
"""
Returns: True if w is a cornell Window; False otherwise.
Parameter w: the value to check
Precondition: NONE (w can be any value)
"""
return type(w) == cornell.Window
def is_valid_color(c):
"""
Returns: True c is a valid turtle color; False otherwise
Parameter c: the value to check
Precondition: NONE (c can be any value)
"""
from cornell.colors.tkcolor import is_tkcolor
return (type(c) == cornell.RGB or type(c) == cornell.HSV or
(type(c) == str and is_tkcolor(c)))
def is_valid_speed(sp):
"""
Returns: True if sp is an int in range 0..10; False otherwise.
Parameter sp: the value to check
Precondition: NONE (sp can be any value)
"""
return (type(sp) == int and 0 <= sp and sp <= 10)
def is_valid_length(side):
"""
Returns: True if side is a number >= 0; False otherwise.
Parameter side: the value to check
Precondition: NONE (side can be any value)
"""
return (is_number(side) and 0 <= side)
def is_valid_iteration(n):
"""
Returns: True if n is an int >= 1; False otherwise.
Parameter n: the value to check
Precondition: NONE (n can be any value)
"""
return (type(n) == int and 1 <= n)
def is_valid_depth(d):
"""
Returns: True if d is an int >= 0; False otherwise.
Parameter d: the value to check
Precondition: NONE (d can be any value)
"""
return (type(d) == int and d >= 0)
def is_valid_turtlemode(t):
"""
Returns: True t is a Turtle with drawmode True; False otherwise.
Parameter t: the value to check
Precondition: NONE (t can be any value)
"""
return (type(t) == cornell.Turtle and t.drawmode)
def is_valid_penmode(p):
"""
Returns: True t is a Pen with fill False; False otherwise.
Parameter p: the value to check
Precondition: NONE (p can be any value)
"""
return (type(p) == cornell.Pen and not p.fill)
def report_error(message, value):
"""
Returns: An error message about the given value.
This is a function for constructing error messages to be used in assert statements.
We find that students often introduce bugs into their assert statement messages, and
do not find them because they are in the habit of not writing tests that violate
preconditions.
The purpose of this function is to give you an easy way of making error messages
without having to worry about introducing such bugs. Look at the function
draw_two_lines for the proper way to use it.
Parameter message: The error message to display
Precondition: message is a string
Parameter value: The value that caused the error
Precondition: NONE (value can be anything)
"""
return message+': '+repr(value)
#################### DEMO: Two lines ####################
def draw_two_lines(w,sp):
"""
Draws two lines on to window w.
In the middle of the window w, this function draws a green line 100 pixels to the
west, and then a red line 200 pixels to the south. It uses a new turtle that moves
at speed sp, 0 <= sp <= 10, with 1 being slowest and 10 fastest (and 0 being "instant").
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# Assert the preconditions
assert is_window(w), report_error('w is not a valid window',w)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
t = cornell.Turtle(w)
t.speed = sp
t.color = 'green'
t.forward(100) # draw a line 100 pixels in the current direction
t.left(90) # add 90 degrees to the angle
t.color = 'red'
t.forward(200)
#################### TASK 1: Triangle ####################
def draw_triangle(t, s, c):
"""
Draws an equilateral triangle of side s and color c at currenct position.
The direction of the triangle depends on the current facing of the turtle.
If the turtle is facing west, the triangle points up and the turtle starts
and ends at the east end of the base line.
WHEN DONE, THE FOLLOWING TURTLE ATTRIBUTES ARE THE SAME AS IT STARTED:
position (x and y, within round-off errors), heading, color, and drawmode.
If you changed any of these in the function, you must change them back.
Parameter t: The drawing Turtle
Precondition: t is a Turtle with drawmode True.
Parameter s: The length of each triangle side
Precondition: s is a valid side length (number >= 0)
Parameter c: The triangle color
Precondition: c is a valid turtle color (see the helper function above)
"""
# Assert the preconditions
assert is_valid_turtlemode(t), report_error('Invalid turtle mode', t)
assert is_valid_length(s), report_error('Invalid side length', s)
assert is_valid_color(c), report_error('Invalid color', c)
# Hint: each angle in an equilateral triangle is 60 degrees.
# Note: In this function, DO NOT save the turtle position and heading
# in the beginning and then restore them at the end. The turtle moves
# should be such that the turtle ends up where it started and facing
# in the same direction, automatically.
# Also, 3 lines have to be drawn. Does this suggest a for loop that
# processes the range 0..2?
pass
#################### TASK 2: Hexagon ####################
def draw_hex(t, s):
"""
Draws six triangles using the color 'orange' to make a hexagon.
The triangles are equilateral triangles, using draw_triangle as a helper.
The drawing starts at the turtle's current position and heading. The
middle of the hexagon is the turtle's starting position.
WHEN DONE, THE FOLLOWING TURTLE ATTRIBUTES ARE THE SAME AS IT STARTED:
position (x and y, within round-off errors), heading, color, and drawmode.
If you changed any of these in the function, you must change them back.
Parameter t: The drawing Turtle
Precondition: t is a Turtle with drawmode True.
Parameter s: The length of each triangle side
Precondition: s is a valid side length (number >= 0)
"""
# Assert the preconditions
assert is_valid_turtlemode(t), report_error('Invalid turtle mode', t)
assert is_valid_length(s), report_error('Invalid side length', s)
# Note: Do not save any of the turtle's properties and then restore them
# at the end. Just use 6 calls on procedures drawTriangle and t.left. Test
# the procedure to make sure that t's final location and heading are the
# same as t's initial location and heading (except for roundoff error).
# The procedure is supposed to draw 6 triangles. Does that suggest a loop
# that processes the integers in 0..5?
pass
#################### Task 3A: Spirals ####################
def draw_spiral(w, side, ang, n, sp):
"""
Draws a spiral using draw_spiral_helper(t, side, ang, n, sp)
This function clears the window and makes a new turtle t. This turtle
starts in the middle of the canvas facing east (NOT the default west).
It then calls draw_spiral_helper(t, side, ang, n, sp). When it is done,
the turtle is left hidden (visible is False).
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter side: The length of each spiral side
Precondition: side is a valid side length (number >= 0)
Parameter ang: The angle of each corner of the spiral
Precondition: ang is a number
Parameter n: The number of edges of the spiral
Precondition: n is a valid number of iterations (int >= 1)
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_window(w), report_error('w is not a valid window',w)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert is_valid_iteration(n), report_error('n is not a valid number of iterations',side)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# HINT: w.clear() clears window.
# HINT: set the turtle's visible attribute to False at the end.
pass
def draw_spiral_helper(t, side, ang, n, sp):
"""
Draws a spiral of n lines at the current position and heading.
The spiral begins at the current turtle position and heading, turning ang
degrees to the left after each line. Line 0 is side pixels long. Line 1
is 2*side pixels long, and so on. Hence each Line i is (i+1)*side pixels
long. The lines alternate between blue, red, and green, in that order, with
the first one blue.
WHEN DONE, THE FOLLOWING TURTLE ATTRIBUTES ARE THE SAME AS IT STARTED:
color, speed, visible, and drawmode. However, the final position and
heading may be different. If you changed any of these four in the function,
you must change them back.
Parameter t: The drawing Turtle
Precondition: t is a Turtle with drawmode True.
Parameter side: The length of each spiral side
Precondition: side is a valid side length (number >= 0)
Parameter ang: The angle of each corner of the spiral
Precondition: ang is a number
Parameter n: The number of edges of the spiral
Precondition: n is a valid number of iterations (int >= 1)
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_turtlemode(t), report_error('Invalid turtle mode', t)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert is_valid_iteration(n), report_error('n is not a valid number of iterations',side)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# NOTE: Since n lines must be drawn, use a for loop on a range of integers.
pass
#################### TASK 3B: Polygons ####################
def multi_polygons(w, side, k, n, sp):
"""
Draws polygons using multi_polygons_helper(t, side, k, n, sp)
This function clears the window and makes a new turtle t. This turtle starts in the
middle of the canvas facing north (NOT the default west). It then calls
multi_polygons_helper(t, side, k, n, sp). When it is done, the turtle is left
hidden (visible is False).
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter side: The length of each polygon side
Precondition: side is a valid side length (number >= 0)
Parameter k: The number of polygons to draw
Precondition: k is an int >= 1
Parameter n: The number of sides of each polygon
Precondition: n is an int >= 1
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_window(w), report_error('w is not a valid window',w)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# HINT: w.clear() clears window.
# HINT: set the turtle's visible attribute to False at the end.
pass
def multi_polygons_helper(t, side, k, n, sp):
"""
Draws k n-sided polygons of side length s.
The polygons are drawn by turtle t, starting at the current position. The turtles
alternate colors between red and green. Each polygon is drawn starting at the same
place (within roundoff errors), but t turns left 360.0/k degrees after each polygon.
At the end, ALL ATTRIBUTES of the turtle are the same as they were in the beginning
(within roundoff errors). If you change any attributes of the turtle. then you must
restore them. Look at the helper draw_polygon for more information.
Parameter t: The drawing Turtle
Precondition: t is a Turtle with drawmode True.
Parameter side: The length of each polygon side
Precondition: side is a valid side length (number >= 0)
Parameter k: The number of polygons to draw
Precondition: k is an int >= 1
Parameter n: The number of sides of each polygon
Precondition: n is an int >= 1
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_turtlemode(t), report_error('Invalid turtle mode', t)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# HINT: make sure that upon termination, t's color and speed are restored
# HINT: since k polygons should be drawn, use a for-loop on a range.
pass
# DO NOT MODIFY
def draw_polygon(t, side, n, sp):
"""
Draws an n-sided polygon using of side length side.
The polygon is drawn with turtle t using speed sp.
WHEN DONE, THE FOLLOWING TURTLE ATTRIBUTES ARE THE SAME AS IT STARTED: position
(x and y, within round-off errors), heading, color, speed, visible, and drawmode.
There is no need to restore these.
Parameter t: The drawing Turtle
Precondition: t is a Turtle with drawmode True.
Parameter side: The length of each polygon side
Precondition: side is a valid side length (number >= 0)
Parameter n: The number of sides of each polygon
Precondition: n is an int >= 1
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# Assert the preconditions
assert is_valid_turtlemode(t), report_error('Invalid turtle mode', t)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert (type(n) == int and n >= 3), report_error('n is an invalid # of poly sides',n)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# Remember old speed
oldspeed = t.speed
t.speed = sp
ang = 360.0/n # exterior angle between adjacent sides
# t is in position and facing the direction to draw the next line.
for _ in range(n):
t.forward(side)
t.left(ang)
# Restore the speed
t.speed = oldspeed
#################### TASK 3C: Radiating lines ####################
def radiate(w, side, n, sp):
"""
Draws n straight radiating lines using radiate_helper(t, side, n, sp)
This function clears the window and makes a new turtle t. This turtle starts in the
middle of the canvas facing east (NOT the default west). It then calls
radiate_helper(t, side, n, sp). When it is done, the turtle is left hidden
(visible is False).
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter side: The length of each radial line
Precondition: side is a valid side length (number >= 0)
Parameter n: The number of lines to draw
Precondition: n is an int >= 2
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_window(w), report_error('w is not a valid window',w)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# HINT: w.clear() clears window.
# HINT: set the turtle's visible attribute to False at the end.
pass
def radiate_helper(t, side, n, sp):
"""
Draws n straight radiating lines of length s at equal angles.
This lines are drawn using turtle t with the turtle moving at speed sp.
A line drawn at angle ang, 0 <= ang < 360 has HSV color (ang % 360.0, 1, 1).
WHEN DONE, THE FOLLOWING TURTLE ATTRIBUTES ARE THE SAME AS IT STARTED: color, speed,
visible, and drawmode. However, the final position and heading may be different. If
you changed any of these four in the function, you must change them back.
Parameter t: The drawing Turtle
Precondition: t is a Turtle with drawmode True.
Parameter side: The length of each radial line
Precondition: side is a valid side length (number >= 0)
Parameter n: The number of lines to draw
Precondition: n is an int >= 2
Parameter sp: The turtle speed.
Precondition: sp is a valid turtle speed.
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_turtlemode(t), report_error('Invalid turtle mode', t)
assert is_valid_length(side), report_error('side is not a valid length',side)
assert is_valid_speed(sp), report_error('sp is not a valid speed',sp)
# Notes:
# 1. Drawing n lines should be done with a loop that processes
# a certain range of integers.
# 2. You should keep the heading of the turtle in the range
# 0 <= heading < 360.
# 3. (t.heading % 360.0, 1, 1) is an HSV representation of the color
# determined by turtle t's heading.
# 4. You can use an HSV object for the turtle's color attribute,
# even though all the examples use strings with color names
pass
#################### Task 4A: Sierpinski Triangle ####################
def triangle(w, side, d):
"""
Draws a Sierpinski triangle with the given side length and depth d.
This function clears the window and makes a new graphics pen p. This pen starts in
the middle of the canvas at (0,0). It draws by calling the function
triangle_helper(p, 0, 0, side, d). The pen is visible during drawing and should set
to hidden at the end.
The pen should have both a 'magenta' fill color and a 'magenta' line color.
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter side: The side length of the triangle
Precondition: side is a valid side length (number >= 0)
Parameter d: The recursive depth of the triangle
Precondition: n is a valid depth (int >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_window(w), report_error('w is not a valid window',w)
# HINT: Remember to make the Pen visible while drawing
pass
def triangle_helper(p, x, y, side, d):
"""
Draws a Sierpinski triangle with the given side length and depth d, anchored at (x, y).
The triangle is draw with the current pen color and visibility attribute. Follow the
instructions on the course website to recursively draw the Sierpinski triangle. The
bottom left corner of the triangle is anchored at (x,y).
Parameter p: The graphics pen
Precondition: p is a Pen with fill attribute False.
Parameter x: The x-coordinate of the triangle bottom left corner
Precondition: x is a number
Parameter y: The y-coordinate of the triangle bottom left corner
Precondition: y is a number
Parameter side: The side-length of the triangle
Precondition: side is a valid side length (number >= 0)
Parameter d: The recursive depth of the triangle
Precondition: n is a valid depth (int >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_penmode(p), report_error('Invalid pen mode', p)
# HINT: Use fill_triangle instead of setting p's position directly
pass
# Useful helper function
def fill_triangle(p, x, y, side):
"""
Fills an equilateral triangle of side length
The triangle is pointing up.
Parameter p: The graphics pen
Precondition: p is a Pen with fill attribute False.
Parameter x: The x-coordinate of the triangle bottom left corner
Precondition: x is a number
Parameter y: The y-coordinate of thetriangle bottom left corner
Precondition: y is a number
Parameter side: The side length of the triangle
Precondition: side is a valid side length (number >= 0)
"""
# Precondition Assertions
assert is_valid_penmode(p), report_error('Invalid pen mode', p)
assert is_number(x), report_error('x is not a valid position',x)
assert is_number(y), report_error('x is not a valid position',y)
assert is_valid_length(side), report_error('side is not a valid length',side)
p.move(x, y)
h = side * math.sqrt(.75)
p.fill = True
p.drawLine(side, 0)
p.drawLine(-side/2.0, h)
p.drawLine(-side/2.0, -h)
p.fill = False
#################### TASK 4B: Sierpinski Snowflake ####################
def snowflake(w, side, d):
"""
Draws a Sierpinski Snowflake with the given side length and depth d.
This function clears the window and makes a new graphics pen p. This pen starts in
the middle of the canvas at (0,0). It draws by calling the function
snowflake_helper(p, 0, 0, side, d). The pen is hidden during drawing and left
hidden at the end.
The pen should have both a 'gray' fill color and a 'gray' line color.
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter side: The side-length of the snowflake
Precondition: side is a valid side length (number >= 0)
Parameter d: The recursive depth of the snowflake
Precondition: n is a valid depth (int >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_window(w), report_error('w is not a valid window',w)
# HINT: Remember to make the Pen hidden while drawing
pass
def snowflake_helper(p, x, y, side, d):
"""
Draws a snowflake with the given side length and depth d centered at (x, y).
The snowflake is draw with the current pen color and visibility attribute. Follow the
instructions on the course website to recursively draw the Sierpinski Snowflake.
Parameter p: The graphics pen
Precondition: p is a Pen with fill attribute False.
Parameter x: The x-coordinate of the snowflake center
Precondition: x is a number
Parameter y: The y-coordinate of the snowflake center
Precondition: y is a number
Parameter side: The side-length of the snowflake
Precondition: side is a valid side length (number >= 0)
Parameter d: The recursive depth of the snowflake
Precondition: n is a valid depth (int >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_penmode(p), report_error('Invalid pen mode', p)
# HINT: Use fill_hex instead of setting p's position directly
pass
# DO NOT MODIFY
def fill_hex(p, x, y, side):
"""
Fills a hexagon of side length s with center at (x, y) using pen p.
Parameter p: The graphics pen
Precondition: p is a Pen with fill attribute False.
Parameter x: The x-coordinate of the hexagon center
Precondition: x is a number
Parameter y: The y-coordinate of the hexagon center
Precondition: y is a number
Parameter side: The side length of the hexagon
Precondition: side is a valid side length (number >= 0)
"""
# Precondition Assertions
assert is_valid_penmode(p), report_error('Invalid pen mode', p)
assert is_number(x), report_error('x is not a valid position',x)
assert is_number(y), report_error('x is not a valid position',y)
assert is_valid_length(side), report_error('side is not a valid length',side)
# Move to the center and draw
p.move(x + side, y)
dx = side*math.cos(math.pi/3.0)
dy = side*math.sin(math.pi/3.0)
p.fill = True
p.drawLine( -dx, dy)
p.drawLine(-side, 0)
p.drawLine( -dx, -dy)
p.drawLine( dx, -dy)
p.drawLine( side, 0)
p.drawLine( dx, dy)
p.fill = False
#################### TASK 5:H-True ####################
def htree(w, side, d):
"""
Draws a H-tree with the given side length and depth d.
This function clears the window and makes a new graphics pen p. This pen starts in
the middle of the canvas at (0,0). It draws by calling the function
htree_helper(p, 0, 0, side, d). The pen is visible during drawing and but is left
hidden at the end.
The pen should have both a 'blue' fill color and a 'blue' line color.
Parameter w: The window to draw upon.
Precondition: w is a cornell Window object.
Parameter side: The side-length of the t-square
Precondition: side is a valid side length (number >= 0)
Parameter d: The recursive depth of the t-square
Precondition: n is a valid depth (int >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_window(w), report_error('w is not a valid window',w)
# HINT: Remember to make the Pen visible while drawing
pass
def htree_helper(p, x, y, side, d):
"""
Draws an H-tree with the given side length and depth d centered at (x, y).
The H-tree is drawn with the current pen color and visibility attribute. Follow the
instructions on the course website to recursively draw the H-Tree.
Parameter p: The graphics pen
Precondition: p is a Pen with fill attribute False.
Parameter x: The x-coordinate of the H-tree center
Precondition: x is a number
Parameter y: The y-coordinate of the H-tree center
Precondition: y is a number
Parameter side: The side-length of the H-tree
Precondition: side is a valid side length (number >= 0)
Parameter d: The recursive depth of the H-tree
Precondition: n is a valid depth (int >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_penmode(p), report_error('Invalid pen mode', p)
# HINT: Use draw_h instead of setting p's position directly
pass
# IMPLEMENT ME
def draw_h(p, x, y, side):
"""
Draw an H at center (x, y) of size s using pen p.
Parameter p: The graphics pen
Precondition: p is a Pen with fill attribute False.
Parameter x: The x-coordinate of the H-tree center
Precondition: x is a number
Parameter y: The y-coordinate of the H-tree center
Precondition: y is a number
Parameter side: The side length of the H-tree
Precondition: side is a valid side length (number >= 0)
"""
# ARE THESE ALL OF THE PRECONDITIONS?
assert is_valid_penmode(p), report_error('Invalid pen mode', p)
# HINT: Remember that (x,y) is the center of the H.
pass
################ Test Functions #################
def prompt(func):
"""
Returns: The answer to a yes or no question.
If the answer is invalid, it is treated as no.
Parameter func: The function to ask about
Precondition: func is string
"""
ans = input('Call '+func+'? [y/n]: ')
return ans.strip().lower() == 'y'
def depth(func):
"""
Returns: The answer to a (recursion) depth question.
If the anser is invalid, it is treated as -1.
Parameter func: The function to ask about
Precondition: func is string
"""
ans = input('Function '+func+' depth? [-1 to skip]: ')
try:
return int(ans.strip())
except:
return -1
def main():
"""
Runs each of the functions, allowing user to skip functions.
"""
w = cornell.Window()
if prompt('draw_two_lines'):
draw_two_lines(w,5)
if prompt('draw_triangle'):
w.clear()
turt = cornell.Turtle(w)
draw_triangle(turt,50,'blue')
if prompt('draw_hex'):
w.clear()
turt = cornell.Turtle(w)
draw_hex(turt,50)
if prompt('draw_spiral'):
draw_spiral(w, 1, 24, 64, 0)
if prompt('multi_polygons'):
multi_polygons(w, 100, 5, 6, 0)
if prompt('radiate'):
radiate(w, 150, 45, 0)
d = depth('triangle')
if d >= 0:
triangle(w, 200, d)
d = depth('snowflake')
if d >= 0:
snowflake(w, 200, d)
d = depth('htree')
if d >= 0:
htree(w, 200, d)
# Pause for the final image
input('Press ')
# Application code
if __name__ == '__main__':
main()