MATH 1MP3 Project

Important notes:

To start the assignment, download the Jupyter notebook le

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 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 specications 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 le project template.ipynb contains several cells, one for each

part of the project. At the start of each cell is the function denition,

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 specied 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 dierent 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