如何在不使其无法解决的情况下洗牌滑动拼图? (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]]
有了这个解决方案:
- 您不需要处理多维列表
- 您可以确定您希望它进行的移动量
- 它应该适用于任何尺寸的拼图 - 甚至是不相等的尺寸
道理很简单。 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]
我想弄清楚是否有一种简单的方法可以在 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]]
有了这个解决方案:
- 您不需要处理多维列表
- 您可以确定您希望它进行的移动量
- 它应该适用于任何尺寸的拼图 - 甚至是不相等的尺寸
道理很简单。 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]