如何在不使其无法解决的情况下洗牌滑动拼图? (Python 3)

How to shuffle a sliding tile puzzle without making it unsolvable? (Python 3)

我想弄清楚是否有一种简单的方法可以在 Python 3 中用 8 个方块滑动方块拼图来编写洗牌 method/function。这就是我的意思。

假设这个数组代表我们的 9 个正方形,其中 0 代表空 space。

puzzle = [[1, 2, 3], [4, 5, 6], [7, 8, 0]]

我知道 Python 中有一个内置的 shuffle() 函数,但我找不到关于它是否可以用来随机播放 3x3 拼图而不使其无法解决的信息。

慢速方法:随机播放直到你得到一个可解决的谜题

一个3X3如果有奇数次反转则不可解。流动此 answer 以解决 3X3 难题。

代码

def is_solvable(puzzle):
  p = puzzle[puzzle != 0]
  inversions = 0
  for i, x in enumerate(p):
    for y in p[i+1:]:
      if x > y:
        inversions += 1
  return inversions % 2==0

# Now lets generate until we get a solvable puzzle
while True:
  puzzle = np.random.choice(np.arange(9), size=9, replace=False)
  if is_solvable(puzzle):
    break

print (puzzle.reshape(3,3))

输出:

[[8 3 4]
 [2 1 7]
 [5 6 0]]

有了这个解决方案:

  1. 您不需要处理多维列表
  2. 您可以确定您希望它进行的移动量
  3. 它应该适用于任何尺寸的拼图 - 甚至是不相等的尺寸

道​​理很简单。 shuffle 运行s 表示您指定的移动量,在每次迭代时获取围绕空白的棋子。然后它从返回的棋子中随机选择一个棋子,并将选定的棋子与空棋子交换。它是相当于手动打乱其中一个谜题的程序。考虑到它总是做出有效的移动,最终的拼图应该总是可以解决的。

lastPiece 用于确保我们不会撤消前一步。它只是存储最后一步的索引,然后在下一次迭代时从选择中删除。

旁白:
我相信您可能会使用长 运行 中的图形列表。 仍然使用数字来随机播放。打乱数字列表后,您可以对其进行迭代并根据数字将图形分配给每个位置。打乱图形数据 CPU 比打乱数字要密集得多。

编辑:
作为奖励,我添加了解决方案和解决方案解析器 (solve)。给我大约一个小时,我会完成你的整个游戏! :D 解决方案被压缩。而不是像 [(2, 1), (1, 4), (4, 5)] 这样每个元组等于 (to, from),因为 from 总是下一个 to(因为 from 是新的空索引)我们这样做这取而代之 [2, 1, 4, 5] 并在 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]