Around The World
LAB6

carmen.jpg

Welcome

Let’s learn some geography as we pursue Carmen Sandiego around the world. We might not catch her, but we’ll increase our worldly knowledge! And we’ll practice with a couple of our recently learned Python skills with functions and data structures.

We’re going to learn more than simple programming here. We’ll focus on building bigger and better software, and gain three powerful skills: how to make programs span multiple files, how to make our projects self-contained, and how to integrate software libraries written by others.

What We Will Learn

Comments Match statements Multi-file Programs Virtual Environments Third-party Libraries

Activity

Begin by creating the folder ~/cmsi1010/lab06, and in it, a new file called carmen.py, with the following contents:

current_country_name = "colombia"

while True:
    print("Guess where Carmen is, or say 'hint' or 'exit'.")
    guess = input("Where are you going to look? ").strip().lower()
    if guess == current_country_name:
        print("She was here, but you missed her by one hour!")
    elif guess == "hint":
        print("Sorry, no hints yet.")
    elif guess == "exit":
        print("Thank you for playing!")
        break
    else:
        print("Oh no, she’s not here!")

Hopefully that program should be not be too hard to follow after doing the two previous labs. Test it out to make sure it’s working.

Let’s make it slightly more interesting, adding a few more countries:

import random

country_names = ["colombia", "barbados", "vanuatu", "eswatini", "bhutan", "iceland"]
current_country_name = random.choice(country_names)

while True:
    print("Guess where Carmen is, or say 'hint' or 'exit'.")
    guess = input("Where are you going to look? ").strip().lower()
    if guess == current_country_name:
        print("She was here, but you missed her by one hour!")
        current_country_name = random.choice(country_names)
    elif guess == "hint":
        print("Sorry, no hints yet.")
    elif guess == "exit":
        print("Thank you for playing!")
        break
    else:
        print("Oh no, she’s not here!")

Multiple Files For Our Program

Now, to provide hints, we’ll create a little “database” of country facts. We’ll also learn some nice programming practices like not letting our files get too big, so we’ll put the country facts in a separate file! Create a new file in the lab06 folder called geography.py, starting things off with:

countries = {
    "colombia":  {
        "capital": "Bogotá",
        "coordinates": (4.7110, -74.0721),
        "region": "South America",
        "landmarks": ["Monserrate", "El Peñón de Guatapé", "Las Lajas Sanctuary"],
    },
    "barbados":  {
        "capital": "Bridgetown",
        "coordinates": (13.0971, -59.6132),
        "region": "Caribbean",
        "landmarks": ["Crane Beach", "Harrison's Cave", "St. Nicholas Abbey"]
    },
    "vanuatu":  {
        "capital": "Port Vila",
        "coordinates": (-17.7430, 168.3173),
        "region": "Oceania",
        "landmarks": ["Mount Yasur", "Champagne Beach", "Chief Roi Mata's Domain"]
    },
    "eswatini":  {
        "capital": "Mbabane",
        "coordinates": (-26.3264, 31.1442),
        "region": "Southern Africa",
        "landmarks": ["Sibebe Rock", "Mlilwane Wildlife Sanctuary", "Mantenga Nature Reserve"]
    },
    "bhutan":  {
        "capital": "Thimphu",
        "coordinates": (27.4716, 89.6386),
        "region": "South Asia",
        "landmarks": ["Paro Taktsang", "Punakha Dzong", "Rinpung Dzong"]
    },
    "iceland":  {
        "capital": "Reykjavik",
        "coordinates": (64.1470, -21.9408),
        "region": "Northern Europe",
        "landmarks": ["Blue Lagoon", "Hallgrímskirkja", "Gullfoss Waterfall"]
    }
}

Congratulate yourself on creating a dictionary containing dictionaries, each with a list and a tuple. (A tuple is like a list, except you cannot update any of its components: we say a tuples are immutable. Later in your career you will learn why immutability is awesome.) A good deal of what programmers do is organize data.

Putting that information in a separate file is a good idea: it keeps things clean! Of course, the main program in carmen.py will need to “see” the code in geography.py. Here’s how we will do it. In carmen.py, right after import random add the line:

from geography import countries
Is this the best way to organize this data?

If you already know Python, you may be wondering why the information above was not represented with a “class.” Answer: We’re just learning dictionaries before classes, that’s all. We have to start somewhere. Later, in a subsequent lab, we’ll see how classes can improve things a bit. Or a lot.

Match Statements

Now that we have this database, we can update our game engine to give hints. How should hints look? We have the capital city, three landmarks, and a geographical region. So there are several possible hints we can give. For Colombia, these are possible hints:

We want to randomly select one of the five hints. Sometimes, when you have an idea, it’s good to write a function for it. You can now safely add the following function to carmen.py. (Since it is never called, there is no harm in adding it.)

def random_hint(country):
    match random.choice(["capital", "region", "landmark"]):
        case "capital":
            hint = "whose capital is " + country["capital"]
        case "region":
            hint = "in " + country["region"]
        case "landmark":
            hint = "where you can find " + random.choice(country["landmarks"])
    return "Carmen is in a country " + hint

The country parameter will be one of the objects in the big countries dictionary we defined in countries.py.

Comments

Previously, we selected a random country name from a list of country names. Now that we have a “database” of countries, our random selection has to come from the dictionary’s keys. But random.choice() only works on lists, so getting a random key from a dictionary can get messy. When things get messy, it’s often advisable to stick all the messy code in a nicely-named function. And sometimes, to explain the funky goings-on, it is useful to write a comment. A comment is like a note in the code. It never gets executed. Comments can be useful for explaining things, and helping you to remember things when you look at your code in the future and forget what you’ve done. But you do need to remember to keep them relevant and accurate. Ready? Add this function to carmen.py:

def random_country_name():
    # Select a random country name from the key in the countries dictionary we imported.
    # We have to convert the keys to a list, because random.choice() only works on lists,
    # and the keys of a dictionary are (surprisingly!) not a list in Python.
    return random.choice(list(countries.keys()))

Now we need to rewrite the game playing logic, just a little, to take advantage of our new functions. You are welcome to try to figure it out on your own, but feel free to follow the in-class code along where we will be growing carmen.py to this:

import random
from geography import countries


def random_country_name():
    # Select a random country name from the key in the countries dictionary we imported.
    # We have to convert the keys to a list, because random.choice() only works on lists,
    # and the keys of a dictionary are (surprisingly!) not a list in Python.
    return random.choice(list(countries.keys()))


def random_hint(country):
    match random.choice(["capital", "region", "landmark"]):
        case "capital":
            hint = "whose capital is " + country["capital"]
        case "region":
            hint = "in " + country["region"]
        case "landmark":
            hint = "where you can find " + random.choice(country["landmarks"])
    return "Carmen is in a country " + hint


current_country_name = random_country_name()
while True:
    country = countries[current_country_name]
    print("Guess where Carmen is, or say 'hint' or 'exit'.")
    guess = input("Where are you going to look? ").strip().lower()
    if guess == current_country_name:
        print("She was here, but you missed her by one hour!")
        current_country_name = random_country_name()
    elif guess == "hint":
        print(random_hint(country))
    elif guess == "exit":
        print("Thank you for playing!")
        break
    else:
        print("Oh no, she’s not here!")

Test it out! You should be able to get hints, and the game should continue until you type exit.

External Libraries

We haven’t used those latitudes and longitudes yet. We can integrate them into our hints, enabling us to say how far away Carmen is. To do that, we have to be able to compute the distance between two points on the Earth’s surface. While you could write that function yourself, it’s often easier to use a library that already has this functionality. One such library is haversine.

We need to fetch this library over the Internet and install it on our local machine, somewhere in our ~/cmsi1010 folder. The accepted way to do this in Python-land is to first create a virtual environment for this folder.

In your cmsi1010 folder, run, if python3 is the way you run Python programs so far, this command:

  python3 -m venv env

or, if you normally use python:

  python -m venv env

A virtual environment, or venv, lets you isolate this folder’s Python code from all the other Python environments on your machine. You can now install whatever libraries you want into this environment, and they won’t affect any other projects you happen to be working on.

Run the ls command to see that you have a new folder env inside of your cmsi1010 folder. You don’t ever want to commit this folder, so make sure the line env is added to your .gitignore file. Add it if it is not already there.

Now, enter the virtual environment:

Your terminal prompt should now be decorated with (env). That’s how you know you are “in” the virtual environment. If you ever need to leave the virtual environment, enter the command deactivate. Whether you leave on purpose, or whether you accidentally get kicked out of the environment, you can always return with the proper command above.

Now, while in the virtual environment:

  pip install haversine

You should see activity on your terminal that indicates the pip program is fetching the haversine library from somewhere on the Internet and downloading it to your machine, where it will be tucked away into the new env folder and therefore usable by programs you write.

Make sure you stay in the virtual environment

When programming in Python, you should always have a virtual environment for each of your projects, and always work “in” this environment. If you ever make a new terminal window, make sure to enter the environment with the proper source command above. Ditto if you ever end up deactivating the venv for any reason.

You can always check that you are in the environment by looking for the (env) prefix on your terminal prompt.

Let’s browse the haversine library documentation in class. Now let’s integrate it into our code. First we need to import from this library. The syntax is the same as importing from one of our own files. Rewrite the top three lines of carmen.py like so:

import random
from haversine import haversine
from geography import countries

Granted, it might look weird at first that the library is called haversine and the function we are importing is also called haversine. But it’s totally fine.

Now, let’s make the distance from Los Angeles (where I am as I am writing this lab) one of hints. Here’s the new random_hint function:

def random_hint(country):
    match random.choice(["capital", "region", "landmark", "distance"]):
        case "capital":
            hint = "whose capital is " + country["capital"]
        case "region":
            hint = "in " + country["region"]
        case "landmark":
            hint = "where you can find " + random.choice(country["landmarks"])
        case "distance":
            los_angeles = (34.0522, -118.2437)
            from_LA = haversine(los_angeles, country["coordinates"], unit="km")
            hint = "approximately " + str(from_LA) + " km from Los Angeles"
    return "Carmen is in a country " + hint

If you run this long enough to get some distance hints, you’ll notice that the distances, while correct, are far too precise. This is something we’ll clean up in the challenges below.

Teaser for the next lab: You might also notice the random_hint function has a lot of string additions (concatenations). This is not a good look. In the next lab, we’ll learn the right way to combine fragments of text together. You’ll like it!

Challenges

Now it’s your turn. Here are some ideas for you to extend the activities above:

Further Study

We’re making still more progress with Python. Continue with your reading:

Summary

We’ve covered:

  • Match statements
  • Import clauses
  • Comments

Recall Practice

Here are some questions useful for your spaced repetition learning. Many of the answers are not found on this page. Some will have popped up in lecture. Others will require you to do your own research.

  1. How do you get out of a while True: loop?
    Use a break statement.
  2. What is a match statement?
    A way to do one of many possible things based on how a particular value matches a set of possible patterns.
  3. What is a tuple?
    An immutable, ordered sequence of values.
  4. How does a tuple differ from a list in Python?
    A tuple is immutable, meaning its contents cannot be changed after creation, while a list is mutable and can be modified. You can update individual elements in a list, and you can grow and shrink a list. You cannot do any of those things to a tuple.
  5. What is a dictionary?
    A dictionary is a collection of “key-value” pairs in Python, used for looking thing up by key.
  6. Suppose you had a file called geometry.py which defined a function called circle_area. How would you arrange to use that function in another file?
    In the other file, you would write: from geometry import circle_area.
  7. How do you select a random element from a list named favorites?
    random.choice(favorites).
  8. Given a dictionary called capitals with contents such as {"CA": "Sacramento", "HI": "Honolulu", ...}, how would you select a random state?
    You would write: random.choice(list(capitals.keys())).
  9. What is a comment in Python?
    A comment is a line of text in the code that is not executed, used to explain or annotate the code for better understanding.
  10. What is the purpose of a comment?
    To explain the code, clarify its purpose, or provide additional context for future readers (including yourself).
  11. What should you be careful about when writing comments?
    You should ensure that comments are relevant, accurate, and kept up to date with the code they describe.
  12. What is a virtual environment?
    A way to isolate a Python project from other Python projects on your machine, allowing you to install libraries without affecting other projects.
  13. How do you create a virtual environment?
    Run python -m venv env in your project folder.
  14. How do you activate a virtual environment?
    On macOS or Linux, use source env/bin/activate; on Windows Git Bash, use source env/Scripts/activate.
  15. What command do you use to install a package in a virtual environment?
    pip install followed by the mane of the package.