Saturday, May 2, 2020

The Teleporter Puzzle

Teleporter Puzzle

Constructing a New Puzzle Using Python

This puzzle was inspired by this puzzle from the MIT Puzzle Hunt:

Our Crew is Replaceable, Your Package is Not

http://web.mit.edu/puzzle/www/2009/puzzles/our_crew_is_replaceable_your_package_isnt/PUZZLE/


The Teleporter Puzzle


There are three camps: Alpha, Beta, and Gamma


Each camp has a teleporter and can swap items with other camps.

The teleporters can only exchange items; something has to be loaded into two teleporters for the teleportation process to work.


There are two items at each camp.

Each item has an attribute and a destination.


Initially, the items are positioned as follows:

ALPHA CAMP

  • Squid Ink (Toxic, belongs at Gamma)
  • Pointy Rock Tied to a Stick (Terrifying, belongs at Gamma)

BETA CAMP

  • Quantum Stirwand (Placebo, belongs at Beta)
  • Ramen Noodles (Edible, belongs at Beta)

GAMMA CAMP

  • Tinfoil Hat (Neutral, belongs at Alpha)
  • Sonic Screwdriver (Helpful, belongs at Alpha)


RULES

  • {Terrifying and Toxic} conflict with {Helpful and Edible}
  • Conflicting items cannot be together at a camp.
  • Conflicting items may pass each other in the teleporter.
  • {Neutral and Placebo} do not conflict with anything.

The goal is to move each item to it's destination while following the conflict rules


In [1]:
import itertools

Camps = ["Alpha","Beta","Gamma"]
Items = ["Ink","Rock","Hat","Wand","Sonic","Ramen"]

State0 = (0,1,2,3,4,5)     # First two items are at Alpha, second two items are at Beta, last two at Gamma
GoalState = (2,4,3,5,0,1)  # keep items sorted ascending order within camp
visited = set()            # do not return to already visited states

# Terrifying and Toxic conflict with Helpful and Edible
ConflictLookup = {0:[2,4],1:[2,4],2:[0,1],3:[],4:[0,1],5:[]}
print("ConflictLookup",ConflictLookup)
ConflictLookup {0: [2, 4], 1: [2, 4], 2: [0, 1], 3: [], 4: [0, 1], 5: []}
In [2]:
def printState(State):
    for c in range(3):
        print(Camps[c],end=": ")
        print(Items[State[2*c]],"and",Items[State[2*c+1]])

printState(State0)
Alpha: Ink and Rock
Beta: Hat and Wand
Gamma: Sonic and Ramen
In [3]:
# only allow item swaps between camps, not within the same camp
candidateSwaps = []
badSwaps = set([(0,1),(2,3),(4,5)])
for swap in itertools.combinations(range(6),2):
    if swap in badSwaps:
        continue
    candidateSwaps.append(swap)
print(candidateSwaps)
[(0, 2), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)]
In [4]:
def checkGoal(tostate):
    if tostate == GoalState:
        return True
    else:
        return False
In [5]:
def ValidState(tostate):
    if tostate in visited:
        return False
    if checkGoal(tostate):
        print("*** GOAL ACHIEVED ***")
    a,b,c,d,e,f = tostate
    # check each camp for conflicts
    if a in ConflictLookup[b] or c in ConflictLookup[d] or e in ConflictLookup[f]:
        return False
    return True
In [6]:
# Keep items sorted within camps -- so that we can recognize visited states and the goal state
# (1,2,3,4,5,6) is the same state as (2,1,3,4,5,6), so only store the sorted representation
def sortCamps(tostate):
    newlist = []
    a,b,c,d,e,f = tostate
    if a>b:
        newlist.append(b)
        newlist.append(a)
    else:
        newlist.append(a)
        newlist.append(b)
    if c>d:
        newlist.append(d)
        newlist.append(c)
    else:
        newlist.append(c)
        newlist.append(d)
    if e>f:
        newlist.append(f)
        newlist.append(e)
    else:
        newlist.append(e)
        newlist.append(f)
    return(tuple(newlist))
In [7]:
# print layer 0
print("Possible States After 0 Swaps")
print(State0)
visited.add(State0)
Possible States After 0 Swaps
(0, 1, 2, 3, 4, 5)
In [8]:
# print layer 1
print("Possible States After 1 Swap")
print("Parent State",State0)
layer1 = []
for a,b in candidateSwaps:
    StateList = list(State0)
    t = StateList[a]
    StateList[a]=StateList[b]
    StateList[b] = t
    StateTup = sortCamps(tuple(StateList))
    if not ValidState(StateTup):
        continue
    print("    ",StateTup,checkGoal(StateTup))
    visited.add(StateTup)
    layer1.append(StateTup)
Possible States After 1 Swap
Parent State (0, 1, 2, 3, 4, 5)
     (0, 1, 3, 4, 2, 5) False
     (0, 1, 3, 5, 2, 4) False
     (0, 1, 2, 4, 3, 5) False
     (0, 1, 2, 5, 3, 4) False
In [9]:
# print layer 2
print("Possible States After 2 Swaps")
layer2 = []
for State1 in layer1:
    print("Parent State",State1)
    for a,b in candidateSwaps:
        StateList = list(State1)
        t = StateList[a]
        StateList[a]=StateList[b]
        StateList[b] = t
        StateTup = sortCamps(tuple(StateList))
        if not ValidState(StateTup):
            continue
        print("    ",StateTup,checkGoal(StateTup))
        visited.add(StateTup)
        layer2.append(StateTup)
