Just a simple text based Tic Tac Toe game for human vs computer play.
The game and AI reside in the TTTGame class. AI uses brute force search for 'perfect play'.
The CLI class provides a text based console user interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | import random
class TTTGame(object):
_coords = [(x, y) for y in range(3) for x in range(3)]
_num_to_coord = dict([(n+1, c) for n, c in enumerate(_coords)])
_win_ways = (# Horizontal
set([1, 2, 3]),
set([4, 5, 6]),
set([7, 8, 9]),
# Verticle
set([1, 4, 7]),
set([2, 5, 8]),
set([3, 6, 9]),
# Diagonal
set([1, 5, 9]),
set([3, 5, 7]))
def __init__(self, game=None):
if not game:
self.board = map(list, ['___'] * 3)
self.avail = set(xrange(1, 10))
self.xnums = set()
self.onums = set()
else:
self.board = map(list, game.board)
self.avail = set(game.avail)
self.xnums = set(game.xnums)
self.onums = set(game.onums)
def __str__(self):
rows = [' | '.join(row).replace('_',' ')
for row in self.board[::-1]]
divs = ['\n---+---+---\n', '\n---+---+---\n', '\n']
return ' ' + ' '.join(map(''.join, zip(rows, divs)))
def winner(self):
"""Return 'X' or 'O' or 'T' else False if game not over."""
for way in self._win_ways:
if way.issubset(self.xnums):
return 'X'
if way.issubset(self.onums):
return 'O'
if len(self.xnums) + len(self.onums) == 9:
return 'T'
return False
def play(self, n, piece=None):
"""
Place piece at spot numbered n and ammend attributes.
if piece is None, remove the piece at cell n and ammend attributes.
"""
x, y = self._num_to_coord[n]
if piece:
self.board[y][x] = piece
(self.xnums if piece == 'X' else self.onums).add(n)
self.avail.remove(n)
else:
self.board[y][x] = '_'
self.avail.add(n)
(self.xnums if n in self.xnums else self.onums).remove(n)
def next_move(self, piece):
"""
Return the next best move for piece as cell number.
"""
if all(map(lambda x: len(set(x)) == 1, self.board)):
return random.choice((1, 3, 7, 9)) # Corners are best first play.
scores = []
avail = list(self.avail)
for n in avail:
node = TTTGame(self)
node.play(n, piece)
scores.append(node._evaluate(piece))
best = max(enumerate(scores), key=lambda x: x[1])[0]
return avail[best]
def _evaluate(self, piece):
"""
Return a score for how favourable the current board is towards piece.
"""
state = self.winner()
if state:
return (1 if state == piece else 0 if state == 'T' else -1)
scores = []
apponent = 'OX'.replace(piece, '')
for n in self.avail:
self.play(n, apponent)
scores.append(0-self._evaluate(apponent))
self.play(n) # reverse play
safest = min(scores)
return safest
class CLI(object):
# - Note that game_loop() is not concerned with any of the display
# attributes or refreshing the screen.
# - Each method is responsible for
# handling it's own display characteristics, for now, with the use of
# the message attribute and refresh().
# - Methods which modify the message attribute should assign an empty
# string to message attribute before returning, except in cases like
# coin_toss().
# - Methods may communicate with each other via return values, not
# via message or any other display attribute.
def __init__(self):
# Display variables.
self.wins = 0
self.losses = 0
self.ties = 0
self.message = ''
# Piece assignments.
self.player = ''
self.computer = ''
# TTTGame instance.
self.game = None
# Players turn or not.
self.turn = None
def refresh(self):
screen = '\n' * 100 # Clear screen.
screen += "TicTacToe\n"
screen += ("wins:%s\tlosses:%s\tties:%s\n\n" %
(self.wins, self.losses, self.ties))
screen += str(self.game) if self.game else '\n' * 4 # The game board.
screen += '\n' + self.message + '\n'
print screen
def coin_toss(self):
"""Assigns player a piece at random, Returns None."""
while True: # until user enters valid input
self.refresh()
option = raw_input("Heads or Tails (or just hit enter)? ")
if option.lower() in ['', 'heads', 'h', 'tails', 't']:
self.player = random.choice(['X', 'O'])
self.computer = 'XO'.replace(self.player, '')
break
else:
self.message = "That's not a valid choice!"
self.message = "You go first" if self.player == 'X' else ''
def player_turn(self):
while True: # until user enters valid input
self.refresh()
option = raw_input("cell number: ").strip().lower()
if option == 'hint':
self.message = str(self.game.next_move(self.player))
elif not (option.isdigit() and 1 <= int(option) <= 9):
self.message = "That's not a valid option!"
elif int(option) not in self.game.avail:
self.message = "That cell is already occupied!"
else:
break
self.game.play(int(option), self.player)
self.message = ''
def computer_turn(self):
self.refresh()
best = self.game.next_move(self.computer)
self.game.play(best, self.computer)
self.message = ''
def play_again(self):
"""Gives user the chance to quit the program or continue."""
while True: # until user enters valid input
self.refresh()
option = raw_input("Play again (enter) or n? ").strip().lower()
if not option:
self.message = ''
return
elif option in ["no", 'n']:
import sys
sys.exit()
else:
self.message = "That's not a valid option!"
def game_loop(self):
"""Simple game loop."""
while True:
self.game = TTTGame()
self.coin_toss()
self.turn = (self.player == 'X') # X always goes first.
while not self.game.winner():
if self.turn:
self.player_turn()
else:
self.computer_turn()
self.turn = not self.turn
winner = self.game.winner()
if winner == 'T':
self.message = "You tied."
self.ties += 1
elif winner == self.player:
self.message = "You won!"
self.wins += 1
else:
self.message = "You lost."
self.losses += 1
self.play_again()
if __name__ == '__main__':
print '\n' * 100
print ("Welome to TicTacToe\n\n" +
"Cells are numbered 1 to 9 and correspond directly\n" +
"with keys on your keyboards numpad.\n\n" +
"To make a play, type the relevent number and hit enter\n\n" +
"You can also type hint when it's your turn to play.\n\n" +
"BTW, the computer is unbeatable. Which means your win\n" +
"statistic will never show anything other than 0.\n" +
"Have fun ;)\n\n")
raw_input(".... (hit enter) ...")
user_interface = CLI()
user_interface.game_loop()
|
Hello. I was wondering if you could give a longer explanation of the code. Maybe add comments directly to the code about what it exactly does. Or just an explanation in the comments. THANKS!!