Thursday, September 28, 2023

# Let’s Roll! — 2 dice…and find the probability and chart it (Python)

What numbers will you get if you roll 2 dice? How about we find out by writing a program?

Can we then chart the results over as many rolls as we want? And then interactively play with the chart?

Asking a lot! But yes, I will do ALL of the above here.

Better yet, why not extend the code to have the ability to create not just 6-sided dice, but up to 8 sided dice each?

Sure! I can handle that!

Let’s ROLL!

First, let’s create a Class that’s extensible to allow the above requirements. We’ll save the Class code in a die.py file and we’ll use/reuse them from our other python codes as requirements change…

`from random import randint`
```class Die():
def __init__(self, num_sides=6):
self.num_sides=num_sides
def roll(self): # method returns a random num between 1 to num_sides inclusive.
return randint(1, self.num_sides)```

That’s all for now. This file (die.py) containing the Class Die says: “I’ll create a 6 sided dice per call by default, but if I’m told to do different sided dice, I’ll do it too!”. This is num_sides parameter. It also has a roll() method, which says: “everytime I’m asked to roll, I’ll return a number between 1 and 6 (or whatever numbers of sides you told me to make).”

Nice!

```Now the code that'll call the Class is below...
from die import Die
die1 = Die(6) # see die.py Class
die2 = Die(6) # see die.py Class

# ask user how many times to roll
i=int(input("How many times to roll? "));

# make an empty list var to store the results
results = [];
# roll it x times and keep loading each result into the list
for rolls in range(i): # NOTE: by default range values start at 0 unless specified another.
result=die1.roll()
results.append(result)
# end rolls

```

So, we create two objects die1, die2 based on the same class Die but we call it independently and NOTE that we pass 6 as the argument to ensure that each is 6-sided. The beauty of this architecture is that we could have called each one with different sides requirements independently, and the code would still work! That’s one of the benefits of this Class design.

Everything is commented/explained in the lines starting with ‘#’. For example, we need to import pygal library for the histogram. The results[] is a local list that gets populated by each roll as specified by variable i. What’s i? It’s the variable we ask the user to input using the input() function in Python. The result is converted from string to an integer of course:

`i=int(input("How many times to roll? "));`

Analysis:

Now we need to get the frequency of appearances of digits 1 through 6 for all the rolls in each dice.

I created two lists/arrays, one for each dice: freqresultDie1[], freqresultDie2[]

NOTE: The whole analysis block below needs to be inside the above for rolls in range(i): block since we want to do this analysis per roll…that means, you need to indent the following block accordingly.

Pay attention to the indexing as I put in comments below…notice how the count() i used to get the result of “face” up value and appended to the list for each freqresultDie1 and freqresultDie2…

```# Analysis block #

freqresultDie1 = []
for value in range(1, die1.num_sides+1): # NOTE: since we're starting from '1' instead of '0' default here, so need to +1
freq = results.count(value) # get the number of freq for that value
freqresultDie1.append(freq) # add it to the list of frequency results

# Do the same for die2 and we can reuse results[] by resetting and creating a new freqresultDie2[] and using it for chart.
results =[] # reset the working list for each die.

for rolls in range(i): # NOTE: by default range values start at 0 unless specified another.
result=die2.roll()
results.append(result)

freqresultDie2 = [] # declare an empty list var for this die.
for value in range(1, die2.num_sides+1):
freq = results.count(value) # get the number of freq for that value
freqresultDie2.append(freq) # add it to the list of frequency results

# End Analysis block #```

Charting/Visualization:

At this point, phew, we have loaded all rolls for each die into its own list. We have enough data to chart it to visualize the result. We will use a histogram / barchart provided by pygal library.

```import pygal

hist=pygal.Bar()
s=str(i) + "x Rolled Results Of 2 Dies:"
hist.title = s;
hist.x_labels = map(str, range(1,7)) # using map's range (we have to add to give it 7 to include 6)
hist.x_title = "Rolled Number"
hist.y_title = "Frequency"

# now add the 2 series to the chart...

hist.render_to_file('dice3-svg.svg'); # save to file in current dir```

Output:

Here we actually export the output chart as a SVG file using render_to_file(). Alternatively, hist.render() could be used for screen only but my Python IDE doesn’t have a view for SVG, so I exported it and then opened the svg file in my browser: IE/Chrome/Edge, or any modern browser will do.

Interactive chart! Hover your mouse over any bar, and you’ll see the value at any point. Click on the labels on left to check/UNcheck and the graph will update accordingly. Very cool.

The above sample output tells us that number 2 appear 183 times on Dice-2 in 1000 rolls of both dies! And number 6 appeared over 180 times on Dice-1.

To create the stacked bar chart, there’s NO CODE CHANGE necessary other than changing the line hist=pygal.Bar() to pygal.StackedBar() and we have the following output:

Also interactive! Click/unclick the labels on the left. What results do you get?

It’s fascinating, isn’t it? No one has figured this predictability for certain including Las Vegas Casinos 😉

Happy coding! Stay curious!