MATH 1MP3 Project

MATH 1MP3 Project
Important notes:
 To start the assignment, download the Jupyter notebook le
project template.ipynb found here:
http://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 le.
 Your assignment must be submitted as a Jupyter notebook le called
yourmacid project.ipynb,
where yourmacid is replaced with your macid from your McMaster
email address.
Since my McMaster email address is valeriot@mcmaster.ca then the
le that I would submit would be valeriot project.ipynb. You must
submit this le 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 le 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
le and do not add python code in any other parts of the le 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 state-
ment 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 func-
tions return statements may not be needed.
 Any le that is submitted for grading that does not conform to the
above speci cations 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 http://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 le project template.ipynb contains several cells, one for each
part of the project. At the start of each cell is the function de nition,
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 ap-
propriate 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 nal 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 immedi-
ately 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 cer-
tainty 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 argu-
ments 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 speci ed 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 execut-
ing 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 ran-
dom 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 rst two
random integers generated for the rst 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 fol-
lowing title: Distribution of dice rolls, and the two axes should
be labelled Sum of the two dice, along the x-axis and along the y-
axis, 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 follow-
ing. 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 prob-
lem. 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 rst).
(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 rst 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 func-
tion will simulate num_players many di erent 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 x-
axis and the string “Games won” as a label for the y-axis. The plot ti-
tle 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 de-
termine 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 rst one was produced, after rst 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