Possible States After 2 Swaps
Parent State (0, 1, 3, 4, 2, 5)
     (0, 1, 4, 5, 2, 3) False
Parent State (0, 1, 3, 5, 2, 4)
     (1, 3, 0, 5, 2, 4) False
     (1, 5, 0, 3, 2, 4) False
     (0, 3, 1, 5, 2, 4) False
     (0, 5, 1, 3, 2, 4) False
Parent State (0, 1, 2, 4, 3, 5)
     (1, 3, 2, 4, 0, 5) False
     (1, 5, 2, 4, 0, 3) False
     (0, 3, 2, 4, 1, 5) False
     (0, 5, 2, 4, 1, 3) False
Parent State (0, 1, 2, 5, 3, 4)
In [10]:
# print layer 3
print("Possible States After 3 Swaps")
layer3 = []
for State2 in layer2:
    print("Parent State",State2)
    for a,b in candidateSwaps:
        StateList = list(State2)
        t = StateList[a]
        StateList[a]=StateList[b]
        StateList[b] = t
        StateTup = sortCamps(tuple(StateList))
        if not ValidState(StateTup):
            continue
        print("    ",StateTup,checkGoal(StateTup))
        visited.add(StateTup)
        layer3.append(StateTup)
Possible States After 3 Swaps
Parent State (0, 1, 4, 5, 2, 3)
Parent State (1, 3, 0, 5, 2, 4)
     (3, 5, 0, 1, 2, 4) False
Parent State (1, 5, 0, 3, 2, 4)
Parent State (0, 3, 1, 5, 2, 4)
Parent State (0, 5, 1, 3, 2, 4)
Parent State (1, 3, 2, 4, 0, 5)
     (3, 5, 2, 4, 0, 1) False
Parent State (1, 5, 2, 4, 0, 3)
Parent State (0, 3, 2, 4, 1, 5)
Parent State (0, 5, 2, 4, 1, 3)
In [11]:
# print layer 4
print("Possible States After 4 Swaps")
layer4 = []
for State3 in layer3:
    print("Parent State",State3)
    for a,b in candidateSwaps:
        StateList = list(State3)
        t = StateList[a]
        StateList[a]=StateList[b]
        StateList[b] = t
        StateTup = sortCamps(tuple(StateList))
        if not ValidState(StateTup):
            continue
        print("    ",StateTup,checkGoal(StateTup))
        visited.add(StateTup)
        layer4.append(StateTup)
Possible States After 4 Swaps
Parent State (3, 5, 0, 1, 2, 4)
     (2, 5, 0, 1, 3, 4) False
     (4, 5, 0, 1, 2, 3) False
     (2, 3, 0, 1, 4, 5) False
     (3, 4, 0, 1, 2, 5) False
Parent State (3, 5, 2, 4, 0, 1)
     (2, 5, 3, 4, 0, 1) False
     (4, 5, 2, 3, 0, 1) False
     (2, 3, 4, 5, 0, 1) False
     (3, 4, 2, 5, 0, 1) False
In [12]:
# print layer 5
print("Possible States After 5 Swaps")
layer5 = []
for State4 in layer4:
    print("Parent State",State4)
    for a,b in candidateSwaps:
        StateList = list(State4)
        t = StateList[a]
        StateList[a]=StateList[b]
        StateList[b] = t
        StateTup = sortCamps(tuple(StateList))
        if not ValidState(StateTup):
            continue
        print("    ",StateTup,checkGoal(StateTup))
        visited.add(StateTup)
        layer5.append(StateTup)
Possible States After 5 Swaps
Parent State (2, 5, 0, 1, 3, 4)
     (2, 4, 0, 1, 3, 5) False
Parent State (4, 5, 0, 1, 2, 3)
Parent State (2, 3, 0, 1, 4, 5)
Parent State (3, 4, 0, 1, 2, 5)
Parent State (2, 5, 3, 4, 0, 1)
*** GOAL ACHIEVED ***
     (2, 4, 3, 5, 0, 1) True
Parent State (4, 5, 2, 3, 0, 1)
Parent State (2, 3, 4, 5, 0, 1)
Parent State (3, 4, 2, 5, 0, 1)
In [13]:
# looking back up the tree and chaining the parents together
chain = [(0, 1, 2, 3, 4, 5),(0, 1, 2, 4, 3, 5),(1, 3, 2, 4, 0, 5),(3, 5, 2, 4, 0, 1),(2, 5, 3, 4, 0, 1),(2, 4, 3, 5, 0, 1)]
In [14]:
for idx,state in enumerate(chain):
    print("\nSTATE",idx)
    printState(state)
STATE 0
Alpha: Ink and Rock
Beta: Hat and Wand
Gamma: Sonic and Ramen

STATE 1
Alpha: Ink and Rock
Beta: Hat and Sonic
Gamma: Wand and Ramen

STATE 2
Alpha: Rock and Wand
Beta: Hat and Sonic
Gamma: Ink and Ramen

STATE 3
Alpha: Wand and Ramen
Beta: Hat and Sonic
Gamma: Ink and Rock

STATE 4
Alpha: Hat and Ramen
Beta: Wand and Sonic
Gamma: Ink and Rock

STATE 5
Alpha: Hat and Sonic
Beta: Wand and Ramen
Gamma: Ink and Rock