Reputation: 7404
If you've ever played risk, you're familiar with rolling dice to determine the outcome of a battle. If not, here is a brief synopsis:
The rules for determining how many dice a player may shake are as follows:
The rules for deciding the outcome of a particular throw of the dice are as follows:
In cases where either the attacker or the defender only rolls a single die, a total of only one army will be lost; in all other cases a total of two armies will be lost.
I'd like to analyze the frequency of wins/losses for each pair of die rolls. I could loop through all the possibilities, but I'm interested in using ndarrays
and slicing to do the calculations.
For instance, consider the scenario where attacker rolls one die and defender rolls one die. We can arrange all possible outcomes in an ndarray
.
In [1]: import numpy as np
In [2]: x = np.tile(np.arange(1,7),(6,1))
In [3]: x
Out[3]:
array([[1, 2, 3, 4, 5, 6],
[1, 2, 3, 4, 5, 6],
[1, 2, 3, 4, 5, 6],
[1, 2, 3, 4, 5, 6],
[1, 2, 3, 4, 5, 6],
[1, 2, 3, 4, 5, 6]])
If defender rolls are the columns and attacker rolls are the rows, then the the area where the defender wins is the upper triangular portion of this array
defence_win_region= array([[1, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 0, 1, 1, 1, 1],
[0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 1]])
How can I obtain an array like defence_win_region
from an array like x
? How can I extend that method to arrays of higher dimensions in order to analyze 2-1,3-1,3-2,1-2 rolls?
Upvotes: 2
Views: 196
Reputation: 14399
import numpy as np
import scipy
import itertools
def riskRoll(ad,dd): #Never gonna give you up . . .
minD=min(ad,dd)
a=np.array(list(itertools.combinations_with_replacement(
np.arange(6,0,-1),ad)))
d=np.array(list(itertools.combinations_with_replacement(
np.arange(6,0,-1),dd)))
na=np.array([scipy.misc.factorial(ad)/np.prod(
scipy.misc.factorial(np.unique(roll,return_counts=True)[1])) for roll in a])
nd=np.array([scipy.misc.factorial(dd)/np.prod(
scipy.misc.factorial(np.unique(roll,return_counts=True)[1])) for roll in d])
a_wins= np.sum(p.where(a[None,:,0:minD]>d[:,None,0:minD],1,-1), axis=-1)+ad-dd
nd_count=na[:,None]*nd[None,:]
return a_wins*nd_count
How it works:
C((6,ad)) x C((6,dd))
with all combinations in descending ordera_wins
are the number of attacker wins, negative numbers are defensive wins. This includes non-contested dice.nd_count
are weighting factors, equal to the number of times that combination will exist in a 6**ad x 6**dd
combination matrixUpvotes: 1
Reputation:
A short answer to your specific question about 2D array is:
np.where(x >= np.transpose(x), 1, 0)
But to push this further, you need tools other than tile
. The natural way of preparing the sample space is meshgrid
:
die = np.arange(1, 7)
a, d = np.meshgrid(die, die)
Now a
and d
are 2d arrays holding the scores of the attacker and defender. As before, np.where(a <= d, 1, 0)
yields a 1-0 table (transpose of the previous one, but that's the matter of choice).
Let's see what happens when each rolls two dice:
a1, a2, d1, d2 = np.meshgrid(die, die, die, die)
Here is where the defender wins the first round (comparing highest scores):
np.where(np.maximum(a1, a2) <= np.maximum(d1, d2), 1, 0)
And here is the comparison of minima, which are 2nd highest scores:
np.where(np.minimum(a1, a2) <= np.minimum(d1, d2), 1, 0)
Both are 4D arrays, since the sample space is 4-dimensional.
Things get more complicated when someone throws 3 or more dice, because the selection of "2nd highest" isn't a straightforward operation like maximum. It can be done by stacking the dice of a player and sorting along the new axis, then slicing:
a1, a2, a3, d1, d2 = np.meshgrid(die, die, die, die, die) # 3-2 roll
attack = np.stack([a1,a2,a3]) # stack attacker into a 6D array
attack.sort(axis=0) # sort their scores
attack_max = attack[-1,:,:,:,:,:] # maximum score
attack_2nd = attack[-2,:,:,:,:,:] # 2nd highest attack score
Now we compare as before:
defender_wins_1 = np.where(attack_max <= np.maximum(d1, d2), 1, 0)
defender_wins_2 = np.where(attack_2nd <= np.minimum(d1, d2), 1, 0)
Upvotes: 0