Skip to content

Instantly share code, notes, and snippets.

@Zimmux
Last active July 1, 2019 14:29
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Zimmux/c0fdb3ddad654b6b0ae2 to your computer and use it in GitHub Desktop.
Save Zimmux/c0fdb3ddad654b6b0ae2 to your computer and use it in GitHub Desktop.
gameaboutsquares solver
from collections import deque
class hashabledict(dict):
def __hash__(self):
return hash(tuple(sorted(self.items())))
def dictFromChar2D(c2d=[], filtr=lambda _:True):
return {
(x,y):c
for y,c1d in zip(range(len(c2d)),c2d)
for x,c in zip(range(len(c1d)),c1d)
if filtr(c)
}
offsets = {">":(1,0), "<":(-1,0), "^":(0,-1), "v":(0,1)}
def nextStates(state,arrowDict):
for (x,y),bd in state.items(): # pick a block to move
s2 = hashabledict(state)
del s2[(x,y)]
b, d = bd
dx, dy = offsets[d]
while bd: # push all adjacent blocks
x,y = x+dx,y+dy
bd2 = s2.get((x,y))
s2[(x,y)] = (bd[0], arrowDict.get((x,y)) or bd[1])
bd = bd2
yield b,s2 # (chosen block, next state)
def isDone(state,targets):
return all(p in state and state[p][0]==b for p,b in targets.items())
def solve(blocks, startDir, targets, arrows):
prune = lambda state: not all(
-numBlocks<x<width+numBlocks-1 and
-numBlocks<y<height+numBlocks-1
for (x,y) in state)
blockDict = dictFromChar2D(blocks, lambda c:c!=' ')
arrowDict = dictFromChar2D(arrows, lambda c:c!=' ')
targetDict = dictFromChar2D(targets, lambda c:c!=' ')
numBlocks = len(blockDict)
width = max(x for d in [blockDict,arrowDict,targetDict] for (x,y) in d)
height = max(y for d in [blockDict,arrowDict,targetDict] for (x,y) in d)
state0 = hashabledict({p:(b,startDir[b]) for p,b in blockDict.items() })
trace = dict()
frontier = deque()
frontier.append(state0)
trace[state0] = None
while frontier:
s = frontier.popleft()
ns = nextStates(s,arrowDict)
lastC = trace[s][1] if trace[s] else None # last clicked block
ns = sorted(ns, key=lambda t: t[0]!=lastC) # we prefer clicking the same block immediately
for c,s2 in ns:
if s2 not in trace and not prune(s2):
trace[s2] = (s,c) # store previous state and chosen block to reconstruct solution
frontier.append(s2)
if isDone(s2,targetDict):
ssc = (s2,'!')
tr = []
while ssc:
tr += [ssc]
ssc = trace.get(ssc[0])
tr.reverse()
print len(trace),"states explored"
return tr
return [] # no solution
solution = solve(
startDir = {
"Y":">",
"R":">",
"B":">"
},
blocks = [
" ",
" ",
" ",
" ",
"YRB "
],
targets = [
" BRY",
" ",
" ",
" ",
" "
],
arrows = [
" v ",
" <",
" ",
"> ",
" ^ "
]
)
# for state, move in solution:
# print state, move
print ''.join(c for ss,c in solution)
print len(solution)-1,"moves"
@tburette
Copy link

I haven't investigated why but it doesn't give the right results for the level 20 (and probably other levels):

solution = solution20 = solve(
        startDir = {
                "D":"v",
                "G":">",
                "L":"^",
                "P":"<"
        },
        blocks = [
                "D P",
                "   ",
                "G L",
                "   ",
                "   "
    ],
        targets = [
                " G ",
                "D L",
                " P ",
                "   ",
                "   "
        ],
        arrows = [
                "v <",
                "   ",
                "> ^",
                "   ",
                "^  "
        ]
)

This result in:

600843 states explored
PDLLLPLDLLGLGGDGGDGGDLP!
23 moves

Which is not a solution

@Zimmux
Copy link
Author

Zimmux commented Aug 9, 2014

That looks like a solution to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment