7

支持我们有一个 n * m 表,两个玩家玩这个游戏。他们依次排除细胞。玩家可以选择一个单元格(i,j)并排除从(i,j)到(n,m)的所有单元格,排除最后一个单元格的人输掉游戏。

例如,在一个 3*5 的棋盘上,玩家 1 排除单元格 (3,3) 到 (3,5),玩家 2 排除单元格 (2,5) 到 (3,5),当前棋盘是这样的: (O 表示不排除该单元格,而 x 表示排除该单元格)

3 O O x x x
2 O O O O x
1 O O O O O
  1 2 3 4 5

在玩家 1 排除从 (2,1) 到 (3,5) 的单元格后,棋盘变为

3 x x x x x
2 x x x x x
1 O O O O O
  1 2 3 4 5

现在玩家 2 排除了从 (1,2) 到 (3,5) 的单元格,只剩下 (1,1) 干净:

3 x x x x x
2 x x x x x
1 O x x x x
  1 2 3 4 5

所以玩家 1 必须排除唯一的 (1,1) 单元格,因为一名玩家必须在一个回合中至少排除一个单元格,并且他输掉了游戏。

很明显,在 n*n、1*n 和 2*n (n >= 2) 的情况下,先玩的人获胜。

我的问题是,有没有什么策略可以让玩家在所有情况下都赢得比赛?他应该先上场吗?

附言

我认为它与动态规划或分而治之等策略有关,但尚未形成想法。所以我把它贴在这里。

答案

感谢sdcwc 的链接。对于大于 1*1 的牌桌,第一个玩家将获胜。证明如下:(借自维基页面)

事实证明,对于任何大于 1 × 1 的矩形起始位置,第一个玩家都可以获胜。这可以用一个策略窃取论点来证明:假设第二个玩家有一个获胜的策略来对抗任何最初的第一个玩家动作。假设第一个玩家只占据右下角的方格。根据我们的假设,第二名玩家对此有反应,这将迫使胜利。但是,如果存在这样的获胜反应,那么第一个玩家可以将其作为他的第一步,从而迫使胜利。因此,第 2 名玩家无法制定获胜策略。

策梅洛定理保证了这种制胜策略的存在。

4

4 回答 4

8

这个游戏被称为Chomp。第一个玩家获胜,请参阅他的策略的链接(非建设性的)。

于 2009-12-11T09:18:55.830 回答
1

这是一个 Python 程序,如果允许先行,它将在大于 1x1 的板上获胜(但对于大于 10x10 的板来说它非常慢):

class State(object):
    """A state is a set of spaces that haven't yet been ruled out.
    Spaces are pairs of integers (x, y) where x and y >= 1."""

    # Only winnable states in this dictionary:
    _next_moves = {}
    # States where any play allows opponent to force a victory:
    _lose_states = set()

    def __init__(self, spaces):
        self._spaces = frozenset(spaces)

    @classmethod
    def create_board(cls, x, y):
        """Create a state with all spaces for the given board size."""
        return State([(r+1, c+1) for r in xrange(x) for c in xrange(y)])

    def __eq__(self, other):
        return self._spaces == other._spaces

    def __hash__(self):
        return hash(self._spaces)

    def play(self, move):
        """Returns a new state where the given move has been played."""
        if move not in self._spaces:
            raise ValueError('invalid move')
        new_spaces = set()
        for s in self._spaces:
            if s[0] < move[0] or s[1] < move[1]:
                new_spaces.add(s)
        return State(new_spaces)

    def winning_move(self):
        """If this state is winnable, return a move that guarantees victory."""
        if self.is_winnable() and not self.is_empty():
            return State._next_moves[self]
        return None

    def random_move(self):
        import random
        candidates = [m for m in self._spaces if m[0] > 1 and m[1] > 1]
        if candidates: return random.choice(candidates)
        candidates = [m for m in self._spaces if m[0] > 1 or m[1] > 1]
        if candidates: return random.choice(candidates)
        return (1,1)

    def minimal_move(self):
        """Return a move that removes as few pieces as possible."""
        return max(self._spaces, key=lambda s:len(self.play(s)._spaces))

    def is_winnable(self):
        """Return True if the current player can force a victory"""
        if not self._spaces or self in State._next_moves:
            return True
        if self in State._lose_states:
            return False

        # Try the moves that remove the most spaces from the board first
        plays = [(move, self.play(move)) for move in self._spaces]
        plays.sort(key=lambda play:len(play[1]._spaces))
        for move, result in plays:
            if not result.is_winnable():
                State._next_moves[self] = move
                return True
        # No moves can guarantee victory
        State._lose_states.add(self)
        return False

    def is_empty(self):
        return not self._spaces

    def draw_board(self, rows, cols):
        string = []
        for r in xrange(rows, 0, -1):
            row = ['.'] * cols
            for c in xrange(cols):
                if (r, c+1) in self._spaces:
                    row[c] = 'o'
            string.append(('%2d ' % r) + ' '.join(row))
        string.append('  ' + ''.join(('%2d' % c) for c in xrange(1, cols+1)))
        return '\n'.join(string)

    def __str__(self):
        if not self._spaces: return '.'
        rows = max(s[0] for s in self._spaces)
        cols = max(s[1] for s in self._spaces)
        return self.draw_board(rows, cols)

    def __repr__(self):
        return 'State(%r)' % sorted(self._spaces)

def run_game(x, y):
    turn = 1
    state = State.create_board(x, y)
    while True:
        if state.is_empty():
            print 'Player %s wins!' % turn
            return
        if state.is_winnable():
            move = state.winning_move()
        else:
            move = state.random_move()
        state = state.play(move)
        print 'Player %s plays %s:' % (turn, move)
        print state.draw_board(x, y)
        print
        turn = 3 - turn

