【问题标题】:Determining three in a row in Python 2d array在 Python 2d 数组中连续确定三个
【发布时间】:2010-07-22 16:31:55
【问题描述】:

我正在用 Python 开发一个带有 M x N 棋盘的井字游戏。我试图找到一种有效的方法来确定玩家是否赢了(垂直、水平或对角方向连续 3 个)。游戏的大多数 3x3 实现只是在每回合后检查所有可能的获胜组合。对于一块巨大的板来说,这似乎有点极端。

4x4 示例:(使用 1s 和 2s 而不是 Xs 和 Os)

board = ([1,0,2,1], [0,0,0,1], [2,2,0,0], [1,0,0,1])
for row in board:
    print row

谢谢- 乔纳森

【问题讨论】:

  • 检查所有组合可能有点矫枉过正,但在 O(M x N) 中仍然可行,因为任何给定的方格都是不超过恒定数量的可能获胜字符串的成员。此外,此检查极不可能花费足够长的时间以引起用户注意。话虽如此,正如 Teodor 所建议的那样,只要注意动作,您就可以击败它。
  • @Brian,特奥多。同意,在传统的井字游戏中,仅检查所选方格可能获胜的情况会更有意义。我实际上是在使用它来尝试确定在没有胜利的情况下可以在板上的最大 Xs(无 Os)数量。我正在遍历填写好的板(MxN 位二进制数)并确定它是否获胜。我认为井字游戏问题可以让我得到一些算法思路,而无需过多解释。
  • 在这种情况下,Teodor 的建议会奏效,不过您可以通过跳过八个方向中的三个方向的测试来稍微加快速度。顺便说一句,我的直觉是,从一个装满 xs 的板子开始并尽可能少地移除可能会更容易解决这个问题。

标签: python arrays


【解决方案1】:

虽然这种方法有一定的吸引力,但它可能不是特别快。

# A bogus game with wins in several directions.
board = (
    [1,1,2,1],
    [0,2,1,1],
    [2,2,2,1],
    [1,0,0,1],
)

# A few convenience variables.    
n_rows = len(board)    
lft = [ [0] * i for i in range(n_rows) ]  # [[], [0], [0, 0], [0, 0, 0]]
rgt = list(reversed(lft))

# Create transpositions of the board to check for wins in various directions.
transpositions = {
    'horizontal' : board,
    'vertical'   : zip(*board),
    'diag_forw'  : zip(* [lft[i] + board[i] + rgt[i] for i in range(n_rows)] ),
    'diag_back'  : zip(* [rgt[i] + board[i] + lft[i] for i in range(n_rows)] ),
}

# Apply Jonathan's horizontal-win check to all of the transpositions.
for direction, transp in transpositions.iteritems():
    for row in transp:
        s = ''.join( map(str, row) )
        for player in range(1,3):
            if s.find(str(player) * 3) >= 0:
                print 'player={0} direction={1}'.format(player, direction)

输出:

player=1 direction=diag_back
player=2 direction=diag_forw
player=2 direction=horizontal
player=1 direction=vertical

对角线换位背后的想法是移动行,使用lftrgt 进行左右填充。例如,添加填充后的diag_forw 列表如下所示(填充字符显示为句点,即使在实际代码中使用了零)。

1 1 2 1 . . .
. 0 2 1 1 . .
. . 2 2 2 1 .
. . . 1 0 0 1 

然后我们使用zip(*foo) 简单地转置该数组,这样我们就可以使用乔纳森的好主意来寻找横向胜利。

【讨论】:

  • 很好的解决方案。为避免出现 IndexErrors,您需要使用 min(n_rows, n_cols),而不是在 diag zip 函数中使用 n_cols。
  • @Jonathan 谢谢。实际上,我认为它只需要改为n_rows
【解决方案2】:

您可以查看玩家的移动是否结束了游戏(查看该行、该列和 2 条对角线,如果它们是 x 连续检查),它的复杂度为 o(x)。假设您正在查看那一排,看看他是否赢了。向左看有多少连续检查,向右看。如果它们的总和超过 x,他就赢了。您将在列和对角线上执行相同的操作。

