Talk:Game complexity/ttt.py


 * 1) !/usr/bin/python


 * 1) Code by User:Gdr. Licensed under the GPL and GFDL.


 * 1) A tic-tac-toe position has cells numbered from 0-8.  It is represented
 * 2) in compressed form as an 18-bit number where bit 2i is 1 iff cell i is
 * 3) full, and bit 2i+1 is 1 iff cell i contains X.

lines = [(0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6)]
 * 1) A list of winning lines.

win_masks = [(sum([3 &lt;&lt; (2 * i) for i in l]),             sum([2 &lt;&lt; (2 * i) for i in l])) for l in lines]
 * 1) List of pairs for each winning line, each pair being:
 * 2) 0. A mask for testing the winning line against the board.
 * 3)    (This is also the result of comparing the mask if line is a win for X.)
 * 4) 1. Result of comparing the mask if line is a win for O.

full_mask = sum([2 &lt;&lt; (2 * i) for i in xrange(9)])
 * 1) A mask for testing that the board is full.

def game_over(pos): if pos &amp; full_mask == full_mask: return True for x,o in win_masks: if pos &amp; x in (x,o): return True return False
 * 1) Return True if the game is over in 'pos', False if play can continue.

def count_games(pos, player, positions): positions[pos] = 1 if game_over(pos): return 1 games = 0 for i in xrange(9): if pos &amp; (2 &lt;&lt; (2 * i)) == 0: new_pos = pos | ((2 + player) &lt;&lt; (2 * i)) games += count_games(new_pos, 1 - player, positions) return games
 * 1) Return the number of games that can be played starting at 'pos' with
 * 2) 'player' to move (0 = O, 1 = X), 'positions' being a dictionary of
 * 3) positions seen so far.

symmetries = [(0,1,2,3,4,5,6,7,8), (2,5,8,1,4,7,0,3,6),     # quarter-turn clockwise (8,7,6,5,4,3,2,1,0),     # half-turn (6,3,0,7,4,1,8,5,2),     # quarter-turn anticlockwise (6,7,8,3,4,5,0,1,2),     # reflection in horizontal line (2,1,0,5,4,3,8,7,6),     # reflection in vertical line (0,3,6,1,4,7,2,5,8),     # reflection in leading diagonal (8,5,2,7,4,1,6,3,0)]     # reflection in trailing diagonal
 * 1) All the symmetries of the tic-tac-toe board, represented as
 * 2) permutations of the cells.

def permute(pos, perm): return sum([((pos &gt;&gt; (2*i)) &amp; 3) &lt;&lt; (2*perm[i]) for i in xrange(9)])
 * 1) Return the position 'pos' transformed by the permutation 'perm'.

def canonical(pos): return min([permute(pos, sym) for sym in symmetries])
 * 1) Return the canonical representative of the position 'pos'. This is the
 * 2) lowest-number encoding of the position under the eight symmetries.

def count_games_2(pos, player, positions): # assert(pos == canonical(pos)) positions[pos] = 1 if game_over(pos): return 1 games = 0 new_positions = {} for i in xrange(9): if pos &amp; (2 &lt;&lt; (2 * i)) == 0: new_pos = canonical(pos | ((2 + player) &lt;&lt; (2 * i))) if not new_positions.has_key(new_pos): new_positions[new_pos] = 1 games += count_games_2(new_pos, 1 - player, positions) return games
 * 1) As 'count_games', but consider positions identical if they are related
 * 2) by a symmetry of the board.

if __name__ == '__main__': p = {} games = count_games(0, 1, p)   print "Neglecting symmetry,", print "there are %d positions and %d games." % (len(p), games) p = {} games = count_games_2(canonical(0), 1, p)   print "With symmetry,", print "there are %d positions and %d games." % (len(p), games)


 * 1) Neglecting symmetry, there are 5478 positions and 255168 games.
 * 2) With symmetry, there are 765 positions and 26830 games.