By the end of this lab, you will be able to:
As in Lab 2 last week, we want you to focus more on design than implementation. Don’t worry if you don’t complete the full implementation. You can go back and complete it later.
In this lab you’ll write code to play a simple number game. This game can be played with two or more players. When the game starts, there is a count that begins at 0. On a player’s turn, they add to the count an integer that must be between a set minimum and a set maximum. The player whose move causes the count to be greater than or equal to a set goal amount is the winner.
Here’s a sample game with two players, where the goal is 21, the minimum move is 1, and the maximum move is 3. Alice is the winner.
| Bob | Alice | count |
|---|---|---|
| 0 | ||
| 2 | 2 | |
| 3 | 5 | |
| 3 | 8 | |
| 1 | 9 | |
| 3 | 12 | |
| 3 | 15 | |
| 1 | 16 | |
| 1 | 17 | |
| 3 | 20 | |
| 1 | 21 |
Play the game several times with your partner, using goal 21, minimum move 1, and maximum move 3. Does a good strategy emerge? (Even if it doesn’t, move on after a few minutes when you understand the game. We’ll come back to strategies later.)
NumberGame
Download module lab3.py into your lab3 folder.
Read the NumberGame class carefully and answer the following questions about it. Note that the
entire class is provided for you, and your job here is to understand it—in other words, you’re practicing your
code reading skills.
turn is 15, whose turn is it?NumberGame that violates one of the
representation invariants. Which of these is it possible to violate by constructing a NumberGame
improperly?Player is stored, an instance attribute of
Player is accessed or set, or a method is called on a Player.mainNow look at function main and answer these questions:
NumberGame constructed?g.play repeatedly in a loop. What aspects of the game can change each time
g.play is called: the goal, the min or max move, the players, the moves?Player is stored, an instance attribute of
Player is accessed or set, or a method is called on a Player.Since you have found all the places where a Player is used, you know the attributes and methods it
must provide as its public interface. You could complete the program by writing a single Player class
Player with methods that provide these. But we’re going to have three different kinds of player. They
will have some things in common, but they will differ on how they choose a move:
Rather than make three unrelated classes, we are going to define a parent class called Player and
make three child classes.
Player, RandomPlayer,
StrategicPlayer, and UserPlayer with lots of space below each in which to describe
their data and their methods. You are going to make a simple diagram like this one.PlayerNow write the abstract class Player. You will probably be able to implement some methods completely.
Other methods you won’t be able to complete at all—they are abstract. In those methods, the body should
simply say
raise NotImplementedError
Be sure to include a complete class docstring. (You can look at class NumberGame for a reminder of
what they look like.) Your docstring should warn that the Player class is abstract and should not be
instantiated. You do not need to include doctest examples.
RandomPlayerNow that we have a Player class, we need one or more child classes that can complete its
unimplemented method(s).
Implement class RandomPlayer as a subclass of Player (review how to do this if
you aren’t sure). Any Player methods that were not implemented must be overridden here in class
RandomPlayer.
We have imported module random for you. You will find the function random.randint
handy—if you aren’t sure how to use it, import it in the Python console and call help on it to learn
more!
Even though you only have one kind of player, you can still make the program run. Fill in the missing part in
make_player to so that it creates a RandomPlayer. (Later, we’ll let the user choose from
among the three types of player.)
Run your game! It should be fun to watch two random players battle it out.
There will likely be small glitches to fix, but they will be things like forgetting an argument, and shouldn’t be hard to fix. Read the error messages carefully—they include very precise information about what’s wrong.
UserPlayerNow implement UserPlayer. If you have time, ensure that the user’s moves are legal. But if you are
running out of time, don’t bother with that. It’s more important to get the other steps done.
Once you have UserPlayer done, update make_player so it gives the user a choice between
the two kinds of player that you have implemented.
Try playing your game with one user player and one random player. We hope you can beat the random player!
If you still have time before this week’s quiz, you can work on the following tasks.
Next, add class StrategicPlayer.
If you haven’t figured out a winning strategy yet, discuss it with some other students. You should be able to figure out a strategy for the game with goal 21, minimum move 1 and maximum move 3 that will guarantee you win if you go first. Even if you go second, if your opponent makes a poor choice you can guarantee a win.
If you have that “21-1-3” version of the game figured out, try generalizing the strategy to work for any goal,
minimum and maximum. (How should you design the code if you can only offer a StrategicPlayer for the
21-1-3 version of the game?)
Once you have StrategicPlayer implemented, update make_player one last time to give the
user the choice of this third kind of player. Try running the game with a strategic and a random player. Does the
strategic one always win?
Because our program allows many rounds of the game to be played, it would be nice to track the record of each player: how many games the player has played, and how many of those games they won.
Add to your code to keep track of this information, using new attributes on the Player class to do
so.
Then, add a method to the Player class to report the player’s name and record. But there’s a twist!
Rather than calling the method like so:
print(p1.report())
wouldn’t it be nice to say just:
print(p1)
In fact you can! If you name your method __str__ and make it return a string, Python will
automatically call it whenever you ask to print an object of this type. __str__ is one of Python’s
“special methods.” These are methods that you can call using special syntax or built-in functions like
print rather than the usual dot notation.
Try to generalize your StrategicPlayer to work when there are more than two players. Is this even
possible?! Read more about the classic game this lab is based on here.