In this section of the lesson you've learned the basic programming concepts of lists, loops, decision structures, and string manipulation. You might be surprised at what you can do with just these skills. In this section, we'll practice putting them all together to address a scenario. This will give us an opportunity to talk about strategies for approaching programming problems in general.
The scenario we'll tackle is to simulate a one-player game of Hasbro's children's game "Hi Ho! Cherry-O." In this simple game of chance, you begin with 10 cherries on a tree. You take a turn by spinning a random spinner which tells you whether you get to add or remove cherries on the turn. The possible spinner results are:
- Remove 1 cherry
- Remove 2 cherries
- Remove 3 cherries
- Remove 4 cherries
- Bird visits your cherry bucket (Add 2 cherries)
- Dog visits your cherry bucket (Add 2 cherries)
- Spilled bucket (Place all 10 cherries back on your tree)
You continue taking turns until you have 0 cherries left on your tree, at which point you have won the game. Your objective here is to write a script that simulates the game, printing the following:
- The result of each spin
- The number of cherries on your tree after each turn. This must always be between 0 and 10.
- The final number of turns needed to win the game
Approaching a programming problem
Although this example may seem juvenile, it's an excellent way to practice everything you just learned. As a beginner, you may seem overwhelmed by the above problem. A common question is, "Where do I start?" The best approach is to break down the problem into smaller chunks of things you know how to do.
One of the most important programming skills you can acquire is the ability to verbalize a problem and translate it into a series of small programming steps. Here's a list of things you would need to do in this script. Programmers call this pseudocode because it's not written in code, but it follows the sequence their code will need to take.
- Spin the spinner
- Print the spin result
- Add or remove cherries based on the result
- Make sure the number of cherries is between 0 and 10
- Print the number of cherries on the tree
- Take another turn or print the number of turns it took to win the game
It also helps to list the variables you'll need to keep track of:
- Number of cherries currently on the tree (Starts at 10)
- Number of turns taken (Starts at 0)
- Value of the spinner (Random)
Let's try to address each of the pseudocode steps. Don't worry about the full flow of the script yet. Rather, try to understand how each step of the problem should be solved with code. Assembling the blocks of code at the end is relatively trivial.
Spin the spinner
How do you simulate a random spin? In one of our previous examples, we used the random module to generate a random number within a range of integers; however, the choices on this spinner are not linear. A good approach here is to store all spin possibilities in a list and use the random number generator to pick the index for one of the possibilities. On its own, the code would look like this:
import random spinnerChoices = [-1, -2, -3, -4, 2, 2, 10] spinIndex = random.randrange(0, 7) spinResult = spinnerChoices[spinIndex]
The list spinnerChoices holds all possible mathematical results of a spin (remove 1 cherry, remove 2 cherries, etc.). The final value 10 represents the spilled bucket (putting all cherries back on the tree).
You need to pick one random value out of this list to simulate a spin. The variable spinIndex represents a random integer from 0 to 6 that is the index of the item you'll pull out of the list. For example, if spinIndex turns out to be 2, your spin is -3 (remove 3 cherries from the tree). The spin is held in the variable spinResult.
The random.randrange() method is used to pick the random numbers. At the beginning of your script, you have to import the random module in order to use this method.
Print the spin result
Once you have a spin result, it only takes one line of code to print it. You'll have to use the str() method to cast it to a string, though.
print ("You spun " + str(spinResult) + ".")
Add or remove cherries based on the result
As mentioned above, you need to have some variable to keep track of the number of cherries on your tree. This is one of those variables that it helps to name intuitively:
cherriesOnTree = 10
After you complete a spin, you need to modify this variable based on the result. Remember that the result is held in the variable spinResult and that a negative spinResult removes cherries from your tree. So your code to modify the number of cherries on the tree would look like:
cherriesOnTree += spinResult
Remember, the above is shorthand for saying cherriesOnTree = cherriesOnTree + spinResult.
Make sure the number of cherries is between 0 and 10
If you win the game you have 0 cherries. You don't have to reach 0 exactly, but it doesn't make sense to say that you have negative cherries. Similarly, you might spin the spilled bucket, which for simplicity we represented with positive 10 in the spinnerChoices. You are not allowed to have more than 10 cherries on the tree.
A simple if/elif decision structure can help you keep the cherriesOnTree within 0 and 10:
if cherriesOnTree > 10: cherriesOnTree = 10 elif cherriesOnTree < 0: cherriesOnTree = 0
This means, if you wound up with more than 10 cherries on the tree, set cherriesOnTree back to 10. If you wound up with fewer than 0 cherries, set cherriesOnTree to 0.
Print the number of cherries on the tree
All you have to do for this step is to print your cherriesOnTree variable, casting it to a string so it can legally be inserted into a sentence.
print ("You have " + str(cherriesOnTree) + "cherries on your tree.")
Take another turn or print the number of turns it took to win the game
You probably anticipated that you would have to figure out a way to take multiple turns. This is the perfect scenario for a loop.
What is the loop condition? There have to be some cherries left on the tree in order to start another turn, so you could begin the loop this way:
while cherriesOnTree > 0:
Much of the code we wrote above would go inside the loop to simulate a turn. Since we need to keep track of the number of turns taken, at the end of the loop we need to increment a counter:
turns += 1
This turns variable would have to be initialized at the beginning of the script, before the loop.
This code could print the number of turns at the end of the game:
print ("It took you " + str(turns) + "turns to win the game.")
Your only remaining task is to assemble the above pieces of code into a script. Below is an example of how the final script would look. Copy this into a new PythonWin script and try to run it:
# Simulates one game of Hi Ho! Cherry-O import random spinnerChoices = [-1, -2, -3, -4, 2, 2, 10] turns = 0 cherriesOnTree = 10 # Take a turn as long as you have more than 0 cherries while cherriesOnTree > 0: # Spin the spinner spinIndex = random.randrange(0, 7) spinResult = spinnerChoices[spinIndex] # Print the spin result print ("You spun " + str(spinResult) + ".") # Add or remove cherries based on the result cherriesOnTree += spinResult # Make sure the number of cherries is between 0 and 10 if cherriesOnTree > 10: cherriesOnTree = 10 elif cherriesOnTree < 0: cherriesOnTree = 0 # Print the number of cherries on the tree print ("You have " + str(cherriesOnTree) + " cherries on your tree.") turns += 1 # Print the number of turns it took to win the game print ("It took you " + str(turns) + " turns to win the game.") lastline = raw_input(">")
Analysis of the final code
Review the final code closely and consider the following things.
The first thing you do is import whatever supporting modules you need, in this case it's the random module.
Next, you declare the variables that you'll use throughout the script. Each variable has a scope, which determines how broadly it is used throughout the script. The variables spinnerChoices, turns, and cherriesOnTree are needed through the entire script, so they are declared at the beginning, outside the loop. Variables used throughout your entire program like this have global scope. On the other hand, the variables spinIndex and spinResult have local scope because they are used only inside the loop. Each time the loop runs, these variables are re-initialized and their values change.
You could potentially declare the variable spinnerChoices inside the loop and get the same end result, but performance would be slower because the variable would have to be re-initialized every time you ran the loop. When possible, you should declare variables outside loops for this reason.
If you had declared the variables turns or cherriesOnTree inside the loop, your code would have logical errors. You would essentially be starting the game anew on every turn with 10 cherries on your tree, having taken 0 turns. In fact, you would create an infinite loop because there is no way to remove 10 cherries during one turn, and the loop condition would always evaluate to true. Again, be very careful about where you declare your variables when the script contains loops.
Notice that the total number of turns is printed outside the loop once the game has ended. The final line lastline = raw_input(">") gives you an empty cursor prompting for input and is just a trick to make sure the application doesn't disappear when it's finished (if you run the script from a command console).
In the above example, you saw how lists, loops, decision structures, and variable casting can work together to help you solve a programming challenge. You also learned how to approach a problem one piece at a time and assemble those pieces into a working script. You'll have a chance to practice these concepts on your own during this week's assignment. The next and final section of this lesson will provide you with some sources of help if you get stuck.
If the above activity made you enthusiastic about writing some code yourself, take the above script and try to find the average number of turns it takes to win a game of Hi-Ho! Cherry-O. To do this, add another loop that runs the game a large number of times, say 10000. You'll need to record the total number of turns required to win all the games, then divide by the number of games (use "/" for the division). Send me your final result and I'll let you know if you've found the correct average.