This lab is adapted from Andrea Vaccari.

Activity 0

Lab Partnership Planning

Before starting today’s lab assignment, exchange contact information with your partner and find at least three hours in your schedule during which you can meet with your partner to finish the lab. If you are genuinely unable to find a time, please come speak with me!

Reminder: you and your partner are a team! You should not move forward to one activity until you are both comfortable with the previous activity.

Getting Started

Create a file called lab8.py in Thonny and add the following line to the start of your program:

import turtle

Please don’t import turtle in a different way - this is the only way that works with the autograder.

Introduction

The goal of this lab is to write simple classes so that we can draw some more complicated shapes with the turtle (without using recursion!)

These shapes are described in files that we will read, so you will also gain some more practice reading a new file type.

In this lab, you’ll practice:

  • Practice writing simple classes
  • Practice reading a file
  • Use the turtle again!

Activity 1: Writing the Point class

Our complex shapes will be represented with lists of Point objects, so the first thing you need to do is define a Point class.

Your Point class should have the following attributes and methods:

Attributes:

  • x: x-coordinate of the point
  • y: y-coordinate of the point

Methods:

  • __init__(): takes two arguments in addition to self (x and y) and initializes a Point object by storing the value of the arguments in the x and y attributes
  • draw(): takes one Boolean argument in addition to self (withdots) and moves the turtle from wherever it might be to the location identified by the x and y attributes (see turtle.goto()). It also draws a dot (see turtle.dot()) if withdots is True.
    • To get credit for this, you must do it in the most efficient way possible (e.g., don’t call goto more than once, even if your turtle ends up in the right place in the end)
  • scale(): takes one argument in addition to self (factor) and scales the x and y attributes by the value of factor. In other words, the x and y attributes are both multiplied by this scaling factor.
  • translate(): takes two arguments in addition to self (tx and ty) and adds these values to the x and y attributes.

Write a Point class implementation that stores the attributes and provides the methods listed above.

After you are done, test out your class by creating a few instances. Is should behave as outlined below:

>>> p = Point(10, 20)
>>> q = Point(20, 30)
>>> p.scale(5)
>>> p.x
50
>>> p.y
100
>>> q.translate(15, 25)
>>> q.x
35
>>> q.y
55
>>> p.draw(False)
>>> q.draw(True)

After these commands, there should be a line from (0, 0) (where the turtle started) to (50, 100), another line from (50, 100) to (35, 55) and a dot at (35, 55). The dot should only be at (35, 55).

Activity 2: Writing the Segment class

Our shapes will be a collection of segments. In our drawings segments are any sequence of points.

The Segment class will have an attribute that stores a list of points that form the segment (similar to the Deck class storing a bunch of instances of the Card class from Tuesday’s lecture).

Your Segment class should have the following attributes and methods:

Attributes:

  • points: a list of Point objects that form a segment. We will provide you sample segments but, if you want to create your own shapes, keep in mind that a line segment will be drawn between to consecutive points so, when you draw your segment, make sure you put your points in the correct order.

Methods:

  • __init__(): takes one argument in addition to self (a list of points) and stores it in the points attribute
  • draw(): takes one Boolean argument in addition to self (withdots) and draws the segment by iterating through the list of Point objects stored in the points attribute and calling the draw method for each of the points passing the withdots variable as argument.

The following figure might help clarify the cascading relationship between the draw method for a Segment and the draw method for a Point:

Write the Segment class and then test your code with:

>>>  p0 = Point(0, 0)
>>>  p1 = Point(100, 0)
>>>  p2 = Point(100, 100)
>>>  p3 = Point(0, 100)
>>>  points = [p0, p1, p2, p3, p0] 
>>>  segment = Segment(points) 
>>>  segment.draw(True)

You should see a square with side length of 100! Notice that we need to include the initial point p0 at the end of the list of points since we want the segment to close in on itself. There should be a dot at every point since we passed True to the draw function.

Activity 3: Writing the Shape class

So we can make Point objects, and Segment objects, yay!

