I'm trying to figure out if there's a simple way to code a shuffling method/function in a sliding tile puzzle with 8 tiles, in Python 3. Here's what I mean.
Let's say this array represents our 9 squares, with 0 representing the empty space.
puzzle = [[1, 2, 3], [4, 5, 6], [7, 8, 0]]
I know there's a built-in shuffle() function in Python, but I cannot find information on whether it can be used to shuffle a 3x3 puzzle without making it unsolvable.
With this solution:
The logic is very simple. shuffle
runs for the amount of moves you designated, getting the pieces surrounding the empty at every iteration. It then randomly chooses a piece from the returned pieces and swaps the selected piece with the empty one. It is the program equivalent of manually scrambling one of these puzzles by hand. Considering it always makes a valid move the final scrambled puzzle should always be solvable.
lastPiece
is used to make sure we don't undo the previous move. It simply stores the index of the last move and then gets removed from the choices on the next iteration.
aside:
I'm sure you will probably use a list of graphics in the long run. Still use the numbers to get the shuffle. Once the number list is shuffled you can iterate over it and assign graphics to each position based on the number. Shuffling graphics data is a lot more CPU intensive than shuffling numbers.
edit:
As a bonus I added in the solution and solution parser (solve
). Gimme about another hour and I'll make your whole game! :D The solution is compressed. Instead of something like [(2, 1), (1, 4), (4, 5)]
where each tuple equals (to, from)
, since the from
is always the next to
(because from
is the new empty index) we do this instead [2, 1, 4, 5]
and juggle the compression in solve
.
import random, math
def surroundingPieces(z, w, h):
x = z%w
y = math.floor(z/h)
left = None if x == 0 else z - 1
up = None if y == 0 else z - w
right = None if x == (w-1) else z + 1
down = None if y == (h-1) else z + w
return list(filter(None, [left, up, right, down]))
def shuffle(puzzle, moves, width, height):
empty = puzzle.index(0) #find initial empty index
lastPiece = empty #init lastPiece
solution = [empty] #init solution
for _ in range(moves):
#get surrounding pieces
pieces = surroundingPieces(empty, width, height)
#remove the last piece we moved
if lastPiece in pieces:
pieces.remove(lastPiece)
#select a piece
pieceIndex = random.choice(pieces)
solution.insert(0, pieceIndex) #insert move in solution
#swap
puzzle[empty] = puzzle[pieceIndex]
lastPiece = empty #store this piece index
puzzle[pieceIndex] = 0
empty = pieceIndex #store new empty index
return puzzle, solution
#this is the program equivalent of manually solving the puzzle
def solve(pzl, sol):
for i in range(len(sol)-1):
pzl[sol[i]] = pzl[sol[i+1]]
pzl[sol[i+1]] = 0
return pzl
puzzle, solution = shuffle([1, 2, 3, 4, 0, 5, 6, 7, 8], 10, 3, 3)
print(puzzle) #[1, 3, 0, 7, 2, 6, 4, 8, 5]
print(solution) #[2, 1, 4, 5, 8, 7, 4, 3, 6, 7, 4]
print(solve(puzzle[:], solution)) #[1, 2, 3, 4, 0, 5, 6, 7, 8]