The Pig in Python

Photo by Andrew Neel on Unsplash

The Pig in Python

Capture dice events

I came across the dice game called Pig and I have attempted an implementation here. Here's the link to the rules of the game Pig.

Simulating a dice throw in Python gives us an opportunity to learn a few things like

  • randomness
  • frequency distribution
  • event outcomes

Let's consider the outcomes of throwing a pair of dice. It is easy to see that the sum of the outcomes can vary from 2 to the maximum of 12.

What are the combinations from which these outcomes are possible? We will go step by step, starting with a single die, then on to a pair of dies.

Event Outcomes Using Python's itertools module

from itertools import  product

die = [x for x in range(1, 7)]
print(die)

# combinations of outcomes
outcomes_1 = set(product(die, repeat = 1))
print(len(outcomes_1))
print(outcomes_1)

outcomes_2 = set(product(die, repeat = 2))
print(len(outcomes_2))
print(outcomes_2)

We declare a list of numbers from 1 to 6, inclusive using the range() function. The function takes the start parameter and the end parameter minus 1.

In order to simulate a throw of dice, we need to import the product() function from the package itertools . We pass two parameters, the sequence of numbers, in this case the possible outcomes of throwing a dice event, and the second parameter repeats the event, in this case it is the number of dies to throw.

Look carefully at the outcomes when a pair of dice is thrown _outcomes2. The number of outcomes is 36 and the outcomes are captured as tuples of pairs of each die throw.

Here's the output

36
{(3, 4), (4, 3), (3, 1), (5, 4), (4, 6), (5, 1), (2, 2), (1, 6), (2, 5), (1, 3), (6, 2), (6, 5), (4, 2), (4, 5), (3, 3), (5, 6), (3, 6), (5, 3), (2, 4), (1, 2), (2, 1), (1, 5), (6, 1), (6, 4), (3, 2), (4, 1), (3, 5), (5, 2), (4, 4), (5, 5), (1, 1), (1, 4), (2, 3), (2, 6), (6, 6), (6, 3)}

These are the possible outcomes when we throw a pair of dice.

It would be interesting to see which outcomes appear more often than others when we throw N number of times, N being any natural number.

Frequency Table

Let's build a frequency table of outcomes when a pair of dice is thrown 11 times.

# throw a double dice 11 times
# associate frequency with outcomes
for key in range(1, 12):
    t1 = random.randint(1, 6)
    t2 = random.randint(1, 6)
    total = t1 + t2
    freq_count[total] += 1 

# display frequency
for k, v in freq_count.items():
    print(f'{k} - {v} times')

You will be surprised to see the output. Most outcomes don't appear at all!

{2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0}
2 - 0 times
3 - 0 times
4 - 0 times
5 - 0 times
6 - 4 times
7 - 1 times
8 - 2 times
9 - 2 times
10 - 1 times
11 - 0 times
12 - 1 times

The Game

The rules of the game from the link shown above.

Players take turns to roll both dice, they can roll as many times as they want in one turn.

A player scores the sum of the two dice thrown and gradually reaches a higher score as they continue to roll.

If a single number 1 is thrown on either die, the score for that whole turn is lost. However a double 1 counts as 25.

The first player to 100 wins unless a player scores more subsequently in the same round. This means that everyone in the game must have the same number of turns.

import random

def roll():
    r1 = random.randint(1, 6)
    r2 = random.randint(1, 6)
    throw = r1 + r2
    return (r1, r2, throw)

#outcome = roll()
#print(outcome)

player1 = input('enter name of player1: ')
player2 = input('enter name of player2: ')

print('A player may roll the dice as many times as they want in their turn.')

current_player = player1
score1 = 0
score1_prev = 0
score2_prev = 0
score2 = 0

def compute_score(outcomes, player):
    global score1
    global score2

    r1, r2, throw = outcomes

    if r1 == 1 and r2 == 1:
        if player == player1:
            score1 += 25
        else:
            score2 += 25
    else:
            if player == player1:
                score1 += throw
            else:
                score2 += throw


def play():
    global current_player, score1, score2, score1_prev, score2_prev

    print(f'\n{current_player}, your turn.')
    n = int(input('how many times do you want to throw the dice: '))

    score1_prev = score1
    score2_prev = score2

    for i in range(0, n):
        throw = roll()
        r1, r2, total = throw
        print( (r1, r2), end=' ')
        if (r1 == 1 and r2 != 1) or (r2 == 1 and r1 != 1):
            score1 = score1_prev
            score2 = score2_prev
            break
        compute_score(throw, current_player)

    print(f'\n{player1} has scored : {score1} and {player2} has scored {score2}')

    if current_player == player1:
        current_player = player2
    elif current_player == player2:
        current_player = player1

while True:
    play()
    if score1 >= 100 or score2 >= 100:
        break

if current_player == player2:
    play()


print('\nFinal scores.')
print(f'{player1} has scored : {score1}')
print(f'{player2} has scored : {score2}')
print(f'{current_player} has finished first!')

Play

Copy-paste the code into a Python IDE (I tested it on Pydroid 3 Android app). Follow the code by running it. Feel free to discuss the code or share your own enhancements. Hopefully not, but if you do find errors and bugs, let's fix'em!

Exercise

  • Allow up to six players

Happy coding.