We can now create more complex shapes by introducing a Shape class, which simply stores a collection of Segment objects.

Shapes are stored in files as sequences of segments separated by “Break” (the actual word) that indicates the end of a segment and the beginning of a new one.

Between breaks, segments are represented by a sequence of x- and y-coordinates of a point. For example:

x1 y1
x2 y2
.
.
.
xn yn
Break
x1 y1
x2 y2
.
.
.
xm ym
Break

Take a look at a sample file with a shape made of a single segment: witch0.txt. You will notice that there is only one “Break” in this file (one single segment). A slightly more refined version (witch.txt) has two segments (two “Break”): the first segment contains n points and the second segment contains m points. If you look at other examples below, you might see that they are made of many more segments.

Your Shape class should have the following attributes and methods:

Attributes:

  • segments: list of Segment objects (very similar to what you did before for the points inside the segments)

Methods:

  • __init__(): takes one argument in addition to self (a string with the name of a file) and opens that file, reads its content, and stores the points into separate segments. You will have to find out where the “Break”s are so that you can create segments and assign points to them.
  • scale(): takes one argument in addition to self (factor) and scales the x and y attributes of every point of every segment by factor using the point scale method.
  • translate(): takes two arguments in addition to self (tx and ty) and adds these values to the x and y attributes of every point of every segment using the point translate method.
  • draw(): takes one Boolean argument in addition to self (withdots) and calls the draw method for every segment object passing the argument withdots to it. W e encourage you to start testing your code using the single segment (provided in the witch0.txt sample).

Here is a hint on how to iterate over every line in the file and clean it up before using it, just in case you forgot the last few assignments and labs:

file = open(filename, "r")  # opens file called filename for reading (that's what the "r" means)
for line in file:
    # read the line, remove the '\n' as you have done previously
    # check if the line contains the 'Break' word (in which case you need to append the current  
    # segment as Segment object to the segments attribute of the shape object)
    # split the line into the x and y coordinates
    # create a new Point object and append it to the points attribute of the current segment
    # in other words, you will have to read the coordinates in each line, append them as Point 
    # objects to points attribute creating a segment that you will append the current segment
    # as Segment object if you find a "Break"
file.close() # closes the file so we cannot read from it anymore

Test your code out by instantiating a shape, and then calling it’s draw method.

witch = Shape('witch0.txt')
witch.scale(20)
witch.translate(500, 0)
witch.draw(True)

Again, note the cascading relationship between the draw() method for a Shape, the draw() method for a Segment, and the draw() method for a Point. Whatever is passed as the variable withdots into the Shape draw() method, is also passed to draw() for a Segment and, hence, draw() for a Point:

Here are some examples with the witch0.txt file. The witch0.txt file contains a single segment describing the outline of a witch. On the left, this outline is drawn with the withdots option as True. On the right, the withdots options is set to False.

Now onto shapes with multiple segments!

If you have multiple segments, every time you encounter the “Break” word in the file, you will need to append the current segment to the segment attribute in the Shape class and instantiate a new segment to store the next set of points. When drawing the segment objects, make sure you lift the Turtle pen after you finish drawing each segment, and place it back down when starting the drawing of the next segment. You should also lift the pen prior to drawing the first segment. You can lift the pen with turtle.penup() and put it down with turtle.pendown(). Otherwise, you will get lines connecting all the segments. Here’s a example:

This piece is not autograded on gradescope. You’ll need to make sure that you don’t have these extra lines by inspecting your picture.

The images below contain several segments:

Play around with some of the sample files. Use the scale and translate functions to place at least three shapes on the same Turtle window. Pick from the following files:

Feel free to make your own points files instead and share them on edstem!

Turning in your work

Most of this lab is autograded. You need to turn in one python file that contains all three class definitions: Point, Segment and Shape. Name this lab8.py. In addition to the Python file, you should also take a screenshot of your final image containing three shapes and submit it.

Partner Feedback

Fill out the partner feedback form by the lab due date for participation points!