Skip to content

Instantly share code, notes, and snippets.

@swizzlevixen
Created October 21, 2017 02:20
Show Gist options
  • Save swizzlevixen/2bffde9a5a0c35734f6486561eceb031 to your computer and use it in GitHub Desktop.
Save swizzlevixen/2bffde9a5a0c35734f6486561eceb031 to your computer and use it in GitHub Desktop.
Trying to perfectly numerically balance numbers on the surface of a d20 icosahedron for fairness.
#!/usr/bin/env python3
"""
Balanced d20 Icosahedron
Code to determine the ideal balanced number layout for a d20 die, so that
it is the most "fair", as explained on this page from The Dice Lab:
http://www.mathartfun.com/thedicelab.com/BalancedStdPoly.html
“The numberings of The Dice Lab's d20 and d30 were worked out by Bob Bosch,
a Professor of Mathematics at Oberlin College.”
The criteria:
- Each pair of opposing faces must add up to 21
- The sum of the five faces surrounding each vertex must average 52.5
(Ideally, half 52 and half 53.)
- The sum of the three faces adjacent to each face must average 31.5
(Ideally, half 31, and half 32.)
Seeing that page made me curious how one would figure this out. Is a brute
force technique enough, or is there a more elegant way? I'm not a mathematics
professor, so my reasoning may not be best, but I'm going to give it a shot.
"""
from string import ascii_lowercase
import random
# 12 vertices on the icosahedron, each labeled with a letter
verts = ascii_lowercase[0:12]
# 20 faces on the icosahedron,
# each named with the three vertices that mark its corners,
# in a clockwise direction, starting with the first letter alphabetically
# Each pair of tuples are opposing faces on the icosahedron,
# with the arbitrary "obverse" face listed first, "reverse" second.
face_pairs = [('abc','jkl'),
('acd','gkj'),
('afb','ijl'),
('bhc','elk'),
('ade','gjh'),
('aef','hji'),
('cid','fkg'),
('chi','ekf'),
('bgh','dle'),
('bfg','dil')]
faces = []
for face_pair in face_pairs:
faces.append(face_pair[0])
faces.append(face_pair[1])
class AssignmentException(Exception):
"""Exception type raised during assignment, for handling"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
# Dictionary of assigned number values on faces
# key: face name, e.g., 'abc'
# value: numerical value for the face
assignments = {}
def assign(assign_face, assign_value):
"""
Assigns a number to a face on the d20,
with some error checking.
"""
# Value must be between 1 and 20, inclusive
if assign_value not in range(1,21):
raise AssignmentException("Value out of range.")
# Value must not already exist on the die
elif assign_value in assignments.values():
raise AssignmentException("Value already assigned.")
elif assignments[assign_face] == 0:
for face_pair in face_pairs:
if face_pair[0] == assign_face:
assignments[face_pair[0]] = assign_value
assignments[face_pair[1]] = 21 - assign_value
return assignments[face_pair[0]]
elif face_pair[1] == assign_face:
assignments[face_pair[1]] = assign_value
assignments[face_pair[0]] = 21 - assign_value
return assignments[face_pair[1]]
def reset_assignments():
"""
Resets all faces on the d20 to 0,
except for the starting opposing faces of 1 and 20
"""
for face_pair in face_pairs:
assignments[face_pair[0]] = 0
assignments[face_pair[1]] = 0
assign('abc', 1)
reset_assignments()
##########
#
# Okay, all set up. Now how do I go about assigning numbers?
#
# I feel like there is a good mathematical way to associate these equations
# but I just don't have the mathematical know-how to do it. Forgive my lack of
# subscript type, but here's how I'm going to denote things:
#
# For a given face, 'f',
# 'fo' is the opposing face,
# 'fa_' is an adjacent face, e.g., fa1, fa2, fa3
# For each vertex, 'v', there are five adjacent faces. But to try to associate
# this with the adjacent faces equation, could we designate them with 'fa_a_'?
# For instance, around one vert is fa1a1, fa1, f, fa2, fa2a2
#
# /sigh/ I'm not sure if I'm going in the right direction at all.
#
# Anyway, the equations:
#
# f + fo = 21
# fa1 + fa2 + fa3 = 31 or 32
# v1 + v2 + v3 + v4 + v5 = 52 or 53
# (OR as faces) fa1a1 + fa1 + f + fa2 + fa2a2 = 52 or 53
#
##########
# Random Assignment
#
# Let's try assigning random numbers.
def rand_from_list(unused_values):
"""
Returns a random value from a list of unused_values
"""
return random.choice(unused_values)
def assign_vert_neighbors(vert):
"""
Assigns values to all face neighbors of a vert
vert: String label for the vertex ('a'-'l')
"""
vert_faces = []
face_total = 0
# All values 1-20 that are not already in assignments
unassigned_values = [x for x in list(range(1, 21)) if x not in assignments.values()]
print(unassigned_values)
for face in faces:
if vert in face:
vert_faces.append(face)
# print(vert_faces)
if len(vert_faces) != 5:
raise AssignmentException("Incorrect number of vert faces")
for face in vert_faces:
if assignments[face] != 0:
# Already assigned; add it to the total
face_total = face_total + assignments[face]
else:
# Assign a random value from those unassigned.
assign(face, random.choice(unassigned_values))
# Remove the value and its opposing pair from the unassigned list
unassigned_values.remove(assignments[face])
unassigned_values.remove(21 - assignments[face])
# print(unassigned_values)
# print(assignments)
assign_vert_neighbors('a')
assign_vert_neighbors('b')
assign_vert_neighbors('c')
##########
#
# This definitely assigns some random numbers, and the opposing numbers
# add up to 21, so that's good, but we haven't done any checking to see if
# our other two balancing equations are being met.
#
# Should I be trying to meet the equation as we fill the numbers,
# or just check them after?
#
##########
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment