Goals

Upon successful completion of this lab, you should be able to:


Setup and Practice

Setup

  1. If your lab machine was not started in MacOS when you went to use it, reboot it to MacOS.
  2. Follow these procedures to mount your blue home-directory on the local machine and create a PyCharm project (call it Lab07). You will use PyCharm in the next part, but, it is important that you set up your project now so that if you run into any issue, we can resolve them quickly.

Practice: String Methods

In this part, you'll use the the online Python 3 tutor to practice splitting strings into lists.

  1. Predict what this code will do and answer Questions 1 and 2 in your writeup:
    x = 'I am a string'
    z = x.split()
    print(z[0])
    print(x)
    
  2. Predict what this code will do and answer Question 3 in your writeup:
    x = 'I am a string'
    for w in x.split():
        print(w, end="")
    print()
    
  3. Predict what this code will do and answer Question 4 in your writeup:
    x = 'I am a string'
    y = x.split()
    print(len(y))
    

Practice: Populating Lists

For this part of the lab, you'll use the online Python 3 tutor to build lists using append.

  1. Answer Question 5 in your Moodle writeup by analyzing this code, using Python Tutor if needed:
    L = []
    for i in range(3):
       L.append(i)
    
  2. Answer Question 6 in your Moodle writeup by analyzing this code, using Python Tutor if needed:
    L = []
    for j in range(5):
       L.append(j ** 2)
    
  3. Answer Question 7 in your Moodle writeup by analyzing this code, using Python Tutor if needed:
    A = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec']
    B = []
    for month in A:
        B.append(month)
    

    Note: there are more efficient ways to do what this code does --- this is just an example that uses append.

Practice: Functions

  1. Enter the following code into the tutor:
    def PrintHello():
        print("Hello!")  # A
    
    
    def PrintGoodbye():
        print("Goodbye!")  # B
    
    
    def HiFive():
        print("HiFive!")  # C
        return 5
    
    
    def main():
        print("Calling PrintHello")  # D
        PrintHello()
        print("Calling HiFive")  # E
        value1 = HiFive()
        print("Result of HiFive:", value1)  # F
    
    
    main()
  2. Answer Question 8 in your Moodle writeup.

Part A: Draw national flags

In this part of the lab, we will write a program that draws the national flags of Japan, Bangladesh, France, Russia and Sudan and displays them one at a time. This is a large chore that can be cleanly broken-down into smaller steps with functions. We will write a separate function to draw each flag and grow our program incrementally by adding more and more functions to it. Your final program will draw all the flags.

    Circles: Japan and Bangladesh

  1. In PyCharm, create a new Python source code file named lab07a.py:
    """
    Program: CS 115 Lab 7a
    Author: Your name
    Description: This program displays national flags.
    """
    
    from graphics import *
    
    
    def draw_japan_flag(flag_width):
        '''Draws the national flag of Japan in a graphics window.
    
        Args:
            flag_width (int): The width of the window.
    
        Returns:
            None
        '''
        flag_height = 2 / 3 * flag_width
        circle_diameter = 3 / 5 * flag_height
    
        # Open a new graphics window with the title 'Japanese flag',
        # the width passed by the caller, and the calculated height
        win = GraphWin('Japanese flag', flag_width, flag_height)
    
        # Set the window background to white
        win.setBackground('white')
    
        # Set up the red circle.
        flag_center = Point(flag_width / 2, flag_height / 2)
        circle_radius = circle_diameter / 2
    
        # Create a circle that is centered in the middle of the flag
        # and has the specified radius
        circ = Circle(flag_center, circle_radius)
    
        # Turn that circle red
        circ.setFill('red')     # the inside of the circle
        circ.setOutline('red')  # the line around the circle
    
        # Actually draw the circle
        circ.draw(win)
    
        # Close?
        input('Press ENTER to close the flag window.')
        win.close()
    
    
    # --- Our main function ---
    def main():
        draw_japan_flag(600)  # Draw a Japanese flag with a width of 600 pixels
    
    
    main()
  2. Run your program. You should see a graphics window pop up with the Japanese flag:

    Japan national flag
  3. Read through the code for the draw_japan_flag() function. Try to understand all of the comments.
  4. In main, change the 600 to other values (200, 800, etc.), run the program, and see what changes.
  5. Answer Question 9 in your writeup.
  6. Over the next few steps, you will write a new function called draw_bangladesh_flag. This function will draw the flag of Bangladesh:

    Bangladesh national flag
  7. Since the Bangladesh flag is similar to the Japanese flag, you can use your old code as a basis for your new code. Just below your docstring and import statement, make a new copy of your entire draw_japan_flag function, and name this copy draw_bangladesh_flag.

    Here is what the structure of your code should look like:

    """
    This is just the structure of your code. Don't copy this into your program!
    """
    import statements
    
    def draw_bangladesh_flag(flag_width):
        # code for Bangladesh flag (we will do this next)
    
    
    def draw_japan_flag(flag_width):
        # code for Japanese flag
    
    
    def main():
        draw_japan_flag(600)
    
    
    main()
  8. Now, you will modify the draw_bangladesh_flag function so it draws the Bangladesh flag. The specifications for the Bangladesh flag (source) are the following:

    Bangladesh Flag Specification

    • The flag height is 3/5 of the width.
    • The background is dark green. (Use the string 'DarkGreen' when setting its color.)
    • The circle is red.
    • The radius of the circle is 20% of the width of the flag.
    • The center of the circle is at 9/20 of the width and half of the height of the flag.
    Modify the code of draw_bangladesh_flag so that it draws the Bangladesh flag correctly.
  9. Inside your main() function, comment out the call to the draw_japan_flag function, and add a call to the draw_bangladesh_flag function, like this:

    #draw_japan_flag(600)
    draw_bangladesh_flag(600)
    
    By doing this, we are demonstrating a useful debugging practice: we are testing the new function separately from the rest of the code, by commenting-out things during development.
  10. Run your program. Verify that it draws the Bangladesh flag. Change the 600 to other values, rerun your program, and be sure that the flag resizes correctly.
  11. Once it is working, be sure to go back through draw_bangladesh_flag and update the docstring so it is accurate.
  12. Rectangles: France and Russia

  13. Next, we will draw flags that have stripes. We'll start with the national flag of France:

    France national flag

    To draw the stripes, we will just draw three rectangles. Recall: to draw a rectangle, we have to specify the coordinates of two opposite corners.

  14. Insert the following code at the top of your program, just below the import statement. The def lines should be against the left margin, just like with the other two flag functions.
    def draw_stripe(window, top_left, bottom_right, color):
        '''Draws a rectangle in the graphics window.
    
        Args:
            window (GraphWin): The window used for drawing.
            top_left (Point): The coordinates of the top left corner.
            bottom_right (Point): The coordinates of the bottom right corner.
            color (str): The color to make the rectangle.
    
        Returns:
            None
        '''
        stripe = Rectangle(top_left, bottom_right)
        stripe.setFill(color)
        stripe.setOutline(color)
        stripe.draw(window)
    
    
    def draw_france_flag(flag_width):
        '''Draws a French flag in the graphics window.
    
        Args:
            flag_width (int): The width of the window.
    
        Returns:
            None
        '''
        flag_height = 2 / 3 * flag_width
        stripe_colors = ['DarkBlue', 'white', 'red']
        stripe_width = flag_width / len(stripe_colors)
    
        # Open a new graphics window with the title 'French flag', the
        #   width provided by the user, and the calculated height
        window = GraphWin('French flag', flag_width, flag_height)
    
        # Draw the blue stripe
        #   The top left of the stripe is the top left of the window
        blue_stripe_top_left = Point(0 * stripe_width, 0)
    
        # The bottom right of the stripe is 1/3 of the way across
        #   the flag, and all the way to the bottom.
        blue_stripe_bottom_right = Point(stripe_width, flag_height)
        draw_stripe(window, blue_stripe_top_left, blue_stripe_bottom_right, stripe_colors[0])
    
        # TODO: Complete the below code 
        # Write similar code for the white and red stripes.
    
        # Wait for keyboard, to close the window
        input('Press ENTER to close the flag window.')
        window.close()
    We are not going to modify the code yet. As it is currently written, it should make a flag with a blue stripe.
  15. In main, comment out the call to draw_bangladesh_flag and add the line:
    draw_france_flag(600)
    
  16. Run your program and verify that you see a dark blue stripe.
  17. Answer Question 10 in your writeup. You are strongly encouraged to use pencil and paper to answer this question.
  18. Now, complete the code: based on the code for the dark blue stripe, write code to draw the white stripe (even though the background may already be white) and write code to draw the red stripe.
  19. Answer Question 11 in your writeup.
  20. Based on your answers to Question 11, re-write your draw_france_flag code to draw the stripes using a loop. The loop should look something like this:
    for i in range(...something...):
        stripe_top_left = Point(...something...)
        stripe_bottom_right = Point(...something...)
        draw_stripe(...four things...)
    
  21. Next, you will draw the Russian flag. To do so, make a new copy of the draw_france_flag function, and rename it to draw_russia_flag. You will adapt that code to draw the national flag of Russia:

    Russia national flag
  22. Here are the specifications for the Russian flag:

    Russian Flag Specification

    • The ratio of width to height is the same as the French flag.
    • For the stripe colors, use 'white', 'blue', and 'red'.
    • The three stripes are all equal in size (1/3 of the height of the flag).
  23. Answer Question 12 in your writeup.
  24. Revise the draw_russia_flag() function to draw the Russian flag. Hint: you should not need to change the definition of draw_stripe().
  25. In main, test your code by calling the draw_russia_flag() function and passing it different values of the window width.
  26. Other Polygons: Sudan

  27. Make a copy of your draw_russia_flag() function, and name the copy draw_sudan_flag().
  28. Modify your main() function to call draw_sudan_flag.
  29. Over the next few steps, you will modify this function to actually draw the national flag of Sudan:

    Sudanese national flag
  30. First, just go through the definition of draw_sudan_flag and update the docstring, replacing Russia with Sudan in anticipation of your changes.
  31. Here are the specifications for the Sudanese flag (source):

    Sudanese Flag Specification

    • The height of the flag is 1/2 of its width.
    • For the stripe colors, use 'red', 'white', and 'black'.
    • The triangle is dark green (use 'DarkGreen').
    • The triangle is an isosceles triangle (2 equal sides) and extends 1/3 of the way across the flag.
  32. Answer Question 13 in your writeup.
  33. You can use the Polygon object to draw the triangle. To make a Polygon that is shaped like a triangle, just pass it Point objects for each vertex of the triangle. The graphics library will connect the vertices in the order listed, plus one more connection between the last point and the first point. For example:

    triangle = Polygon(Point(50, 50), Point(100, 100), Point(25, 100))
    
    The above code draws a triangle with vertices at (50, 50), (100, 100), and (25, 100).
  34. Write code to draw the Sudanese flag's triangle using Polygon.
  35. Save your work as the file lab07a.py.
  36. Continue to Part B.

