In [1]:
import forge
from puzzle.puzzlepedia import puzzlepedia
puzzle = puzzlepedia.parse("""
# NB: Column is implied from alpha-order. See #4.
items in {
Alarm, Chills, Dread, Terror, # Children’s Tinned Fears.
Balls, Floss, Biscuits, Mints, # Monster Treats.
Anger, Boredom, Laughter, Sorrow, # Salts.
Collywobbles, Panic, Jeebies, Unease, # Tinned Fears.
}
aisle in range(1, 4+1)
height in {three, four, five, six}
knocked in {down, up}
# Custom constraints needed.
model.disable_constraints()
groups = [
[Alarm, Chills, Dread, Terror], # Children’s Tinned Fears.
[Balls, Floss, Biscuits, Mints], # Monster Treats.
[Anger, Boredom, Laughter, Sorrow], # Salts.
[Collywobbles, Panic, Jeebies, Unease], # Tinned Fears.
]
front = [Alarm, Balls, Anger, Collywobbles]
def aisle_n(item):
return sum(i*item[i] for i in range(1, 4+1))
#1 DONE: Each aisle was dedicated to one type.
for g in groups:
for a, b in zip(g, g[1:]):
all(a[i] == b[i] for i in aisle)
# Each group is only in one aisle.
sum(g[0][i] for i in aisle) == 1
for i in aisle:
# Every aisle has at least one group.
any(f[i] for f in front)
#2 DONE: In setup.
#3 DONE: Each aisle has a 1 knocked down (values are sync'd).
for g in groups:
for a, b in zip(g, g[1:]):
all(a[h] == b[h] for h in height)
for i in items:
# Each item can only have 1 height.
sum(i[h] for h in height) == 1
# Up one of each height was knocked down.
for h in height:
sum(h[f] for f in front) == 1
#4 DONE: Implicit.
#5 DONE.
#5a One spill per row / column.
for g in groups:
# All items are either down or up.
all(x.down or x.up for x in g)
# One knocked down in every group.
sum(x.down for x in g) == 1
#5b One knocked down for every row.
for row in range(4):
sum(g[row].down for g in groups) == 1
#6 DONE.
if Mints.down: Laughter.down
#7 DONE:
#7a 18 levels were knocked over.
# In setup. 3+4+5+6=18 is only possibility.
#7b 4 stack is not left-most.
all((f.four and f[1]) == False for f in front)
#8 DONE: The 3 stack is not left-most.
all((f.three and f[1]) == False for f in front)
#9 DONE.
#9a (Collywobbles is an example of "Fears")
Collywobbles == 1 or Collywobbles == 4
#9b
Collywobbles == three or Collywobbles == five
#10 DONE.
#10a "Zombie Fresh Mints are on the (...) corner"
Mints == 1 or Mints == 4
#10b Mints are on the opposite corner from something else knocked down.
# -> Something knocked over is TL or TR.
any(f.down and (f == 1 or f == 4) for f in front)
# -> The first item in the Mints category (Balls) is *not* down.
Balls.down == False
#11 TODO.
#11 "[six] was immediately to the right of an alliterative item."
#alliterative = [
# Balls, # Banshee Balls, 1st in aisle.
# Floss, # Fang Floss, 2nd in aisle.
#]
# -> alliterative item cannot be in aisle 4.
Balls != 4
# -> Balls' row doesn't have six.
Balls != six
# -> six is in one row higher than Balls.
#aisle_n(six) == aisle_n(Balls) + 1
#eligible_front = [Alarm, Anger, Collywobbles]
#any(i == six and )
# -> six has to be in row 1 or 2.
eligible_front_rows = [
Alarm, Chills,
Anger, Boredom,
Collywobbles, Panic,
]
ineligible_back_rows = [
Dread, Terror,
Laughter, Sorrow,
Jeebies, Unease,
]
#for i in ineligible_back_rows:
# (i == six and i == down) == False
#if Balls == 1: sum(i == 2 and i == down and i == six for i in eligible_front_rows) == 1
Balls == 1
# HACK
#Laughter == up
#all(if f.down and f.six)
#12 DONE. ("Anger" is an example of Salts, "Alarm" is an example of Children's Fears.)
aisle_n(Anger) < aisle_n(Alarm)
# OBSERVATIONS:
# Aisle 1 is five because everything else is "right of" something.
# Aisle 4 is three because the fears are odd.
# Zombie Mints are 1 or 4 (because corner) but they also have alliteration and are
# to the left of six.
# If alliteration is 1 then six is 2.
if Alarm == 3: Alarm != six
# HACK.
if Laughter == 2: Laughter == up
if Sorrow == 2: Sorrow == up
""")
# Triangle numbers.
hack_map = {
'three': 6,
'four': 10,
'five': 15,
'six': 21,
}
hack_map2 = {
'three': 3,
'four': 4,
'five': 5,
'six': 6,
}
item_map = {
'Chills': 'The Chills',
'Dread': 'Creeping Dread',
'Terror': 'Night Terror',
'Balls': 'Banshee Balls',
'Floss': 'Fang Floss',
'Biscuits': 'Werewolf Biscuits',
'Mints': 'Zombie Fresh Mints',
'Anger': 'Salt Made Tears of Anger',
'Boredom': 'Salt Made from Tears of Boredom',
'Laughter': 'Salt Made from Tears of Laughter',
'Sorrow': 'Salt Made from Tears of Sorrow',
'Collywobbles': 'The Collywobbles',
'Panic': 'Escalating Panic',
'Jeebies': 'The Heebie-Jeebies',
'Unease': 'A Vague Sense of Unease',
}
def extract(s):
result = {}
for line in s.split('\n'):
if 'down' not in line:
continue
columns = line.split()
item = columns[0]
isle = int(columns[2])
height = hack_map[columns[4]]
result[isle] = (item, height)
for i in range(1, 4+1):
item, height = result[i]
item = item_map.get(item, item)
item_short = item.replace(' ', '')
print(i, item, height + 1, item[height], item_short[height])
extract(puzzle.solutions()[0])
In [95]:
len('Salt Made from Tears of Laughter')
Out[95]:
In [ ]: