Chapter 5: Making Decisions

Notes

Caution

A number of the code examples in this use the file siren.wav, this can be found in the corresponding chapter in the samples submodule. For space reasons we haven’t uploaded it to the github

Boolean Data

  • Boolean values are a type that is used to distinguish between values that are True or False
  • For example, we could use an int to count the number of hairs on a person’s head, but a bool to indicate if they are bald

Create Boolean Values

  • Simply declare a variable with a value of True or False
    • Python will infer the type
  • For example, to declare a true valued boolean,
it_is_time_to_get_up = True
  • we can then set the value to false,
it_is_time_to_get_up = False
  • Note that True and False are python keywords, and are case-sensitive, e.g. true and false will not work as expected

Code Analysis: Working with Boolean Values

Open up the python interpreter and work through the following questions to understand booleans

  1. What do you think would happen if you printed the contents of a boolean value?

    it_is_time_to_get_up = True
    print(it_is_time_to_get_up)
    True
    • Python will try to print out something meaningful, for a boolean this is either True or False
  2. What do you think would happen if you gave the word True to the input function?

    x = input("True or False: ")
    True or False: True
    • input returns it’s input as a string, so in this case x will not be a bool but rather a string with the value "True"
  3. Is there a python function called bool that will convert things into Boolean, just like there are int and float functions?

    • Yes there is, consistent behaviour with bool. Consider the following examples
    print(bool(1))
    print(bool(0))
    print(bool(0.0))
    print(bool(0.1))
    print(bool(''))
    print(bool('Hello'))
    True
    False
    False
    True
    False
    True
    • We can see that non-zero numbers evaluate True while zero, evaluates as False Similarly the empty string evaluates False while a non-empty string evaluates as True
  4. What happens if a program combines bool values with other values?

    • We should already expect that if we try to mix incompatible data that an error should be generated
     'Hello' + True
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    Cell In[3], line 1
    ----> 1 'Hello' + True
    
    TypeError: can only concatenate str (not "bool") to str
    • We can see that we cannot concatenate a boolean value to a string
    • The behaviour can be a little less intuitive with numbers though,
    1 + True
    2
    • True is implicitly converted to the integer value \(1\)
    1 + False
    1
    • Similarly, False is implicitly converted to the integer value \(0\)
    • We can see that numeric operations on bool thus have well-allowed behaviour
    • String operations (i.e. textual data) are not compatible

Boolean Expressions

  • Normally we don’t declare a boolean with True or False explicitly but instead as the result of evaluating an expression
    • Some expressions evaluate to True or False which naturally suits being stored in a boolean
  • Consider a simple alarm clock,
    • We can get the time through the time library we’ve seen before

        import time
      
        current_time = time.localtime()
      
        hour = current_time.tm_hour
    • time.localtime returns an object containing information about the current time.

      • This different blocks of information are called attributes, below is a list of the attributes contained in the object returned by localtime
Attribute Value
tm_year Year (for example, 2017)
tm_mon Month (in the range 1 … 12, 1 represents January)
tm_mday Day in the Month (in the range 1 … month length)
tm_hour Hour in the Day (in the range 0 … 23)
tm_min Minute in the Hour ( in the range 0 … 59)
tm_sec Seconds in the Minute ( in the range 0 … 59)
tm_wday Day of the Week (in the range 0 … 6 with Monday as 0)
tm_yday Day in the Year (in the range 0 … 364 or 365 depending on if the year is a leap year)
  • An example of a localtime object might look like,
Attribute Value
tm_year 2017
tm_mon 7
tm_mday 19
tm_hour 11
tm_min 40
tm_sec 30
tm_wday 2
tm_yday 200
Make Something Happen: One-Handed Clock

Lets make a clock that displays only the hour value, using localtime. These one-handed clocks are supposed promote a more relaxed attitude. Create a new program (OneHandedClock.py) and copy the below text.

    # Example 5.1: One Handed Clock
    #
    # Uses time to display the hour

    import time

    current_time = time.localtime()
    hour = current_time.tm_hour

    print("The hour is", hour)
The hour is 6

Run the program, it should print out the current hour

Exercise: Improved Clock

Improve the previous example to produce a more fully featured clock that reports the time, and date when run

We can use the table above to grab the correct attributes. We then simply need to format the attribute as necessary. The final program is given below

    # Exercise 5.1: Improved Clock
    #
    # An improved clock that displays the date and time when run

    import time

    current_datetime = time.localtime()

    day = current_datetime.tm_mday
    month = current_datetime.tm_mon
    year = current_datetime.tm_year
    print("The date is", day, "/", month, "/", year)

    seconds = current_datetime.tm_sec
    minutes = current_datetime.tm_min
    hours = current_datetime.tm_hour
    print("The time is", hours, ":", minutes, ":", seconds)
The date is 13 / 3 / 2026
The time is 6 : 24 : 49

Comparing Values

  • We’ve seen expressions as being made of operators and operands
  • One type of operator is a comparison operator
    • Returns a value that is True or False, e.g.

block-beta
    columns 3
    space
    title["Breakdown of an Example Comparison Expression"]
    space

    block:Input
    columns 1
        operand1["hour"]
        operand1Word["Operand"]
        operand1descr["(thing to work on)"]
    end

    block:Middle
    columns 1
        operator[">"]
        operatorWord["Operator"]
        operatorDescr["(thing to do)"]
    end

    block:Output
    columns 1
        operand2["6"]
        operand2Word["Operand"]
        operand2descr["(thing to work on)"]
    end

classDef BG stroke:transparent, fill:transparent
class title BG
class operand2Word BG
class operand2descr BG
class operand1Word BG
class operand1descr BG
class operatorWord BG
class operatorDescr BG

Comparison Operators
  • Below is a table of the common comparison operators
Operator Name Effect
\(>\) Greater than True if the left argument is greater than the right, else False
\(<\) Less than True if the left argument less than the right, else False
\(>=\) Greater than or Equal As for Greater than but also True if the left argument equals the right
\(<=\) Less than or Equal As for Less than but also True if the left argument equals the right
\(==\) Equals True if the left argument equals the right argument, else False
\(!=\) Not Equals True if the left argument does not equal the right argument, else False
  • A program can use a comparison operator to set a boolean variable, e.g. the below code fragment which sets the boolean variable it_is_time_to_get_up to True if the variable hour is greater than \(6\) else sets it to False
    it_is_time_to_get_up = hour > 6
Code Analysis: Examining Comparison Operators

Use the python interpreter to work through the following questions in order to understand Comparison Operators

  1. How does the equality operator work?

    • The equality operator evaluates to True if the two operands hold the same value
    1 == 1
    True
    • The equality operator can be used to compare strings and bools
     'Rob' == 'Rob'
    True
     True == True
    True
  2. How do I remember which relational operator to use?

    • Practice, patience and muscle memory
  3. Can we apply relational operators between other types of expressions?

    • Yes. For example, the \(>\) and \(<\) operators when used to compare strings will use an alphabetic ordering, e.g.

      'Alice' < 'Brian'
      True
Warning

Equality and Floating-point Values

In Chapter 4 we noted that floating points only approximate a specific real-value. These approximations can cause issues when using comparison operators, e.g.

x = 0.3
y = 0.1 + 0.2
x == y
False
  • The variables x and y should both notionally store \(0.3\), but the equality shows they are unequal. This is because the addition of \(0.1\) and \(0.2\) actually leads to y storing the slightly inaccurate \(0.3000...4\)
  • If comparing floating-point numbers for equality, the best approach is to check that the values are appropriately close
  • Python provides the type function, type(x) returns the type of the variable x, especially useful for investigating the type of values returned by library functions you’ve never seen before

Boolean Operations

  • What if we want to combine boolean expressions to create a new boolean expression
  • e.g. An alarm might want to go off when the hour is after \(7\) and the minute is after \(30\)
  • Python provides logic operators for combining boolean expressions
Code Analysis: Examining Boolean Operators

Use the python interpreter to answer the following questions and investigate boolean operators

  1. What does the following expression evaluate too?

    not True
    False
    • not inverts the value of a boolean, so True is converted to False
  2. How about this expression?

    True and True
    True
    • and is True iff both arguments are True as is the case above, so the result is True
  3. How about this expression?

    True and False
    False
    • Since one of the arguments is False and will evaluate to False
  4. How about this expression?

    True or False
    True
    • Since one of the arguments is True or will evaluate to True
  5. So far, the examples have only used boolean values. What happens if we mix boolean and numeric values?

     True and 1
    1
    • Recall that python can convert numeric expressions to boolean ones, this implicitly happens to the \(1\) in the above. So we would expect and to return True. However ,instead \(1\) is returned. This is due to some odd python behaviour
      • Python sees True \(\rightarrow\) result of and implied by second argument

        • So simply returns the second argument, since the truthfulness of \(1\), is equivalent to the original expression
      • If we flip the arguments, we should see this more clearly

          1 and True
        True
        • This time the above expressions should return True
      • The same behaviour will also occur with or

          1 or False
        1
        • Here the or operator short-circuits on \(1\), so returns \(1\)
          0 or True
        True
        • Here the or evaluates the first argument as false, so cannot short-circuit, the second argument is returned, i.e True
  • Let us now use and to try construct an expression that will correctly evaluate when the time is after \(7:30\), naively we might expect,
    it_is_time_to_get_up = hour > 6 and minute > 29
  • We can use a truth table to check,
Hour Minute Desired Output
6 0 False False
7 29 False False
7 30 True True
8 0 True False
  • We can see in the last case the result is not what we want!
    • hour > 7 is true, but minute > 29 is false, so we need to be more precise,
    it_is_time_to_get_up = (hour > 7) or (hour == 7 and minute > 29)
  • We use brackets to make the expression more readable
  • Here we use short-circuiting, if the the hour is greater than \(7\) we don’t need to check the minutes value
Warning

Be Careful with Logic Operations

When working with boolean operations you should always check that the logic matches what you expect!

The if Construct

  • Suppose we want a program to tell me if it’s time to get out of bed
  • Need the ability to run code if a boolean condition is met
    • Can do so using the aptly named if operator

Example: Simple Alarm Clock

# Example 5.2: Simple Alarm Clock
#
# Demonstrates `if` using a simple alarm clock

import time

current_time = time.localtime()
hour = current_time.tm_hour
minute = current_time.tm_sec

it_is_time_to_get_up = (hour > 7) or (hour == 7 and minute > 29)

if it_is_time_to_get_up:
    print("IT IS TIME TO GET UP")
  • The program should print IT IS TIME TO GET UP only if the time is after \(7:30\)
  • The if construct starts with the word if, following by a boolean value called the condition, then a :
  • Any statements we want to execute if the if is True are then written below the if and indented one level

Conditions in Python

  • Condition is a term for the expression that controls which branch of the if is executed
  • If the condition evaluates True the indented branch is run
  • If the condition evaluates False the indented branch is skipped
  • We could simply the above code by including the check directly in the if rather than an intermediate variable
if (hour > 7) or (hour == 7 and minutes > 29):
    print("IT IS TIME TO GET UP")

Combine Python Statements into a Suite

  • What if we want multiple statements to run after an if statement
  • We just write them as a sequence of indented statements
Example: Siren Alarm Clock

Let us improve the previous example to also play a sound if it’s time to get up. Create a program (SirenAlarmClock.py) with the contents below

# Example 5.3: Siren Alarm Clock
#
# Improves the Simple Alarm Clock to also play a sound

import time

import snaps

current_time = time.localtime()
hour = current_time.tm_hour
minute = current_time.tm_min

if (hour > 7) or (hour == 7 and minute > 29):
    snaps.display_message("TIME TO GET UP")
    snaps.play_sound("siren.wav")
    # pause the program to give time for the sound to play
    time.sleep(10)

This program now runs three statements in the if

  1. First a message is displayed
  2. Second a sound is played
  3. Third the program sleeps so the sound has time to play
  • If we want something to run regardless of the if condition, we write it either before or after the if statement
Example: Time Display Alarm Clock

Add to the simple Alarm Clock, by making it so the program will always print the current time regardless of if the alarm goes off. Create a new program (AlarmClockWithTimeDisplay.py). Enter the following contents,

# Example 5.4: Alarm Clock with Time Display
#
# A variant of Alarm Clock to also always display the time

import time

current_time = time.localtime()
hour = current_time.tm_hour
minute = current_time.tm_min

if (hour > 7) or (hour == 7 and minute > 29):
    print("TIME TO GET UP")
    print("RISE AND SHINE")
    print("THE EARLY BIRD GETS THE WORM")
print("The time is", hour, ":", minute)
The time is 6 : 24
  • The program above will always print the current time, regardless of if the alarm block is run
Caution

Indented Text can cause Big Problems

As seen above, python uses indentation for control flow, this has the advantage in that it follows normal code style practices, but has some pitfalls

  1. If the indention is wrong the program won’t run

    • i.e. if one line is indented four spaces, and the next three an error will be thrown

      import time
      current_time = time.localtime()
      hour = current_time.tm_hour
      minutes = current_time.tm_min
      
      if (hour > 7) or (hour == 7 and minute > 29):
        print("IT IS TIME TO GET UP")
            print("The early bird gets the worm...")
        Cell In[24], line 8
          print("The early bird gets the worm...")
          ^
      IndentationError: unexpected indent
      
  2. A more insidious error, occurs if one mixes tabs and spaces in the indentation, since the code may appear to be fine until it attempts to run

     import time
     current_time = time.localtime()
     hour = current_time.tm_hour
     minutes = current_time.tm_min
    
     if (hour > 7) or (hour == 7 and minute > 29):
         print("IT IS TIME TO GET UP")
         print("The early bird gets the worm...")
    • Most programmers and even text editors will automatically convert one style of indentation to the other (commonly tabs to spaces, but sometimes spaces to tabs) to avoid this issue
      • In the above code, my editor converted the second line which was indented with spaces to a tab to match the previous line

Structure of an if Statement

  • Formally, an if has a structure like

block-beta
    columns 4
    space
    title["Breakdown of an if statement"]:2
    space

    block:Input
    columns 1
        if["if"]
        ifDescr["(start of the if construction)"]
    end

    block:Condition
    columns 1
        condition["condition"]
        conditionDescr["(value that is true or false)"]
    end

    block:Colon
    columns 1
        colon[":"]
        colonDescr["colon"]
    end

    block:Suite
    columns 1
        suite["suite"]
        suiteDescr["statements"]
    end

classDef BG stroke:transparent, fill:transparent
class title BG
class condition BG
class conditionDescr BG
class colon BG
class colonDescr BG
class suite BG
class suiteDescr BG
class if BG
class ifDescr BG

  • There are two ways to write the suite
    1. A set of indented statements on the lines proceeding the if

    2. A set of statements on the same line as the if each seperated by a semicolon (;) e.g.

       if (hour > 6): print('IT IS TIME TO GET UP'); print('THE EARLY BIRD GETS THE WORM')
Warning

You can’t combine inline if statements, with indented if statements, e.g.

    import time
    current_time = time.localtime()
    hour = current_time.tm_hour
    minutes = current_time.tm_min

    if (hour > 7) or (hour == 7 and minute > 29): print("IT IS TIME TO GET UP"); print("RISE AND SHINE")
        print("The early bird gets the worm...")
  Cell In[26], line 7
    print("The early bird gets the worm...")
    ^
IndentationError: unexpected indent
Code Analysis: Layout of Conditional Statements

Use the python interpreter to answer the following questions to understand conditional statements

  1. Can we work with conditional statements using the python shell?

    • Yes you can, type the following into the shell,
    if True:
    • The shell may display ... instead of >>> or, omit >>> and indent

      • In the first case we can indent ourselves to write the suite
      • In the second we simply write the suite
    • Once done writing the if statement, simply deindent

    • Try write the following in the shell, and verify the output

        if True:
            print('True')
            print('Still True')
      True
      Still True
  2. How many spaces must you indent a suite of Python statements controlled by an if statement?

    • There is no approved value, but it must be consistent
    • i.e. if the first indentation is four, then all future indentations must also be four
      • Common choices are 4, 8 or 2

Add an else to an if Construction

  • Sometimes we want conditional behaviour on both the True and False branches
  • else is a keyword that lets us add behaviour that executes when an if evaluates as False
Example: Simple Alarm Clock with Else

Modify the Simple Alarm Clock to now print a message telling us to go back to bed if it before our alarm should go off. Write a new program (SimpleAlarmClockWithElse.py) with the following contents,

# Example 5.5: Simple Alarm Clock
#
# Variant of the Simple Alarm Clock
# that modifies the output depending on if its time to get up

import time

current_time = time.localtime()
hour = current_time.tm_hour
minute = current_time.tm_min

if (hour > 7) or (hour == 7 and minute > 29):
    print("IT IS TIME TO GET UP")
else:
    print("Go back to bed")
Go back to bed
  • Observe that only one line of the paired if-else statements is printed
Code Analysis: If Constructions

Work through the following questions to understand if constructions

  1. Must an if construction have an else part?

    • No, we saw when first working with if that we could exclude the else in that case no additional code runs if the if evaluates False
  2. What happens if a condition is never True?

    • It simply never executes

Compare Strings in Programs

Example: Broken Greeter

The following program uses the equality operator and an if statement to greet a person if their name matches. What is a potential issue with this program?

# Example 5.6: Broken Greeter
#
# A Greeter program using string matching
# Identify the issues with this program

name = input("Enter your name: ")

if name == "Rob":
    print("Hello, Oh great one")
  • The equality operator checks against the string "Rob" exactly
  • i.e. it is case sensitive, if we write "ROB", or "rob" or some variation thereof, the statement will not match.

We can fix this by using the string method upper, this converts all forms of the word "rob" to "ROB" which we can reliably check against. The new program looks like

# Example 5.7: Uppercase Greeter
#
# A Greeter program using string matching
# Fixes the issues with Example 5.6 by using
# str.upper()

name = input("Enter your name: ")

if name.upper() == "ROB":
    print("Hello, Oh great one")
  • We could also use the string method lower to compare against an all lowercase word
Code Analysis: Methods and Functions

Consider the following questions to learn about methods and functions

  1. How do lower() and upper() work?

    • Python types are objects that provide methods.
    • Methods are called like functions
  2. Why do we have to write lower() and not lower?

    • Leave the parentheses off, and see what happens

        name = 'Rob'
        name.upper
      <function str.upper()>
    • We are instead returned a description of the method itself

  3. What’s the difference between functions and methods?

    • They are used the same way, but they differ in where they are created
    • Functions are not associated any specific object
    • Methods are bound as attributes of objects

Nesting if Conditions

  • You can nest conditions, e.g. if you want to perform sequential checks
Example: Protected Greeter

Let us demonstrate nested if through a greeter which requires a follow on code word to confirm the identity of the user. Create a program (CodedGreeter.py) with the following contents

# Example 5.8: Coded Greeter
#
# Asks the user for a follow on code to confirm their ID
# before the program greets them

name = input("Enter your name: ")

if name.upper() == "ROB":
    code = input("Enter the codeword: ")
    if code == "secret":
        print("Hello, Oh great one")
    else:
        print("Begone. Imposter")
  • Play around with the above code to see what happens for various input combinations.
  • You should see if the first input is not a variant of "rob" the second prompt never occurs and the program ends.
  • Adjust the above by writing a new program (CodedGreeterWithOuterElse.py)*
# Example 5.9: Coded Greeter with Outer Else
#
# Asks the user for a follow on code to confirm their ID
# before the program greets them
# Has an additional outer else clause for the case that the nested
# if does not run

name = input("Enter your name: ")

if name.upper() == "ROB":
    code = input("Enter the codeword: ")
    if code == "secret":
        print("Hello, Oh great one")
    else:
        print("Begone. Imposter")
else:
    print("You are not Rob. Shame.")
  • The above code uses a second else clause, attached to the first outer, if condition
  • This means that it will run whenever the original name is not some variant of "ROB"

Working with Logic

Make Something Happen: Make an Advanced Alarm Clock

Improve the Alarm Clock. Make the alarm display the date as well as the time, and let the user sleep in on the weekends.

Our implementation is given below,

# Exercise 4.2: Advanced Alarm Clock
#
# An Advanced Alarm Combining the Behaviour
# of most increments of the alarm clock
# and allowing you to sleep in on weekends

import time

import snaps

current_time = time.localtime()
hour = current_time.tm_hour
minute = current_time.tm_min
day = current_time.tm_mday
month = current_time.tm_mon
is_weekend = current_time.tm_wday >= 5

date_message = "The date is " + str(day) + "/" + str(month)
time_message = "The time is " + str(hour) + ":" + str(minute)

msg = ""
up_hour = 7 + is_weekend  # get to sleep in an extra hour on weekends

if (hour > up_hour) or (hour == up_hour and minute > 29):
    msg = msg + "TIME TO GET UP"
    snaps.play_sound("siren.wav")
else:
    msg = msg + "Go back to bed!"
msg = msg + "\n" + date_message + "\n" + time_message
snaps.display_message(msg, size=50)
time.sleep(10)  # leave time for the sound and to read

Most of the text simply exists to correctly create the final message we will display on the screen. The most important parts are, is_weekend = current_time.tm_wday >= 5 which uses the fact that Saturday and Sunday have the value \(5\) and \(6\) in the current_time.tm_wday attribute (A number representing the day in the week) to set a boolean flag. We then use the fact that True acts numerically as one, and False acts numerically as zero to let us sleep in an hour on the weekend using up_hour = 7 + is_weekend which is \(8\) on weekends and \(7\) on weekdays.

We then run through the code as we have for most of the alarm clock cases, using an else clause to ensure we always have a message for the user, and appending the date and time message to this output.

Lastly we pass the method to snaps for display

Use Decisions to make an Application

  • In this next section we’ll write our first semi-sophisticated program

Scenario:

A local theme park wants you to write a program that will let users check if they meet the age requirements to go on a ride. They provide the following table covering the current rides

Ride Restrictions
Scenic River Cruise None
Carnival Carousel At least 3 years old
Jungle Adventure Water Splash At least 6 years old
Downhill Mountain Run At least 12 years old
The Regurgitator At least 12 years old and less than 70

Design the User Interface

  • We will use a simple text interface
Welcome to our Theme Park

These are the available rides

1. Scenic River Cruise
2. Carnival Carousel
3. Jungle Adventure Water Splash
4. Downhill Mountain Run
5. The Regurgitator

Please enter the ride you want: 1
You have selected the Scenic River Cruise
There are no age limits for this ride
Important

Design the User Interface with the Customer

