In [28]:
import collections

SIZE = 11
PM = collections.namedtuple('PM', ['name', 'prime', 'row', 'drow', 'col', 'dcol', 'dir', 'ddir'])

DIRECTION_MAP = {
  'N': 0,
  'NE': 1,
  'E': 2,
  'SE': 3,
  'S': 4,
  'SW': 5,
  'W': 6,
  'NW': 7,
}
DEGREE_STR_MAP = {
  '90deg CCW': 8 - 2,
  '45deg CCW': 8 - 1,
  'no change': 0,
  '45deg CW': 1,
  '90deg CW': 2,
  '135deg CW': 3,
}
DEGREE_DR_DC_MAP = {
  0: (0, -1),  # N.
  1: (1, -1),
  2: (1, 0),  # E.
  3: (1, 1),
  4: (0, 1),  # S.
  5: (-1, 1),
  6: (-1, 0),  # W.
  7: (-1, -1),  # NW.
}

  
def parse_data(data):
  result = {}
  lines = data.split('\n')
  for line in lines[1:]:
    properties = line.split('\t')
    for i, v in enumerate(properties):
      try:
        properties[i] = int(v)
      except:
        pass
    properties[2] -= 1
    properties[4] -= 1
    properties[-2] = DIRECTION_MAP[properties[-2]]
    result[properties[0]] = PM(*properties)
  return result

pms = parse_data("""
PM	Prime	Start Row	Row Δ	Start Col	Col Δ	Start Dir	"Dir Δ [45 deg CW]"
TRUDEAU	23	2	7	9	4	W	1
CAMPBELL	19	5	6	3	8	N	3
TURNER	17	7	7	9	5	S	7
DIEFENBAKER	13	8	8	4	4	E	2
BENNETT	11	5	2	6	7	NW	6
LAURIER	7	8	8	6	1	NW	3
BOWELL	5	6	1	1	9	NW	0
ABBOTT	3	2	9	2	2	SE	6
MACKENZIE	2	2	8	3	9	S	1
""".strip())
"""
"""


Out[28]:
'\n'

In [29]:
pms


Out[29]:
{'ABBOTT': PM(name='ABBOTT', prime=3, row=1, drow=9, col=1, dcol=2, dir=3, ddir=6),
 'BENNETT': PM(name='BENNETT', prime=11, row=4, drow=2, col=5, dcol=7, dir=7, ddir=6),
 'BOWELL': PM(name='BOWELL', prime=5, row=5, drow=1, col=0, dcol=9, dir=7, ddir=0),
 'CAMPBELL': PM(name='CAMPBELL', prime=19, row=4, drow=6, col=2, dcol=8, dir=0, ddir=3),
 'DIEFENBAKER': PM(name='DIEFENBAKER', prime=13, row=7, drow=8, col=3, dcol=4, dir=2, ddir=2),
 'LAURIER': PM(name='LAURIER', prime=7, row=7, drow=8, col=5, dcol=1, dir=7, ddir=3),
 'MACKENZIE': PM(name='MACKENZIE', prime=2, row=1, drow=8, col=2, dcol=9, dir=4, ddir=1),
 'TRUDEAU': PM(name='TRUDEAU', prime=23, row=1, drow=7, col=8, dcol=4, dir=6, ddir=1),
 'TURNER': PM(name='TURNER', prime=17, row=6, drow=7, col=8, dcol=5, dir=4, ddir=7)}

In [30]:
PM = collections.namedtuple('PM', ['name', 'prime', 'row', 'drow', 'col', 'dcol', 'dir', 'ddir'])

def wrap(n):
  return (n + SIZE) % SIZE

def pm_coord_iter(pm, n):
  n %= pm.prime
  row = (pm.row + n * pm.drow) % SIZE
  col = (pm.col + n * pm.dcol) % SIZE
  dir = (pm.dir + n * pm.ddir) % len(DIRECTION_MAP)
  dcol, drow = DEGREE_DR_DC_MAP[dir]
  for i, c in enumerate(pm.name):
    yield (c, wrap(row + drow * i), wrap(col + dcol * i))

In [31]:
def score_n(n, pms):
  scored = collections.defaultdict(dict)
  visited = set()
  for pm in pms.values():
    for c, row, col in pm_coord_iter(pm, n):
      visited.add((row, col))
  return len(visited)

def score_valid(n, pms):
  scored = collections.defaultdict(dict)
  grid = {}
  
  for pm in pms.values():
    for c, row, col in pm_coord_iter(pm, n):
      key = (row, col)
      if key not in grid:
        grid[key] = c
      elif grid[key] != c:
        return False
  return len(grid)

In [33]:
"""
best 32 748448
worst 65 2854719
best 31 3045743
best 30 6204717
best 28 23607102
worst 66 48000127
"""

best = float('inf')
best_i = -1
worst = float('-inf')
worst_i = -1

threshold = 2

for i in range(99999999 + 1):
  score = score_valid(i, pms)
  if i > threshold:
    threshold *= 2
    print(i)
  if score is False:
    continue
  print(i, score)
#   if score < best:
#     best = score
#     best_i = i
#     print('best', best, best_i)
#   if score >= worst:
#     worst = score
#     worst_i = i
#     print('worst', worst, worst_i)


3
5
9
17
33
65
129
257
513
1025
2049
4097
8193
16385
32769
65537
131073
262145
524289
1048577
2097153
4194305
8388609
16777217
24519388 60
28663528 59
29566738 60
33554433
63888718 58
67108865
94279078 62
95182288 63

In [118]:
list(pm_coord_iter(pms['MACKENZIE'], 1))


Out[118]:
[('M', 9, 0),
 ('A', 10, 10),
 ('C', 0, 9),
 ('K', 1, 8),
 ('E', 2, 7),
 ('N', 3, 6),
 ('Z', 4, 5),
 ('I', 5, 4),
 ('E', 6, 3)]

In [ ]:
# DIEFENBAKER
# TRUDEAU

In [ ]: