How do I get more Klotski in my life?

15

I really love sliding tile puzzles, but recently, I haven't had time for them. Hence, I need a program to give me my fix of sliding-tile puzzles, specifically Klotski puzzles.

Your input will be in the following format:

#######
#001gg#
##.222#
.######

where # represents walls, . represents an open area, g represents the goal, and adjacent numbers represent different blocks. You can assume that:

  1. There will be no more than 10 blocks
  2. There won't be two blocks with the same number
  3. All blocks will be enclosed by walls
  4. The grid is rectangular
  5. The 0 block is large enough to cover all of the goal squares.
  6. There is a valid solution

You need to return a sequence of moves that will put the 0 block so that it covers all of the goal squares. Blocks cannot pass through walls or other blocks. For the above puzzle, an appropriate sequence would be

2L,1R,1R,1D,0R,0R,0R

while represents moving the 2 block 1 square left, the 1 block 2 squares right (on top of the goal) then 1 square down, and then 0 block 3 squares right.

There are actually several sequences that will work for the above problem, and producing any of them is acceptable. Your solution should be optimal, meaning that it should produce a sequence that solves the puzzle in as few as steps as possible.

The sequence should be printed out as above, but can be comma, newline, or space separated. I don't care if there are trailing commas or whitespace. You should produce the output in reasonable time (maximum 120 seconds on the puzzles below).

Puzzle 1:

..####..
..#00#..
###00###
#......#
#.1122.#
##3124##
.#3344#.
.##55##.
..#gg#..
..####..

Puzzle 2:

######
#1002#
#1002#
#3445#
#3675#
#8gg9#
######

Puzzle 3:

.####.
##1g##
#22g3#
#4255#
#4.56#
#.006#
#7008#
######

Puzzle 4:

.####.
##00##
#.00g#
#.0.1#
#..g2#
######

This is code-golf, so the shortest solution (in bytes) wins!

Nathan Merrill

Posted 2015-03-27T19:00:18.153

Reputation: 13 591

Just a thought - as I read this, I found something a bit confusing. The goals, being "hidden" were hard to see sometimes. In the example you have, they can be "guessed" with resonable accuracy, however, in the event a block is covering the goal entirely, you should have a way to clearly denote the entire goal. What if: Letters for blocks, Upper case when that spot is on a goal? . for space, * for goal ? everything else same ? would that be more clear ? – Ditto – 2015-03-27T19:45:38.570

@Ditto there is never a case where a block starts on a goal square. The last example simply has two disconnected goal squares. – Nathan Merrill – 2015-03-27T20:42:40.353

Can we assume every input puzzle has a solution? – orlp – 2015-03-27T23:31:08.937

@orlp yes, I'll add that to the problem statement. – Nathan Merrill – 2015-03-27T23:36:12.390

@NathanMerrill To make sure we're doing things correct, could you add the optimal amount of moves for puzzle 1-4? – orlp – 2015-03-28T01:28:49.343

@orlp, I don't want to post all of them, but the first one should take 87 moves if my program is accurate. – Nathan Merrill – 2015-03-28T03:11:47.600

@NathanMerrill If you mean Puzzle 1, then I've got a solution in 70 steps. This is a big problem, seeing as we now have no way of making sure an entry is correct.

– orlp – 2015-03-28T03:52:07.200

@orlp I'm assuming that multiple entries will provide a reasonable consensus of what the shortest solution actually is. – Nathan Merrill – 2015-03-28T04:19:28.427

Puzzle 1, 70 steps in 67 seconds. Now golfing – edc65 – 2015-03-28T19:00:55.650

@edc65 Need to optimize more then, question states a limit of 30 seconds. – orlp – 2015-03-28T23:42:06.453

For puzzle 2 & 3, I dubt an optimal solution can be found in 30 second of less – edc65 – 2015-03-30T00:19:38.777

I'm raising the time limit. I simply don't want dumb brute forcing solutions. – Nathan Merrill – 2015-03-30T02:35:57.970

@edc65 You're wrong, my answer solves all optimally in < 30 seconds. – orlp – 2015-04-03T18:21:57.117

Why unaccepting? Did you spot some mistake in my answer? – edc65 – 2017-08-05T08:30:09.243

@edc65 My current opinion is that all questions are open, and so no answer should be accepted :) I upvoted your answer so you at least can redeem some of that rep. – Nathan Merrill – 2017-08-05T15:51:21.873

Answers

5

Python, 1761

Kind of burnt out on this question, so couldn't bring myself to golf it. Either way, right now it's the only solution that solves everything within the time limit (longest, #3, takes 27 seconds).

pieces = {}
taken = set()
goals = set()

y = 0
while True:
    try:
        for x, c in enumerate(input()):
            if c == ".": continue
            if c == "g":
                goals.add((x, y))
            else:
                if c in "0123456789":
                    if c not in pieces: pieces[c] = set()
                    pieces[c].add((x, y))
                taken.add((x, y))

        y += 1

    except: break

def translate_comp(coords):
    o = min(sorted(coords))
    return set((c[0] - o[0], c[1] - o[1]) for c in coords)

similar = {}
for piece in pieces:
    k = tuple(translate_comp(pieces[piece]))
    if k not in similar: similar[k] = []
    similar[k].append(piece)


seen = set()
states = [(pieces, taken, ())]
while states:
    state = states.pop(0)
    if not goals - state[0]["0"]:
        names = {
            (-1, 0): "L",
            (1, 0): "R",
            (0, 1): "D",
            (0, -1): "U",
        }

        print(len(state[2]))
        print(" ".join(piece + names[d] for d, piece in state[2]))
        break

    for piece in pieces:
        for d in ((-1, 0), (1, 0), (0, 1), (0, -1)):
            new_pieces = state[0].copy()
            new_pieces[piece] = {(c[0] + d[0], c[1] + d[1]) for c in state[0][piece]}
            new_taken = state[1] - state[0][piece]

            # Collision
            if new_pieces[piece] & new_taken:
                continue

            gist = tuple(frozenset().union(*(new_pieces[piece] for piece in similar_set))
                         for similar_set in similar.values())

            if gist in seen:
                continue

            seen.add(gist)
            new_taken |= new_pieces[piece]
            states.append((new_pieces, new_taken, state[2] + ((d, piece),)))

orlp

Posted 2015-03-27T19:00:18.153

Reputation: 37 067

Wow great! And surely not in the fastest language – edc65 – 2015-04-03T19:37:23.497

It seems a totally different approach and I don't understand Python well. But I like the idea of finding pieces with the same shape. That could reduce a lot the space of visited position in my code. May I borrow it for my solution? – edc65 – 2015-04-05T09:24:18.040

@edc65 Sure. It's not a different approach though, I also do a breadth first search - I just do not look into the same board twice (and blocks with the same shape swapped counts as the same board). – orlp – 2015-04-05T12:04:51.333

4

JavaScript (ES6), 446 388

Breadth First Search, so the first solution found is the shortest.
While I still think that's a good solution, it's not good enough. Even after checking millions of position (runtime several minutes), I could not find a solution for example 2 and 3.

Edit ES6 modified version to overcome the javascript execution time limit. Puzzle 3 solved in 7min, 145 steps. Puzzle 2 solved in 10min, 116 steps

Edit 2 Big speedup, using @orlp's idea of considering equal any two blocks with the same shape (excluding block '0' that is special). This reduces the space of visited positions during BSF. For example, for puzzle 2, any position with block 1,2,3 or 5 exchanged is really the same.

Timing: the longest is puzzle 3, ~20 sec on my laptop.

Use Firefox to play with the new JsFiddle to test.

F=g=>(o=>{
for(u=[i=s=''],v={},h=[],b={},k={'.':-1},l={},
g=[...g].map((c,p)=>c>'f'?(h.push(p),'.'):c<'0'?c:
l[k[c]?k[c]+=','+(p-b[c]):k[b[c]=p,c]=~(c>0),k[c]]=c),
b=Object.keys(b),b.map(c=>k[c]=l[k[c]]);
h.some(p=>g[p]!='0');[s,g]=u[++i])
b.map(b=>[-1,1,o,-o].map((d,i)=>
g.every((c,p)=>c!=b?1:(c=g[p+d])!=b&c!='.'?0:m[g[p-d]!=b?m[p]='.':1,p+d]=b,m=g.slice(0))
&&!v[q=m.map(c=>k[c]||'')+'']?v[q]=u.push([s+b+'LRUD'[i]+' ',m]):0))
})(o=~g.search(/\n/))||s

Outdated

EcmaScript 6 (FireFox) JSFiddle

EcmaScript 5 (Chrome) JSFiddle

Example

#######
#001gg#
##.222#
.######
T(ms) 10,Len7
1R 0R 1R 0R 2L 1D 0R

Puzzle 1

..####..
..#00#..
###00###
#......#
#.1122.#
##3124##
.#3344#.
.##55##.
..#gg#..
..####..

T(ms) 8030,Len70
1U 2U 3U 4U 5U 5L 4D 2R 1R 3U 5U 4L 4D 5R 5R 3D 1L 3D 1L 5L 5U 5U 2D 5R 
1R 5R 1R 1D 0D 4D 1D 0D 0L 0L 1U 1U 1U 1U 2L 2L 2U 5D 2R 0R 3R 3R 0D 0D
2L 2L 2L 5U 0U 3U 3U 4U 4U 4R 0D 3L 3U 5D 5L 5L 5L 4U 4U 0R 0D 0D

Puzzle 2

######
#1002#
#1002#
#3445#
#3675#
#8gg9#
######

T(ms) 646485, Checked 10566733, Len 116
8R 3D 4L 7U 9L 5D 7R 4R 3U 8L 9L 5L 7D 4R 6U 9U 8R 3D 6L 4L 2D 7D 2D 0R
1R 6U 6U 3U 3U 9L 8L 5L 7L 7U 2D 4R 5U 8R 8R 5D 1D 6R 3U 9U 5L 1D 1D 9R
9U 4L 4L 2U 8R 7D 2L 8U 7R 2D 4R 3D 6L 9U 4R 1U 1U 2L 8L 8D 4D 0D 9R 6R
3U 9R 6R 1U 5U 2U 8L 8L 7L 7L 4D 0D 6D 6R 1R 2U 2U 0L 6D 9D 6D 9D 1R 2R
3R 5U 5U 0L 9L 6U 4U 7R 8R 7R 8R 0D 9L 9L 6L 6L 4U 8U 8R 0R

Puzzle 3

.####.
##1g##
#22g3#
#4255#
#4.56#
#.006#
#7008#
######

T(ms) 433049, Checked 7165203, Len 145
3L 3U 5U 6U 0U 7U 8L 8L 8L 0D 0R 7R 7U 7R 4D 2D 8R 4D 2D 5L 5L 3D 1R 3R
1D 1D 5R 5U 3L 6U 2U 4U 7R 1D 8L 0L 7D 1R 2R 4U 4U 8U 8U 0L 2D 3D 3L 6L  
1U 7D 2R 0R 8D 4D 8D 4D 3L 3U 4U 4R 8U 8U 0L 7L 2D 1D 6R 4R 4U 1L 1L 1U
2U 2L 6D 6D 4R 1R 1U 2U 2L 6L 6U 4D 1R 6U 7U 7U 0R 8D 0R 2D 3D 8D 2D 3D
7L 6D 5D 5L 1L 1U 1L 6U 4U 7R 7R 6D 6L 4L 4U 7U 7U 0U 0U 2R 3D 2R 3R 3D 
6D 5D 1D 1L 4L 7L 7U 0U 2U 3R 6D 5D 4D 7L 3R 6R 8R 5D 4D 7D 4L 7D 7D 0L 
0U

Puzzle 4

.####.
##00##
#.00g#
#.0.1#
#..g2#
######

T(ms) 25,Len6
1L 1D 1L 1L 0D 0R

edc65

Posted 2015-03-27T19:00:18.153

Reputation: 31 086

To verify your solution (and other solutions), can you post the number of moves you get for each problem I posted? – Nathan Merrill – 2015-03-29T03:30:03.157