The UI can be the most important and most difficult part of design because it can be very subjective. Ultimately the Customer is the one paying and so they should be involved in the UI design throughout!

Implement the User Interface

  • We have a UI design, now we need to implement it
  • Our code starts as below,
# Example 5.10: Ride Selector Start
#
# The basic shell of the Ride Selector UI

print("""Welcome to our Theme Park
      These are the available ride:

      1. Scenic River Cruise
      2. Carnival Carousel
      3. Jungle Adventure Water Splash
      4. Downhill Mountain Run
      5. The Regurgitator
      """)

ride_number_text = input("Please enter the ride number you want: ")
ride_number = int(ride_number_text)

if ride_number == 1:
    print("You have selected Scenic River Cruise")
    print("There are no age limits for this ride")
  • We first print out our Menu, using a triple-delimited string so we can multiline it
  • Then we implement the menu using a series of if statements.
    • For the first case (Scenci River Cruise) we don’t need the user’s age so we can output the result immediately
  • For other rides the user needs to supply their age, so we continue,
else:  # need to get the age of the user
    age_text = input("Please enter your age: ")
    age = int(age_text)
  • We have to get the age using another input pair
  • This should already be familiar to you
Testing User Input
  • Once we have the age, we need to compare against the restrictions for the selected ride
  • We can do this with a nested series of if statements which
    1. First selects the appropriate ride
    2. Checks the age against the ride’s age requirements
    if ride_number == 2:
        print("You have selected the Carnival Carousel")
        if age >= 3:
            print("You can go on the ride")
        else:
            print("Sorry, you are too young")
    if ride_number == 3:
        print("You have selected Jungle Adventure Water Splash")
        if age >= 6:
            print("You can go on the ride")
        else:
            print("Sorry, you are too young")
  • Downhill Mountain Splash (ride number 4) can be implemented exactly as above
Tip

Reduce duplicated Code

You may have noticed that the above statement appears to have a bunch of duplicated code. The rough structure is,

select ride
if age of user is greater than or equal to the rides min age
    Inform the user they can go on the ride
else:
    Inform the user they cannot go on the ride

Programmers typically don’t like to repeat themselves as it increases the number of ways a program can go wrong. So ideally we would like a way were we could write something like the above once and have the appropriate checks be carried out, and the message printed without having to write it out for every case. We’ll look at some ways to do this later in the book.

Complete the Program
  • The last ride introduces an additional check, we have a minimum and a maximum age.
    • Need to introduce another layer nested conditional to differentiate between the too old and too young case
    if ride_number == 5:
        print("You have selected The Regurgitator")
        if age >= 12:
            # first check age not too low
            if age > 70:
                # Age is too old
                print("Sorry, you are too old")
            else:
                # In the valid range
                print("You can go on the ride")
        else:
            # Age is too young
            print("Sorry, you are too young")

Input Snaps

  • We can add some extra quality to our implementation using snaps
  • snaps get_string method
# Example 5.11: Snaps get_string function
#
# Demonstrates using the get_string function
# in snaps to get user input via a graphical
# interface

import time

import snaps

name = snaps.get_string("Enter your name: ")
snaps.display_message("Hello " + name)

time.sleep(5)
Important

As written, the snaps get_string method on my machine, did not work when running Pygame 2. I had to modify the method to the following,

def get_string(prompt, size=50, margin=20,
               color=(255, 0, 0), horiz='left', vert='center',
               max_line_length=20):
    '''
    Reads a string from the user
    '''

    setup()

    result = ''
    cursor_char = '*'
    cursor = None

    def redraw():
        clear_display()

        render_message(prompt+result, margin=margin, size=size,
                       horiz=horiz, vert=vert, color=color, cursor=cursor)

    def cursor_flip():
        nonlocal cursor

    # create a timer for the cursor

    cursor_event = pygame.USEREVENT+1

    pygame.time.set_timer(cursor_event,500)
    pygame.key.start_text_input()

    while True:
        event = pygame.event.wait()

        if event.type == cursor_event:
            if cursor:
                cursor = None
            else:
                cursor = cursor_char
            redraw()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN:
                break
            elif event.key == pygame.K_BACKSPACE:
                if len(result) > 0:
                    result = result[:-1]
                    redraw()
        elif event.type == pygame.TEXTINPUT:
            if len(result) < max_line_length:
                result += event.text
                redraw()

    # disable the timer for the cursor
    pygame.time.set_timer(cursor_event,0)
    return result

I won’t go into detail on explaining the changes since it’s above the level we’ve currently been discussing but if you have issues with the running any of the snaps get_string programs in this book, I would recommend trying the above replacement to the function

  • Using the above and some optional arguments to play around with text placement we can create the start of a GUI implementation of the Ride Selector Program
Example: Theme Park Snaps Display

The below program is the outline for an implementation of the Ride Selector Program using snaps to provide a GUI

# Example 5.12: Theme Park Snaps Display
#
# Reimplments the shell of the Ride Selector Menu using Snaps

import time
import snaps


snaps.display_image("themepark.png")

prompt = """Welcome to our Theme Park
      These are the available ride:

      1. Scenic River Cruise
      2. Carnival Carousel
      3. Jungle Adventure Water Splash
      4. Downhill Mountain Run
      5. The Regurgitator

      Select your ride: """

ride_number_text = snaps.get_string(prompt, vert="bottom", max_line_length=3)
confirm = "Ride " + ride_number_text

snaps.display_message(confirm)
time.sleep(5) #gives user time to read the output
Make Something Happen: Snaps Ride Selector

Using the previous example, complete the ride selector program. Extending its features where reasonable

We’ll reimplement all the features of the original text-based interface, but add in the siren sound effect if the user is unable to ride the ride. Otherwise this proceeds as with most of our conversions to snaps. We replace print with snaps.display_message and introduce some work to build the string that we want to send to snaps.display_message. In this case we create a string that is nicely formatted to output

  1. The ride number the user selected
  2. The name of the ride
  3. A message letting them know if they are allowed to ride

Our final implementation can be found in the file ThemeParkSnapsDisplay.py, or read from down below, observe the usual use of the time.sleep function to prevent the window from immediately closing

# Exercise 5.3: Snaps Ride Selector
#
# Reimplments the entirety of the Theme Park Ride Selector using
# a snaps interface, and adds some audio ques to warn the user
# when they are ineligable for ride

import time
import snaps


snaps.display_image("themepark.png")

prompt = """Welcome to our Theme Park
      These are the available ride:

      1. Scenic River Cruise
      2. Carnival Carousel
      3. Jungle Adventure Water Splash
      4. Downhill Mountain Run
      5. The Regurgitator

      Select your ride: """

ride_number_text = snaps.get_string(prompt, vert="bottom", max_line_length=3)
confirm = "Ride " + ride_number_text

snaps.display_message(confirm)
time.sleep(2)  # gives user time to read the output

ride_number = int(ride_number_text)

if ride_number == 1:
    msg = confirm + "\nScenic River Cruise\n\nThere are no age limits for this ride"
    snaps.display_message(msg, size=100)
else:  # need to get the age of the user
    age_text = snaps.get_string(
        "Please enter your age: ", vert="bottom", max_line_length=3
    )
    age = int(age_text)

    if ride_number == 2:
        msg = confirm + "\nCarnival Cruise"
        if age >= 3:
            msg = msg + "\n\nYou can go on the ride"
            snaps.display_message(msg, size=100)
        else:
            snaps.play_sound("siren.wav")
            msg = msg + "\n\nSorry, you are too young"
            snaps.display_message(msg, size=100)
    if ride_number == 3:
        msg = confirm + "\nJungle Adventure Water Splash"
        if age >= 6:
            msg = msg + "\n\nYou can go on the ride"
            snaps.display_message(msg, size=100)
        else:
            msg = msg + "\n\nSorry, you are too young"
            snaps.play_sound("siren.wav")
            snaps.display_message(msg, size=100)
    if ride_number == 4:
        msg = confirm + "\nDownhill Mountain Run"
        if age >= 12:
            msg = msg + "\n\nYou can go on the ride"
            snaps.display_message(msg, size=100)
        else:
            msg = msg + "\n\nSorry, you are too young"
            snaps.play_sound("siren.wav")
            snaps.display_message(msg, size=100)
    if ride_number == 5:
        msg = confirm + "\nThe Regurgitator"
        if age >= 12:
            # first check age not too lowe
            if age > 70:
                # Age is too old
                msg = msg + "\n\nSorry, you are too old"
                snaps.play_sound("siren.wav")
                snaps.display_message(msg, size=100)
            else:
                msg = msg + "\n\nYou can go on the ride"
                snaps.display_message(msg, size=100)
        else:
            msg = msg + "\n\nSorry, you are too young"
            snaps.display_message(msg, size=100)
time.sleep(5)
Make Something Happen: Weather Helper

Using snaps and the weather functions it includes, write a simple program to remind the user to wrap up warm, wear sunscreen etc.

We’ll use the basic outline of the solution in the book,

#EG5-14 Weather Helper

import snaps

temp = snaps.get_weather_temp(latitude=47.61, longitude=-122.33)
print("The temperature is:", temp)

