Last active
December 19, 2015 15:59
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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