# Enigmatic Code

Programming Enigma Puzzles

## Enigma 290: Dice with a difference

From New Scientist #1438, 10th January 1985 [link]

Throwing two dice will give you a number from 2 to 12. Of course, some numbers are more likely that others. The probability of 2 for instance is 1/36; of 3 is 2/36; of 4 is 3/36; …; of 7 is 6/36; …; of 8 is 5/36; …; of 12 is 1/36.

That is true of two ordinary 6-sided dice, each bearing the letters of ENIGMA (which stand for the numbers one to six).

It is also true of this special pair of dice I have made — one with 9 sides bearing the letters IMAGINING, the other with 4 sides bearing the letters of GAGS. (S is a positive integer).

I’m not going to tell you how I constructed 9-sided and 4-sided dice. But I did, and they are fair dice. Can you interpret the MEANINGS of these fascinating facts?

[enigma290]

### 4 responses to “Enigma 290: Dice with a difference”

1. Jim Randell 26 June 2015 at 8:11 am

This Python code runs in 49ms.

```# each die must have a single 1 on it (so that there is only one combination that makes 2)
# so one of the following holds:
#  A = 1 and S > 1
#  M = 1 and S = 1
#
# also there can only be one combination that makes 12
# so one of the following holds:
#  A = 6 and S < 6
#  M = 6 and S = 6
#  E = 6 and (A = 5 or M = 5) and S = 7

from itertools import permutations, product
from collections import defaultdict
from enigma import irange, printf

# counts for a normal pair of dice
normal = { 2: 1, 3: 2, 4: 3, 5: 4, 6: 5, 7: 6, 8: 5, 9: 4, 10: 3, 11: 2, 12: 1 }

# possible values for E, N, I, G, M, A
for (E, N, I, G, M, A) in permutations(irange(1, 6)):
if not(A == 1 or M == 1): continue
if not(A == 6 or M == 6 or E == 6): continue
if E == 6 and not(A == 5 or M == 5): continue
# and S
S = (M if M == 1 or M == 6 else 7)
# count the values for the new dice
count = defaultdict(int)
for (d1, d2) in product((I, M, A, G, I, N, I, N, G), (G, A, G, S)):
v = d1 + d2
if v not in normal: break
c = count[v] + 1
if c > normal[v]: break
count[v] = c
else:
# check the counts have been achieved
if all(c == normal[v] for (v, c) in count.items()):
# output the solution
printf("E={E} N={N} I={I} G={G} M={M} A={A} S={S}")
```

Solution: MEANINGS = 56123247.

So the 9-sided die has values 1, 2, 2, 3, 3, 3, 4, 4, 5. And the 4-sided die has values 1, 4, 4, 7.

Without the analysis from the 2 and 12 cases the same program examining all the possible permutations of values for ENIGMA from 1 to 6 and S from 1 to 7 runs in 113ms.

2. Brian Gladman 26 June 2015 at 10:11 am

A Funny Dice problem involving non standard dice was published in the Sunday Times in March this year. The ensuing discussion involved one contributor alerting me to an interesting mathematical approach for solving puzzles of this general type. Here is a solution to this enigma teaser using the approach (it requires my number theory and polynomial libraries).

```from itertools import combinations, permutations
from functools import reduce
from operator import mul

from number_theory import divisors
from polynomial import Poly, cyclotomic

# output the face values of a die specified by polynomial p
def die(p):
d = []
for e, v in enumerate(p.coeff):
d += [e] * v
return tuple(sorted(d))

def dice(faces):
# for saving solutions
sol = []
# holds the cyclotomic polynomial factorisations of the dice
# generating polynomial x + x^2 + ... + x^faces
l_poly = []
for d in divisors(faces):
if d > 1:
l_poly += [cyclotomic(d)]
ln_poly = len(l_poly)
l_p2 = l_poly + l_poly
# now choose the number of polynomial factors for the
# first die
for nf in range(1, ln_poly + 1):
# check all combinations of this number of polynomials
for f in combinations(range(len(l_p2)), nf):
# list the polynomials for the first die
t = [l_p2[i] for i in f]
# multiply them (and x) for the polynomial for the first die
p1 = reduce(mul, t, Poly([0, 1]))
# list the remaining polynomials for the second die
t = [l_p2[i] for i in range(len(l_p2)) if i not in f]
# multiply them (and x) for the polynomial for the second die
p2 = reduce(mul, t, Poly([0, 1]))
# all polynomial coefficients must be non-negative without zeros
if all(x >= 0 for x in p1.coeff + p2.coeff) and p1(0) == p2(0):
# save the solution if it is new
t = tuple(sorted((die(p1), die(p2))))
if t not in sol:
sol += [t]
yield t

# generate pairs of dice giving the same results as normal
# six sided dice when thrown as pairs and added
for p in dice(6):
# order the dice on their numbers of faces
d1, d2 = sorted(p, key=len)
# consider only dice pairs with 4 and 9 faces that have three
# and five distinct face values respectively
if (len(d1) == 4 and len(d2) == 9 and
len(set(d1)) == 3 and len(set(d2)) == 5):

# they must share a value that must also occur twice on each die
for g in (x for x in set(d1) if d1.count(x) == d2.count(x) == 2):
# now permute the other two numbers on die one for A and S
for a, s in permutations(set(d1).difference([g])):
# and check that A's number occurs only once on die 2
if d2.count(a) == 1:
# now allocate the other numbers on die two to I, M and N
for i, m, n in permutations(set(d2).difference([g, a])):
# and check that they occur the correct number of times
if d2.count(i) == 3 and d2.count(m) == 1 and d2.count(n) == 2:
# E's value occurs on normal die but not on die two
for e in set(range(1, 7)).difference(d2):
d = dict(zip('EGASIMN', (e, g, a, s) + (i, m, n)))
mngs = ''.join(str(d[x]) for x in 'MEANINGS')
print('MEANINGS = {}, dice = {}, {}'.format(mngs, d1, d2))
```

