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.
This game is known as Chomp. The first player wins, see the link for his strategy (nonconstructive).
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With