if temp < 40:
    print("Wear a coat - it is cold out there")
elif temp > 70:
    print("Remember to wear sunscreen")

The first step is to convert the print statements to instead use the snaps, display_message function. This requires us to do the usual work of building the string before we display it. Next we also want to display an image, either a sun or a snowflake depending on if the weather is hot or cold. Since we’re grabbing some new images, we run into an issue that snaps doesn’t work to rescale the images out of the box. We can fix this by adding the line image = image.convert_alpha() before the image = pygame.transform.smoothscale(image, window_size) line in display_image in snaps. Our final program (Weather Helper) looks like,

# Exercise 5.4 Weather Helper
#
# Simple Weather Program that reminds the user about
# the weather conditions, with helpful text and
# pictures

import time
import snaps

temp = snaps.get_weather_temp(latitude=47.61, longitude=-122.33)
conditions = snaps.get_weather_desciption(latitude=47.61, longitude=-122.33)

if temp is None or conditions is None:
    msg = "Could not retrieve Weather..."
else:
    msg = "The temperature is: " + str(temp)
    if temp < 40:
        msg = msg + "\n\nWear a coat - it is cold out there"
        snaps.display_image("snowflake.png")
    elif temp > 70:
        msg = msg + "\n\nRemember to wear sunscreen"
        snaps.display_image("sun.png")
    msg = msg + "\n\nThe weather is " + conditions

snaps.display_message(msg, size=100, color="red")
time.sleep(5)

Ignore the line if temp is None or conditions is None, this is some error handling code we’ll look at in a latter chapter. Notice that since no matter which path we go through the if, elif chain we’ll post a message at the end. So we use the branch code in order to set up the appropriate message, while the call to display_message sits outside the loop, so we don’t have to call it on every path.

Make Something Happen: Fortune Teller

Using randint and if statements write a fortune teller program that gives random fortunes to the user

We’ll expand on the prototype given by providing two additional statements, one relating to the future and the other relating to the wealth. We’ll follow the structure of using random.randint(1, 6) to simulate rolling a six-sided die, but spice it up by using if-elif-else clauses to play with the relative weighting of different statements.

# Exercise 5.5 Fortune Teller Program
#
# A simple program that uses random numbers to generate a sequence of
# fortunes for the user

import random

# Meeting someone
if random.randint(1, 6) < 4:
    print("You will meet a tall, dark stranger")
else:
    print("Nobody unexpected will enter your life")

# Money
result = random.randint(1, 6)
if result == 1:
    print("I see untold riches in your future")
elif result <= 3:
    print("A life of comfort is coming")
elif result < 6:
    print("You would do well to husband your wealth")
else:
    print("I see a future lived on the streets...")

# Advice
result = random.randint(1, 6)
if result <= 2:
    print("Sometimes the answers to our future, come from the past")
elif result < 6:
    print("To define your future, avoid getting hung up on the past")
else:
    print("You will soon face a decision that will redefine everything")
Nobody unexpected will enter your life
I see a future lived on the streets...
You will soon face a decision that will redefine everything

We use a mix of ==, <= and < operators to emphasise the clarity of the branching. This implementation is quite simple (because the exercise does not personally interest me that much) Feel free to expand on my solution

Summary

  • Python can work with boolean values
    • Bool values are either True or False
  • Comparison operators compare expressions to generate boolean values
  • if is used to control program execution in response to boolean expressions
    • if executes code if a condition is True
  • Logic operators and, or and not are used to create new boolean expressions from existing ones
    • and is True if both expressions are True else False
    • or is True if either expression is True else False
    • not flips the truth of a boolean expression
      • e.g. True \(\rightarrow\) False
      • False \(\rightarrow\) True

Questions and Answers

  1. Does the use of Boolean values mean a program will always do the same thing given the same data inputs?
    • It is very important that given the same inputs (including any inputs from a source of randomness) a program behaves the same way
  2. Will the computer always do the right thing when we write programs that make decisions?
    • A computer running a program is only as correct as the program that was written. Formally verifing anything but the most trivial program as being correct is very difficult (and the problem in general is not-computable (see The Halting Problem))
    • It is typically the responsibility of the programmer to to ensure a program behaves correctly (in conjuction with the customer.) Even in cases where a user inputs wrong data, the customer would probably expect the programmer to build into the program the appropriate checks to deal with these wrong data inputs
  3. Is there a limit to how many if conditions you can nest inside each other?
    • No, the python interpreter should be able to handle many many layers of nested if statements. Most people will emphasise that if you’re finding that you’re needing to write heavily nested code (the exact number of what constitutes heavy is debated but \(3\) is a rough guide) you should look at if there’s a better way to write