逻辑游戏,9 张方牌和 1 个大方格
我不确定这是正确的地方,也许其他一些堆栈交换,告诉我我会在其他地方发布。
这是我的问题,我在朋友那里发现了一个老游戏,它应该是一个智力游戏:9张小方形卡片,你必须将它们放置在一起,以便它们全部组合在一起,这是一张图片:
在游戏前几个小时后,我发现没有真正简单公平的方法来完成游戏,所以我采用了程序化的方式。
这就是我遇到困难的地方,我想我可以使用一些随机函数,一个大循环,然后就可以完成它。但是有类似 (4*9)^9 的解决方案,所以看起来并不那么容易。
这是我写的代码,目前没什么用: 每次我进入循环时,我都会洗牌数组,按随机值旋转卡片并检查拼图是否正确,浪费了很多周期,但我不知道从哪里开始才能提高效率。
编辑 : 固定代码,我得到了几副 8 张牌,但没有 9 张牌,如果有人修复了我的代码,或者可能没有解决方案?
require 'json'
class Array
def rotate n
a =dup
n.times do a << a.shift end
a
end
end
@grid = [[{"type"=>"p", "head" => 1},{"type"=>"c", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"o", "head" => 2}],
[{"type"=>"o", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"c", "head" => 2},{"type"=>"p", "head" => 1}],
[{"type"=>"c", "head" => 1},{"type"=>"p", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"a", "head" => 1}],
[{"type"=>"p", "head" => 1},{"type"=>"c", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"a", "head" => 1}],
[{"type"=>"p", "head" => 2},{"type"=>"c", "head" => 2},{"type"=>"a", "head" => 1},{"type"=>"c", "head" => 1}],
[{"type"=>"a", "head" => 1},{"type"=>"p", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"p", "head" => 1}],
[{"type"=>"a", "head" => 1},{"type"=>"o", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"c", "head" => 2}],
[{"type"=>"o", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"c", "head" => 2},{"type"=>"p", "head" => 1}],
[{"type"=>"p", "head" => 1},{"type"=>"c", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"a", "head" => 1}]]
@new_grid = [nil, nil, nil,nil, nil, nil,nil, nil, nil]
@used = [false, false, false,false, false, false,false, false, false]
def check_validity(card, position, orientation)
# since I'm adding from top left to bottom, I only need to check top and left
try_card = @grid[card].rotate orientation
valid = true
# top
if (@new_grid[position-3])
if (try_card[0]["type"] != @new_grid[position-3][2]["type"] || try_card[0]["head"] == @new_grid[position-3][2]["head"])
valid = false
end
end
# left
if (@new_grid[position-1] && (position % 3) != 0)
if (try_card[3]["type"] != @new_grid[position-1][1]["type"] || try_card[3]["head"] == @new_grid[position-1][1]["head"])
valid = false
end
end
return valid
end
def solve_puzzle(position)
(0..8).each do |card|
unless (@used[card])
(0..3).each do |orientation|
if (check_validity(card, position, orientation))
@used[card] = true
@new_grid[position] = @grid[card].rotate orientation
if position == 7
puts @new_grid.to_json
end
if (position < 8)
solve_puzzle(position + 1)
else
puts "I WON"
puts @new_grid.to_json
end
@new_grid[position] = nil
@used[card] = false
end
end
end
end
end
solve_puzzle(0)
I'm not sure this is the right place to go, maybe some other stackexchange, tell me I would post somewhere else.
Here is my problem, I found an old game at my friends place, it's supposed to be a mind game : 9 small square cards, and you have to place them so that they all fit together, here is a picture :
After a few hours in front of the game i gathered that there was no real easy fair way to finish the game, and so I went the programmatical way.
This is where I'm having a hard time, I though I could just use some random functions, a big loop, and get it over with. But there is something like (4*9)^9 solutions, so it seems not that easy.
Here is the code I wrote, which is pretty useless for now :
Every time I go into the loop, I shuffle my array, rotate my cards by a random value and check if the puzzle is right, a lot of wasted cycles, but I don't know where to start to make it more efficient.
EDIT :
Fixed code, i get a few deck with 8 cards, but no 9 cards deck, if anyone has a fix to my code, or maybe there is no solution ?
require 'json'
class Array
def rotate n
a =dup
n.times do a << a.shift end
a
end
end
@grid = [[{"type"=>"p", "head" => 1},{"type"=>"c", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"o", "head" => 2}],
[{"type"=>"o", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"c", "head" => 2},{"type"=>"p", "head" => 1}],
[{"type"=>"c", "head" => 1},{"type"=>"p", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"a", "head" => 1}],
[{"type"=>"p", "head" => 1},{"type"=>"c", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"a", "head" => 1}],
[{"type"=>"p", "head" => 2},{"type"=>"c", "head" => 2},{"type"=>"a", "head" => 1},{"type"=>"c", "head" => 1}],
[{"type"=>"a", "head" => 1},{"type"=>"p", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"p", "head" => 1}],
[{"type"=>"a", "head" => 1},{"type"=>"o", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"c", "head" => 2}],
[{"type"=>"o", "head" => 1},{"type"=>"a", "head" => 2},{"type"=>"c", "head" => 2},{"type"=>"p", "head" => 1}],
[{"type"=>"p", "head" => 1},{"type"=>"c", "head" => 2},{"type"=>"o", "head" => 2},{"type"=>"a", "head" => 1}]]
@new_grid = [nil, nil, nil,nil, nil, nil,nil, nil, nil]
@used = [false, false, false,false, false, false,false, false, false]
def check_validity(card, position, orientation)
# since I'm adding from top left to bottom, I only need to check top and left
try_card = @grid[card].rotate orientation
valid = true
# top
if (@new_grid[position-3])
if (try_card[0]["type"] != @new_grid[position-3][2]["type"] || try_card[0]["head"] == @new_grid[position-3][2]["head"])
valid = false
end
end
# left
if (@new_grid[position-1] && (position % 3) != 0)
if (try_card[3]["type"] != @new_grid[position-1][1]["type"] || try_card[3]["head"] == @new_grid[position-1][1]["head"])
valid = false
end
end
return valid
end
def solve_puzzle(position)
(0..8).each do |card|
unless (@used[card])
(0..3).each do |orientation|
if (check_validity(card, position, orientation))
@used[card] = true
@new_grid[position] = @grid[card].rotate orientation
if position == 7
puts @new_grid.to_json
end
if (position < 8)
solve_puzzle(position + 1)
else
puts "I WON"
puts @new_grid.to_json
end
@new_grid[position] = nil
@used[card] = false
end
end
end
end
end
solve_puzzle(0)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
图像中的方块实际上没有解。我通过将最后一块右侧的绿色/蓝色底部更改为红色/白色底部(就像上面右侧的那块一样)来实现它。
随机化是一个坏主意,最好进行彻底的搜索,尝试每一种可能性,就像 Petar Minchev 回答的那样。您也许可以通过预处理来加快速度。除了第一块之外,您始终会知道需要匹配的一侧或两侧。每种类型在所有 9 件中只有 3 到 6 个实例。想象一下你有这些空间:
最初我是通过填充位置 4 来做到这一点的,然后是 1、3、5、7,最后是角落。通过 2371 次递归调用,在 3.5 毫秒内找到了 4 个解决方案。通过将顺序更改为 4, 1, 5, 2, 7, 8, 3, 0, 6,递归调用次数降至 1.2 毫秒,因为角落里的选项较少。现在想想,按顺序进行会介于两者之间,但修改后的顺序也会同样快(0、1、3、4、2、5、6、7、8)。重要的是,必须检查两个匹配项可以减少进行昂贵的递归调用的次数。
以下是我的卡片的设置方式,请注意我更改了最后一张卡片的一侧以获得解决方案。 1 是红色,2 是脂肪,3 是蓝色/绿色,4 是黄色。我与 0x10 进行异或表示它是相同颜色的底部。这样,您可以对 2 个类型进行异或,然后与 0x10 进行比较,看看它们是否匹配,或者您可以对一个类型与 0x10 进行异或,以找到您要查找的类型。
在预处理时,我想要一个按我正在寻找的类型索引的数组,它将为我提供具有该类型的所有卡片以及该类型所在的方向。我还将 NextType(顺时针)放在那里,以便更容易比较角点:
这是我的主要递归方法:
有趣的说明:我编写了代码,使用现有的修改牌组和 4 个解决方案以及使用随机种子创建的 9999 个其他牌组来随机化卡片。 1 和 9999。我在 7.2 秒内发现了 3,465 个解决方案分布在 1,555 个起始布局上,因此每次运行的平均时间约为 0.72 毫秒。它总共调用了 416 万次 Method1Step()。
There is actually no solution to the squares in the image. I made it work by changing the green/blue bottom on the right side of the last piece to a red/white bottom (like the piece above it has on the right).
Randomizing is a bad idea, it is better to do an exhaustive search where you try every possibility like Petar Minchev answered. You may be able to speed it up by pre-processing. Except for the first piece, you will always know either one or two of the sides you need to match. Each type has only between 3 and 6 instances among all 9 pieces. Imagine you have these spaces:
Originally I did this by filling position 4, then 1, 3, 5, 7, and finally the corners. This found 4 solutions in 3.5 milliseconds with 2371 recursive calls. By altering the order to 4, 1, 5, 2, 7, 8, 3, 0, 6 it dropped to 1.2 milliseconds with only 813 recursive calls because there were fewer options to put in the corners. Thinking about it now, going in order would be somewhere in-between, but a modified order would just as fast (0, 1, 3, 4, 2, 5, 6, 7, 8). The important thing is that having to check two matches narrows down the number of times you will make the expensive recursive call.
Here are how my cards are setup, notice I changed one of the sides on the last card to get a solution. 1 is red, 2 is fat, 3 is blue/green and 4 is yellow. I xor with 0x10 to signify that it is the bottom of the same color. That way you can xor 2 types and compare with 0x10 to see if they match or you can xor a type with 0x10 to find the type you are looking for.
When pre-processing, I want to have an array indexed by type I am looking for which will give me all the cards that have that type and which orientation the type is on. I also throw the NextType (clockwise) in there to make comparing the corners easier:
Here's my main recursive method:
Interesting note: I wrote code to randomize the cards using the existing modified deck with 4 solutions and 9999 other decks created with random seeds between 1 and 9999. I found 3,465 solutions spread over 1,555 starting layouts in 7.2 seconds so it averaged to about .72 milliseconds per run. It made a total of 4.16 million calls to Method1Step().
这个问题是另一种形式的平铺拼图,其他形式包括Pentominoes 和 数独。
解决此类难题的最有效算法之一是 算法 X,由 算法 X 开发="http://en.wikipedia.org/wiki/Donald_Knuth" rel="nofollow">唐纳德·高德纳。算法 X 的最佳实现技术之一被称为跳舞链接。
算法 X 在解决难题方面非常高效(通常在几毫秒内解决数独)的关键原因是,无用的配置被有效地从搜索空间中删除。
例如,对于您的拼图,如果左上角的棋子在南边缘有 Getafix 头,则可以消除在其正下方的北边缘没有 Getafix 脚的每个解决方案配置。
在制定算法 X 的约束矩阵方面,您有 9 个棋子,每个棋子可以放置在 4 个方向的 9 个位置,因此您的约束矩阵将有 324 行,每一行对应于每个可能的棋子放置。
识别约束列更为复杂。您将需要 9 个约束列 - 每个位置一个,以确保各个部分不会位于同一位置。您还需要一堆附加列来捕获跨越每个边缘的字符必须兼容的约束。有 12 个交互点,四个角色,每个角色分为两部分 - 另外 96 列。
更新
这是启动约束矩阵的尝试。让我们以 OP 图片中显示的第一块为例,它有以下四个部分:
对于我们的约束矩阵,我们需要
我们通过列出从特定位置得到的东西以及从特定位置获得的东西来构建约束行这个位置阻止了。
(本质上,在 TopCentreWest 上只有罗马脚是可以的)
(只有 Asterix-Head 在 MiddleLeftNorth 位置可以)
现在,在左上位置的这件作品的其他三个方向上重复此操作 3 次...
...
位置:TopLeft,Piece1
...
位置:TopLeft,Piece1
重复这 4 行 8 个以上次,对于 8 个剩余空间中的 4 个方向中的每一个(另外 32 行,总共 36 行)。
然后,为剩余 8 块的每个位置/方向再创建 36 行(另外 288 行)。
您可能想要编写代码来生成矩阵,而不是手动计算它!
This problem is another form of a tiling puzzle, other forms of which include Pentominoes and Soduku.
One of the most effective algorithms for solving these kinds of puzzles is Algorithm X, developed by Donald Knuth. One of the best implementation techniques for Algorithm X is known as Dancing Links.
The key reason why Algorithm X is spectacularly efficient at solving the puzzles (solving Suduku in a few milliseconds is typical) is that useless configurations are removed from the search space efficiently.
For example, with your puzzle, if piece at top left has a Getafix head on the south edge, then every solution configuration without Getafix feet on the north edge just below that can be eliminated.
In terms of formulating the constraint matrix for Algorithm X, you have 9 pieces that each can be placed in 9 locations with 4 orientations, so your constraint matrix will have 324 rows, one for each possible piece placement.
Identifying the constraint columns is more complex. You'll need 9 constraint columns - one for each location, to ensure pieces aren't co-located. You'll also need a bunch of additional columns to capture the constraint that the characters crossing each edge must be compatible. There are twelve interaction points, four characters each divided into two parts - another 96 columns.
Update
Here's a stab at starting the constraint matrix. Let's take the first piece shown in the OP picture, which has these four parts:
For our constraint matrix, we need
We construct a contraint row by listing both the things we get from a particular position, and the things that this position prevents.
(Essentially, only a Roman-Feet is Ok at TopCentreWest)
(Only Asterix-Head is Ok at MiddleLeftNorth)
Now, repeat this 3 more times for the other three orientations of this piece in the TopLeft location ...
...
Location: TopLeft, Piece1
...
Location: TopLeft, Piece1
Repeat these 4 rows 8 more times, for each of 4 orientations in the 8 remaining spaces (32 more rows, for 36 rows total).
Then, create 36 more rows for each location/orientation of the remaining 8 pieces (288 more rows).
You'll probably want to write code to generate the matrix, rather than calculate it by hand!
我确信了解 prolog 在这里会有很大的帮助,但我认为我没有资格回答那。不过,我也许可以帮助您计算出您当前的解决方案可行的可能性有多大:
9 块可以放入 9 块中! = 362880 种排列(仅位置)
它们每个都可以旋转 4 种方式 -> 4^9 = 262114
然而,你将有 4 个解,它们只是彼此的旋转,所以 /=4。
在找到解决方案之前平均给出 23,781,703,680 种可能的安排来尝试:(
I'm sure that knowing prolog would be a great help here, but I don't think I'm qualified to answer that. However I may be able to help you work out how likely your current solution is to work:
The 9 pieces can be placed in 9! = 362880 permutations (position only)
They can each be rotated 4 ways -> 4^9 = 262114
However you will have 4 solutions which are just rotations of each other, so /=4.
Giving an average of 23,781,703,680 possible arrangements to try before you find a solution :(
使用递归和修剪。我的意思是,当您放置当前卡片时,它必须与您已放置的卡片的方向相匹配。这样你就消除了许多不可能的情况:)
像这样:
Use recursion with pruning. I mean when you put the current card it must match the orientation of the cards you have already put. So you eliminate many impossible situations:)
Like this:
直到最近我一直以约束规划研究为生,我想我可以提供一些建议。
您最好的选择是尝试使用一些合理的搜索启发式方法和一点狡猾来生成和测试,以最大程度地减少浪费的搜索工作量。
这样思考这个问题:你有九个要分配的逻辑变量,{x1,...,x9},其中x1,x2,x3是底行,x4,x5,x6是中间行,x7, x8、x9 顶行。
每个变量可以采用集合 D = {(p, r) | 中的 36 个可能值之一。 p 是一个片段 {p1, p2, ..., p9},r 是一个旋转 {0, 90, 180, 270}}。
一种解决方案是从 D 到 x1,...,x9 的分配,使得每个块都恰好用于一个分配中,并且每对相邻图块具有兼容的分配(即,边缘匹配)。
您的搜索应该跟踪每个变量的可能分配范围。特别是:
一个好的搜索策略是始终选择剩余域最小的变量来尝试下一步分配。这样,您始终可以查看受您在搜索分支上所做的决策影响最大的变量。
无论如何,希望这会有所帮助。
干杯!
I used to do constraint programming research for a living until recently and I think I can offer some advice.
Your best option is to try generate-and-test with some sensible search heuristics and a little cunning to minimise the amount of wasted search effort.
Think of the problem this way: you have nine logical variables you want to assign, {x1, ..., x9}, where x1, x2, x3 are the bottom row, x4, x5, x6 the middle row, and x7, x8, x9 the top row.
Each variable can take on one of thirty six possible values from the set D = {(p, r) | p is a piece {p1, p2, ..., p9} and r is a rotation {0, 90, 180, 270}}.
A solution is an assignment to x1, ..., x9 from D such that each piece is used in exactly one assignment and each pair of neighbouring tiles have compatible assignments (i.e., the edges match up).
Your search should keep track of the domain of possible assignments for each variable. In particular:
A good search strategy is to always choose the variable with the smallest remaining domain to try assigning next. This way you're always looking at the variables that have been most affected by the decisions you've already made on the search branch.
Anyway, hope this helps.
Cheers!
我认为你的错误在于检查有效性。您不需要检查位置 3 和 6 的左侧,因为它们是拼图的左侧,不需要与上一行的右侧匹配。
编辑:这是我正在考虑的一行:
编辑2:检查你的作品,我看到中心作品如下:
我认为应该是
I think your bug is in check validity. You don't need to check the left side of positions 3 and 6 since those are the left side of the puzzle that don't need to match the right side of the previous row.
Edit: here's the line I'm thinking of:
Edit 2: Check your pieces, I'm seeing the following for the center piece:
which I believe should be