我曾经浏览了很多教如何解决它的网站,但我想知道如何创建它。我对它的编码方面不太感兴趣,但想更多地了解其背后的算法。例如,当生成包含10个地雷的网格时,我会使用任意随机函数在网格上分布地雷,但我该如何设置与之相关的数字并决定打开哪个方块呢?我无法构思出通用算法来完成这项工作。
我曾经浏览了很多教如何解决它的网站,但我想知道如何创建它。我对它的编码方面不太感兴趣,但想更多地了解其背后的算法。例如,当生成包含10个地雷的网格时,我会使用任意随机函数在网格上分布地雷,但我该如何设置与之相关的数字并决定打开哪个方块呢?我无法构思出通用算法来完成这项工作。
grid = [n,m] // initialize all cells to 0
for k = 1 to number_of_mines
get random mine_x and mine_y where grid(mine_x, mine_y) is not a mine
for x = -1 to 1
for y = -1 to 1
if x = 0 and y = 0 then
grid[mine_x, mine_y] = -number_of_mines // negative value = mine
else
increment grid[mine_x + x, mine_y + y] by 1
就这样了...
** 编辑 **
由于该算法可能导致某些地雷分组过于密集,或者更糟糕的是非常分散(因此无聊),因此在生成mine_x
和mine_y
数量时可以添加额外的验证。例如,要确保至少有3个相邻单元格不是地雷,甚至可能限制过于相距的地雷数量,等等。
** 更新 **
我已经尝试使用 JS bin 并创建了一个功能性的扫雷游戏演示,仅是为了展示本答案中描述的算法。我没有优化生成的地雷位置的随机性,因此有些游戏可能会变成不可能或者太容易。而且,在网格中没有限制地雷数量的验证,因此您可以创建一个 2x2 的网格,有 1000 个地雷...但是这只会导致无限循环 :) 好好享受吧!
alert
了,我很快就会做出这个改变!关于第一个问题和额外的1个地雷,实际上更好的想法是在用户选择第一个方格后生成地雷区(即将该单元标记为不可选),这样可以减少冗余,因此第一次选择总是保证为空单元。最后,“404”标题是因为我不想在我的域名根目录下有一个网站,所以我生成了这些“错误”,这个演示是迄今为止的少数之一(未来还会有更多)。 - Yanick Rochon您只需种植地雷,然后遍历每个单元格并计算相邻的地雷数量。
或者您可以将每个计数器设置为0,并随着每个种植的地雷,增加所有相邻单元格的计数器。
count(x,y) = sum(
for i = -1 to 1
for j = -1 to 1
1 if (x+i,y+j) contains a mine
0 otherwise
)
这里是一个扫雷算法的实现。该算法考虑了一些条件来生成地图,并执行一个求解器算法,以确保生成的地图至少有一个可能的解决方案。
这个示例的实现过程太长了,无法作为代码发布,因此将通过一些示例进行说明,以便其他人可以在所需的编程语言中复制它。在这个示例中,使用了Python。
首先,我们需要创建一个主循环,在其中执行任意数量的地图生成尝试,包括每次尝试结束时的求解器算法验证。该循环将具有max_fails
限制,以确保它不会成为一个无限循环。请注意,我们稍后将使用更多的继承循环,所有这些循环都必须设置自己的限制。
当一个fail
发生时,意味着一个地雷放置尝试失败或者生成的地图没有可能的解决方案。通过这种方法,我们可以尝试动态地生成地图,无论生成尝试有多大或者做了多少次。如果达到了max_fails
,它总是会停止尝试地图生成。这意味着如果网格很小,在达到限制之前可能会进行很多次尝试,另一方面,如果网格很大,则可能只进行少量尝试。我们还可以根据网格的面积动态地定义max_fails
限制,以允许更大的网格在需要时执行更长时间。
同样,在这里,我们应该定义地雷数量,这可以使用网格的面积动态地完成,也可以使用difficulty
变量...或同时使用两者!
mines = int((height * width)//(15 - difficulty))
max_fails = 500 #<- This could be defined dynamically using exponential functions.
possible_solution_found = False
fails = 0
while not possible_solution_found and fails < max_fails:
我们需要创建一个网格的矩阵表示,通常在给定的维度值内完成。这可以通过迭代这些维度值来创建一个矩阵的列表列表表示来完成。首先在Y轴上创建行列表
,然后在X轴上将0
值添加到行列表中。
最好创建一个寄存器的矩阵副本。我们需要向每个单元格添加单元格的位置,而不是添加0
值。例如,它可以是坐标的元组。(x, y)
寄存器对于稍后优化放置地雷的随机选择过程非常有用,这将防止在同一单元格上两次失败。
matrix = list()
matrix_register = list()
for row_index in range(height):
row = list()
row_register = list()
for cell_index in range(width):
row.append(0)
row_register.append((cell_index, row_index))
matrix.append(row)
matrix_register.append(row_register)
max_fails
的限制,此循环应结束。total_mines = mines
while total_mines > 0 and fails < max_fails:
# subtract 1 to total_mines each time a mine is placed correctly.
try:
row_choice: list = random.choice(matrix_register)
except IndexError:
# No more mines can be placed!
break
cell_coordinates: dict = random.choice(row_choice)
row_index = cell_coordinates[0]
column_index = cell_coordinates[1]
value = matrix[row_index][column_index]
top_left_x = column_index
top_left_y = row_index
bottom_right_x = column_index
bottom_right_y = row_index
if column_index >= 1:
top_left_x -= 1
if row_index >= 1:
top_left_y -= 1
if column_index <= width-2:
bottom_right_x += 1
if row_index <= height -2:
bottom_right_y += 1
for y in range(top_left_y, bottom_right_y +1):
for x in range(top_left_x, bottom_right_x +1):
周围有效空单元格:首先,此条件将评估所选单元格的周围(这意味着其周围的8个单元格)是否至少有一个任意数量的有效空单元格,在本例中至少为三个,但您可以根据需要设置数字。 有效空单元格是指被至少一个任意数量的空单元格包围的单元格,在本例中至少为四个,但您可以根据需要设置数字。因此,基本上,我们迭代周围的空单元格,并递归地迭代它们周围的空单元格。此递归仅执行一次,这意味着我们不检查我们正在检查的空单元格周围的空单元格是否也有效。例如,一个有效空单元格有四个周围的空单元格,但这些空单元格没有其他四个周围的空单元格,因此它们可能无效,但我们不检查。出于性能原因,当达到所需数量的周围有效空单元格和它们周围的空单元格时,不应再进行迭代以搜索有效空单元格,因为我们已经满足条件。如果所选单元格位置没有足够的周围有效空单元格,则被丢弃为无效以放置地雷。
所有连续的地雷仍然有效:此第二个条件将评估在将所选单元格放置为地雷的情况下,所有连续的地雷是否仍然有效。此条件应在与周围有效空单元格相同的迭代中进行检查。但是这里有一个区别,此条件将递归地评估,直到达到mine_concatenation_limit
或所有地雷仍然有效。在这种情况下,mine_concatenation_limit
设置为三,这意味着永远不应该有超过三个地雷在一起,但您可以根据需要设置数字。当我们评估此条件时,对于每个连接的地雷,我们再次检查周围有效空单元格条件。如果一个地雷无效,则所选单元格将被丢弃为无效以放置地雷。
如果所有条件都满足,则我们可以将所选单元格放置为地雷。如果没有,则将失败尝试计数器加1。
max_fails
。1
添加到每个周围单元格的当前值中。这将得到一个地图,其中包含地雷及其周围所有单元格的数字。for y in range(top_left_y, bottom_right_y +1):
for x in range(top_left_x, bottom_right_x +1):
# If the coordinates x & y are the mine cell itself...
if x == column_index and y == row_index:
continue
# Value of the surrounding cell.
value = matrix[y][x]
# If the surrounding cell does not contain a mine...
if value != -1:
matrix[y][x] += 1
验证器不会被解释为一个单独的算法,但至少会对那些想知道我们是如何做到的人进行总结。
我们的验证器由两部分组成:逻辑操作和矩阵操作。
逻辑运算:首先,验证器使用逻辑运算来简化和解决大多数情况。通常这会使游戏接近完成。但如果发现一个复杂的情况,验证器就会切换到使用矩阵运算。
矩阵运算:使用矩阵和数学(解线性矩阵方程组)来确定哪些单元格可能是地雷,如果情况太复杂,它可能会使用预测和近似(最小二乘法解线性矩阵方程组)。在使用预测的情况下,如果算法失败并“输掉了游戏”,则求解器确定生成的地图没有至少一个可能的解。与逻辑运算相比,矩阵运算的灵活性要低得多,但在最复杂的情况下计算速度更快,效果更好。
这是一种构建强大且高效算法的好方法,它考虑了某些条件并进行某些验证,以确保生成的MineSweeper地图不仅可以解决,而且还要有趣和具有挑战性。此实现动态生成地图,允许许多配置,例如难度或算法执行时间。该算法非常灵活,适用于小游戏(网格区域只有9个单元格)以及对面积为数千个单元格的巨大网格也强大且高效。