Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to win this game?

Support we have an n * m table, and two players play this game. They rule out cells in turn. A player can choose a cell (i, j) and rule out all the cells from (i,j) to (n, m), and who rules out the last cell loses the game.

For example, on a 3*5 board, player 1 rules out cell (3,3) to (3,5), and player 2 rules out (2,5) to (3,5), current board is like this: (O means the cell is not ruled out while x mean it is ruled out)

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

and after player 1 rules out cells from (2,1) to (3,5), the board becomes

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

Now player 2 rules out cells from (1,2) to (3,5), which leaves only (1,1) clean:

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

So player 1 has to rules out the only (1,1) cell, since one player has to rule out at least one cell in a turn, and he loses the game.

It is clearly that in n*n, 1*n, and 2*n (n >= 2) cases, the one who plays the first wins.

My problem is that, is there any strategy for a player to win the game in all cases? Should he plays first?

P.S

I think it is related to strategies like dynamic programming or divide-and-conquer, but has not come to an idea yet. So I post it here.

The answer

Thanks to sdcwc's link. For tables bigger than 1*1, the first player will win. The proof is follow: (borrowed from the wiki page)

It turns out that for any rectangular starting position bigger than 1 × 1 the 1st player can win. This can be shown using a strategy-stealing argument: assume that the 2nd player has a winning strategy against any initial 1st player move. Suppose then, that the 1st player takes only the bottom right hand square. By our assumption, the 2nd player has a response to this which will force victory. But if such a winning response exists, the 1st player could have played it as his first move and thus forced victory. The 2nd player therefore cannot have a winning strategy.

And Zermelo's theorem ensures the existence of such a winning strategy.

like image 634
ZelluX Avatar asked Dec 11 '09 08:12

ZelluX


2 Answers

This game is known as Chomp. The first player wins, see the link for his strategy (nonconstructive).

like image 52
sdcvvc Avatar answered Sep 28 '22 16:09

sdcvvc


Here's a Python program that will win for boards larger than 1x1 if allowed to go first (but it's pretty slow for boards larger than 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)

And the output from a sample run:

$ 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!
like image 42
Miles Avatar answered Sep 28 '22 17:09

Miles