Part B: Draw user-specified flags

In this part, we will build a more functional program that calls the functions we wrote in Part A. It will get input from the user to draw specific flags, in a specific order.

Instructions

  1. Save lab07a.py file as lab07b.py file and work on the lab07b.py file from this point onwards.
  2. In your main function, before calling any of your other functions, write code to ask the user for the width of the graphics window:

    Window width: 700
    
    When drawing a flag, pass the user's chosen value as the argument for the flag_width parameter, instead of 700.
  3. You may assume that the user always enters an integer. However, if that integer is not between 100 and 1000 (inclusive), then you should print an error message and exit the program.

    To cause a program to exit, you call the exit function (provided by the sys module). The step-by-step instructions for doing this are:

    • Between your docstring and your first function definition, add the line
      import sys
      
    • Where you want to exit, call sys.exit(). You may pass it an optional string argument which it will display as an error message before exiting. For example:
      sys.exit('Error: Window size should be between 100 and 1000.')
      
  4. In main, after prompting the user for the window size, write code to print the following menu:

    Which national flag(s) do you want to draw?
    - Japan
    - Bangladesh
    - France
    - Russia
    - Sudan
    Name your country:
    
  5. Modify main to read the user's input for the country. If the user's input is one of the five country names, then call the function that draws that country's flag, and pass it the chosen width. Otherwise, print an error message.

    To do this, treat the user's input is case-insensitive. In other words, the inputs JAPAN, Japan, and even JaPaN should all draw the Japanese flag. (Hint: this should simply your logic to call the appropriate function, since you will not need to manually list every possible combination of upper- and lowercase letters.)

  6. Next, you will modify your program so that the user can type multiple country names on a line. For example:

    Which national flag(s) do you want to draw?
    - Japan
    - Bangladesh
    - France
    - Russia
    - Sudan
    Name your countries: jaPan FRANCE
    

    Here's what you will need to do:

    • Split the user's string input into a list of strings.
    • For each element in that list, make it case-insensitive, and call the appropriate flag function. You will want to move your if-statements into your new loop.
    • When your code runs, the first flag in the user's list should pop up in a new window. When you hit enter in the text window, the graphical window should close and the next flag (if any) should pop up.
  7. Print an error message for each word the user typed that does not name a valid country. For example:

    Name your countries: JapaN CS115istan CS115land Russia
    Press ENTER to close the flag window.
    Error: CS115istan is not a valid country.
    Error: CS115land is not a valid country.
    Press ENTER to close the flag window.
    
  8. Demo.When your code is working, call an instructor over to demo.
  9. Continue to Part C.

