Thoughts on Puzzle 12 - Arrow Maze?

This was a fun one. Got in late so I missed the deadline but it was fun to revisit backtracking and write a script to solve it. My total respect to people who solved it by hand, that takes a lot of patience!

2 Likes

Right on, Maris.
Backwards was how I moved forward. I found all the places that had only one option as to where to go. I treated these squares as linked. That limited the number of options.

Really, it’s all about limiting options. like sudoku.

Then I tried lining up the numbers closest together. There is less possibility for variation there.

It was good fun!

3 Likes

Anyone notice puzzle 13 is up and anyone know how to start it cause im very lost lol. didn’t give much direction

2 Likes

I solved it already. Yeah, they didn’t give a lot of instructions. One hint that I can give you: only search for those body part names (internal or external) which are made of only 8 letters, some letters within the words may repeat as well. That’s the most hint I can give.

1 Like

Hi, congratulations. I just saw that you won the Interim Period 1 prize. Enjoy your telescope. :+1:.

2 Likes

Now that puzzle 12 has expired, will anyone who has created a program to solve it might be interested to share it? I’d like to learn for it.

4 Likes

Yeah, even I want that.

1 Like

Sure. Here’s my solution in python 3. It’s not super clean or efficient
 I kinda just brute-forced it in a hurry. It takes 1m 44s to run on my laptop.

I started by defining a map/array with the directions for each square, and then created an array with the given values. It counts upwards towards 64, starting from 1, creating a new array for each possible location of the current number. This approach generated several hundred million possibilities VERY quickly
 too much for my patience (and my poor laptop) to handle. To fix this, I made it check each possible array every time it reached one of the given numbers, and discard any arrays that didn’t stand up to the test. This got it down to just over a million possibilities, which narrowed down to just one correct answer by the time it reached 64. (for some reason, at least one incorrect array escaped the first check, 9–>10, which made it into the final output
 I just ignored it).

This approach could be improved by filling in the numbers in a different way - say, finding the given numbers that are closest together and filling the spaces between them first, instead of incrementing sequentially from 1 like I did.

#%%
import numpy as np

maze = np.array([
    ['SE',  'S',  'S', 'SE',  'W',  'W',  'S', 'SW'],
    ['NE',  'S',  'E', 'SE',  'S',  'S',  'S',  'W'],
    [ 'E',  'W',  'N', 'SE', 'SW', 'NE',  'S',  'W'],
    [ 'S',  'E', 'NE', 'NW', 'NW',  'E',  'S',  'W'],
    [ 'E',  'W', 'NW',  'E', 'SE',  'W',  'S',  'W'],
    [ 'S',  'E',  'S',  'W', 'SE',  'E', 'NW',  'S'],
    ['NE', 'SE',  'E', 'NE', 'NW',  'W',  'N',  'N'],
    [ 'E', 'NW',  'N',  'W',  'E',  'N',  'W', 'END']
    ])

vals = np.array([
    [ 1, 0, 0, 0, 0, 0, 0, 0],
    [ 0, 0,16,17,52, 0, 0, 0],
    [44,43, 0,14, 0, 0,10,45],
    [ 0,27, 0, 0,15,28, 0, 0],
    [60, 0,26, 0,63, 0, 0,61],
    [ 0, 0,55,54, 0, 0, 0, 0],
    [ 0, 0,56, 0,53, 0, 0, 0],
    [ 0, 0, 0,33, 0, 0, 0,64]
])

a = [1,7]
b = [5,1]
y = [5,6]
d = [7,4]
e = [2,4]
l = [1,0]

checkvals = vals[vals!=0]
tempvals = 1*vals


def get_idxs(idx, direction):
    if direction == 'E':
        Px = np.arange(idx[1]+1, 8)
        Py = np.ones(len(Px))*idx[0]
    if direction == 'W':
        Px = np.flip(np.arange(0, idx[1]))
        Py = np.ones(len(Px))*idx[0]
    if direction == 'S':
        Py = np.arange(idx[0]+1, 8)
        Px = np.ones(len(Py))*idx[1]
    if direction == 'N':
        Py = np.flip(np.arange(0, idx[0]))
        Px = np.ones(len(Py))*idx[1]
    if direction == 'NE':
        Px = np.arange(idx[1]+1, 8)
        Py = np.flip(np.arange(0, idx[0]))
    if direction == 'NW':
        Px = np.flip(np.arange(0, idx[1]))
        Py = np.flip(np.arange(0, idx[0]))
    if direction == 'SE':
        Px = np.arange(idx[1]+1, 8)
        Py = np.arange(idx[0]+1, 8)
    if direction == 'SW':
        Px = np.flip(np.arange(0, idx[1]))
        Py = np.arange(idx[0]+1, 8)
    if len(Px) > len(Py):
        Px = Px[0:len(Py)]
    elif len(Px) < len(Py):
        Py = Py[0:len(Px)]
    Px = np.array(Px).astype(int)
    Py = np.array(Py).astype(int)
    return Px, Py


def prune(i, current_sols):
    crop_sols = []
    for p_sol in current_sols:
        previous_num = i - 1

        getit = np.where(p_sol == previous_num)
        prev_y = getit[0]
        prev_x = getit[1]
        prev_idx = [prev_y[0], prev_x[0]]
        prev_dir = maze[prev_y, prev_x]
        Px, Py = get_idxs(prev_idx, prev_dir)

        getit = np.where(p_sol == i)
        curr_y = getit[0]
        curr_x = getit[1]

        if curr_y in Py and curr_x in Px:
            crop_sols.append(p_sol)
    return crop_sols


possible_sols = [tempvals]
for i in range(1, 64, 1):
    current_sols = list([])

    if i in possible_sols[0]:
        skipval = True
        if i == 1:
            possible_sols = possible_sols
        else: 
            possible_sols = prune(i, possible_sols)
    else:
        for psol in possible_sols:
            prev_y = np.where(psol == i-1)[0]
            prev_x = np.where(psol == i-1)[1]
            prev_idx = [prev_y[0], prev_x[0]]
            direction = maze[prev_y, prev_x]
            Px, Py = get_idxs(prev_idx, direction)
            for poss in range(0, len(Px)):
                x = Px[poss]
                y = Py[poss]
                if psol[y,x] == 0:
                    temp = 1*psol
                    temp[y,x] = i
                    current_sols.append(temp)
        
        possible_sols = current_sols

correct_sol = possible_sols[1]

a = [1,7]
b = [5,1]
y = [5,6]
d = [7,4]
e = [2,4]
l = [1,0]

nums = [
    correct_sol[a[0], a[1]],
    correct_sol[b[0], b[1]],
    correct_sol[y[0], y[1]],
    correct_sol[d[0], d[1]],
    correct_sol[e[0], e[1]],
    correct_sol[l[0], l[1]]
    ]

for i in range(0, len(nums)):
    val = nums[i]
    if val > 52:
        nums[i] = val-52
    if val > 26:
        nums[i] = val-26

alphabet = 'abcdefghijklmnopqrstuvwxyz'

string = ''
for num in nums:
    string = string + alphabet[num-1]
print(string)
# %%

6 Likes

The 13th puzzle is easier but the third BODYPART word is abit tricky 
 cant find many 8 letter words describing the body that match Z and Y

1 Like

Because there are none with Z and Y.

2 Likes

If you look at the other two letters in yellow and then google 8 letter body parts, it’s quite an obscure one :slight_smile:

1 Like

I got a slightly different solution, also in Python 3.
I made it go backwards from the bottom-right square (containing 64), as there are more fixed values close to 64 than to 1, but it would be easy to change to a forwards one.

It is a recursive solution, based on a graph representation of the problem: the vertices (V) are the 64 square coordinates (0,0) 
 (7,7) and an edge is created (in V) from square x to square y if square y contains an arrow directed towards x (here is the backwards bit).

The first half of the program is building the graph.

The real algorithm is in the recursive function visit(v, n), which traverses every legal path in the maze, starting from the bottom right square v=(7,7) and value n=64, decreasing the value by 1 at every step, until it reaches value 1.

It finds a (the only) solution in about 1 second and finishes exploration in 4 seconds.

from collections import defaultdict

V = [(i, j) for i in range(8) for j in range(8)]
E = defaultdict(list)
val = {(0,0): 1, (1,3): 17, (1,4): 52, (2,1): 43, (2,3): 14, (2,6): 10, (2,7): 45, (3,4): 15, 
       (3,5): 28, (4,0): 60, (4,2): 26, (4,7): 61, (5,3): 54, (6,2): 56, (7,3): 33, (7,7): 64}
fixedpos = set(val.keys())
fixedval = set(val.values())
pos = {v:k for k, v in val.items()}

data = """12214423
72012224
04613724
20755024
04501424
20241052
71075466
05640640
"""
dirs = [(0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1)]
for i, line in enumerate(data.split()):
    for j, c in enumerate(line):
        di, dj = dirs[int(c)]
        i2, j2 = i + di, j + dj
        while 0 <= i2 < 8 and 0 <= j2 < 8:
            E[(i2,j2)].append((i,j))
            i2 += di
            j2 += dj

onpath = defaultdict(bool)
val2 = defaultdict(int)

def convert(n):
    while n > 26:
        n -= 26
    return chr(n - 1 + ord("A"))

def solution():
    return "".join(convert(val2[x]) for x in [(1,7),(5,1),(5,6),(7,4),(2,4),(1,0)])

def visit(v, n):
    if n in fixedval and v != pos[n]:
        return
    if v in fixedpos and n != val[v]:
        return
    if n == 1:
        print(solution())
        return
    onpath[v] = True
    val2[v] = n
    for w in E[v]:
        if not onpath[w]:
            visit(w, n - 1)
    val2.pop(v)
    onpath[v] = False

visit((7,7), 64)
3 Likes

Ok I have been looking all day!! haha

1 Like

I still have not found the third word but the current converted word makes no sense, I have taken note of how we have to rearrange the bodypart words.

I am currently trying vertically and reverse horizontal

1 Like

The 3rd BODYPART was the toughest to find for me as well. Hint: It’s a bone. Not going to reveal on which part of the body it lies. But I think you can figure it out, somehow. :+1:

3 Likes

I figured it out!! That bone hint helped a lot!! Thanks very much!!! Not gonna reveal for obvious reasons

3 Likes

Can share my Java version but it is very ugly in compare to the python code shred here, even the directions is hard coded

1 Like

Just share anyway! It shows your work

1 Like

none of these words making sense, I am trying moving the letters (X) amount of times to the right and left and see what happens

1 Like

“arrange the results in a particular way”. There is no x.

1 Like