Advertisement
Guest User

Untitled

a guest
Feb 26th, 2016
630
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.51 KB | None | 0 0
  1. import shelve
  2.  
  3. class ImmutableDict:
  4.     def __init__(self, d=None):
  5.         self.d = {} if d is None else d
  6.     def __getitem__(self, key):
  7.         return self.d[key]
  8.     def __hash__(self):
  9.         return hash(tuple((k,v) for k,v in self.d.items()))
  10.     def __eq__(self, other):
  11.         return self.d == other.d
  12.     def updated(self, key, value):
  13.         d = self.d.copy()
  14.         d[key] = value
  15.         return ImmutableDict(d)
  16.  
  17. def other(s):
  18.     if s == "X": return "O"
  19.     if s == "O": return "X"
  20.     raise Exception("Unknown player {}".format(s))
  21.  
  22. class State:
  23.     def __init__(self, board, active_player, previous_moves=None):
  24.         self.board = board
  25.         self.active_player = active_player
  26.         self.previous_moves = previous_moves or ImmutableDict({"X": None, "O": None})
  27.     def iter_possible_moves(self):
  28.         if self.winner() is not None:
  29.             return
  30.         right_column = [self.board[y][2] for y in range(3)]
  31.         if any(cell == self.active_player for cell in right_column):
  32.             can_slide = True
  33.         elif all(cell is None for cell in right_column):
  34.             can_slide = True
  35.         else:
  36.             can_slide = False
  37.         for x in range(3):
  38.             for y in range(3):
  39.                 if self.previous_moves[self.active_player] == (x,y):
  40.                     continue
  41.                 if self.board[y][x] is None:
  42.                     yield (False, x,y)
  43.                 if can_slide and (x == 0 or self.board[y][x-1] is None):
  44.                     yield (True, x,y)        
  45.     def winner(self):
  46.         raw_runs = "012, 345, 678, 036, 147, 258, 048, 246".split(", ")
  47.         for raw_run in raw_runs:
  48.             run = map(int, raw_run)
  49.             cells = []
  50.             for idx in run:
  51.                 x = idx % 3
  52.                 y = idx / 3
  53.                 cells.append(self.board[y][x])
  54.             for c in "XO":
  55.                 if all(cell == c for cell in cells):
  56.                     return c
  57.         return None
  58.  
  59.     def apply(self, move):
  60.         do_slide, x, y = move
  61.         next_board = []
  62.         for j in range(3):
  63.             row = []
  64.             for i in range(3):
  65.                 if x == i and y == j:
  66.                     row.append(self.active_player)
  67.                 elif do_slide:
  68.                     row.append(self.board[j][i-1] if i > 0 else None)
  69.                 else:
  70.                     row.append(self.board[j][i])
  71.             next_board.append(tuple(row))
  72.         next_board = tuple(next_board)
  73.         return State(
  74.             next_board,
  75.             other(self.active_player),
  76.             self.previous_moves.updated(self.active_player, (x,y))
  77.         )
  78.  
  79.     @staticmethod
  80.     def from_idx(idx):
  81.         x = idx % 3
  82.         y = idx / 3
  83.         return x,y
  84.     @staticmethod
  85.     def to_idx(x,y):
  86.         return x + y*3
  87.  
  88.     def tuple(self):
  89.         return (self.board, self.active_player, self.previous_moves)
  90.     def __hash__(self):
  91.         return hash(self.tuple())
  92.     def __eq__(self, other):
  93.         return self.tuple() == other.tuple()
  94.     def __str__(self):
  95.         ret = """
  96. +---+---+---+
  97. |   |   |   |
  98. | Q | Q | Q |
  99. |   |   |   |
  100. +---+---+---+
  101. |   |   |   |
  102. | Q | Q | Q |
  103. |   |   |   |
  104. +---+---+---+
  105. |   |   |   |
  106. | Q | Q | Q |
  107. |   |   |   |
  108. +---+---+---+
  109. +Q's turn   +
  110. +-----------+
  111. |last moves:|
  112. |X: Q Q     |
  113. |O: Q Q     |
  114. +-----------+""".replace("Q", "{}")
  115.         args = []
  116.         for j in range(2,-1,-1):
  117.             for i in range(3):
  118.                 cell = self.board[j][i]
  119.                 glyph = str(cell) if cell is not None else " "
  120.                 args.append(glyph)
  121.         args.append(self.active_player)
  122.         for p in "XO":
  123.             coord = self.previous_moves[p]
  124.             if coord is None:
  125.                 args.extend(("?", "?"))
  126.             else:
  127.                 args.extend(map(str, coord))
  128.         return ret.format(*args)
  129.  
  130.     def serialize(self):
  131.         #pack the state into a string.
  132.         ret= []
  133.         #first nine characters: state of the board.
  134.         for i in range(9):
  135.             x,y = State.from_idx(i)
  136.             c = self.board[y][x]
  137.             if c is None: c = " "
  138.             ret.append(c)
  139.         #tenth character: active player.
  140.         ret.append(self.active_player)
  141.         #characters 11 through 15: last placed positions for players X and O.
  142.         for player in "XO":
  143.             if self.previous_moves[player] is None:
  144.                 ret.append(" ")
  145.             else:
  146.                 ret.append(State.to_idx(*self.previous_moves[player]))
  147.         return "".join(ret)
  148.  
  149. def is_round(x):
  150.     if x < 10: return True
  151.     if x % 10 != 0: return False
  152.     return is_round(x/10)
  153.  
  154. def create_state_tree(start):
  155.     d = {start: {"to": {}, "from": []}}
  156.     to_visit = set([start])
  157.     visited = set()
  158.  
  159.     milestones_seen = set()
  160.  
  161.     while to_visit:
  162.         if is_round(len(visited)) and len(visited) not in milestones_seen:
  163.             milestones_seen.add(len(visited))
  164.             print len(visited)
  165.             #if len(visited) == 100: return d
  166.  
  167.         cur = to_visit.pop()
  168.         for move in cur.iter_possible_moves():
  169.             neighbor = cur.apply(move)
  170.             d[cur]["to"][move] = neighbor
  171.             if neighbor not in d:
  172.                 d[neighbor] = {"to": {}, "from": []}
  173.             d[neighbor]["from"].append(cur)
  174.             if neighbor not in visited:
  175.                 to_visit.add(neighbor)
  176.         visited.add(cur)
  177.     return d
  178.  
  179. def determine_ultimate_winners(d):
  180.     ultimate_winner = {state: None for state in d.keys()}
  181.     time_to_victory = {state: None for state in d.keys()}
  182.     while True:
  183.         updates_made_this_round = 0
  184.         for state in ultimate_winner:
  185.             if ultimate_winner[state] is not None: #already found the result for this one
  186.                 continue
  187.             if state.winner() is not None:
  188.                 ultimate_winner[state] = state.winner()
  189.                 time_to_victory[state] = 0
  190.                 updates_made_this_round += 1
  191.             neighbors = list(d[state]["to"].values())
  192.             if len(neighbors) == 0: #shouldn't happen when exploring full tree
  193.                 continue
  194.             winning_neighbors = [neighbor for neighbor in neighbors if ultimate_winner[neighbor] == state.active_player]
  195.             losing_neighbors = [neighbor for neighbor in neighbors if ultimate_winner[neighbor] == other(state.active_player)]
  196.             if winning_neighbors:
  197.                 ultimate_winner[state] = state.active_player
  198.                 time_to_victory[state] = 1 + min(time_to_victory[neighbor] for neighbor in winning_neighbors)
  199.                 updates_made_this_round += 1
  200.             elif len(losing_neighbors) == len(neighbors):
  201.                 ultimate_winner[state] = other(state.active_player)
  202.                 time_to_victory[state] = 1 + max(time_to_victory[neighbor] for neighbor in losing_neighbors)
  203.                 updates_made_this_round += 1
  204.         if updates_made_this_round == 0:
  205.             break
  206.     return ultimate_winner, time_to_victory
  207.  
  208. def prompt_choice(items):
  209.     for i, item in enumerate(items):
  210.         print "{:3}: {}".format(i, item)
  211.     while True:
  212.         response = raw_input("Choose one: ")
  213.         try:
  214.             response = int(response)
  215.         except:
  216.             print "Sorry, did not understand that response."
  217.             continue
  218.         if response < 0 or response >= len(items):
  219.             print "Sorry, that is not a valid choice."
  220.             continue
  221.         return response
  222.  
  223. starting_state = State(
  224.     ((None, None, None),(None, None, None),(None, None, None)),
  225.     "X")
  226.  
  227. shelf = shelve.open("results.dat")
  228.  
  229. force_generate = True
  230. print "Attempting to load data from file..."
  231. if force_generate:
  232.     print "Made to fail by configuration setting."
  233.     generate = True
  234. elif "value" not in shelf:
  235.     generate = True
  236. else:
  237.     generate = False
  238.  
  239. if generate:
  240.     print "Failed. generating data from scratch..."
  241.     d = create_state_tree(starting_state)
  242.     print "Generated."
  243.     #shelf["value"] = d
  244. else:
  245.     print "Success."
  246.     d = shelf["value"]
  247.  
  248. shelf.close()
  249.  
  250. print "Determining winners from all states..."
  251. winners, time_to_victory = determine_ultimate_winners(d)
  252. print "Determined."
  253. print "\a"
  254. print "Winner of starting state:", winners[starting_state]
  255. print "Moves to end of game, assuming optimal play:", time_to_victory[starting_state]
  256.  
  257. print "Proportion of state space with known winners: {} out of {}".format(sum(1 for state in winners if winners[state] is not None), len(winners))
  258.  
  259. print "Moves makable by active player:"
  260. for move in starting_state.iter_possible_moves():
  261.     neighbor = starting_state.apply(move)
  262.     state = {"X": "Winner", "O": "Loser", None: "Tie?"}[winners[neighbor]]
  263.     print move, state
  264.  
  265. # import pdb; pdb.set_trace()
  266.  
  267. #returns a (state, commentary) tuple
  268. def best_choice(state):
  269.     if state not in winners: #shouldn't happen
  270.         return next(state.iter_possible_moves()), "I never explored this possibility."
  271.         #return random.choice(list(state.iter_possible_moves()))
  272.     if winners[state] is None: #stalemate or unexplored state
  273.         return next(state.iter_possible_moves()), "Not sure who will win this."
  274.     if winners[state] != state.active_player: #hopeless
  275.         choice = max(state.iter_possible_moves(), key=lambda move: time_to_victory[state.apply(move)])
  276.         return choice, "I can be defeated in {} moves.".format(time_to_victory[state.apply(choice)])
  277.     candidates = []
  278.     for move in state.iter_possible_moves():
  279.         neighbor = state.apply(move)
  280.         if winners[neighbor] == state.active_player:
  281.             candidates.append(move)
  282.     choice = min(candidates, key=lambda move: time_to_victory[state.apply(move)])
  283.     return choice, "I win in no more than {} moves.".format(time_to_victory[state.apply(choice)])
  284.  
  285. def construct_minimal_tree(state):
  286.     move, _ = best_choice(state)
  287.     neighbor = state.apply(move)
  288.     if neighbor.winner() is not None:
  289.         return (move, None)
  290.     responses = {}
  291.     for next_move in neighbor.iter_possible_moves():
  292.         responses[next_move] = construct_minimal_tree(neighbor.apply(next_move))
  293.     return (move, responses)
  294.  
  295. def tree_size(t):
  296.     if t[1] is None: return 1
  297.     return 1 + sum(tree_size(branch) for branch in t[1].values())
  298.  
  299. tree = construct_minimal_tree(starting_state)
  300. print "Size of optimal move tree:", tree_size(tree)
  301.  
  302.  
  303.  
  304. import random
  305.  
  306. try:
  307.     while True:
  308.         print "Let's play a game. you can be O."
  309.         move_repr = lambda move: ("Move board right" if move[0] else "Leave board as-is") + " and take spot {} {}".format(move[1], move[2])
  310.         state = starting_state
  311.         while True:
  312.             print state
  313.             possible_moves = list(sorted(state.iter_possible_moves()))
  314.             if len(possible_moves) == 0:
  315.                 print "Looks like a tie."
  316.                 break
  317.             if state.active_player == "O":
  318.                 reply = prompt_choice(map(move_repr, possible_moves))
  319.                 state = state.apply(possible_moves[reply])
  320.             else:
  321.                 move, comment = best_choice(state)
  322.                 print "I'll {}. {}".format(move_repr(move).lower(), comment)
  323.                 state = state.apply(move)
  324.             if state.winner() is not None:
  325.                 print state
  326.                 print "{} is the winner!".format(state.winner())
  327.                 break
  328.         reply = raw_input("Play again? ")
  329.         if reply.lower() == "n": break
  330. except KeyboardInterrupt:
  331.     import pdb; pdb.set_trace()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement