如何构建程序以使用扫雷配置

时间:2015-08-12 13:49:22

标签: python probability minesweeper

编辑:这是一段时间以前我已经开始工作,如果你想看到github.com/LewisGaul/minegaulerQt中包含的代码。

我正在尝试编写一个程序来计算游戏扫雷的概率,并且在如何最好地构建它时遇到了一些困难。虽然首先使用下面的示例看起来很简单,但我想知道允许更复杂配置的最佳方法。注意我不是在寻找有关如何计算概率的帮助 - 我知道方法,我只需要实现它!

为了清楚我想要计算的内容,我将通过一个简单的例子来完成,这个例子可以手工完成。考虑扫雷配置
# # # #
# 1 2 #
# # # #
其中#表示未被单击的单元格。 1告诉我们最左边7个未被点击的单元格中只有1个,2告诉我们最右边有2个。为了计算每个单元格包含矿井的概率,我们需要确定所有不同的情况(在这个简单的情况下只有2个):

  1. 最左边3个细胞中的1个矿井,最右边3个细胞中的2个矿井(总共3个矿井,3x3 = 9个组合)。

  2. 中心4个单元中的1个矿井,最右边3个单元中的1个矿井(总共2个矿井,4x3 = 12个组合)。

  3. 鉴于矿井在随机单元格中的概率约为0.2,它(在随机选择的单元格中)总共2个地雷的可能性大约是4倍,而不是总共3个,所以配置中的地雷总数很重要,以及每个配置的组合数量。所以在这种情况下,情况1的概率是9 /(9 + 4x12)= 0.158,因此在给定的最左边单元格中存在矿的概率约为0.158 / 3 = 0.05,因为这些单元格实际上是等价的(它们分享完全相同的透露邻居。)

    我已经使用Tkinter创建了一个GUI,它允许我轻松输入配置,例如示例中的配置,该配置将网格存储为numpy数组。然后我创建了一个NumberGroup类,它隔离了每个被点击/编号的单元格,存储了未被点击的邻居的数量和一组坐标。可以减去这些以获得等价组...虽然如果有三个或更多数字而不是两个数字,这将不会那么简单。但我不确定如何从这里获得不同的配置。我玩弄了Configuration课程,但我并不十分熟悉不同课程应该如何协同工作。请参阅下面的工作代码(需要numpy)。

    注意:我知道我本可以尝试使用蛮力方法,但如果可能的话我想避免这种情况,将等效组分开(在上面的例子中有3个等价组,最左边3个,中间4,最右边3)。我想听听你对此的看法。

    import numpy as np
    
    grid = np.array(
        [[0, 0, 0, 0],
         [0, 2, 1, 0],
         [0, 0, 0, 0]]
        )
    dims = (3, 4) #Dimensions of the grid
    
    class NumberGroup(object):
        def __init__(self, mines, coords, dims=None):
            """Takes a number of mines, and a set of coordinates."""
            if dims:
                self.dims = dims
            self.mines = mines
            self.coords = coords
    
        def __repr__(self):
            return "<Group of {} cells with {} mines>".format(
                len(self.coords), self.mines)
    
        def __str__(self):
            if hasattr(self, 'dims'):
                dims = self.dims
            else:
                dims = (max([c[0] for c in self.coords]) + 1,
                        max([c[1] for c in self.coords]) + 1)
            grid = np.zeros(dims, int)
            for coord in self.coords:
                grid[coord] = 1
            return str(grid).replace('0', '.').replace('1', '#')
    
        def __sub__(self, other):
            if type(other) is NumberGroup:
                return self.coords - other.coords
            elif type(other) is set:
                return self.coords - other.coords
            else:
                raise TypeError("Can only subtract a group or a set from another.")
    
    
    def get_neighbours(coord, dims):
        x, y = coord
        row = [u for u in range(x-1, x+2) if u in range(dims[0])]
        col = [v for v in range(y-1, y+2) if v in range(dims[1])]
        return {(u, v) for u in row for v in col}
    
    groups = []
    all_coords = [(i, j) for i in range(dims[0])
        for j in range(dims[1])]
    for coord, nr in [(c, grid[c]) for c in all_coords if grid[c] > 0]:
        empty_neighbours = {c for c in get_neighbours(coord, dims)
            if grid[c] == 0}
        if nr > len(empty_neighbours):
            print "Error: number {} in cell {} is too high.".format(nr, coord)
            break
        groups.append(NumberGroup(nr, empty_neighbours, dims))
    print groups
    for g in groups:
        print g
    print groups[0] - groups[1]
    

    更新
    我添加了一些其他类并进行了重组(请参阅下面的工作代码),它现在能够创建和显示等价组,这是朝着正确方向迈出的一步。但是,我仍然需要弄清楚如何迭代所有可能的矿井配置,方法是以创建有效配置的方式为每个组分配一些矿井。任何帮助表示赞赏。

    例如,
    # # # #
    # 2 1 #
    # # # #
    有三个等价组G1:左边3,G2:中间4,G3:右边3.我希望代码循环,以下列方式分配带有地雷的组:

    • G1 = 2(第一组最大)=&gt; G2 = 0 =&gt; G3 = 1(这是所有配置G1 = 2)
    • G1 = 1(减1)=&gt; G2 = 1 =&gt; G3 = 0(这都是G1 = 1)
    • G1 = 0 =&gt; G2 = 2无效

    所以我们得出了两种配置。这需要适用于更复杂的设置!

    import numpy as np
    
    def get_neighbours(coord, dims):
        x, y = coord
        row = [u for u in range(x-1, x+2) if u in range(dims[0])]
        col = [v for v in range(y-1, y+2) if v in range(dims[1])]
        return {(u, v) for u in row for v in col}
    
    class NrConfig(object):
        def __init__(self, grid):
            self.grid = grid
            self.dims = grid.shape # Dimensions of grid
            self.all_coords = [(i, j) for i in range(self.dims[0])
                for j in range(self.dims[1])]
            self.numbers = dict()
            self.groups = []
            self.configs = []
            self.get_numbers()
            self.get_groups()
            self.get_configs()
    
        def __str__(self):
            return str(self.grid).replace('0', '.')
    
        def get_numbers(self):
            for coord, nr in [(c, self.grid[c]) for c in self.all_coords
                if self.grid[c] > 0]:
                empty_neighbours = {c for c in get_neighbours(
                    coord, self.dims) if self.grid[c] == 0}
                if nr > len(empty_neighbours):
                    print "Error: number {} in cell {} is too high.".format(
                        nr, coord)
                    return
                self.numbers[coord] = Number(nr, coord, empty_neighbours,
                    self.dims)
    
        def get_groups(self):
            coord_neighbours = dict()
            for coord in [c for c in self.all_coords if self.grid[c] == 0]:
                # Must be a set so that order doesn't matter!
                coord_neighbours[coord] = {self.numbers[c] for c in
                    get_neighbours(coord, self.dims) if c in self.numbers}
            while coord_neighbours:
                coord, neighbours = coord_neighbours.popitem()
                equiv_coords = [coord] + [c for c, ns in coord_neighbours.items()
                    if ns == neighbours]
                for c in equiv_coords:
                    if c in coord_neighbours:
                        del(coord_neighbours[c])
                self.groups.append(EquivGroup(equiv_coords, neighbours, self.dims))
    
        def get_configs(self):
            pass # WHAT GOES HERE?!
    
    
    class Number(object):
        """Contains information about the group of cells around a number."""
        def __init__(self, nr, coord, neighbours, dims):
            """Takes a number of mines, and a set of coordinates."""
            self.nr = nr
            self.coord = coord
            # A list of the available neighbouring cells' coords.
            self.neighbours = neighbours
            self.dims = dims
    
        def __repr__(self):
            return "<Number {} with {} empty neighbours>".format(
                int(self), len(self.neighbours))
    
        def __str__(self):
            grid = np.zeros(self.dims, int)
            grid[self.coord] = int(self)
            for coord in self.neighbours:
                grid[coord] = 9
            return str(grid).replace('0', '.').replace('9', '#')
    
        def __int__(self):
            return self.nr
    
    
    class EquivGroup(object):
        """A group of cells which are effectively equivalent."""
        def __init__(self, coords, nrs, dims):
            self.coords = coords
            # A list of the neighbouring Number objects.
            self.nr_neighbours = nrs
            self.dims = dims
            if self.nr_neighbours:
                self.max_mines = min(len(self.coords),
                    max(map(int, self.nr_neighbours)))
            else:
                self.max_mines = len(coords)
    
        def __repr__(self):
            return "<Equivalence group containing {} cells>".format(
                len(self.coords))
    
        def __str__(self):
            grid = np.zeros(self.dims, int)
            for coord in self.coords:
                grid[coord] = 9
            for number in self.nr_neighbours:
                grid[number.coord] = int(number)
            return str(grid).replace('0', '.').replace('9', '#')
    
    
    grid = np.array(
        [[0, 0, 0, 0],
         [0, 2, 1, 0],
         [0, 0, 0, 0]]
        )
    config = NrConfig(grid)
    print config
    print "Number groups:"
    for n in config.numbers.values():
        print n
    print "Equivalence groups:"
    for g in config.groups:
        print g
    

