We included lots of challenges throughout Captain Code, sometimes multiple per chapter. If you need some help, you’ll find solutions to the challenges are presented here. Click on any challenge below to expand it.

Just keep in mind that there is never one solution to any coding challenge, so if your solutions look different but produce the desired result, that’s ok.


Part 1: It’s All Fun & Games

Challenge 2.1

So, here is your first Challenge. Hello4.py created two variables, firstName and lastName, and then combined them into a new variable, named fullName. Modify that code so that it asks the user for a first name and last name instead of using the hard-coded values.

Here’s a hint: You only need to change the first two lines of code so that each line uses an input() function. Can you figure this one out?

firstName = input("What is your first name? ")
lastName = input("What is your last name? ")
fullName = firstName + " " + lastName

print("Hello, my name is", fullName, "and I'm a coder!")

Challenge 2.2

Make your Mad Lib interesting by prompting for at least 15 different words. And then personalize it. At the start, when you provide instructions, ask the user for their name, and then use that in the instructions to create a more personalized experience.

print("Hello, please answer the following prompts.")
print()

animal = input("Enter an animal: ")
name = input("Enter a name: ")
adjective1 = input("Enter an adjective: ")
color1 = input("Enter a color: ")
adjective2 = input("Enter an adjective: ")
noun1 = input("Enter a noun: ")
noun2 = input("Enter a noun: ")
noun3 = input("Enter a noun: ")
adjective3 = input("Enter an adjective: ")
color2 = input("Enter a color: ")
noun4 = input("Enter a noun: ")
verb = input("Enter a verb: ")
adjective4 = input("Enter an adjective: ")
color3 = input("Enter a color: ")
animal2 = input("Enter an animal: ")

print("Thank you. Here is your story.")
print()
print("I have a pet", animal, "named", name, ".")
print("He is", adjective1, ",", color1, ", and", adjective2, ".")
print(name, "eats", noun1, ",", noun2, ", and", noun3, ".")
print("His favorite toy is a", adjective3, color2, noun4, ".")
print("He likes to", verb, "with his friend the", color3, adjective4, animal2)

Challenge 3.1

Ok, this one is a little harder, but you can do it, promise! See that list with the five animals? Write code that creates two lists, one with animals, like this:

animals=["ant","bat","cat","dog","eel"]

You can use your own list of animals, and you can have more than five (the more the better).

Then create a similar list of adjectives, things like big, green, smelly, cute, and so on. (Again, the more, the better. And it doesn’t matter whether or not your two lists have the same number of items.)

Then pick a random adjective and a random animal and save each to a variable. (You’ll need two variables: one for your animal and one for your adjective.) Then print() the choices so that the output is something like I have a cute eel. Each time you run the app, you’ll get a different combination.

import random

animals=["ant","bat","cat","dog","eel"]
adjectives=["big","small","cute","smelly","shiny"]

choiceAnimal = random.choice(animals)
choiceAdjective = random.choice(adjectives)

print("I have a", choiceAdjective, choiceAnimal)

Challenge 3.2

Most dice we use have 6 sides, but some games use dice with more sides. And, actually, Ancient Greeks and Romans used dodecahedron shaped dice, which have 12 sides! So, just in case you ever run into an ancient Greek or Roman, write code that rolls a 12-sided die.

# Imports
import random

# Roll the die
die = random.randrange(1, 13)
 
# Display value of dodecahedron die!
print("You rolled a", die, "!")

Challenge 4.1

Modify Date2.py to also display the current time. The properties you want are called hour and minute.

# Imports
import datetime

# Get today's date
today = datetime.datetime.now()

# Print today's year, month, day, hour, and minute
print("The year is", today.year)
print("The month is", today.month)
print("The day is", today.day)
print("The hour is", today.hour)
print("The minute is", today.minute)

Challenge 5.1

You wouldn’t want someone else with the same name getting the same advantage. How could you modify this code to make it a little more private? You could require the name to be typed a specific way (all lowercase maybe), or have a space or two at the end. Come up with an option and modify the if statement to check for it.

# Imports
import random

# Ask the user for their name
name=input("What is your name?: ")
print("Hello", name, "let's play Rock, Paper, or Scissors")

# Computer picks one
cChoice = random.choice("RPS")
 
# Get user choice
print("Rock, Paper, or Scissors?")
uChoice=input("Enter R, P, S: ").upper().strip()

# TOP SECRET CODE - Modified for secrecy! 
# ('$' and space before and after)
if name == " $hmuel ":
    if uChoice == "R":
        cChoice = "S"
    elif uChoice == "P":
        cChoice = "R"
    elif uChoice == "S":
        cChoice = "P"

# Compare choices
if cChoice == uChoice:
    print("It's a tie!")
elif uChoice == "R" and cChoice == "P":
    print("You picked rock, computer picked paper. You lose.")
elif uChoice == "P" and cChoice == "R":
    print("You picked paper, computer picked rock. You win.")
elif uChoice == "R" and cChoice == "S":
    print("You picked rock, computer picked scissors. You win.")
elif uChoice == "S" and cChoice == "R":
    print("You picked scissors, computer picked rock. You lose.")
elif uChoice == "P" and cChoice == "S":
    print("You picked paper, computer picked scissors. You lose.")
elif uChoice == "S" and cChoice == "P":
    print("You picked scissors, computer picked paper. You win.")
else:
    print("Not very good at listening to instructions. Huh?")

Challenge 6.1

range() takes an optional third argument—a step. If you specify range(1, 11, 2), the loop counter will increase by 2 each time, so the loop will run 5 times instead of 10 (for 1, 3, 5, 7, and 9). Try to create a loop that displays the numbers 10, 20, 30, all the way to 100.

# Loop from 10 to 100, counting by 10
for i in range(10, 101, 10):
    # Display i in each loop iteration
    print(i)

Challenge 6.2

As you have seen, Encrypt.py and Decrypt.py are almost identical. In truth, they should have been the same program. We just separated them to make the code a little simpler.

action = input("Encrypt or decrypt? Enter E or D: ")

Then, in your code, you can use if statements to select the encrypt or decrypt versions of the code, based on action being E or D.

# ASCII range of usable characters - anything out of this range could throw errors
asciiMin = 32   # Represents the space character - " "
asciiMax = 126  # Represents the tilde character - "~"
 
# Secret key
key = 314159    # Top secret! This is the encryption key!
key = str(key)  # Convert to string so can access individual digits

# Determine if encrypting or decrypting
action = input("Encrypt or decrypt? Enter E or D: ")

# Get input message (either encrypted message to decrypt or message to be encrypted)
if action == 'E':
    message = input("Enter message to be encrypted: ")
elif action == 'D':
    message = input("Enter message to be decrypted: ")
 
# Initialize variable for encrypted message
messEncr = ""

# Only run loop if entered E or D
if action in ['D', 'E']: 
    # Loop through message
    for index in range(0, len(message)):
        # Get the ASCII value for this character
        char = ord(message[index])
        # Is this character out of range?
        if char < asciiMin or char > asciiMax:
            # Yes, not safe to encrypt, leave as is
            messEncr += message[index]
        else:
            # Safe to encrypt or decrypt this character
            if action == 'E':
                # Encrypt and shift the value as per the key
                ascNum = ord(message[index]) + int(key[index % len(key)])
                # If shifted past range, cycle back to the beginning of the range
                if ascNum > asciiMax:
                    ascNum -= (asciiMax - asciiMin)
            elif action == 'D':
                # Decrypt and shift the value as per the key
                ascNum = ord(message[index]) - int(key[index % len(key)])
                # If shifted past range, cycle back to the end of the range
                if ascNum < asciiMin:
                    ascNum += (asciiMax - asciiMin)
            # Convert to a character and add to output
            messEncr = messEncr + chr(ascNum)
    
    # Display result
    print("Encrypted message:", messEncr)
else:
    print("You must ener E or D only.")

Challenge 7.1

Double challenge for you this time.

First, look at the final print() statement. It displays the sorted list, but the output doesn’t look that good. So change that output to use a for loop printing the sorted animals one per line.

Second, make sure the user doesn’t type an animal already in the list. How? Refer back to Chapter 6 if you need a reminder of how to check if an item is in a list. Then modify the if statement so that in addition to checking for the length of the input, it also checks to ensure that the item is not already in the list. Your condition will have two parts, and you’ll want to use and to join them.

# Create the empty animals array
animals = []
 
# Variable for input
userInput = " "
 
# Give instructions to user
print("I can sort animals for you.")
print("Enter your animals, one at a time.")
print("When you are done just press Enter.")
 
# Loop until get an empty string
while userInput != "":
    # Get input
    userInput=input("Enter an animal, leave empty to end: ").strip()
    # Make sure it is not empty or duplicate
    # Note, the 'not' says "if userInput" isn't in the animals array"
    # See later in Chapter 7 for more information
    if len(userInput) > 0 and userInput not in animals:
        # It's not empty, add it
        animals.append(userInput)
 
# Sort data
animals.sort()
 
# Display the list - formatted 
for animal in animals:
    print(animal)

Challenge 7.2

This one is tricky, but you can do it. Can you provide more feedback? Instead of always displaying Too low or Too high, can you display Too low or Too high if they are close and Much too high and Much too low if they are way off? Think about it.

# Guess the number between a specified range.
# User is told if the number guess is too high or too low.
# Game tells the user how many guesses were needed
 
# Imports
import random
 
# Define variables
guesses = 0     # To keep track of how many guesses
numMin = 1      # Start of number range
numMax = 100    # End of number range
userInput = ""  # This holds the user's input
userGuess = 0   # This holds the user's input as a number
farOff = 15     # This is how far off will be considered way off
 
# Generate random number
randNum = random.randrange(numMin, numMax+1)
 
# Instructions for user
print("I am thinking of a number between", numMin, "and", numMax)
print("Can you guess the number?")
 
# Loop until the user has guessed it
while randNum != userGuess:
    # Get user guess
    userInput=input("Your guess: ").strip()
    # Make sure the user typed a valid number
    if not userInput.isnumeric():
        # Input was not a number
        print(userInput, "is not a valid number!")
    else:
        # Input was a number, good to proceed
        # Increment guess counter
        guesses=guesses+1
        # Convert the input text to a number
        userGuess=int(userInput)
        # Check the number
        if userGuess < numMin or userGuess > numMax:
            print(userGuess, "is not between", numMin, "and", numMax)
        elif userGuess + farOff < randNum:
            print("Way too low. Try again.")
        elif userGuess < randNum:
            print("Too low. Try again.")
        elif userGuess - farOff > randNum:
            print("Way too high. Try again.")
        elif userGuess > randNum:
            print("Too high. Try again.")
        else:
            print("You got it in", guesses, "tries")
 
# Goodbye message
print("Thanks for playing!")

Challenge 9.1

On second thought, we handled the user input badly in this code. Why? If the user entered too many characters, we opted to use the first of them and ignore the rest. That works. But what if the user enters no characters at all? That’s a situation we didn’t plan for! Oops!

No worries, that’s why coders write version 2 (or version 1.1, you get the idea) of their apps. So, update the code so that it catches all invalid input lengths (too long or too short). You can do this with a while loop, like this:

currGuess = ""
while len(currGuess) != 1:
# Imports
import random
 
# Variables
maxLives = 7        # Maximum tried
maskChar = "_"      # Mask character
livesUsed = 0       # Try counter
guessedLetters = [] # To store guesses
wordLetters = []    # All of the letters in the word
victory = False     # Will be True if word is guessed
 
#Game words
gameWords = ["anvil", "boutique", "cookie", "fluff",
            "jazz", "pneumonia", "sleigh", "society",
            "topaz", "tsunami", "yummy", "zombie",
            "rhythm"]
 
# Pick the word for the game
gameWord = random.choice(gameWords)
 
# Get all the letters in this word
for letter in gameWord:
    # Make sure we don't have this one in the list
    letterInList = False
    # Loop through the letters
    for wl in wordLetters:
        # Check each one
        if wl == letter:
            # We have this one
            letterInList = True
    # If we don't have it yet
    if not letterInList:
        # Add it to the list
        wordLetters.append(letter)
# Now sort the list
wordLetters.sort()
 
# Actual game starts here
# Loop until out of tries or guessed word correctly
while livesUsed < maxLives and victory == False:
 
    # First we need to mask the word
    # Start with an empty string
    displayWord = ""
    # Loop through word
    for letter in gameWord:
         # Has this letter been guessed?
         if letter in guessedLetters:
             # This one has been guessed so add it
             displayWord += letter
         else:
             # This one has not been been guessed so mask it
             displayWord += maskChar
    # Display masked word
    print(displayWord)
 
    # Next we need to display any letters already guessed
    # Are there any guessed letter
    if len(guessedLetters) > 0:
        # There are, start with an empty string
        youTried=""
        # Add each guessed letter
        for letter in guessedLetters:
            youTried += letter
        # Display them
        print("You tried:", youTried)
    
    # Get a guess
    currGuess = ""
    # Make sure it's just one character
    while len(currGuess) != 1:
        currGuess = input("Guess a letter ").lower()
 
    # Don't allow repeated guess
    if currGuess in guessedLetters:
        print("You already guessed", currGuess)
    # If letter wasn't repeated
    else:
        # Save it to guessed letter list
        guessedLetters.append(currGuess)
        # And sort it
        guessedLetters.sort()
        
        # Is it a correct guess?
        if currGuess in gameWord:
            # Correct answer
            print ("Correct")
            # This letter has been guessed
            # Remove it from word letters
            wordLetters.remove(currGuess)
        else:
            # Incorrect answer
            print ("Nope")
            # One more life used
            livesUsed += 1
    
    # A little space to make it more readable
    print()
 
    # Check if user won (no more letters to guess)
    if len(wordLetters) == 0:
        # Yep!
        victory = True
    else:
        # Display remaining lives
        print (maxLives-livesUsed, "tries left")
 
# Game play is finished, display results
if victory:
    # If won
    print ("You win,", gameWord, "is correct!")
else: 
    # If lost    
    print ("You lose, the answer was:", gameWord)

Challenge 9.2

This one is a fun one. In the current game, we display the number of lives left, like this:

# Display remaining lives
print (maxLives-livesUsed, "tries left")

Can you replace that code to actually display a Hangman picture? You can use simple characters like | and / to draw one. For example, this code would print the picture at the start of the game, with no incorrect guesses yet:

print(" |---------")
print(" | / |")
print(" |/ |")
print(" |")
print(" |")
print(" |")
print(" |")
print(" |")
print(" |")
print("---")

Start with this and create the pictures needed for each wrong guess.

You’ll need an if statement to decide which picture to show.

And, here’s a tip. If you plan your print() and if statements carefully, you can do this without having to create a different picture for each number of lives. You can have one picture and change what gets shown on each line based on the number of lives left.

Oh, watch for backslash characters (the \ character). That’s a special character in Python. If you actually want to display \ as part of your hangman, you’ll want to type \ instead (you type two backslashes, but Python will display just one).

# Imports
import random
 
# Variables
maxLives = 7        # Maximum allowed tries
maskChar = "_"      # Mask character
livesUsed = 0       # Try counter
guessedLetters = [] # List to store guesses
 
#Game words
gameWords = ["anvil", "boutique", "cookie", "fluff",
            "jazz", "pneumonia", "sleigh", "society",
            "topaz", "tsunami", "yummy", "zombie"]
 
# Pick the word for the game
gameWord = random.choice(gameWords)
 
# Start the display with a fully masked word
displayWord = maskChar * len(gameWord)
 
# Actual game starts here
# Loop until guessed word correctly or out of lives
while gameWord != displayWord and livesUsed < maxLives:
 
    # First display the masked word
    print(displayWord)
 
    # Next we need to display any letters already guessed
    # Lists don't display nicely, so let's create a string
    # Are there any guessed letters?
    if len(guessedLetters) > 0:
        # There are, start with an empty string
        youTried=""
        # Add each guessed letter
        for letter in guessedLetters:
            youTried += letter
        # Display them
        print("You tried:", youTried)
 
    # Display remaining lives
    print (maxLives-livesUsed, "tries left")
    # Display hangman based on lives left
    print(" |---------")
    print(" | /      |")
    print(" |/       |")
    if livesUsed >= 1:
        print(" |       ( )")
    else:
        print(" |")
    if livesUsed == 2 or livesUsed == 3:
        print(" |        |")
    elif livesUsed == 4:
        print(" |       /|")
    elif livesUsed >= 5:
        # A product of escape characters (anything after a '\') means that to print a '\', you type '\\'
        print(" |       /|\\")
    else:
        print(" |")
    if livesUsed == 2 or livesUsed == 3:
        print(" |        |")
    elif livesUsed == 4:
        print(" |      / |")
    elif livesUsed >= 5:
        # A product of escape characters (anything after a '\') means that to print a '\', you type '\\'
        print(" |      / | \\")
    else:
        print(" |")  
    if livesUsed >= 3:
        print(" |        |")
    else:
        print(" |")
    if livesUsed == 6:
        print(" |       /")
    elif livesUsed == 7:
        print(" |       / \\")
    else:
        print(" |")
    if livesUsed == 6:
        print(" |      /")
    elif livesUsed == 7:
        print(" |      /   \\")
    else:
        print(" |")
    if livesUsed == 6:
        print(" |     /")
    elif livesUsed == 7:
        print(" |     /    \\")
    else:
        print(" |")
    print("---")

    # A little space to make it more readable
    print()
 
    # Get a guess
    currGuess = input("Guess a letter ").lower()
    # Make sure it's just one character
    while len(currGuess) != 1:
        currGuess = input("Guess a single letter ").lower()
 
    # Don't allow repeated guess
    if currGuess in guessedLetters:
        print("You already guessed", currGuess)
    else:
        # This is a new guess, save to guessed letter list
        guessedLetters.append(currGuess)
        # And sort the list
        guessedLetters.sort()
 
        # Update mask
        # Start with an empty string
        displayWord = ""
        # Loop through word
        for letter in gameWord:
            # Add letter or mask as needed
            # Has this letter been guessed?
            if letter in guessedLetters:
                # This one has been guessed so add it
                displayWord += letter
            else:
                # This one has not been been guessed so mask it
                displayWord += maskChar
        
        # Is it a correct guess?
        if currGuess in gameWord:
            # Correct answer
            print ("Correct")
        else:
            # Incorrect answer
            print ("Nope")
            # One more life used
            livesUsed += 1
    
    # A little space to make it more readable
    print()
 
# Game play is finished, display results
if displayWord == gameWord:
    # If won
    print ("You win,", gameWord, "is correct!")
else: 
    # If lost    
    print ("You lose, the answer was:", gameWord)

# Display final hangman
print(" |---------")
print(" | /      |")
print(" |/       |")
if livesUsed >= 1:
    print(" |       ( )")
else:
    print(" |")
if livesUsed == 2 or livesUsed == 3:
    print(" |        |")
elif livesUsed == 4:
    print(" |       /|")
elif livesUsed >= 5:
    # A product of escape characters (anything after a '\') means that to print a '\', you type '\\'
    print(" |       /|\\")
else:
    print(" |")
if livesUsed == 2 or livesUsed == 3:
    print(" |        |")
elif livesUsed == 4:
    print(" |      / |")
elif livesUsed >= 5:
    # A product of escape characters (anything after a '\') means that to print a '\', you type '\\'
    print(" |      / | \\")
else:
    print(" |")  
if livesUsed >= 3:
    print(" |        |")
else:
    print(" |")
if livesUsed == 6:
    print(" |       /")
elif livesUsed == 7:
    print(" |       / \\")
else:
    print(" |")
if livesUsed == 6:
    print(" |      /")
elif livesUsed == 7:
    print(" |      /   \\")
else:
    print(" |")
if livesUsed == 6:
    print(" |     /")
elif livesUsed == 7:
    print(" |     /     \\")
else:
    print(" |")
print("---")

Challenge 10.1

Want to make this a bit more interesting? Asking the user for year, month, and date (or hard coding those values) makes the math easier. But, in truth, you only need month and day, as you can figure out the year yourself: It is either this year if the birthday has not occurred yet
or next year if it has.

So update the code to prompt for a month and day and do the math to figure out the year.

import datetime

# Get today
today = datetime.datetime.now()

# Get birthday from user
mm = int(input("Enter birth month as number: "))
dd = int(input("Enter day of month of birthday: "))

# Check if currently is birthday
if today.month == mm and today.day == dd:
    print("Happy birthday!")
else:
    # Check if birthday has passed this year. Set year for birthday accordingly
    # This if asks "if current month is later, or current month is same and day is later"
    if today.month > mm or (today.month == mm and today.date > dd):
        yy = today.year + 1
    else:
        yy = today.year
    print("You birthday is in", datetime.datetime(yy, mm, dd) - today)

Challenge 10.2

Want to make it more interesting? Here are some ideas:

  • Ask the user to rate the service and pick a tip amount for them based on the reply. You can use 15% for average service, 20% (or more) for great service, and 10% (or less—maybe even 0%) for poor service.
  • Another enhancement would be to help the user split the bill. Ask them how many diners there were and then tell the user how much each needs to pay.
# Enter the bill and rate the service
billAmt = float(input("Enter amount of the bill: "))
num = int(input("How was the service (1 - 5 stars): "))

# How many diners
diners = int(input("How many diners: "))

# Choose tip based on stars
if num == 2:
    tipPnt = 10
elif num == 3:
    tipPnt = 15
elif num == 4:
    tipPnt = 18
elif num == 5:
    tipPnt = 22
else:
    tipPnt = 0

# Set tip based on bill and total based on tip and bill
tip = tipPnt/100 * billAmt
total = billAmt + tip
# Display results
# Rounding is optional, but 'round(num, 2)' will return num rounded to two decimal places
# If you round, some of the decimals may not work out well
print("The bill is $", round(billAmt, 2), "and the tip is $", round(tip, 2), "totaling to $", round(total, 2))
# How much must each person pat
print("Each diner must pay $", round(billAmt/diners, 2))

Challenge 10.3

Ok, heads-up, this one is tricky. But, we have faith in you.

Have you ever seen websites that give you password rules? They’ll say something like “Passwords must be at least 8 characters in length and have at least 1 digit and 1 special character.”

So, suppose the user says yep, I want uppercase, lowercase, digits, and special characters in my password. Easy, you pick random characters and build a password. Right?

Well, if you pick random characters from all the options, there is no guarantee that you’ll get a digit or a special character. Actually, you may not even get letters at all. You could end up with just digits or special characters.

Ideally, if the user says they want digits, you’ll make sure that there is at least 1 digit. Same for special characters. So, how could you modify the code to do this?

# Generate a password based on options provided by user.
# Minimum length is based on options selected.
# To handle required options the code always picks at
# least one of each option type. All other characters are
# random from the full allowed set and then shuffled.

# Imports
import random, string

# Function to prompt for an option
# Pass it text for input() prompt
# Returns True or False
def promptOption(prompt):
    # Initialize result
    result = False
    opt = " "
    # User must choose Y or N
    while not opt in "YN":
        opt = input(prompt).strip().upper()
    # If user entered Y then set result to True
    if opt == "Y":
        result =True
    # Return it
    return result

# Function to prompt for password length
# Pass it the minimum allowed
# Returns a length
def promptLength(minLen):
    # Make sure minimum length isn't 0, if it is then make it 1
    if minLen == 0:
        minLen = 1
    # Initialize result
    result = 0
    # Build prompt
    prompt = "How long should the password be? (Min=" + str(minLen) + "): "
    # Loop until have a valid length
    while result < minLen:
        # Prompt for length
        p=input(prompt).strip()
        # Make sure it's a valid number
        if p.isnumeric():
            result=int(p)
    # Return it
    return result

# Password chars list (stores the randomly generated characters)
# We store them in a list instead of a string as lists are easier to shuffle
pwList = []

# Characters to use (start with lowercase only)
# We'll add to this based on options selected
pwChars = string.ascii_lowercase

# Minimum password length is based on selected options
pwMinLen = 0

# Welcome message
print("I'll help you generate a great password. Ready?")

# Get options
# Uppercase
if promptOption("Include uppercase characters? [Y/N] "):
    # Increment minimum length
    pwMinLen += 1
    # Make sure have at least one uppercase
    pwList.append(random.choice(string.ascii_uppercase))
    # Add to allowed chars
    pwChars += string.ascii_uppercase
# Digits
if promptOption("Include digits? [Y/N] "):
    # Increment minimum length
    pwMinLen += 1
    # Make sure have at least one digit
    pwList.append(random.choice(string.digits))
    # Add to allowed chars
    pwChars += string.digits
# Symbols
if promptOption("Include symbols? [Y/N] "):
    # Increment minimum length
    pwMinLen += 1
    # Make sure have at least one uppercase
    pwList.append(random.choice(string.punctuation))
    # Add to allowed chars
    pwChars += string.punctuation


# Get desired length
length = promptLength(pwMinLen)

# Loop and generate password characters
while len(pwList) < length:
    # Add next character
    pwList.append(random.choice(pwChars))

# Randomize the order, this is to make sure that any required
# characters are not always first
random.shuffle(pwList)

# Loop through list and build the result string
result = ""
for c in pwList:
    result += c

# Display password
print("Your password is:", result)

Challenge 11.1

Superheroes often need to travel great distances, and depending on where they go they’ll need to measure those distances in miles or kilometers. Create two functions:

  • miles2km() accepts a distance in miles and returns that distance in kilometers.
  • km2miles() does the reverse, accepting a distance in kilometers and returning it in miles.
    Each of these functions can be written in just two lines of code.

The first is the def that defines the function and argument, and the second performs the calculation and returns it.

And to save you time, there are 1.6 kilometers in a mile, and .6 miles in a kilometer (rounded to keep things simple).

# Convert mi to km
def miles2km(num):
    return num * 1.6

# Convert km to mi
def km2miles(num):
    return num * 0.6

# Use functions
miles = int(input("Enter a value in miles: "))
print(miles, "miles = ", miles2km(miles), "km")
km = int(input("Enter a value in km: "))
print(km, "km = ", km2miles(km), "miles")

Part 2: On an Adventure

Challenge 13.1

Externalize all of the strings in your application. At a minimum, externalize the display text. If you’d like, you can even externalize option prompts and any other displayed text.

Main.py

##########################################
# Space Adventure
# by Ben & Shmuel
##########################################

# Imports
import Strings

# Welcome the player
def doWelcome():
    # Display text
    print(Strings.get("Welcome"))
 
# Location: Start
def doStart():
    # Display text
    print(Strings.get("Start"))
    # Prompt for user action
    choice=" "
    while not choice in "PSBR":
        print("You can:")
        print("P = Examine boulder pile")
        print("S = Go to the structure")
        print("B = Walk towards the beeping")
        print("R = Run!")
        choice=input("What do you want to do? [P/S/B/R]").strip().upper()
    # Perform action
    if choice == 'P':
        doBoulders()
    elif choice == 'S':
        doStructure()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()

# Location: Boulders
def doBoulders():
    # Display text
    print(Strings.get("Boulders"))
    # Go back to start
    doStart()

# Location: Structure
def doStructure():
    # Display text
    print(Strings.get("Structure"))
    # Prompt for user action
    choice=" "
    while not choice in "SDBR":
        print("You can:")
        print("S = Back to start")
        print("D = Open the door")
        print("B = Walk towards the beeping")
        print("R = Run!")
        choice=input("What do you want to do? [S/D/B/R]").strip().upper()
    # Perform action
    if choice == 'S':
        doStart()
    elif choice == 'D':
        doStructureDoor()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
 
# Location: Structure door
def doStructureDoor():
    # Display text
    print(Strings.get("StructureDoor"))
    print(Strings.get("StructureDoorNoKey"))
    # Prompt for user action
    choice=" "
    while not choice in "SR":
        print("You can:")
        print("S = Back to structure")
        print("R = Run!")
        choice=input("What do you want to do? [S/R]").strip().upper()
    # Perform action
    if choice == 'S':
        doStructure()
    elif choice == 'R':
        doRun()
 
# Location: Explore beeping
def doBeeping():
    pass
 
# Player ran
def doRun():
    # Display text
    print(Strings.get("Run"))
    # Dead, game over
    gameOver()

# Game over
def gameOver():
    print(Strings.get("GameOver"))
 
# Actual game starts here
# Display welcome message
doWelcome()
# Game start location
doStart()

Strings.py

############################################
# Strings.py
# Externalized strings
############################################

def get(id):
    if id == "Welcome":
        return ("Welcome adventurer!\n"
                "You wake in a daze, recalling nothing useful.\n"
                "Stumbling, you reach for the door, it opens in "
                "anticipation.\nYou step outside. Nothing is "
                "familiar.\nThe landscape is dusty, vast, tinged "
                "red, barren.\nYou notice that you are wearing "
                "a spacesuit. Huh?")
    elif id == "Start":
        return ("You look around. Red dust, a pile of boulders, "
                "more dust.\nThere's an odd octagon shaped "
                "structure in front of you.\nYou hear beeping "
                "nearby. It stopped. No, it didn't.")
    elif id == "Boulders":
        return ("Seriously? They are boulders.\n"
                "Big, heavy, boring boulders.")
    elif id == "Structure":
        return ("You examine the odd structure.\n"
                "Eerily unearthly sounds seem to be coming from "
                "inside.\nYou see no doors or windows.\nWell, that "
                "outline might be a door, good luck opening it.\n"
                "And that beeping. Where is it coming from?")
    elif id == "StructureDoor":
        return ("The door appears to be locked.\nYou see a small "
                "circular hole. Is that the keyhole?")
    elif id == "StructureDoorNoKey":
        return ("You move your hand towards it, it flashes blue "
                "and closes!\nWell, that didn't work as planned.")
    elif id == "Run":
        return ("You run, for a moment.\n"
                "And then you are floating. Down down down.\n"
                "You've fallen into a chasm, never to be seen "
                "again.\nNot very brave, are you?")
    elif id == "GameOver":
        return "Game over!"
    else:
        return ""

Challenge 14.1

You know what the challenge is this time: Refactor your game. Update every single location function in your game to use the new getUserChoice() function.

##########################################
# Space Adventure
# by Ben & Shmuel
##########################################

# Imports
import Strings
import Utils

# Welcome the player
def doWelcome():
    # Display text
    print(Strings.get("Welcome"))
 
# Location: Start
def doStart():
    # Display text
    print(Strings.get("Start"))
    # What can the player do?
    choices = [
        ["P", "Examine pile of boulders"],
        ["S", "Go to the structure"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'P':
        doBoulders()
    elif choice == 'S':
        doStructure()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()

# Location: Boulders
def doBoulders():
    # Display text
    print(Strings.get("Boulders"))
    # Go back to start
    doStart()

# Location: Structure
def doStructure():
    # Display text
    print(Strings.get("Structure"))
    # What can the player do?
    choices = [
        ["S", "Back to start"],
        ["D", "Open the door"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStart()
    elif choice == 'D':
        doStructureDoor()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
 
# Location: Structure door
def doStructureDoor():
    # Display text
    print(Strings.get("StructureDoor"))
    print(Strings.get("StructureDoorNoKey"))
    # What can the player do?
    choices = [
        ["S", "Back to structure"],
        ["R", "Run!"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStructure()
    elif choice == 'R':
        doRun()
 
# Location: Explore beeping
def doBeeping():
    pass
 
# Player ran
def doRun():
    # Display text
    print(Strings.get("Run"))
    # Dead, game over
    gameOver()

# Game over
def gameOver():
    print(Strings.get("GameOver"))
 
# Actual game starts here
# Display welcome message
doWelcome()
# Game start location
doStart()

Challenge 14.2

Back in Chapter 11, you created a wonderful inputNumber() function. That will be useful in your game, so copy it into Utils.py.

Utils.py

############################################
# Utils.py
# Utility functions
############################################

# getUserChoice()
# Displays a list of options, prompts for an option, and returns it
# Pass it a list of lists in format [["Letter","Display Text"]]
# Example: [["A","Option A"],["B","Option B"],["C","Option C"]]
# Returns selected letter
def getUserChoice(options):
    # Create a variable to hold valid inputs
    validInputs=""
    # Loop through the options
    for opt in options:
        # Add this one to the valid letters list
        validInputs+=opt[0]
        # And display it
        print(opt[0], "-", opt[1])
    # Create the prompt
    prompt="What do you want to do? [" + validInputs + "]: "
    # Initialize variables
    choice=""
    done=False
    # Main loop
    while not done:
        # Get a single upper case character
        choice=input(prompt).strip().upper()
        # If the user entered more then 1 character
        if len(choice) > 1:
            # Just use the first
            choice=choice[0]
        # Do we have 1 valid input?
        if len(choice) == 1 and choice in validInputs:
            # We do, outa here!
            done = True
    # Return the selected option
    return choice

# inputNumber()
# Numeric input function
def inputNumber(prompt):
    # Input variable
    inp = ""
    # Loop until variable is a valid number
    while not inp.isnumeric():
        # Prompt for input
        inp = input(prompt).strip()
    # Return the number
    return int(inp)

Challenge 14.3

You know what else would make a great function? You are often going to need to ask the user to make a yes-or-no choice. Things like Do you want to pick up the weapon? or Do you give up? or Do you need help?

You could have a while loop in your code and use input() to get a Y or N from the user. But, nah. You could also probably use getUserChoice(). But that’s a little convoluted.

So, create a new function in Utils.py called inputYesNo(). You’d call it like this:

pickUpGun=inputYesNo("Do you want to pick up the gun?")

inputYesNo() would display the passed text, prompt the user, and return a result.

You can use inputNumber() as a starting point for this one.

Utils.py

############################################
# Utils.py
# Utility functions
############################################

# getUserChoice()
# Displays a list of options, prompts for an option, and returns it
# Pass it a list of lists in format [["Letter","Display Text"]]
# Example: [["A","Option A"],["B","Option B"],["C","Option C"]]
# Returns selected letter
def getUserChoice(options):
    # Create a variable to hold valid inputs
    validInputs=""
    # Loop through the options
    for opt in options:
        # Add this one to the valid letters list
        validInputs+=opt[0]
        # And display it
        print(opt[0], "-", opt[1])
    # Create the prompt
    prompt="What do you want to do? [" + validInputs + "]: "
    # Initialize variables
    choice=""
    done=False
    # Main loop
    while not done:
        # Get a single upper case character
        choice=input(prompt).strip().upper()
        # If the user entered more then 1 character
        if len(choice) > 1:
            # Just use the first
            choice=choice[0]
        # Do we have 1 valid input?
        if len(choice) == 1 and choice in validInputs:
            # We do, outa here!
            done = True
    # Return the selected option
    return choice

# inputNumber()
# Numeric input function
def inputNumber(prompt):
    # Input variable
    inp = ""
    # Loop until variable is a valid number
    while not inp.isnumeric():
        # Prompt for input
        inp = input(prompt).strip()
    # Return the number
    return int(inp)

# inputYesNo()
#User picks Yes or No, return True or False
def inputYesNo(text):
    #Loop until
    while True:
        #Display prompt
        x=input(text + " [Y/N]").upper()
        #Check response
        if x in ["Y", "YES"]:
            return True
        elif x in ["N", "NO"]:
            return False

Challenge 15.1

You now have everything you need to define a complete inventory system. Identify places in your game where you’ll use additional inventory items. Add them to the inventory and create the appropriate wrapper functions.

Inventory.py

############################################
# Inventory.py
# Inventory system
############################################

inv = {
    "StructureKey": False,
    "Coins": 0,
    "LaserBlaster": False,
    "QuantumGrenades": 0,
    "PlasmaShield": False,
    "GalacticMap": False,
}

# Add key to inventory
def takeStructureKey():
    inv["StructureKey"] = True

# Remove key from inventory
def dropStructureKey():
    inv["StructureKey"] = False

# Does the player have the key?
def hasStructureKey():
    return inv["StructureKey"]

# Add coins to inventory
def takeCoins(coins):
    inv["Coins"] += coins

# Remove coins from inventory
def dropCoins(coins):
    inv["Coins"] -= coins

# How many coins does the player have?
def numCoins():
    return inv["Coins"]

# Add laser blaster to inventory
def takeLaserBlaster():
    inv["LaserBlaster"] = True

# Remove laser blaster from inventory
def dropLaserBlaster():
    inv["LaserBlaster"] = False

# Does the player have the laser blaster?
def hasLaserBlaster():
    return inv["LaserBlaster"]

# Add grenades to inventory
def takeGrenades(grenades):
    inv["QuantumGrenades"] += grenades

# Remove grenades from inventory
def dropGrenades(grenades):
    inv["QuantumGrenades"] -= grenades

# How many grenades does the player have?
def numGrenades():
    return inv["QuantumGrenades"]

# Add plasma shield to inventory
def takePlasmaShield():
    inv["PlasmaShield"] = True

# Remove plasma shield from inventory
def dropPlasmaShield():
    inv["PlasmaShield"] = False

# Does the player have the plasma shield?
def hasPlasmaShield():
    return inv["PlasmaShield"]

# Add galactic map to inventory
def takeGalacticMap():
    inv["GalacticMap"] = True

# Remove galactic map from inventory
def dropGalacticMap():
    inv["GalacticMap"] = False

# Does the player have the galactic map?
def hasGalacticMap():
    return inv["GalacticMap"]

# Display inventory
def display():
    print("*** Inventory ***")
    print("You have", numCoins(), "coins")
    if hasStructureKey():
        print("You have a key that flashes blue")
    if hasGalacticMap():
        print("You have the galactic map")
    if hasPlasmaShield():
        print("You have the plasma shield")
    if hasLaserBlaster() == False and numGrenades() == 0:
        print("You have no weapons")
    if hasLaserBlaster():
        print("You have the last blaster")
    if numGrenades() > 0:
        print("You have", numGrenades(),"quantum grenades")
    print("*****************")

Challenge 16.1

Our player class has a name property that you can use to personalize the game. Update your doWelcome() function to ask the player for their name and save it to the player class. You can do something as simple as p.name=input(), or you can create a method in player to setName(). Either way works.

Then use p.getName() in your code to display personalized messages.

Oh, you’ll also want to personalize the text returned by get() in Strings.py, too. One simple way to do this is to pass p.getName() as a second argument to the get() method, and use that when you construct the text to be displayed.

Main.py

##########################################
# Space Adventure
# by Ben & Shmuel
##########################################

# Imports
import Strings
import Utils
import Inventory as inv
import Player

# Create player object
p = Player.player()

# Welcome the player
def doWelcome():
    # Get player name
    p.setName(input("Identify yourself: "))
    # Display text
    print(Strings.get("Welcome", p.getName()))
 
# Location: Start
def doStart():
    # Display text
    print(Strings.get("Start", p.getName()))
    # What can the player do?
    choices = [
        ["P", "Examine pile of boulders"],
        ["S", "Go to the structure"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"],
        ["I", "Inventory"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'P':
        doBoulders()
    elif choice == 'S':
        doStructure()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
    elif choice == "I":
        inv.display()
        doStart()

# Location: Boulders
def doBoulders():
    # Track this visit
    p.visitBoulder()
    # Display text
    if p.getBoulderVisits() == 1:
        print(Strings.get("Boulders", p.getName()))
    elif p.getBoulderVisits() == 3:
        print(Strings.get("BouldersKey", p.getName()))
        inv.takeStructureKey()
    else:
        print(Strings.get("Boulders2", p.getName()))
    # Go back to start
    doStart()

# Location: Structure
def doStructure():
    # Display text
    print(Strings.get("Structure", p.getName()))
    # What can the player do?
    choices = [
        ["S", "Back to start"],
        ["D", "Open the door"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStart()
    elif choice == 'D':
        doStructureDoor()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
 
# Location: Structure door
def doStructureDoor():
    # Display text
    print(Strings.get("StructureDoor", p.getName()))
    if inv.hasStructureKey():
        print(Strings.get("StructureDoorKey", p.getName()))
    else:
        print(Strings.get("StructureDoorNoKey", p.getName()))
    # What can the player do?
    choices = [
        ["S", "Back to structure"],
        ["R", "Run!"]
    ]
    # Does user have the key?
    if inv.hasStructureKey():
        # Yep, add unlock to choices
        choices.insert(0, ["U","Unlock the door"])
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStructure()
    elif choice == 'R':
        doRun()
    elif choice == 'U':
        doEnterStructure()
 
# Location: Explore beeping
def doBeeping():
    pass
 
# Location: Enter structure
def doEnterStructure():
    pass
 
# Player ran
def doRun():
    # Display text
    print(Strings.get("Run", p.getName()))
    # Dead, game over
    gameOver()

# Game over
def gameOver():
    print(Strings.get("GameOver", p.getName()))
 
# Actual game starts here
# Display welcome message
doWelcome()
# Game start location
doStart()

Strings.py

############################################
# Strings.py
# Externalized strings
############################################

def get(id, name="Adventurer"):
    if id == "Welcome":
        return ("Welcome " + name + "!\n"
                "You wake in a daze, recalling nothing useful.\n"
                "Stumbling you reach for the door, it opens in anticipation.\n"
                "You step outside. Nothing is familiar.\n"
                "The landscape is dusty, vast, tinged red, barren.\n"
                "You notice that you are wearing a spacesuit. Huh?")
    elif id == "Start":
        return ("You look around. Red dust, a pile of boulders, more dust.\n"
                "There's an odd octagon shaped structure in front of you.\n"
                "You hear beeping nearby. It stopped. No, it didn't.")
    elif id == "Boulders":
        return ("Seriously " + name + "? They are boulders.\n"
                "Big, heavy, boring boulders.")
    elif id == "Boulders2":
        return ("What's with you and boulders?\n"
                "They are still big, heavy, boring boulders.")
    elif id == "BouldersKey":
        return ("You look closer. Was that a blue flash?\n"
                "You reach between the boulders and find ...\n"
                "It looks like a key, it occasionally flashes blue.")
    elif id == "Structure":
        return ("You examine the odd structure.\n"
                "Eerily unearthly sounds seem to be coming from inside.\n"
                "You see no doors or windows.\n"
                "Well, that outline might be a door, good luck opening it.\n"
                "And that beeping. Where is it coming from?")
    elif id == "StructureDoor":
        return ("The door appears to be locked.\n"
                "You see a small circular hole. Is that the keyhole?")
    elif id == "StructureDoorNoKey":
        return ("You move your hand towards it, it flashes blue and closes!\n"
                "Well, that didn't work as planned, " + name + ".")
    elif id == "StructureDoorKey":
        return ("You look at the key you are holding.\n"
                "It is flashing blue, as is the keyhole.")
    elif id == "Run":
        return ("You run, for a moment.\n"
                "And then you are floating. Down down down.\n"
                "You've fallen into a chasm, never to be seen again.\n"
                "Not very brave, are you, " + name + "?")
    elif id == "GameOver":
        return "Game over!"
    else:
        return ""

Challenge 16.2

When a player runs, they die (method doRun()). Change the code so that when they run, they lose a life. (Hint: we have a method in the class that does just this.) Then use isAlive() in an if statement. If the player is still alive, send them to doStart() to continue playing; if not, use gameOver().

##########################################
# Space Adventure
# by Ben & Shmuel
##########################################

# Imports
import Strings
import Utils
import Inventory as inv
import Player

# Create player object
p = Player.player()

# Welcome the player
def doWelcome():
    # Display text
    print(Strings.get("Welcome"))
 
# Location: Start
def doStart():
    # Display text
    print(Strings.get("Start"))
    # What can the player do?
    choices = [
        ["P", "Examine pile of boulders"],
        ["S", "Go to the structure"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"],
        ["I", "Inventory"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'P':
        doBoulders()
    elif choice == 'S':
        doStructure()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
    elif choice == "I":
        inv.display()
        doStart()

# Location: Boulders
def doBoulders():
    # Track this visit
    p.visitBoulder()
    # Display text
    if p.getBoulderVisits() == 1:
        print(Strings.get("Boulders"))
    elif p.getBoulderVisits() == 3:
        print(Strings.get("BouldersKey"))
        inv.takeStructureKey()
    else:
        print(Strings.get("Boulders2"))
    # Go back to start
    doStart()

# Location: Structure
def doStructure():
    # Display text
    print(Strings.get("Structure"))
    # What can the player do?
    choices = [
        ["S", "Back to start"],
        ["D", "Open the door"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStart()
    elif choice == 'D':
        doStructureDoor()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
 
# Location: Structure door
def doStructureDoor():
    # Display text
    print(Strings.get("StructureDoor"))
    if inv.hasStructureKey():
        print(Strings.get("StructureDoorKey"))
    else:
        print(Strings.get("StructureDoorNoKey"))
    # What can the player do?
    choices = [
        ["S", "Back to structure"],
        ["R", "Run!"]
    ]
    # Does user have the key?
    if inv.hasStructureKey():
        # Yep, add unlock to choices
        choices.insert(0, ["U","Unlock the door"])
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStructure()
    elif choice == 'R':
        doRun()
    elif choice == 'U':
        doEnterStructure()
 
# Location: Explore beeping
def doBeeping():
    pass
 
# Location: Enter structure
def doEnterStructure():
    pass
 
# Player ran
# This function changed to accommodate lives left
def doRun():
    # Display text
    print(Strings.get("Run"))
    # Player died, lose a life
    p.died()
    # Are there any lives left?
    if p.isAlive():
        # Back to the start
        doStart()
    else:
        # Dead, game over
        gameOver()

# Game over
def gameOver():
    print(Strings.get("GameOver"))
 
# Actual game starts here
# Display welcome message
doWelcome()
# Game start location
doStart()

Challenge 17.1

Now that you know how to color your output, go ahead and color the whole game.

Main.py

##########################################
# Space Adventure
# by Ben & Shmuel
##########################################

# Imports
import Strings
import Utils
import Inventory as inv
import Player
from colorama import init, Fore

# Create player object
p = Player.player()

# Initialize colorama
init()

# Welcome the player
def doWelcome():
    # Display text
    print(Fore.GREEN+Strings.get("Welcome"))
 
# Location: Start
def doStart():
    # Display text
    print(Fore.GREEN+Strings.get("Start"))
    # What can the player do?
    choices = [
        ["P", "Examine pile of boulders"],
        ["S", "Go to the structure"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"],
        ["I", "Inventory"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'P':
        doBoulders()
    elif choice == 'S':
        doStructure()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
    elif choice == "I":
        inv.display()
        doStart()

# Location: Boulders
def doBoulders():
    # Display text
    if p.getBoulderVisits() == 0:
        print(Fore.GREEN+Strings.get("Boulders1"))
    elif p.getBoulderVisits() == 2:
        print(Fore.GREEN+Strings.get("BouldersKey"))
        inv.takeStructureKey()
    else:
        print(Fore.GREEN+Strings.get("Boulders2"))
    p.visitBoulder()
    # Go back to start
    doStart()

# Location: Structure
def doStructure():
    # Display text
    print(Fore.GREEN+Strings.get("Structure"))
    # What can the player do?
    choices = [
        ["S", "Back to start"],
        ["D", "Open the door"],
        ["B", "Walk towards the beeping"],
        ["R", "Run!"]
    ]
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStart()
    elif choice == 'D':
        doStructureDoor()
    elif choice == 'B':
        doBeeping()
    elif choice == 'R':
        doRun()
 
# Location: Structure door
def doStructureDoor():
    # Display text
    print(Fore.GREEN+Strings.get("StructureDoor"))
    if inv.hasStructureKey():
        print(Fore.GREEN+Strings.get("StructureDoorKey"))
    else:
        print(Fore.RED+Strings.get("StructureDoorNoKey"))
    # What can the player do?
    choices = [
        ["S", "Back to structure"],
        ["R", "Run!"]
    ]
    # Does user have the key?
    if inv.hasStructureKey():
        # Yep, add unlock to choices
        choices.insert(0, ["U","Unlock the door"])
    # Prompt for user action
    choice = Utils.getUserChoice(choices)
    # Perform action
    if choice == 'S':
        doStructure()
    elif choice == 'R':
        doRun()
    elif choice == 'U':
        doEnterStructure()
 
# Location: Explore beeping
def doBeeping():
    pass
 
# Location: Enter structure
def doEnterStructure():
    pass
 
# Player ran
def doRun():
    # Display text
    print(Fore.GREEN+Strings.get("Run"))
    # Dead, game over
    gameOver()

# Game over
def gameOver():
    print(Fore.GREEN+Strings.get("GameOver"))
 
# Actual game starts here
# Display welcome message
doWelcome()
# Game start location
doStart()

Utils.py

############################################
# Utils.py
# Utility functions
############################################

from colorama import Fore

# getUserChoice()
# Displays a list of options, prompts for an option, and returns it
# Pass it a list of lists in format [["Letter","Display Text"]]
# Example: [["A","Option A"],["B","Option B"],["C","Option C"]]
# Returns selected letter
def getUserChoice(options):
    # Create a variable to hold valid inputs
    validInputs=""
    # Loop through the options
    for opt in options:
        # Add this one to the valid letters list
        validInputs+=opt[0]
        # And display it
        print(Fore.YELLOW+opt[0], "-", opt[1])
    # Create the prompt
    prompt="What do you want to do? [" + validInputs + "]: "
    # Initialize variables
    choice=""
    done=False
    # Main loop
    while not done:
        # Get a single upper case character
        choice=input(prompt).strip().upper()
        # If the user entered more then 1 character
        if len(choice) > 1:
            # Just use the first
            choice=choice[0]
        # Do we have 1 valid input?
        if len(choice) == 1 and choice in validInputs:
            # We do, outa here!
            done = True
    # Return the selected option
    return choice

Inventory.py

############################################
# Inventory.py
# Inventory system
############################################

# Imports
from colorama import Fore

inv = {
    "StructureKey": False,
    "Coins": 0
}

# Add key to inventory
def takeStructureKey():
    inv["StructureKey"] = True

# Remove key from inventory
def dropStructureKey():
    inv["StructureKey"] = False

# Does the player have the key?
def hasStructureKey():
    return inv["StructureKey"]

# Add coins to inventory
def takeCoins(coins):
    inv["Coins"] += coins

# Remove coins from inventory
def dropCoins(coins):
    inv["Coins"] -= coins

# How many coins does the player have?
def numCoins():
    return inv["Coins"]

# Display inventory
def display():
    print(Fore.CYAN+"*** Inventory ***")
    print(Fore.CYAN+"You have", numCoins(), "coins")
    if hasStructureKey():
        print(Fore.CYAN+"You have a key that flashes blue")
    print(Fore.CYAN+"*****************")

Part 3: Hit the Road

Challenge 19.1

Try changing the window size passed to set_mode(). Make it bigger, smaller, wider … you get the idea.

We defined three colors. Try changing fill() to use RED instead of WHITE. And then create your own color variables and use them. Remember, you can use any values between 0 and 255 for each of the RGB values. You may want to use just 0 and 255 as you play with the colors. (That still gives you 8 combinations to try.)

# Imports
import sys
import pygame
from pygame.locals import *
 
# Game colors 
# A bunch have been added!
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
ORANGE = (255, 127, 0)
YELLOW = (255, 255, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
INDIGO = (75, 0, 130)
VIOLET = (148, 0, 211)
 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Initialize game screen
screen = pygame.display.set_mode((1000, 600))    # Update Window size here
 
# Set background color
screen.fill(VIOLET) # Select color of choice here
 
# Update screen
pygame.display.update()
 
# Main game loop
while True:
    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
    # Update screen
    pygame.display.update()

Challenge 20.1

We’re only using images right now, and thus we have just an Images folder. But it’s good to plan for the future. Assume that you have Sound and Videos folders and create variables for each of those. You should be able to create each with a single line of code.

# Imports
import sys
import os
import pygame
from pygame.locals import *
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")
SOUND_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Sound")
VIDEO_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Video")

# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Initialize game screen
screen = pygame.display.set_mode((500, 800))
 
# Set background color
screen.fill(WHITE)
 
# Update screen
pygame.display.update()
 
# Main game loop
while True:
    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
    # Update screen
    pygame.display.update()

Challenge 20.2

We provided three different enemy cars for you to use. We’ll actually use all three in later chapters, but for now, you can experiment with them. Change the code so that the enemy car that gets placed at the top of the screen is Enemy2 or Enemy3.

# Imports
import sys, os, random
import pygame
from pygame.locals import *
 
# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")
 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
 
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Load images
IMG_ROAD = pygame.image.load(os.path.join(IMAGE_FOLDER, "Road.png"))
IMG_PLAYER = pygame.image.load(os.path.join(IMAGE_FOLDER, "Player.png"))
IMG_ENEMY = pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy3.png")) # Code change is here!
 
# Initialize game screen
screen = pygame.display.set_mode(IMG_ROAD.get_size())
 
# Create game objects
# Calculate initial player position
h=IMG_ROAD.get_width()//2
v=IMG_ROAD.get_height() - (IMG_PLAYER.get_height()//2)
# Create player sprite
player = pygame.sprite.Sprite()
player.image = IMG_PLAYER
player.surf = pygame.Surface(IMG_PLAYER.get_size())
player.rect = player.surf.get_rect(center = (h, v))
 
# Enemy
# Calculate initial enemy position
hl=IMG_ENEMY.get_width()//2
hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
h=random.randrange(hl, hr)
v=0
# Create enemy sprite
enemy = pygame.sprite.Sprite()
enemy.image = IMG_ENEMY
enemy.surf = pygame.Surface(IMG_ENEMY.get_size())
enemy.rect = enemy.surf.get_rect(center = (h, v))
 
# Main game loop
while True:
    # Place background
    screen.blit(IMG_ROAD, (0,0))
 
    # Place player on screen
    screen.blit(player.image, player.rect)
 
    # Place enemy on screen
    screen.blit(enemy.image, enemy.rect)
 
    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
    # Update screen
    pygame.display.update()

Challenge 21.1

Game speed is controlled by the moveSpeed variable. It specifies how many pixels the enemy should advance by and how many pixels the player sprite moves with each left or right arrow key press.

Try changing the moveSpeed value. You can try smaller numbers and larger numbers. Get a feel for how changing this value impacts game play.

# Imports
import sys, os, random
import pygame
from pygame.locals import *
 
# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")

# Game variables
moveSpeed = 10  # Change is here!
 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
 
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Load images
IMG_ROAD = pygame.image.load(os.path.join(IMAGE_FOLDER, "Road.png"))
IMG_PLAYER = pygame.image.load(os.path.join(IMAGE_FOLDER, "Player.png"))
IMG_ENEMY = pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy.png"))
 
# Initialize game screen
screen = pygame.display.set_mode(IMG_ROAD.get_size())
 
# Create game objects
# Calculate initial player position
h=IMG_ROAD.get_width()//2
v=IMG_ROAD.get_height() - (IMG_PLAYER.get_height()//2)
# Create player sprite
player = pygame.sprite.Sprite()
player.image = IMG_PLAYER
player.surf = pygame.Surface(IMG_PLAYER.get_size())
player.rect = player.surf.get_rect(center = (h, v))
 
# Enemy
# Calculate initial enemy position
hl=IMG_ENEMY.get_width()//2
hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
h=random.randrange(hl, hr)
v=0
# Create enemy sprite
enemy = pygame.sprite.Sprite()
enemy.image = IMG_ENEMY
enemy.surf = pygame.Surface(IMG_ENEMY.get_size())
enemy.rect = enemy.surf.get_rect(center = (h, v))
 
# Main game loop
while True:
    # Place background
    screen.blit(IMG_ROAD, (0,0))
 
    # Place player on screen
    screen.blit(player.image, player.rect)

    # Get keys pressed
    keys = pygame.key.get_pressed()
    # Check for LEFT key
    if keys[K_LEFT] and player.rect.left > 0:
        # Move left
        player.rect.move_ip(-moveSpeed, 0)
        # Make sure we didn't go too far left
        if player.rect.left < 0:
            # To far, fix it
            player.rect.left = 0
    #  Check for RIGHT key
    if keys[K_RIGHT] and player.rect.right < IMG_ROAD.get_width():
        # Move right
        player.rect.move_ip(moveSpeed, 0)
        # Make sure we didn't go too far right
        if player.rect.right > IMG_ROAD.get_width():
            # To far, fix it
            player.rect.right = IMG_ROAD.get_width()

 
    # Place enemy on screen
    screen.blit(enemy.image, enemy.rect)

    # Move enemy downwards        
    enemy.rect.move_ip(0, moveSpeed)
    # Check didn't go off edge of screen
    if (enemy.rect.bottom > IMG_ROAD.get_height()):
        # Calculate new random location
        hl=IMG_ENEMY.get_width()//2
        hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
        h=random.randrange(hl, hr)
        v=0
        # And place it
        enemy.rect.center = (h, v)

    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
    # Update screen
    pygame.display.update()

Challenge 21.2

We used the left and right keys to move the player sprite. You don’t have to use those keys; you can pick your own. Many games use A and S, for example. Update the code to use those keys. You’ll want to check for K_a and K_s.

Or allow both sets of keys, letting players use left arrow or A to go left and right arrow or S to go right. Here’s a hint: You can do this easily with an or in your if statement. If you do this, be careful to group your conditions correctly with parentheses because you’ll have an and and an or.

# Imports
import sys, os, random
import pygame
from pygame.locals import *
 
# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")

# Game variables
moveSpeed = 5
 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
 
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Load images
IMG_ROAD = pygame.image.load(os.path.join(IMAGE_FOLDER, "Road.png"))
IMG_PLAYER = pygame.image.load(os.path.join(IMAGE_FOLDER, "Player.png"))
IMG_ENEMY = pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy.png"))
 
# Initialize game screen
screen = pygame.display.set_mode(IMG_ROAD.get_size())
 
# Create game objects
# Calculate initial player position
h=IMG_ROAD.get_width()//2
v=IMG_ROAD.get_height() - (IMG_PLAYER.get_height()//2)
# Create player sprite
player = pygame.sprite.Sprite()
player.image = IMG_PLAYER
player.surf = pygame.Surface(IMG_PLAYER.get_size())
player.rect = player.surf.get_rect(center = (h, v))
 
# Enemy
# Calculate initial enemy position
hl=IMG_ENEMY.get_width()//2
hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
h=random.randrange(hl, hr)
v=0
# Create enemy sprite
enemy = pygame.sprite.Sprite()
enemy.image = IMG_ENEMY
enemy.surf = pygame.Surface(IMG_ENEMY.get_size())
enemy.rect = enemy.surf.get_rect(center = (h, v))
 
# Main game loop
while True:
    # Place background
    screen.blit(IMG_ROAD, (0,0))
 
    # Place player on screen
    screen.blit(player.image, player.rect)

    # Get keys pressed
    keys = pygame.key.get_pressed()
    # Check for LEFT key
    if (keys[K_LEFT] or keys[K_a]) and player.rect.left > 0:
        # Move left
        player.rect.move_ip(-moveSpeed, 0)
        # Make sure we didn't go too far left
        if player.rect.left < 0:
            # To far, fix it
            player.rect.left = 0
    #  Check for RIGHT key
    if (keys[K_RIGHT] or keys[K_d]) and player.rect.right < IMG_ROAD.get_width():
        # Move right
        player.rect.move_ip(moveSpeed, 0)
        # Make sure we didn't go too far right
        if player.rect.right > IMG_ROAD.get_width():
            # To far, fix it
            player.rect.right = IMG_ROAD.get_width()

 
    # Place enemy on screen
    screen.blit(enemy.image, enemy.rect)

    # Move enemy downwards        
    enemy.rect.move_ip(0, moveSpeed)
    # Check didn't go off edge of screen
    if (enemy.rect.bottom > IMG_ROAD.get_height()):
        # Calculate new random location
        hl=IMG_ENEMY.get_width()//2
        hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
        h=random.randrange(hl, hr)
        v=0
        # And place it
        enemy.rect.center = (h, v)
 
    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
    # Update screen
    pygame.display.update()

Challenge 22.1

The game gets faster (and thus harder) the longer you play. But the scoring stays the same: 1 point per car avoided. Can you change that so that once the game doubles in speed, players get 2 points per car avoided?

# Imports
import sys, os, random
import pygame
from pygame.locals import *
 
# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")

# Game variables
moveSpeed = 5
maxSpeed = 10
score = 0

# Game over function
def GameOver():
    # Quit Pygame 
    pygame.quit()
    sys.exit()
 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
 
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Load images
IMG_ROAD = pygame.image.load(os.path.join(IMAGE_FOLDER, "Road.png"))
IMG_PLAYER = pygame.image.load(os.path.join(IMAGE_FOLDER, "Player.png"))
IMG_ENEMY = pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy.png"))
 
# Initialize game screen
screen = pygame.display.set_mode(IMG_ROAD.get_size())
 
# Create game objects
# Calculate initial player position
h=IMG_ROAD.get_width()//2
v=IMG_ROAD.get_height() - (IMG_PLAYER.get_height()//2)
# Create player sprite
player = pygame.sprite.Sprite()
player.image = IMG_PLAYER
player.surf = pygame.Surface(IMG_PLAYER.get_size())
player.rect = player.surf.get_rect(center = (h, v))
 
# Enemy
# Calculate initial enemy position
hl=IMG_ENEMY.get_width()//2
hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
h=random.randrange(hl, hr)
v=0
# Create enemy sprite
enemy = pygame.sprite.Sprite()
enemy.image = IMG_ENEMY
enemy.surf = pygame.Surface(IMG_ENEMY.get_size())
enemy.rect = enemy.surf.get_rect(center = (h, v))
 
# Main game loop
while True:
    # Update caption with score
    pygame.display.set_caption("Crazy Driver - Score " + str(score))

    # Place background
    screen.blit(IMG_ROAD, (0,0))
 
    # Place player on screen
    screen.blit(player.image, player.rect)

    # Get keys pressed
    keys = pygame.key.get_pressed()
    # Check for LEFT key
    if keys[K_LEFT] and player.rect.left > 0:
        # Move left
        player.rect.move_ip(-moveSpeed, 0)
        # Make sure we didn't go too far left
        if player.rect.left < 0:
            # To far, fix it
            player.rect.left = 0
    #  Check for RIGHT key
    if keys[K_RIGHT] and player.rect.right < IMG_ROAD.get_width():
        # Move right
        player.rect.move_ip(moveSpeed, 0)
        # Make sure we didn't go too far right
        if player.rect.right > IMG_ROAD.get_width():
            # To far, fix it
            player.rect.right = IMG_ROAD.get_width()

 
    # Place enemy on screen
    screen.blit(enemy.image, enemy.rect)

    # Move enemy downwards        
    enemy.rect.move_ip(0, moveSpeed)
    # Check didn't go off edge of screen
    if (enemy.rect.bottom > IMG_ROAD.get_height()):
        # Calculate new random location
        hl=IMG_ENEMY.get_width()//2
        hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
        h=random.randrange(hl, hr)
        v=0
        # And place it
        enemy.rect.center = (h, v)
        # Update the score
        # 2 points if doubled in speed
        # Note: if maxSpeed wasn't double moveSpeed, you may need to make a startSpeed variable to test for when speed has doubled 
        if moveSpeed == maxSpeed:
            score += 2
        # 1 point otherwise
        else:
            score += 1
        # Increase the speed
        if moveSpeed < maxSpeed:
            moveSpeed += 1

    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
     # Check for collisions
    if pygame.sprite.collide_rect(player, enemy):
        # Crash! Game over
        GameOver()

    # Update screen
    pygame.display.update()

Challenge 23.1

We created a paused variable to track whether or not the game is paused: True if it is, False if not. Was this necessary? Actually, no, it wasn’t. There is another way to know if we’re paused or not: just look at moveSpeed, which will be 0 only if the game is paused. Modify the code to remove the paused variable, and use the existing moveSpeed variable to pause (and unpause) game play.

# Imports
import sys, os, random, time
import pygame
from pygame.locals import *
 
# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")

# Game variables
moveSpeed = 5
maxSpeed = 10
score = 0
textFonts = ['comicsansms','arial']
textSize = 48

# GameOver function
# Displays message and cleans things up
def GameOver():
    # Game Over text creation
    fontGameOver = pygame.font.SysFont(textFonts, textSize)
    textGameOver = fontGameOver.render("Game Over!", True, RED)
    rectGameOver = textGameOver.get_rect()
    rectGameOver.center = (IMG_ROAD.get_width()//2, IMG_ROAD.get_height()//2)
    # Black screen with game over text
    screen.fill(BLACK)
    screen.blit(textGameOver, rectGameOver)
    # Update the display
    pygame.display.update()
    # Destroy objects
    player.kill()
    enemy.kill()
    # Pause
    time.sleep(5)
    # Quit pygame 
    pygame.quit()
    sys.exit()

 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
 
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Load images
IMG_ROAD = pygame.image.load(os.path.join(IMAGE_FOLDER, "Road.png"))
IMG_PLAYER = pygame.image.load(os.path.join(IMAGE_FOLDER, "Player.png"))
IMG_ENEMY = pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy.png"))
 
# Initialize game screen
screen = pygame.display.set_mode(IMG_ROAD.get_size())
 
# Create game objects
# Calculate initial player position
h=IMG_ROAD.get_width()//2
v=IMG_ROAD.get_height() - (IMG_PLAYER.get_height()//2)
# Create player sprite
player = pygame.sprite.Sprite()
player.image = IMG_PLAYER
player.surf = pygame.Surface(IMG_PLAYER.get_size())
player.rect = player.surf.get_rect(center = (h, v))
 
# Enemy
# Calculate initial enemy position
hl=IMG_ENEMY.get_width()//2
hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
h=random.randrange(hl, hr)
v=0
# Create enemy sprite
enemy = pygame.sprite.Sprite()
enemy.image = IMG_ENEMY
enemy.surf = pygame.Surface(IMG_ENEMY.get_size())
enemy.rect = enemy.surf.get_rect(center = (h, v))
 
# Main game loop
while True:
    # Update caption with score
    pygame.display.set_caption("Crazy Driver - Score " + str(score))

    # Place background
    screen.blit(IMG_ROAD, (0,0))
 
    # Place player on screen
    screen.blit(player.image, player.rect)

    # Get keys pressed
    keys = pygame.key.get_pressed()
 
    # Are we paused?
    if moveSpeed == 0:
        # Check for SPACE
        if not keys[K_SPACE]:
            # Turn off pause
            # Set speed back to what it was
            moveSpeed=tempSpeed
    else:
        # Check for LEFT key
        if keys[K_LEFT] and player.rect.left > 0:
            # Move left
            player.rect.move_ip(-moveSpeed, 0)
            # Make sure we didn't go too far left
            if player.rect.left < 0:
                # To far, fix it
                player.rect.left = 0
        #  Check for RIGHT key
        if keys[K_RIGHT] and player.rect.right < IMG_ROAD.get_width():
            # Move right
            player.rect.move_ip(moveSpeed, 0)
            # Make sure we didn't go too far right
            if player.rect.right > IMG_ROAD.get_width():
                # To far, fix it
                player.rect.right = IMG_ROAD.get_width()
        # Check for SPACE key
        if keys[K_SPACE]:
            # Turn on pause
            # Save speed
            tempSpeed=moveSpeed
            # Set speed to 0
            moveSpeed=0
 
    # Place enemy on screen
    screen.blit(enemy.image, enemy.rect)

    # Move enemy downwards        
    enemy.rect.move_ip(0, moveSpeed)
    # Check didn't go off edge of screen
    if (enemy.rect.bottom > IMG_ROAD.get_height()):
        # Calculate new random location
        hl=IMG_ENEMY.get_width()//2
        hr=IMG_ROAD.get_width()-(IMG_ENEMY.get_width()//2)
        h=random.randrange(hl, hr)
        v=0
        # And place it
        enemy.rect.center = (h, v)
        # Update the score
        score += 1
        # Increase the speed
        if moveSpeed < maxSpeed:
            moveSpeed += 1

    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
     # Check for collisions
    if pygame.sprite.collide_rect(player, enemy):
        # Crash! Game over
        GameOver()

    # Update screen
    pygame.display.update()

Challenge 23.2

Can you add additional vehicles to the game? You can create your own PNG files or try to find some online. Save the images to the Images folder and then add them to IMG_ENEMIES.

# Imports
import sys, os, random, time
import pygame
from pygame.locals import *
 
# Game colors 
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED   = (255, 0, 0)
 
# Build game paths
GAME_ROOT_FOLDER=os.path.dirname(__file__)
IMAGE_FOLDER=os.path.join(GAME_ROOT_FOLDER, "Images")

# Game variables
moveSpeed = 5
maxSpeed = 10
score = 0
textFonts = ['comicsansms','arial']
textSize = 48
paused = False
eNum = -1

# GameOver function
# Displays message and cleans things up
def GameOver():
    # Game Over text creation
    fontGameOver = pygame.font.SysFont(textFonts, textSize)
    textGameOver = fontGameOver.render("Game Over!", True, RED)
    rectGameOver = textGameOver.get_rect()
    rectGameOver.center = (IMG_ROAD.get_width()//2, IMG_ROAD.get_height()//2)
    # Black screen with game over text
    screen.fill(BLACK)
    screen.blit(textGameOver, rectGameOver)
    # Update the display
    pygame.display.update()
    # Destroy objects
    player.kill()
    enemy.kill()
    # Pause
    time.sleep(5)
    # Quit pygame 
    pygame.quit()
    sys.exit()

 
# Main game starts here
# Initialize PyGame
pygame.init()
 
# Initialize frame manager
clock = pygame.time.Clock()
 
# Set frame rate
clock.tick(60)
 
# Set caption bar
pygame.display.set_caption("Crazy Driver")
 
# Load images
IMG_ROAD = pygame.image.load(os.path.join(IMAGE_FOLDER, "Road.png"))
IMG_PLAYER = pygame.image.load(os.path.join(IMAGE_FOLDER, "Player.png"))
IMG_ENEMIES = []
IMG_ENEMIES.append(pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy.png")))
IMG_ENEMIES.append(pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy2.png")))
IMG_ENEMIES.append(pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy3.png"))) 
IMG_ENEMIES.append(pygame.image.load(os.path.join(IMAGE_FOLDER, "Enemy4.png"))) 

# Initialize game screen
screen = pygame.display.set_mode(IMG_ROAD.get_size())
 
# Create game objects
# Calculate initial player position
h=IMG_ROAD.get_width()//2
v=IMG_ROAD.get_height() - (IMG_PLAYER.get_height()//2)
# Create player sprite
player = pygame.sprite.Sprite()
player.image = IMG_PLAYER
player.surf = pygame.Surface(IMG_PLAYER.get_size())
player.rect = player.surf.get_rect(center = (h, v))
 
# Main game loop
while True:
    # Update caption with score
    pygame.display.set_caption("Crazy Driver - Score " + str(score))

    # Place background
    screen.blit(IMG_ROAD, (0,0))

    # Make sure we have an enemy
    if eNum == -1:
        # Get a random enemy
        eNum = random.randrange(0, len(IMG_ENEMIES))
        # Calculate initial enemy position
        hl=IMG_ENEMIES[eNum].get_width()//2
        hr=IMG_ROAD.get_width()-(IMG_ENEMIES[eNum].get_width()//2)
        h=random.randrange(hl, hr)
        v=0
        # Create enemy sprite
        enemy = pygame.sprite.Sprite()
        enemy.image = IMG_ENEMIES[eNum]
        enemy.surf = pygame.Surface(IMG_ENEMIES[eNum].get_size())
        enemy.rect = enemy.surf.get_rect(center = (h, v))

    # Place player on screen
    screen.blit(player.image, player.rect)

    # Get keys pressed
    keys = pygame.key.get_pressed()
 
    # Are we paused?
    if paused:
        # Check for SPACE
        if not keys[K_SPACE]:
            # Turn off pause
            # Set speed back to what it was
            moveSpeed=tempSpeed
            # Turn off flag
            paused=False
    else:
        # Check for LEFT key
        if keys[K_LEFT] and player.rect.left > 0:
            # Move left
            player.rect.move_ip(-moveSpeed, 0)
            # Make sure we didn't go too far left
            if player.rect.left < 0:
                # To far, fix it
                player.rect.left = 0
        #  Check for RIGHT key
        if keys[K_RIGHT] and player.rect.right < IMG_ROAD.get_width():
            # Move right
            player.rect.move_ip(moveSpeed, 0)
            # Make sure we didn't go too far right
            if player.rect.right > IMG_ROAD.get_width():
                # To far, fix it
                player.rect.right = IMG_ROAD.get_width()
        # Check for SPACE key
        if keys[K_SPACE]:
            # Turn on pause
            # Save speed
            tempSpeed=moveSpeed
            # Set speed to 0
            moveSpeed=0
            # Turn on flag
            paused=True
 
    # Place enemy on screen
    screen.blit(enemy.image, enemy.rect)

    # Move enemy downwards        
    enemy.rect.move_ip(0, moveSpeed)
    # Check didn't go off edge of screen
    if (enemy.rect.bottom > IMG_ROAD.get_height()):
        # Kill enemy object
        enemy.kill()
        # No enemy
        eNum = -1
        # Increment the score
        score += 1
        # Increase the speed
        moveSpeed += 1
        # Increase the speed
        if moveSpeed < maxSpeed:
            moveSpeed += 1


    # Check for events
    for event in pygame.event.get():
        # Did the player quit?
        if event.type == QUIT:
            # Quit pygame
            pygame.quit()
            sys.exit()
 
     # Check for collisions
    if pygame.sprite.collide_rect(player, enemy):
        # Crash! Game over
        GameOver()

    # Update screen
    pygame.display.update()