It takes a lot more effort to program (and is slower too) but the maths is elegant and that appeals to me.

3. hakank 26 June 2015 at 7:58 pm

Here’s a MiniZinc solution of #290: http://hakank.org/dice_with_a_difference_enigma_290.mzn
I especially like the declarative constraints of this.

```int: n = 9; % first die, 9 sides, IMAGINING
int: f = 4; % second die, 4 sides, GAGS
int: e = 6; % ENIGMA dice, 6 sides

int: k = 12;
array[2..k] of int: probs = array1d(2..k, [1,2,3,4,5,6,5,4,3,2,1]);

% decision variables
var 1..k: I;
var 1..k: M;
var 1..k: A;
var 1..k: G;
var 1..k: N;
var 1..k: S;
var 1..e: E;

array[1..n] of var int: dice9 = [I,M,A,G,I,N,I,N,G];
array[1..f] of var int: dice4 = [G,A,G,S];
array[1..e] of var int: dice6 = [E,N,I,G,M,A];

solve satisfy;

constraint
forall(k in 2..k) (
probs[k] = sum(i in 1..n, j in 1..f) ( dice9[i]+dice4[j] == k) /\
probs[k] = sum(i,j in 1..e) ( dice6[i]+dice6[j] == k)
)
;

output [
"dice9 (IMAGINING): ", show(dice9), "\n",
"dice4 (GAGS): ", show(dice4), "\n",
"dice6 (ENIGMA): ", show(dice6), "\n",
"MEANINGS: ", show([M,E,A,N,I,N,G,S]), "\n"
];
```
4. hakank 26 June 2015 at 8:39 pm

And here’s a MiniZinc model of the Funny Dice problem, using the same basic approach for calculating the probabilities (though now they are decision variables instead of constants): http://hakank.org/minizinc/funny_dice.mzn

Gecode solves this in 48ms using “var int”, or 36ms when using var 1..12 as the domains.

```include "globals.mzn";
int: n = 6;

% decision variables
% Note: It doesn't matter if we use "var int" or var 1..12 as the domains.
% Gecode solves this in 48ms using var int, and 38ms using var 1..12.
% In "production", I always use as small domains as possible (but not smaller).
array[1..n] of var 1..12: red;
array[1..n] of var 1..12: blue;
array[2..12] of var 1..12: probs;

% array[1..n] of var int: red;
% array[1..n] of var int: blue;
% array[2..12] of var int: probs;

% solve satisfy;
solve :: int_search(probs ++ red ++ blue, occurrence, indomain_min, complete) satisfy;

constraint
% domains
forall(i in 1..n) ( red[i] >= 1 /\ blue[i] >= 1) /\

% The total of the six numbers on the red die is higher than the same total for the blue die.
sum(red) > sum(blue) /\

% ensure the probabilities
forall(k in 2..12) (
probs[k] >= 1 /\ % domains
probs[k] = sum(i,j in 1..n) ( red[i]+blue[j] == k)
)
/\ % the relations of the probabilities
forall(i in 2..6) (
probs[i] = probs[12-i+2] /\
probs[i]  probs[6+i]
)
/\ probs[7] = max(probs)

% symmetry breaking (without it, it takes longer time)
/\ increasing(red)
/\ increasing(blue)
;

output [
"probs: ", show(probs), "\n",
"red: ", show(red), "\n",
"blue: ", show(blue), "\n",
];
```

Related: The Nontransitive dice problem (with some explorations): http://hakank.org/minizinc/nontransitive_dice.mzn