lundi 15 mars 2021

How to deal cards according to probabilities?

I am trying to write a function that deals cards to players given a probability distribution. This can be thought of as a dealer who is cheating and wants to give certain cards to certain players but doesn't want to be obvious.

For example with a three card deck and three players:


Ace King Queen
Player 1 0% 66% 33%
Player 2 33% 33% 33%
Player 3 66% 0% 33%

The input of my function is the probabilities and the output is the cards dealt represented as:

probabilities = [
    [0.0   , 0.6666, 0.3333],
    [0.3333, 0.3333, 0.3333],
    [0.6666, 0.0   , 0.3333],
]

# example result:
result = [
    1, # Player 1 - King
    2, # Player 2 - Queen
    0, # Player 3 - Ace
]

I use a weighted version of random choice to sample according to the rows of probabilities. But that is not the problem.

The problem is that a card can only be at a single player so just using weighted choice doesn't work.

I tried a few ways of dealing the cards but the resulting distribution is not the input distribution I used as a parameter in this case for example.

One way I tried is to deal the cards per player. Then check if there are duplicates and if there are I deal again till there are no duplicate cards.

Another way that I tried is to deal Player 1's card. Then remove that from the possible Player 2 cards and deal a card out of that for Player 2. The card for player 3 is the remaining card. This I saw is clearly stupid because Player 3 even had Kings which he should have with 0% probability.

Both of these gave the wrong distribution of cards when averaging together loads of runs.

How is it possible to pick random numbers like this?

If I only had to pick a card for a single player I would just use some kind of weighted choice like it was suggested. But that doesn't work when there are multiple cards to choose because when a card is picked that card cannot possible be at the other players.

Here is the code for the second version I mentioned:

import random


def sample(probabilities):
    p1_card = random.choices([0, 1, 2], weights=probabilities[0])[0]

    p2_possible_cards = [0, 1, 2]
    p2_weights = probabilities[1][:]

    p2_possible_cards.pop(p1_card)
    p2_weights.pop(p1_card)

    p2_card = random.choices(p2_possible_cards, weights=p2_weights)[0]

    if 0 not in [p1_card, p2_card]:
        p3_card = 0
    elif 1 not in [p1_card, p2_card]:
        p3_card = 1
    else:
        p3_card = 2

    return [p1_card, p2_card, p3_card]


probabilities = [
    [0.0, 0.666666, 0.333333],
    [0.333333, 0.333333, 0.333333],
    [0.666666, 0.0, 0.333333],
]

distribution = [
    [0.0, 0.0, 0.0],
    [0.0, 0.0, 0.0],
    [0.0, 0.0, 0.0],
]

for _ in range(100_000):
    for card, dist in zip(sample(probabilities), distribution):
        dist[card] += 1

distribution = [[card / 100_000 for card in player] for player in distribution]

print(distribution)
# prints:
# [[0.0, 0.66734, 0.33266], [0.50087, 0.16556, 0.33357], [0.49913, 0.1671, 0.33377]]
# which is not even close to probabilities



Aucun commentaire:

Enregistrer un commentaire