Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@rojoca
Last active December 19, 2015 15:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rojoca/5980444 to your computer and use it in GitHub Desktop.
Save rojoca/5980444 to your computer and use it in GitHub Desktop.
This is a playable tic tac toe command line game developed via TDD. The AI is not terribly smart.
SPACE = " "
X = "X"
O = "O"
class Board(object):
"""
A tic-tac-toe board that determine if there
is a winner
"""
def __init__(self, n=3):
self.n = n
self.vals = [[SPACE for __ in range(n)] for __ in range(n)]
def move(self, player, row, col):
if row > self.n or row < 0 or col > self.n or col < 0:
raise ValueError("Row %s Col %s is invalid" % (row, col))
if self.vals[row][col] != SPACE:
raise ValueError("Row %s Col %s is already taken (%s)" % (
row, col, self.vals[row][col]))
self.vals[row][col] = player
return True
def line_win(self, line):
return len(set(line)) == 1 and set(line).pop() != SPACE
def row_winner(self):
winners = [row[0] for row in self.vals if self.line_win(row)]
if len(winners):
return winners[0]
return None
def col_winner(self):
index = range(self.n)
for i in index:
if self.line_win([self.vals[j][i] for j in index]):
return self.vals[0][i]
return None
def row(self, index):
return self.vals[index]
def col(self, index):
return [self.vals[i][index] for i in range(self.n)]
def d1(self):
return [self.vals[i][i] for i in range(self.n)]
def d2(self):
return [self.vals[i][self.n - i - 1] for i in range(self.n)]
def d1_winner(self):
if self.line_win(self.d1()):
return self.vals[0][0]
return None
def d2_winner(self):
if self.line_win(self.d2()):
return self.vals[self.n - 1][0]
return None
def winner(self):
for w in [self.row_winner(),
self.col_winner(),
self.d1_winner(),
self.d2_winner()]:
if w is not None:
return w
return None
def is_space_available(self):
for row in self.vals:
if SPACE in row:
return True
return False
def __str__(self):
sep = "\n " + "---".join(["+"] * (self.n + 1)) + "\n "
return (
sep +
sep.join(["| " + " | ".join(row) + " |" for row in self.vals]) +
sep)
class Ai(object):
"""
An AI that can find a winning or blocking move. If
neither exist the first available space is used.
"""
def move(self, board, ai, opponent):
win = self.get_winning_move(board, ai)
if win:
return board.move(ai, win[0], win[1])
block = self.get_winning_move(board, opponent)
if block:
return board.move(ai, block[0], block[1])
best_move = self.best_move(board, ai)
return board.move(ai, best_move[0], best_move[1])
def can_win_row(self, board, player):
for i in range(board.n):
row = board.row(i)
if row.count(player) == board.n - 1 and SPACE in set(row):
return (i, row.index(SPACE))
return None
def can_win_col(self, board, player):
for j in range(board.n):
col = board.col(j)
if col.count(player) == board.n - 1 and SPACE in set(col):
return (col.index(SPACE), j)
return None
def can_win_d1(self, board, player):
d1 = board.d1()
if d1.count(player) == board.n - 1 and SPACE in set(d1):
return (d1.index(SPACE), d1.index(SPACE))
return None
def can_win_d2(self, board, player):
d2 = board.d2()
if d2.count(player) == board.n - 1 and SPACE in set(d2):
return (d2.index(SPACE), board.n - 1 - d2.index(SPACE))
return None
def get_winning_move(self, board, player):
# could win row
for win in [self.can_win_row(board, player),
self.can_win_col(board, player),
self.can_win_d1(board, player),
self.can_win_d2(board, player)]:
if win is not None:
return win
return None
def best_move(self, board, player):
# find the first space
for i in range(board.n):
row = board.row(i)
if SPACE in row:
return (i, row.index(SPACE))
def test_board_created_with_correct_board():
board = Board(5)
if len(board.vals) == 5 and len(board.vals[0]) == 5:
print "."
else:
print "FAILED: test_board_created_with_correct_board"
def test_move_made():
board = Board()
if board.move(O, 0, 0) and board.vals[0][0] == O:
print "."
else:
print "FAILED: test_move_made"
def test_move_to_invalid_position_raises_error():
board = Board()
try:
board.move(O, 1000, -1000)
print "FAILED: test_move_to_invalid_position_raises_error"
except ValueError:
print "."
def test_move_to_taken_position_raises_error():
board = Board()
try:
board.move(O, 0, 0)
board.move(X, 0, 0)
print "FAILED: test_move_to_taken_position_raises_error"
except ValueError:
print "."
def test_line_is_a_winner():
board = Board()
if board.line_win([O, O, O]) and board.line_win([X, X, X]):
print "."
else:
print "FAILED: test_line_is_a_winner"
def test_line_is_not_a_winner():
board = Board()
if not board.line_win([SPACE, SPACE, SPACE]):
print "."
else:
print "FAILED: test_line_is_not_a_winner"
def test_row_is_a_winner():
board = Board()
board.move(O, 0, 0)
board.move(O, 0, 1)
board.move(O, 0, 2)
if board.row_winner() == O:
print "."
else:
print "FAILED: test_row_is_a_winner"
def test_row_is_not_a_winner():
board = Board()
if board.row_winner() is None:
print "."
else:
print "FAILED: test_row_is_not_a_winner"
def test_col_is_a_winner():
board = Board()
board.move(O, 0, 0)
board.move(O, 1, 0)
board.move(O, 2, 0)
if board.col_winner() == O:
print "."
else:
print "FAILED: test_row_is_a_winner"
def test_col_is_not_a_winner():
board = Board()
if board.col_winner() is None:
print "."
else:
print "FAILED: test_row_is_not_a_winner"
def test_d1_is_a_winner():
board = Board()
board.move(O, 0, 0)
board.move(O, 1, 1)
board.move(O, 2, 2)
if board.d1_winner():
print "."
else:
print "FAILED: test_d1_is_a_winner"
def test_d1_is_not_a_winner():
board = Board()
board.move(X, 0, 0)
board.move(O, 1, 1)
board.move(O, 2, 2)
if board.d1_winner():
print "FAILED: test_d1_is_not_a_winner"
else:
print "."
def test_d2_is_a_winner():
board = Board()
board.move(O, 0, 2)
board.move(O, 1, 1)
board.move(O, 2, 0)
if board.d2_winner():
print "."
else:
print "FAILED: test_d2_is_a_winner"
def test_d2_is_not_a_winner():
board = Board()
board.move(X, 0, 2)
board.move(O, 1, 1)
board.move(O, 2, 0)
if board.d2_winner():
print "FAILED: test_d2_is_not_a_winner"
else:
print "."
def test_board_can_have_a_winner():
wins = [[(0, 0), (0, 1), (0, 2)],
[(0, 0), (1, 1), (2, 2)],
[(0, 0), (1, 0), (2, 0)],
[(0, 1), (1, 1), (2, 1)],
[(0, 2), (1, 2), (2, 2)],
[(1, 0), (1, 1), (1, 2)],
[(2, 0), (2, 1), (2, 2)],
[(0, 2), (1, 1), (2, 0)]]
for win in wins:
board = Board()
for pos in win:
board.move(O, pos[0], pos[1])
if board.winner() != O:
print "FAILED: test_board_can_have_a_winner %s" % win
return
def test_space_available_at_start():
board = Board()
if board.is_space_available():
print "."
else:
print "FAILED: test_space_available_at_start"
def test_no_space_available():
board = Board()
board.move(O, 0, 0)
board.move(X, 0, 1)
board.move(O, 0, 2)
board.move(O, 1, 0)
board.move(X, 1, 1)
board.move(O, 1, 2)
board.move(X, 2, 0)
board.move(O, 2, 1)
board.move(X, 2, 2)
if board.is_space_available():
print "FAILED: test_no_space_available"
else:
print "."
def test_ai_can_win_row():
board = Board(5)
board.move(X, 0, 0)
board.move(X, 0, 1)
board.move(X, 0, 3)
board.move(X, 0, 4)
ai = Ai()
move = ai.can_win_row(board, X)
if move is not None and move == (0, 2):
print "."
else:
print "FAILED: test_ai_can_win_row"
def test_ai_can_win_col():
board = Board(5)
board.move(X, 0, 2)
board.move(X, 1, 2)
board.move(X, 3, 2)
board.move(X, 4, 2)
ai = Ai()
move = ai.can_win_col(board, X)
if move is not None and move == (2, 2):
print "."
else:
print "FAILED: test_ai_can_win_col"
def test_ai_can_win_d1():
board = Board(5)
board.move(X, 0, 0)
board.move(X, 1, 1)
board.move(X, 3, 3)
board.move(X, 4, 4)
ai = Ai()
move = ai.can_win_d1(board, X)
if move is not None and move == (2, 2):
print "."
else:
print "FAILED: test_ai_can_win_d1"
def test_ai_can_win_d2():
board = Board(5)
board.move(X, 0, 4)
board.move(X, 1, 3)
board.move(X, 2, 2)
board.move(X, 3, 1)
ai = Ai()
move = ai.can_win_d2(board, X)
if move is not None and move == (4, 0):
print "."
else:
print "FAILED: test_ai_can_win_d2"
def test_ai_can_win():
board = Board()
board.move(X, 0, 0)
board.move(X, 1, 1)
ai = Ai()
move = ai.can_win(board, X)
if move is None or move != (2, 2):
print "FAILED: test_ai_can_win"
else:
print "."
class Player(object):
def __init__(self, name, opponent):
self.name = name
self.opponent = opponent
class InputPlayer(Player):
def move(self, board):
while True:
raw_move = raw_input(
"Enter player %s's move as \"row col\" (e.g. 0 1):" %
self.name)
try:
move = map(int, raw_move.split(" "))
except ValueError:
print "Invalid move"
else:
break
board.move(self.name, move[0], move[1])
class AiPlayer(Player):
def move(self, board):
Ai().move(board, self.name, self.opponent)
def turns():
a, b = InputPlayer(O, X), AiPlayer(X, O)
while True:
yield a
a, b = b, a
option = raw_input("Enter 1 to play or anything else to run the tests:")
if option == "1":
board = Board()
turns = turns()
player = turns.next()
print board
while board.winner() is None and board.is_space_available():
try:
player.move(board)
except ValueError as v:
print v
else:
player = turns.next()
print board
print board
print "Winner: %s" % board.winner()
else:
print "Running tests"
test_move_to_invalid_position_raises_error()
test_move_to_taken_position_raises_error()
test_line_is_a_winner()
test_line_is_not_a_winner()
test_row_is_a_winner()
test_row_is_not_a_winner()
test_col_is_a_winner()
test_col_is_not_a_winner()
test_d1_is_a_winner()
test_d1_is_not_a_winner()
test_d2_is_a_winner()
test_d2_is_not_a_winner()
test_move_made()
test_board_can_have_a_winner()
test_space_available_at_start()
test_no_space_available()
test_ai_can_win_row()
test_ai_can_win_col()
test_ai_can_win_d1()
test_ai_can_win_d2()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment