MATH 1MP3 Project

MATH 1MP3 Project
Due:
11:59pm, Tuesday, April 9.
Important notes:
To start the assignment, download the Jupyter notebook file
project template.ipynb found here:
https://ms.mcmaster.ca/~matt/1mp3/homework/project_template.
ipynb
You might need to copy the above url and paste it into a browser to
download the file.
Your assignment must be submitted as a Jupyter notebook file called
yourmacid project.ipynb,
where
yourmacid is replaced with your macid from your McMaster
email address.
Since my McMaster email address is
[email protected] then the
file that I would submit would be
valeriot project.ipynb. You must
submit this file to the Project Dropbox on the MATH 1MP3 Avenue
to Learn site.
There are several parts to this project, but your code will be contained
in the single Jupyter notebook file that you will submit.
To complete the project, place your code for each part as indicated in
the template.
Do not alter any other part of the
project template.ipynb
file and do not add python code in any other parts of the file that you
submit.
To see an example of what is expected, look over the solutions to the
earlier homework assignments.
1

The code that you enter cannot contain any import statements. The
template already has
import numpy as np, import numpy.random as
rand
, and import matplotlib.pyplot as plt statements in the cells
where they will be needed. The functions should use the
return statement to return the result of the function call, so print statements should
not occur in the code that you produce. While developing and testing
your code, it might be helpful to use print statements, but they should
be removed before submitting your solution. Note that the template
contains several print statements that when executed will test your
code. Some of the questions involve displaying a plot; for these functions return statements may not be needed.
Any file that is submitted for grading that does not conform to the
above specifications will lead to a grade of 0 on the assignment.
Before submitting your solution, you should make sure that it runs
properly. To do this, you can open it in the Jupyter notebook server
that comes installed with Anaconda, or you can use the McMaster
Jupyter server, by going to the website
https://mcmaster.syzygy.
ca/
. You may want to use the Spyder IDE, or some other IDE to
develop your code before entering it into the Jupyter notebook that
you submit.
The file project template.ipynb contains several cells, one for each
part of the project. At the start of each cell is the function definition,
followed by a placeholder docstring entry. At the end of each cell are
some print statements that when executed will print out the results of
running your code on a few test cases.
Do not remove, alter, or add
to these print statements.
For each question, you should include a suitable docstring in the appropriate place. Your docstrings should have the same format as those
that appear in the solutions for the earlier assignments. This is the
numpy docstring format. Note that for functions that produce plots,
you do not need to provide examples in your docstrings.
Carefully read over each of the following questions. Once you have
produced code for a given question, you should run it in the Jupyter
server or on your IDE on the test cases provided to make sure that it
2

is working properly. You should also try out your code on other test
cases of your own design.
Your grade for each question will depend on whether or not your code
correctly handles not just the test cases provided in the template, but
other test cases that will be run on your solutions. Points will also be
awarded based on the quality of the docstrings that you provide for
each function. This assignment is worth 5% of your final grade in the
course.
Do not leave this until the last minute, since you might encounter
computer/internet/Avenue issues.
Late assignments will not be accepted.
All work submitted for grading must be your own. You may discuss
homework problems and related material with other students, but you
must not submit work copied from others or from the internet.
3