【讨论】:

    【解决方案3】:

    检查横向胜利

    for row in board:
        rowString = ''.join(row)
        if(rowString.count('111') > 2 or rowString.count('222') > 2):
            print "Somebody won"
    

    检查垂直胜利

    for col in xrange(len(board[0])):
        colString = ""
        for row in board:
            colString = colString.append(row[col])
        if(colString.count('111') > 2 or colString.count('222') > 2):
            print "Somebody won"
    

    仍然被对角线难住......

    【讨论】:

      【解决方案4】:

      如果您有如下设置的板:

      board = 
      ([1,0,2,0],
       [0,1,2,0],
       [0,0,0,0],
       [0,0,0,0])
      

      您可以将其想象为 x 和 y 坐标,从左上角开始,向下移动为正 y,向右移动为正 x。任一玩家在board[3][3] 的移动将是一个获胜的移动。使用 Teodor Pripoae 过程,我们可以围绕最后一步构建水平、垂直和对角线。横向的情况很简单。

      def horizontal(board, y_coord):
          return board[y_coord]
      

      垂直情况需要我们从每一行中选择x_coord:

      def vertical(board, x_coord):
          return [row[x_coord] for row in board]
      

      对角线的情况有点棘手。对于第一个函数,它计算从上到下从左到右的对角线。距离基本上表示当 y 等于 0 时到零的水平距离。

      def diagonal1(board, x_coord, y_coord):
          length = len(board[0])
          distance = x_coord - y_coord
          if distance >= 0:
              return [y[x] for x, y in enumerate(board) if distance + x <= length]
          else:
              return [y[x] for x, y in enumerate(board) if x - distance >= 0 and x - distance <= length]
      

      第二个函数计算从上到下从右到左的对角线。在此函数中,距离表示与零的垂直距离,因为水平距离为零。

      def diagonal2(board, x_coord, y_coord):
          length = len(board[0])
          distance = y_coord + x_coord
          return [y[distance - x] for x, y in enumerate(board) if distance - x <= length]
      

      一旦你定义了这些,你只需要一种方法来检查玩家是否赢了。这样的事情可能会做:

      def game_over(direction, number_to_win, player_number):
          count = 0
          for i in direction:
              if i == player_number:
                  count += 1
                  if count = number_to_win:
                      return True
              else:
                  count = 0
          return False
      

      写完所有这些,似乎这有点过头了,除非你有相当大的 M 和 N。虽然它可能比检查每个胜利条件更有效,但它确实构建了整个水平、垂直和对角线方向,而不是不仅仅是围绕最后一步的那些坐标,它没有它应该的效率。

      也许这会有所帮助,但似乎 Brian 的建议只是删除 x 可能会更好。

      【讨论】:

        【解决方案5】:

        我在软件开发人员面试中一直使用这个问题的一个变体,所以我对这个问题进行了相当多的思考。这是一个更好的答案:它可以处理任意数量的玩家、任意方形井字游戏和任意“运行规模”。该方法相当简单,提供有关找到的所有序列的信息,并且是 O(N),其中 N 是细胞数。

        # Given a square tic-tac-toe grid of any size, with any number of players, find
        # all sequences (horizontal, vertical, diagonal) of some minimum size.
        
        def main():
            raw_grid = [
                [1, 1, 2, 1, 0],  # Zero means open spot.
                [0, 2, 1, 1, 1],
                [2, 2, 2, 1, 2],
                [1, 0, 1, 1, 2],
                [1, 0, 0, 0, 2],
            ]
            for run in get_runs(raw_grid, 3):
                print run
        
        def get_runs(raw_grid, run_size):
            # Offsets to find the previous cell in all four directions.
            offsets = {
                'h' : ( 0, -1), # _
                'v' : (-1,  0), # |
                'f' : (-1,  1), # /
                'b' : (-1, -1), # \
            }
        
            # Helpers to check for valid array bounds and to return a new cell dict.
            size      = len(raw_grid)
            in_bounds = lambda r, c: r >= 0 and c >= 0 and r < size and c < size
            new_cell  = lambda i, j, p: dict(h=1, v=1, f=1, b=1, i=i, j=j, player=p)
        
            # Use the raw grid to create a grid of cell dicts.
            grid = []
            for i, row in enumerate(raw_grid):
                grid.append([])
                for j, player in enumerate(row):
                    # Add a cell dict to the grid (or None for empty spots).
                    cell = new_cell(i, j, player) if player else None
                    grid[i].append(cell)
                    if not cell: continue
        
                    # For each direction, look to the previous cell. If it matches the
                    # current player, we can extend the run in that direction.
                    for d, offset in offsets.iteritems():
                        r, c = (i + offset[0], j + offset[1])
                        if in_bounds(r, c):
                            prev = grid[r][c]
                            if prev and prev['player'] == cell['player']:
                                # We have a match, so the run size is one bigger,
                                # and we will track that run in the current cell,
                                # not the previous one.
                                cell[d] = prev[d] + 1
                                prev[d] = None
        
            # For all non-None cells, yield run info for any runs that are big enough.
            for cell in (c for row in grid for c in row if c):
                for d in offsets:
                    if cell[d] and cell[d] >= run_size:
                        yield dict(
                            player    = cell['player'],
                            endpoint  = (cell['i'], cell['j']),
                            direction = d,
                            run_size  = cell[d],
                        )
        
        main()
        

        输出:

        {'player': 1, 'direction': 'h', 'endpoint': (1, 4), 'run_size': 3}
        {'player': 2, 'direction': 'f', 'endpoint': (2, 0), 'run_size': 3}
        {'player': 2, 'direction': 'h', 'endpoint': (2, 2), 'run_size': 3}
        {'player': 1, 'direction': 'b', 'endpoint': (2, 3), 'run_size': 3}
        {'player': 1, 'direction': 'f', 'endpoint': (3, 2), 'run_size': 3}
        {'player': 1, 'direction': 'v', 'endpoint': (3, 3), 'run_size': 4}
        {'player': 2, 'direction': 'v', 'endpoint': (4, 4), 'run_size': 3}
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-04-13
          • 1970-01-01
          • 1970-01-01
          • 2023-03-22
          • 2018-08-21
          • 2022-01-07
          • 2016-02-01
          • 1970-01-01
          相关资源
          最近更新 更多