1 个答案:

答案 0 :(得分:2)

如果您不想暴力破解它,您可以将流程建模为决策树。假设我们从你的例子开始:

####
#21#
####

如果我们想开始在有效配置中放置地雷,我们此时基本上有八个选择。因为我们在等价组中选择哪个方格并不重要,我们可以将其缩小到三个选择范围。树枝。让我们走下一个分支:

*###
#11#
####

我在G1中放了一个地雷,用星号表示。此外,我更新了与此等效组相关联的数字(在这种情况下只是一个数字),以表明这些编号的方格现在可以边界少一个地雷。

这并没有减少我们在下一步的选择自由,我们仍然可以在任何等价组中放置一个矿。让我们在G1中再放一个:

*XX#
*01#
XXX#

另一个星号标志着新的矿井,编号的广场又被降低了一个。它现在已达到零,这意味着它不能再与更多的地雷接壤。这意味着,对于我们下一次选择的矿井位置,所有依赖于此编号方格的等价组都被排除。 X s标记正方形,我们现在不能放置任何矿井。我们现在只能做出一个选择:

*XX*
*00X
XXXX

此处分支结束,您找到了有效的配置。通过以这种方式运行此树中的所有分支,您应该找到所有这些分支。这里我们找到了您的第一个配置当然,实现目标的方法不止一种。如果我们开始在G3中放置一个矿井,我们就会被迫将另外两个放在G1中。该分支导致相同的配置,因此您应该检查重复项。我现在没有办法避免这种冗余。

第二种配置是从G2开始,或者在G1中放置一个矿,然后在G2中放置第二个。在任何一种情况下,你最终都会在分支结束时结束:

**XX
X00X
XXXX

无效配置(如G1中的零地雷示例)不会弹出。沿着树的路上没有有效的选择。这是有效选择的整个树。

Choice 1:        1     |     2     |     3
Choice 2:    1   2   3 | 1         | 1   
Choice 3:     3     1  |           |1

有效配置是分支末端,无法进一步选择,即

113
12
131
21
311
如果忽略数字的顺序,

显然属于两个等价的类。