Tuesday, April 28, 2020

WGC Puzzle / Breadth First Search

WGC

Wolf, Goat, and Cabbage Puzzle

In [1]:
import itertools

entities = ['Man','Wolf','Goat','Cabbage']
state = (1,1,1,1)      # 0-1 list to indicate which entities are on the Start riverbank
goalstate = (0,0,0,0)  # if entities are not on the Start riverbank, they are on the Final riverbank
In [2]:
# Helper function to print the current state tuple in readable text
def printState(State): 
    e = ' '.join(itertools.compress(entities,State))
    print(e,end=" ")
    
    print("\t|~~~| ".expandtabs(22-len(e)),end=" ")
    opp = [(x+1)%2 for x in State]  
    e = ' '.join(itertools.compress(entities,opp))
    print(e,end=" ") 
    print("\n")
In [3]:
printState(state)
Man Wolf Goat Cabbage  |~~~|   

In [4]:
printState(goalstate)
                       |~~~|  Man Wolf Goat Cabbage 

In [5]:
visited = set()             # do not return to already visited states
visited.add(state)
In [6]:
# Helper function to perform logical AND on 0-1 tuples
def listAND(list1,list2):  
    return([a and b for a, b in zip(list1,list2)])
In [7]:
# Helper function to determine if state violates conflict rules
def validState(State):
    if State[0]==1:  # if State has Man, invert to opposite riverbank
        State = [(x+1)%2 for x in State] 
    WGconflict = (0,1,1,0)  # Wolf and Goat cannot be together without Man
    GCconflict = (0,0,1,1)  # Goat and Cabbage cannot be together without Man
    if sum(listAND(WGconflict,State))==2 or sum(listAND(GCconflict,State))==2:
        return False
    else:
        return True    
In [8]:
# Return a set of all valid states that can be reached from the current state of the start riverbank
def candidateMoves(State): 
    candidates = set()
    if State[0]==1:
        # print("Man on start riverbank")
        template=[0]+list(State[1:]) # all candidate moves will have the man on the final riverbank
        newstate = template[:]
        if validState(newstate) and tuple(newstate) not in visited:
            candidates.add(tuple(newstate))
            visited.add(tuple(newstate))
        for i in range(1,4):
            if template[i]==1:
                newstate = template[:]
                newstate[i]=0
                if validState(newstate) and tuple(newstate) not in visited:
                    candidates.add(tuple(newstate))
                    visited.add(tuple(newstate))
        return candidates
    else:
        # print("Man on final riverbank")
        template=[1]+list(State[1:]) # all candidate moves will have the man on the start riverbank
        newstate = template[:]
        if validState(newstate) and tuple(newstate) not in visited:
            candidates.add(tuple(newstate))
            visited.add(tuple(newstate))
        for i in range(1,4):
            if template[i]==0:
                newstate = template[:]
                newstate[i]=1
                if validState(newstate) and tuple(newstate) not in visited:
                    candidates.add(tuple(newstate))
                    visited.add(tuple(newstate))
        return candidates
In [9]:
# Generate all possible first moves 
print("Move 1")
print("Root:",state)
branches1 = candidateMoves(state)
root2 = set()
for b in branches1:
    print("\t",b)
    root2.add(b)
    if b == goalstate:
        print("*** Goal State Reached ***")
Move 1
Root: (1, 1, 1, 1)
  (0, 1, 0, 1)
In [10]:
# Generate all possible second moves, etcoks/WGC10.ipynb
print("Move 2")
for s in root2:
    print("Root:",s)
    branches2 = candidateMoves(s)
    root3 = set()
    for b in branches2:
        print("\t",b)
        root3.add(b)
        if b == goalstate:
            print("*** Goal State Reached ***")
Move 2
Root: (0, 1, 0, 1)
  (1, 1, 0, 1)
In [11]:
print("Move 3")
for s in root3:
    print("Root:",s)
    branches3 = candidateMoves(s)
    root4 = set()
    for b in branches3:
        print("\t",b)
        root4.add(b)
        if b == goalstate:
            print("*** Goal State Reached ***")
Move 3
Root: (1, 1, 0, 1)
  (0, 0, 0, 1)
  (0, 1, 0, 0)
In [12]:
print("Move 4")
for s in root4:
    print("Root:",s)
    branches4 = candidateMoves(s)
    root5 = set()
    for b in branches4:
        print("\t",b)
        root5.add(b)
        if b == goalstate:
            print("*** Goal State Reached ***")
Move 4
Root: (0, 0, 0, 1)
  (1, 0, 1, 1)
Root: (0, 1, 0, 0)
  (1, 1, 1, 0)
In [13]:
print("Move 5")
for s in root5:
    print("Root:",s)
    branches5 = candidateMoves(s)
    root6 = set()
    for b in branches5:
        print("\t",b)
        root6.add(b)
        if b == goalstate:
            print("*** Goal State Reached ***")
Move 5
Root: (1, 1, 1, 0)
  (0, 0, 1, 0)
In [14]:
print("Move 6")
for s in root6:
    print("Root:",s)
    branches6 = candidateMoves(s)
    root7 = set()
    for b in branches6:
        print("\t",b)
        root7.add(b)
        if b == goalstate:
            print("*** Goal State Reached ***")
Move 6
Root: (0, 0, 1, 0)
  (1, 0, 1, 0)
In [15]:
print("Move 7")
for s in root7:
    print("Root:",s)
    branches7 = candidateMoves(s)
    root8 = set()
    for b in branches7:
        print("\t",b)
        root8.add(b)
        if b == goalstate:
            print("*** Goal State Reached ***")
Move 7
Root: (1, 0, 1, 0)
  (0, 0, 0, 0)
*** Goal State Reached ***


Manually follow the chain backwards up the tree to assemble the sequence of states that led to the Goal State.

[0,0,0,0] -- has parent --> [1,0,1,0]

[1,0,1,0] -- has parent --> [0,0,1,0]

[0,0,1,0] -- has parent --> [1,1,1,0]

[1,1,1,0] -- has parent --> [0,1,0,0]

[0,1,0,0] -- has parent --> [1,1,0,1]

[1,1,0,1] -- has parent --> [0,1,0,1]

[0,1,0,1] -- has parent --> [1,1,1,1]

In [16]:
# order the sequence in the forward direction, print in a readable format
chain = [[1,1,1,1],[0,1,0,1],[1,1,0,1],[0,1,0,0],[1,1,1,0],[0,0,1,0],[1,0,1,0],[0,0,0,0]]
# print it
for s in chain:
    printState(s)
Man Wolf Goat Cabbage  |~~~|   

Wolf Cabbage           |~~~|  Man Goat 

Man Wolf Cabbage       |~~~|  Goat 

Wolf                   |~~~|  Man Goat Cabbage 

Man Wolf Goat          |~~~|  Cabbage 

Goat                   |~~~|  Man Wolf Cabbage 

Man Goat               |~~~|  Wolf Cabbage 

                       |~~~|  Man Wolf Goat Cabbage