Introduction
In this project, you will develop a simulator and multiple strategies for the dice game Hog.
You will need to use control statements and higher-order functions together, as described in Sections 1.2 through 1.6 of Composing Programs, the online textbook.
Rules of Hog
In Hog, two players alternate turns trying to be the first to end a turn with at least GOAL
total points, where GOAL
defaults to 100.
On each turn, the current player chooses some number of dice to roll, up to 10.
That player's score for the turn is the sum of the dice outcomes.
However, a player who rolls too many dice risks:
-
Pig Out.(俚语,狼吞虎咽)
If any of the dice outcomes is a 1, the current player's score for the turn is 1.
- Example 1: The current player rolls 7 dice, 5 of which are 1's. The player scores 1 point for the turn.
- Example 2: The current player rolls 4 dice, all of which are 3's. Since Pig Out did not occur, they score 12 points for the turn.
In a normal game of Hog, those are all the rules.
To spice up the game, we'll include some special rules:
-
Picky Piggy.
A player who chooses to roll zero dice scores \( (2 \times \vert \texttt{tens} - \texttt{ones} \vert + 1) \) points; where
tens
,ones
are the tens and ones digits of the opponent's score. The ones digit refers to the rightmost digit and the tens digit refers to the second-rightmost digit.- Example 1: The opponent has 46 points, and the current player chooses to roll zero dice. \( 2 \times \vert 4 - 6 \vert + 1 = 5 \), so the player gains 5 points.
- Example 2: The opponent has 73 points, and the current player chooses to roll zero dice. \( 2 \times \vert 7 - 3 \vert + 1 = 9 \).
-
Swine Swap.
After points of the turn are added to the current player's score, if the resulting score is a perfect square, then swap the scores of both players. A perfect square is any integer \( n \) where \( n = d \times d \) for some integer \( d \).
- Example 1: At the end of a turn, the current player has score 4 and the other player has score 2. Since 4 is a perfect square, the scores are swapped.
- Example 2: At the end of a turn, the current player has score 11 and the other player has score 5. Since 11 is not a perfect square, the scores are not swapped.
- Example 3: At the end of a turn, the current player has score 99 and the other player has score 16. Since 99 is not a perfect square, the scores are not swapped.
Project Logistics
When you finish the project, you'll have implemented a significant part of this game yourself.
To get started, download all of the project code as a zip archive.
Below is a list of all the files you will see in the archive once unzipped.
For the project, you'll only be making changes to hog.py
.
hog.py
: A starter implementation of Hogdice.py
: Functions for rolling dicehog_gui.py
: A graphical user interface (GUI) for Hogok
: The autogradertests
: A directory of tests used by okgui_files
: A directory of various things used by the web GUI
You may notice some files other than the ones listed above too -- those are needed for making the autograder and portions of the GUI work.
Please do not modify any files other than hog.py
.
You will turn in the following files:
hog.py
You do not need to modify or turn in any other files to complete the project. To submit the project, run the following command:
$ python ok --submit
For the functions that we ask you to complete, there may be some initial code that we provide. If you would rather not use that code, feel free to delete it and start from scratch. You may also add new function definitions as you see fit.
However, please do not modify any other functions. Doing so may result in your code failing our autograder tests. Also, please do not change any function signatures (names, argument order, or number of arguments).
Throughout this project, you should be testing the correctness of your code. It is good practice to test often, so that it is easy to isolate any problems. However, you should not be testing too often, to allow yourself time to think through problems.
You can debug your code by printing messages or using a debugger.
- To print a debug message, use
print('DEBUG:', ...)
. - To use the integrated debugger in VS Code, refer to the debugging tutorial in week 5.
Phase 1: Simulator
In the first phase, you will develop a simulator for the game of Hog.
Problem 0 (0pts)
The dice.py
file represents dice using non-pure zero-argument functions.
These functions are non-pure because they may have different return values each time they are called.
The documentation of dice.py
describes the two different types of dice used in the project:
- A fair dice produces each possible outcome with equal probability.
Two fair dice are already defined,
four_sided
andsix_sided
, and are generated by themake_fair_dice
function. - A test dice is deterministic: it always cycles through a fixed sequence of values that are passed as arguments.
Test dice are generated by the
make_test_dice
function.
Before writing any code, read over the dice.py
file and check your understanding by unlocking the following tests.
$ python ok -q 00 -u
This should display a prompt that looks like this:
=====================================================================
Assignment: proj01: Hog
Ok, version vx.y.z
=====================================================================
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unlocking tests
At each "? ", type what you would expect the output to be.
Type exit() to quit
---------------------------------------------------------------------
Question 0 > Suite 1 > Case 1
(cases remaining: 1)
>>> test_dice = make_test_dice(4, 1, 2)
>>> test_dice()
?
You should type in what you expect the output to be.
To do so, you need to first figure out what test_dice
will do, based on the description above.
You can exit the unlocker by typing exit()
.
Typing Ctrl-C
on Windows to exit out of the unlocker has been known to cause problems, so avoid doing so.
Problem 1 (200pts)
Implement the roll_dice
function in hog.py
.
It takes two arguments: a positive integer called num_rolls
giving the number of dice to roll and a dice
function.
It returns the number of points scored by rolling the dice that number of times in a turn: either the sum of the outcomes or 1 (due to Pig Out).
To obtain a single outcome of a dice roll, call dice()
.
You should call dice()
exactly num_rolls
times in the body of roll_dice
.
Remember to call dice()
exactly num_rolls
times even if Pig Out happens in the middle of rolling.
In this way, you correctly simulate rolling all the dice together.
Note: The
roll_dice
function, and many other functions throughout the project, makes use of default argument values -- you can see this in the function heading:def roll_dice(num_rolls, dice=six_sided): ...
The argument
dice=six_sided
means that whenroll_dice
is called, thedice
argument is optional. If no value fordice
is provided, thensix_sided
is used by default.For example, calling
roll_dice(3, four_sided)
, or equivalentlyroll_dice(3, dice=four_sided)
, simulates rolling 3 four-sided dice, while callingroll_dice(3)
simulates rolling 3 six-sided dice.
Understand the problem:
Before writing any code, unlock the tests to verify your understanding of the question. Note: you will NOT be able to test your code using ok
until you unlock the test cases for the corresponding question.
$ python ok -q 01 -u
Write code and check your work: Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 01
Problem 2 (200pts)
Implement picky_piggy
, which takes the opponent's current score and returns the number of points scored by rolling 0 dice (see Picky Piggy rule).
Don't assume that scores are below 100. Write your
picky_piggy
function so that it works correctly for any non-negative score.
Important: Your implementation should NOT use
str
, lists, or contain square brackets[ ]
. The test cases will check if those have been used. Remember to remove the"*** YOUR CODE HERE ***"
string from the function once you've implemented it so that you're not getting an unintentional str check error.
Before writing any code, unlock the tests to verify your understanding of the question.
$ python ok -q 02 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 02
You can also test picky_piggy
interactively by entering python -i hog.py
in the terminal and then calling picky_piggy
with various inputs.
Problem 3 (200pts)
Implement the take_turn
function, which returns the number of points scored for a turn by rolling the given dice
num_rolls
times.
Your implementation of take_turn
should call both roll_dice
and picky_piggy
when possible.
Before writing any code, unlock the tests to verify your understanding of the question.
$ python ok -q 03 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 03
Problem 4 (100pts)
Implement swine_swap
, which takes the opponent score and returns True
if the scores of both players will be swapped due to Swine Swap.
Before writing any code, unlock the tests to verify your understanding of the question.
$ python ok -q 04 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 04
Problem 5 (500pts)
Implement the play
function, which simulates a full game of Hog.
Players take turns rolling dice until one of the players reaches the goal
score, and the final scores of both players are returned by the function.
To determine how many dice are rolled each turn, each player uses their respective strategy (Player 0 uses strategy0
and Player 1 uses strategy1
).
A strategy is a function that, given a player's score and their opponent's score, returns the number of dice that the current player will roll in the turn.
An example strategy is always_roll_5
which appears below play
.
If any player achieves the goal score by the end of their turn, i.e. after all applicable rules have been applied, the game ends.
Important:
For the user interface to work, a strategy function should be called only once per turn. Only call
strategy0
when it is Player 0's turn and only callstrategy1
when it is Player 1's turn.Hints:
- You should call the functions you have implemented already.
- If
who
is the current player, the next player is1 - who
.
Before writing any code, unlock the tests to verify your understanding of the question.
$ python ok -q 05 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 05
Once you are finished, you will be able to play a graphical version of the game. We have provided a file called hog_gui.py
that you can run from the terminal:
$ python hog_gui.py
You can exit the gui by typing Ctrl + C
from terminal.
The GUI relies on your implementation, so if you have any bugs in your code, they will be reflected in the GUI. This means you can also use the GUI as a debugging tool; however, it's better to run the tests first.
Congratulations! You have finished Phase 1 of this project!
Phase 2: Strategies
In this phase, you will experiment with ways to improve upon the basic strategy of always rolling five dice. A strategy is a function that takes two arguments: the current player's score and their opponent's score. It returns the number of dice the player will roll, which can be from 0 to 10 (inclusive).
Problem 6 (200pts)
Implement always_roll
, a higher-order function that takes a number of dice n
and returns a strategy that always rolls n
dice. Thus, always_roll(5)
would be equivalent to always_roll_5
.
Before writing any code, unlock the tests to verify your understanding of the question:
$ python ok -q 06 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 06
Problem 7 (200pts)
A pure strategy only has a fixed number of possible argument values. In a game to 100, there are 100 possible score
values (0-99) and 100 possible opponent_score
values (0-99), giving 10,000 possible argument combinations.
Implement is_always_roll
, which takes a pure strategy and returns whether that strategy always rolls the same number of dice for every possible argument combination.
Before writing any code, unlock the tests to verify your understanding of the question:
$ python ok -q 07 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 07
Problem 8 (200pts)
Implement make_averaged
, which is a higher-order function that takes a function original_function
as an argument.
The return value of make_averaged
is a function that takes in the same number of arguments as original_function
.
When we call this returned function on the arguments, it will return the average value of repeatedly calling original_function
on the arguments passed in.
Specifically, this function should call original_function
a total of total_samples
times and return the average of the results of these calls.
Important: To implement this function, you will need to use a new piece of Python syntax. We would like to write a function that accepts an arbitrary number of arguments, and then calls another function using exactly those arguments. Here's how it works.
Instead of listing formal parameters for a function, you can write
*args
, which represents all of the arguments that get passed into the function. We can then call another function with these same arguments by passing these*args
into this other function. For example:>>> def printed(f): ... def print_and_return(*args): ... result = f(*args) ... print('Result:', result) ... return result ... return print_and_return >>> printed_pow = printed(pow) >>> printed_pow(2, 8) Result: 256 256 >>> printed_abs = printed(abs) >>> printed_abs(-10) Result: 10 10
Here, we can pass any number of arguments into
print_and_return
via the*args
syntax. We can also use*args
inside ourprint_and_return
function to make another function call with the same arguments.
Before writing any code, unlock the tests to verify your understanding of the question:
$ python ok -q 08 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 08
Problem 9 (200pts)
Implement max_scoring_num_rolls
, which runs an experiment to determine the number of rolls (from 1 to 10) that gives the maximum average score for a turn.
Your implementation should use make_averaged
and roll_dice
.
If two numbers of rolls are tied for the maximum average score, return the lower number. For example, if both 3 and 6 achieve a maximum average score, return 3.
You might find it useful to read the doctest and the example shown in the doctest for this problem before doing the unlocking test.
Important: In order to pass all of our tests, please make sure that you are testing dice rolls starting from 1 going up to 10, rather than from 10 to 1.
Before writing any code, unlock the tests to verify your understanding of the question:
$ python ok -q 09 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 09
The provided run_experiments
function calls max_scoring_num_rolls(six_sided)
and prints the result.
You will likely find that rolling 6 dice maximizes the result of roll_dice
using six-sided dice.
To call this function and see the result, run hog.py
with the -r
flag:
$ python hog.py -r
In addition, run_experiments
compares various strategies to always_roll(6)
.
You are welcome to change the implementation of run_experiments
as you wish.
Note that running experiments with picky_strategy
and swine_strategy
will not have accurate results until you implement them in the next two problems.
Some of the experiments may take up to a minute to run.
You can always reduce the number of trials in your call to make_averaged
to speed up experiments.
Running experiments won't affect your score on the project.
Problem 10 (200pts)
注意:下载的代码中,Problem 10和 Problem 11的代码都包含一行
assert False
,这是因为hog_gui.py
可能会调用这些函数,而调用没实现的代码可能会产生莫名其妙的错误。请在编写完自己的代码前将这行代码删除,不然你编写的代码会无法运行。
A strategy can try to take advantage of the Picky Piggy rule by rolling 0 when it is most beneficial to do so.
Implement picky_strategy
, which returns 0 whenever rolling 0 would give at least threshold
points and returns num_rolls
otherwise.
This strategy should not also take into account the Swine Swap rule.
Hint: You can use the function
picky_piggy
you defined in Problem 2.
Before writing any code, unlock the tests to verify your understanding of the question.
$ python ok -q 10 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 10
Once you have implemented this strategy, change run_experiments
to evaluate your new strategy against the baseline.
Is this strategy an improvement over the baseline?
Problem 11 (200pts)
A strategy can also take advantage of the Swine Swap rules.
Implement swine_strategy
, which returns 0 if rolling 0 dices gives the player at least threshold
points in this turn.
In other cases, the strategy rolls num_rolls
.
Before writing any code, unlock the tests to verify your understanding of the question.
$ python ok -q 11 -u
Once you are done unlocking, begin implementing your solution. You can check your correctness with:
$ python ok -q 11
Once you have implemented this strategy, update run_experiments
to evaluate your new strategy against the baseline.
Problem 12 (0pts)
Implement final_strategy
, which combines these ideas and any other ideas you have to achieve a high win rate against the baseline strategy.
Some suggestions:
picky_strategy
orswine_strategy
are default strategies you can start with.- If you know the goal score (by default it is 100), there's no point in scoring more than the goal. Check whether you can win by rolling 0, 1 or 2 dice. If you are in the lead, you might decide to take fewer risks.
- Choose the
num_rolls
andthreshold
arguments carefully. - Take the action that is most likely to win the game.
You can check that your final strategy is valid by running ok
.
$ python ok -q 12
You can also play against your final strategy with the graphical user interface:
$ python hog_gui.py
Project Submission
Run ok on all problems to make sure all tests are unlocked and pass:
$ python ok
You can also check your score on each part of the project:
$ python ok --score
Once you are satisfied, submit to complete the project.
$ python ok --submit
Congratulations, you have reached the end of your first SICP project!
If you haven't already, relax and enjoy a few games of Hog with a friend.