def challenge_computer(x, y):
    state = State.create_board(x, y)
    print "Your turn:"
    print state.draw_board(x, y)
    while True:
        # Get valid user input
        while True:
            try:
                move = input('Enter move: ')
                if not (isinstance(move, tuple) and len(move) == 2):
                    raise SyntaxError
                state = state.play(move)
                break
            except SyntaxError, NameError:
                print 'Enter a pair of integers, for example: 1, 1'
            except ValueError:
                print 'Invalid move!'
            except (EOFError, KeyboardInterrupt):
                return
        print state.draw_board(x, y)
        if state.is_empty():
            print 'Computer wins!'
            return
        if state.is_winnable():
            move = state.winning_move()
        else:
            move = state.minimal_move()
        state = state.play(move)
        print
        print 'Computer plays %s:' % (move,)
        print state.draw_board(x, y)
        print
        if state.is_empty():
            print 'You win!'
            return

if __name__ == '__main__':
    challenge_computer(8, 9)

示例运行的输出:

$ python -c 'import game; game.run_game(8, 9)'
Player 1 plays (6, 7):
 8 o o o o o o . . .
 7 o o o o o o . . .
 6 o o o o o o . . .
 5 o o o o o o o o o
 4 o o o o o o o o o
 3 o o o o o o o o o
 2 o o o o o o o o o
 1 o o o o o o o o o
   1 2 3 4 5 6 7 8 9

Player 2 plays (4, 8):
 8 o o o o o o . . .
 7 o o o o o o . . .
 6 o o o o o o . . .
 5 o o o o o o o . .
 4 o o o o o o o . .
 3 o o o o o o o o o
 2 o o o o o o o o o
 1 o o o o o o o o o
   1 2 3 4 5 6 7 8 9

Player 1 plays (5, 1):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 o o o o o o o . .
 3 o o o o o o o o o
 2 o o o o o o o o o
 1 o o o o o o o o o
   1 2 3 4 5 6 7 8 9

Player 2 plays (3, 7):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 o o o o o o . . .
 3 o o o o o o . . .
 2 o o o o o o o o o
 1 o o o o o o o o o
   1 2 3 4 5 6 7 8 9

Player 1 plays (4, 1):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 o o o o o o . . .
 2 o o o o o o o o o
 1 o o o o o o o o o
   1 2 3 4 5 6 7 8 9

Player 2 plays (2, 3):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 o o . . . . . . .
 2 o o . . . . . . .
 1 o o o o o o o o o
   1 2 3 4 5 6 7 8 9

Player 1 plays (1, 5):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 o o . . . . . . .
 2 o o . . . . . . .
 1 o o o o . . . . .
   1 2 3 4 5 6 7 8 9

Player 2 plays (2, 2):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 o . . . . . . . .
 2 o . . . . . . . .
 1 o o o o . . . . .
   1 2 3 4 5 6 7 8 9

Player 1 plays (1, 4):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 o . . . . . . . .
 2 o . . . . . . . .
 1 o o o . . . . . .
   1 2 3 4 5 6 7 8 9

Player 2 plays (2, 1):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 . . . . . . . . .
 2 . . . . . . . . .
 1 o o o . . . . . .
   1 2 3 4 5 6 7 8 9

Player 1 plays (1, 2):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 . . . . . . . . .
 2 . . . . . . . . .
 1 o . . . . . . . .
   1 2 3 4 5 6 7 8 9

Player 2 plays (1, 1):
 8 . . . . . . . . .
 7 . . . . . . . . .
 6 . . . . . . . . .
 5 . . . . . . . . .
 4 . . . . . . . . .
 3 . . . . . . . . .
 2 . . . . . . . . .
 1 . . . . . . . . .
   1 2 3 4 5 6 7 8 9

Player 1 wins!
于 2009-12-11T12:17:23.857 回答
0

想到的一件事:如果棋盘是 2x2,第一个玩家输了:事实上,从这个棋盘:

O O 
O O

有两种变体(a 和 b):

a.1)

1 1
O O

a.2) 第一个玩家输了

1 1
O 2

或者,b.1)

1 O
O O

b.2) 第一个玩家输了

1 2
O 2

在这一点上,策略归结为迫使棋盘变为 2x2 平方;然后,第一个进入该板的人将失去它。这将引导您进入策略的第二步,主要是:

如何确保您不是进入此类配置的人?

或者,

有多少配置会导致我获得这样的配置,从一个更大的配置开始?例如,从 3x3 板开始:

O O O
O O O
O O O

有几种策略,取决于谁先开始以及有多少无效;在这一点上,我建议使用遗传算法来探索最佳解决方案(这很有趣!相信我):)

于 2009-12-11T08:58:28.907 回答
0

这类似于经常玩火柴的游戏(记不起名字了)

无论如何,我认为这取决于获胜者的董事会形状。2*2 对第二个玩家来说是微不足道的失败,而 2 * N 对第一个玩家来说是微不足道的失败,方法是将棋盘减少到 2*2 并迫使其他玩家玩。我认为所有方形板都是第二玩家获胜,而矩形是第一玩家获胜,但尚未证明

编辑:

好的,我认为这是对于方板 p1 总是选择 2,2 然后平衡行和列确保 p2 输

与 sdcwc 的评论一样,矩形板也是第一个玩家获胜。只有退化的棋盘 1 * 1 是第二名玩家获胜

于 2009-12-11T09:01:26.503 回答