Project Description
In this project you will simulate the playing of a simple game of dice. Here
is a description of this game:
The game is played with two 6-sided dice and consists of a number of
rolls of the dice. Each roll of the dice will be referred to as a
round of
the game
.
The outcome of rolling a die (the singular form of the plural word dice)
is an integer from 1 through to 6. We assume that the dice are fair and
so any one of the 6 possible outcomes is equally likely to occur.
If the sum of the two dice rolled is the number N, then it is said that
the number
N was rolled. For example, if the two dice rolled produce
the numbers 3 and 4, then we say that a 7 was rolled (since 3 + 4 = 7).
Note that when rolling two 6-sided dice, the only possible sums are the
integers from 2 to 12, inclusive.
In round zero, after the dice have been rolled,
{ the player immediately wins the game if the sum of the two dice
rolled is either 7 or 11,
{ if the sum of the two dice rolled is 2, 3, or 12, the player immediately loses the game, and
{ if the sum of the two dice rolled is any other number (so one of 4,
5, 6, 8, 9, or 10) then that sum is called the
point for this game,
and the game continues on to the next round.
If the game continues past round zero, then the player continues to roll
the two dice until one of the two following events occur:
{ a 7 is rolled, i.e., the sum of the two dice rolled in the current
round is equal to 7. In this case, the player loses the game.
{ the point is rolled again, i.e., the sum of the two dice rolled in the
current round sum to the point that was established in round zero
of the game. In this case, the player wins the game.
{ if some number other than 7 or the point is rolled, then the game
continues on to the next round and the dice are rolled again.
4

So, in principal, playing this game could take arbitrarily many rounds.
In practice, games will end after a very few rolls, but there is no certainty to this.
Here are a few sample plays of this game. Each of the following lists
of numbers represents the dice rolls in the corresponding rounds of the
game.
{ [5, 8, 2, 11, 7]: The player loses this game, since in round
zero, the point of 5 is established. The player continued to roll
the dice and ended up rolling a 7 before rolling the point of 5.
{ [2]: The player loses this game in round zero since a 2 was rolled.
{ [6, 12, 8, 4, 3, 2, 8, 9, 10, 6]: The player wins this game.
{ [7]: The player wins this game.
In this project, you will produce code that simulates the playing of this game
and to visualize the outcome of playing this game multiple times. To do so,
you will need to provide code for the following functions.
(a)
roll_dice(num_rolls=50, sides=6): This function has two arguments num_rolls and sides that are both positive integers. The
default value for
num_rolls is 50 and the default value for sides
is 6. This function will randomly generate a numpy array of shape
(num_rolls, 2) that consists of a sequence of randomly generated
pairs of integers between 1 and
sides and that represents the rolling
of two dice, each with the specified number of sides,
num_roll many
times.
So, if the statement
roll_array = roll_dice(5, 6) is executed, then
roll_array[2, 0] and roll_array[2,1] will be integers between 1
and 6 and represent the outcome of roll number 2 of two 6-sided dice.
You must use the numpy random submodule to generate the integers
and none of your functions should set the random number generator
seed. Once you have produced your code, you can test it out by executing the following commands:
rand.seed(2019) and print(roll_dice(5,12)).
This should produce the numpy array
[[9,3],[6,9],[7,9],[11,1],[1,8]].
NOTE: You should use the function
rand.randint to generate the random integers needed for this function. To ensure that the sequence of
5

rolls that your code produces can replicate the above example sequence,
after setting the seed to 2019, your function should use the first two
random integers generated for the first pair of dice rolls, then the next
two for the next pair of dice rolls, and so on.
(b)
rolls_hist(rolls_array, sides=6): This function will display the
sequence of dice rolls that are stored in the numpy array
rolls_array
as a histogram. The parameter sides is used to determine the possible
dice rolls that could be displayed. Its default value is 6. If
N is the
value of
sides then the possible outcomes of rolling two dice with N
sides are the integers from 2 to 2N. This function will not return any
values, it will just display the histogram that it produces. Your function
should use the command
plt.show() to display the histogram once it
has been set up.
You should use the graphics package
matplotlib and its submodule
plyplot to produce and display the histogram. It should have the following title: Distribution of dice rolls, and the two axes should
be labelled
Sum of the two dice, along the x-axis and along the yaxis, Number of occurrences of a roll. The bin edges (along the
x-axis) should range from 2 to
2 * sides, and the displayed bar for
each bin
n should represent the number of times the dice rolls in the
array
rolls_array summed to n.
With
test_rolls equal to the numpy array with entries
[[1
; 2]; [2; 4]; [4; 4]; [1; 2]; [2; 4]; [3; 2]; [6; 2]; [6; 6]; [2; 3]; [1; 6]]
the command
rolls_hist(test_rolls) should produce the following
histogram:
6

You should also try out your code with some very large array that was
generated by your function
roll_dice. If you use 6-sided dice, then
the histogram that is displayed should look something like the following. This particular histogram was generated from 1000 rolls of two
6-sided dice produced with
roll_dice after executing the command
rand.seed(2019).
7

(c) round_zero(dice): This function has one argument dice that is a
tuple of length two whose entries are integers between 1 and 6. This
tuple represents the outcome of rolling two 6-sided dice. This function
will determine the outcome of round zero of the game with the roll of
the dice provided by the parameter
dice.
If the sum of the entries of dice is 2, 3, or 12, then the function
should return the string
“lose”. So round_zero((2, 1)) should
return
“lose”.
If the sum of the entries of dice is 7, or 11, then the function
should return the string
“win”. So round_zero((2, 5)) should
return
“win”.
If the sum of the entries of dice is any other number, then function
should return the sum, as an integer. So
round_zero((5, 5))
should return 10.
(d)
later_round(point, dice): This function has two arguments, point
that is one of the following integers: 4, 5, 6, 8, 9, 10, and dice is as in
8

the previous part. This function will determine the outcome of a round
of the game, beyond round zero.
If the sum of the dice roll provided by the parameter dice is 7,
then the function returns the string
“lose”.
If the sum of the dice roll provided by the parameter dice is equal
to
point, then the function returns the string “win”.
Otherwise, the function returns the string “neither”.
(e)
play_game(rolls_array): this function has one argument, rolls_array,
that is a 2-dimensional numpy array of integers between 1 and 6 that
represents a sequence of rolls of two 6-sided dice (the function
roll_dice
produces such an array). This function will simulate playing the dice
game, using, in sequence, the dice rolls that are provided by the array
rolls_array.
If the player wins the game, the function will return the string
“win”
and if the player loses the game, the function will return the string
“lose”. It is likely that the game will be resolved before all of the
dice rolls provided by
rolls_array are used, but that is not a problem. Depending on the length of the array rolls_array, there is some
chance that the game being played will not end before the dice rolls in
the array have all been used. In that case, the function should return
no value, i.e., it should return the python object
None. Note that if
the length of
rolls_array is long enough (say of length 50), then the
chance of this last outcome occurring is practically zero.
You should make use of the functions
round_zero and later_round in
your code for this function (that was the point of having you produce
those functions first).
(f)
game_session(num_games=100): this function simulates playing the
dice game a number of times, depending on the value of the parameter
num_games. The default value of this integer parameter is 100. The
function should return a numpy array of type int of length
num_games
that tracks the number of games that have been won at any point in
the simulation. So if the simulation of playing 6 games results in the
sequence of win/lose/None: [win, win, lose, None, lose, lose] then the
function should return the numpy array
[1, 2, 1, 1, 0, -1].
9

Your function should use the play_game function from the previous
part and also use
roll_dice(50,6) from the first part to generate a
sequence of pseudo-random dice rolls to use for each play of the game.
If done correctly, then the commands
rand.seed(2019) followed by
game_session(10) should produce the numpy array
[-1, -2, -3, -4, -5, -6, -5, -6, -5, -6].
(g)
multi_player_plot(num_players=100, num_games=100): This function will simulate num_players many different players, each playing the
dice game
num_games many times. It will return the number of players
who end up winning more games than they have lost over the course
of playing the games. It will also produce a single plot that displays,
for each player, a history of the number of games that they have won
over the session.
The plot should have the string
“Games played” as a label for the xaxis and the string “Games won” as a label for the y-axis. The plot title should be the string “N players, M games played, P winners”,
where
N is the value of the parameter num_players, M is the value of
num_games, and P is the number of players that ended up winning more
games than they have lost over the course of playing the games. There
should be a single plot that contains the playing history of each of the
players. Your function should use the command
plt.show() to display
the plot once it has been set up.
You should use the function
game_session to generate the playing
session for each player. The function
multi_player_plot essentially
just plots the numpy arrays that
game_session produces for the given
number of players, plus keeping track of the number of overall winners.
If
session_array is the array that game_session returns, then to determine if the player ended up winning more games than they lost, you
just need to check if the last entry of the array,
session_array[-1],
is greater than 0.
Here are a couple of examples of what the plots should look like that
this function produces. The first one was produced, after first setting
the random number generator seed to 2019. Note that as the number
of games played increases, the proportion of winners goes down. In the
limit, this proportion will be 0, since this game is biased against the
player.
10

11