Part C: Refactor: geometric calculations

In this part, you will write the code that you actually turn in for grading. The code you will write is a refactored version of logic from a prior lab. Refactoring is just a fancy term for rewriting old code, usually to improve its organization, enhance its generality or increase its efficiency.

In this case, we will refactor and expand on the Lab 1 code that did various geometric calculations (the area of a square, volume of a cube, etc) so that it is organized into sensible functions.

To do some computations, you will need the definition of π provided by the math library. To do this, import the math library using an import math statement. Place this statement early in your program (after the program's docstring and before def main()):

import math

Once this statement is made, all the items in the library become accessible in your problem. For example, the variable math.pi holds the value of π. The function call math.sqrt(x) computes the square root of variable x. You can use these to compute things like:

The area of a circle with radius r
A = π * r2
The area of an equilateral triangle
A = s2 * (square root of 3) / 4
The volume of a sphere with radius r
V = 4/3 * π * r3

Instructions

  1. Create and open a new Python source code file named lab07c.py:
    """
    Program: CS 115 Lab 7c
    Author: Your name
    Description: This program computes geometric quantities.
    """
    import sys
    import math
    
    
    def get_numeric_val():
        '''Prompts the user for a number. Exits if the user does not
        enter a positive value; otherwise, returns the value they entered.
    
        Returns:
            float: The number entered by the user.
        '''
        num = float(input('Enter a positive numeric value: '))
        if num <= 0:
            sys.exit('Error: that number was not positive.')
        return num
    
    
    def get_menu_choice():
        '''Prints a menu and returns the user's selection.
    
        Returns:
            str: A single character ('q', 'a', 'b', 'c', etc).
        '''
        pass
    
    
    def compute_square_area(side):
        '''Computes the area of a square.
    
        Args:
            side (float): The side length for the square.
    
        Returns:
            float: The area.
        '''
        pass
    
    
    def compute_circle_area(radius):
        '''Computes the area of a circle.
    
        Args:
            radius (float): The radius length for the circle.
    
        Returns:
            float: The area.
        '''
        pass
    
    
    def compute_cube_volume(edge):
        '''Computes the volume of a cube.
    
        Args:
            edge (float): The side length for the cube.
    
        Returns:
            float: The volume.
        '''
        pass
    
    
    def main():
        menu_choice = get_menu_choice()  # Get the user's first choice
    
        while menu_choice != 'q':
            user_num = get_numeric_val()  # Get the side length (etc.)
    
            if (menu_choice == 'a'):
                print('The area of a square with side length ', user_num,
                      ' is ', round(compute_square_area(user_num), 5), '.', sep="")
            elif (menu_choice == 'b'):
                print('The area of a circle with radius length ', user_num,
                      ' is ', round(compute_circle_area(user_num), 5), '.', sep="")
            elif (menu_choice == 'c'):
                print('The volume of a cube with edge length ', user_num, 
                      ' is ', round(compute_cube_volume(user_num), 5), '.', sep="")
    
            menu_choice = get_menu_choice()  # Get user's next choice
    
    
    main()
    
  2. Note the use of round() function in main(). The round() function is used to round numeric values. For example, the below code rounds the value 2.6753 to 2 decimal places, and produces the output The value is 2.68.

    print("The value is", round(2.6753, 2))
  3. Answer Question 14 in your writeup.
  4. Right now, some of your functions have empty definitions --- this is what pass means (it is a valid Python statement that has no effect). Replace the logic inside get_menu_choice() with code to do the following:
    • Print this menu:
      Would you like to
      a. Calculate the area of a square?
      b. Calculate the area of a circle?
      c. Calculate the volume of a cube?
      d. Calculate the volume of a sphere?
      e. Calculate the area of an equilateral triangle?
      q. Quit?
      
    • Read in the user's selection and convert it to lowercase. You may assume that the lowercase version is always one of the 6 options listed in the menu.
    • Use a return statement to return the user's choice to the function's caller.
  5. Fill in the logic for the function compute_square_area, so that it returns the area of a square whose side length is side. Note:
    • Do not modify the main function.
    • Do not change the def line of this function.
    • Do not use print within this function.
    • You can refer back to Lab 1 for the area formula.
    • It is possible to write this function in only one line of code (not counting the def line).
  6. Run your program. Note that the program will round off the output to 5 decimal places. Here is a sample input/output sequence:
    Would you like to
    a. Calculate the area of a square?
    b. Calculate the area of a circle?
    c. Calculate the volume of a cube?
    d. Calculate the volume of a sphere?
    e. Calculate the area of an equilateral triangle?
    q. Quit?
    A
    Enter a numeric value: 5
    The area of a square with side length 5.0 is 25.0.
    Would you like to
    a. Calculate the area of a square?
    b. Calculate the area of a circle?
    c. Calculate the volume of a cube?
    d. Calculate the volume of a sphere?
    e. Calculate the area of an equilateral triangle?
    q. Quit?
    q
  7. Fill in the function compute_circle_area so that it returns the area of a circle whose radius is radius.

    Note: Just as before, do not modify the main function; do not change the def line of this function; do not use print within this function. You can refer back to Lab 1 and above for all the formulas in this lab.

  8. Fill in the function compute_cube_volume so that it returns the volume of a cube whose edge length is edge.

    Note: Again, do not modify the main function; do not change the def line of this function; do not use print within this function.

  9. Write a new function compute_sphere_volume that computes the volume of a sphere. Your function should take one parameter, radius, and return the volume of a sphere with that radius.

    Be sure to write a docstring for your function, modeled after the format of the other function docstrings provided in the lab.

  10. Write a new function compute_tri_area that computes the area of an equilateral triangle. Your function should take one parameter, side, and return the area of an equilateral triangle with that side length.

    Be sure to write a docstring for your function, modeled after the format of the other function docstrings provided in the lab.

  11. Add code to the main function to call your new functions and print their output when the user selects 'd' or 'e'. You should round their outputs to 5 decimal places.
  12. Test your program thoroughly. Here is a sample input/output sequence:
    Would you like to
    a. Calculate the area of a square?
    b. Calculate the area of a circle?
    c. Calculate the volume of a cube?
    d. Calculate the volume of a sphere?
    e. Calculate the area of an equilateral triangle?
    q. Quit?
    c
    Enter a numeric value: 2
    The volume of a cube with edge length 2.0 is 8.0.
    Would you like to
    a. Calculate the area of a square?
    b. Calculate the area of a circle?
    c. Calculate the volume of a cube?
    d. Calculate the volume of a sphere?
    e. Calculate the area of an equilateral triangle?
    q. Quit?
    d
    Enter a positive numeric value: 5
    The volume of a sphere with radius length 5.0 is 523.59878.
    Would you like to
    a. Calculate the area of a square?
    b. Calculate the area of a circle?
    c. Calculate the volume of a cube?
    d. Calculate the volume of a sphere?
    e. Calculate the area of an equilateral triangle?
    q. Quit?
    e
    Enter a positive numeric value: 12.2
    The area of an equilateral triangle with side length 12.2 is 64.44961.
    Would you like to
    a. Calculate the area of a square?
    b. Calculate the area of a circle?
    c. Calculate the volume of a cube?
    d. Calculate the volume of a sphere?
    e. Calculate the area of an equilateral triangle?
    q. Quit?
    q
  13. Demo.When you are convinced that your program is working correctly, demo it for an instructor.
  14. Continue to the next part to submit your program.

Assignment Submission

Instructions

  1. Answer the last question (#15) in your Moodle writeup. Review your answers, and then click the "Next" button at the bottom of the quiz. Once you do that, you should see a "Summary of Attempt" screen.
  2. Click the "Submit all and finish" button. Warning: You must hit "Submit all and finish" so that your writeup can be graded! It is not submitted until you do this. Once you have submitted your quiz, you should see something similar to this at the top of your Moodle window. The important part is that the State shows up as Finished.
    Quiz confirmation
    Please leave this tab open in your browser.
  3. Click on the "Lab 7 code" link in Moodle and open in a new tab. Follow the instructions to upload your source code (lab07c.py) for Lab07. You could either browse for your code or, using a finder window, drag and drop your lab07c.py from your cs115/Lab07 folder to Moodle. You should subsequently see a dialog box which indicates 'Submission Status' as 'Submitted for grading'. Leave this tab open in your browser.
  4. With these confirmation pages open in your browser, you may call an instructor over to verify that you have completed every part of the lab. Otherwise, you are done!