Last active
March 30, 2016 17:31
-
-
Save T-Dimov/261ed1a9e244025d84dc8ad00674b827 to your computer and use it in GitHub Desktop.
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
import unittest | |
from tic_tac_toe import ( | |
Board, IllegalMoveError, WrongTurnError, ChangeMoveError, | |
GameEndError, | |
IN_PROGRESS, X_WON, O_WON, DRAW, X, O, EMPTY as E) | |
class TestBoard(unittest.TestCase): | |
def setUp(self): | |
self.board = Board() | |
self.big_board = Board(5) | |
def test_input_format(self): | |
with self.assertRaises(ValueError): | |
self.board["1A"] = X | |
def test_make_a_move(self): | |
self.board["A1"] = X | |
self.assertEqual(self.board['A1'], X) | |
def test_make_an_invalid_move(self): | |
with self.assertRaises(IndexError): | |
self.board["Щ5"] = X | |
with self.assertRaises(IndexError): | |
self.board["A42"] = O | |
def test_misunderstood_move(self): | |
with self.assertRaises(IndexError): | |
self.board["A12"] = X | |
self.assertEqual(self.board['A1'], E) | |
def test_change_move(self): | |
self.board['A1'] = X | |
with self.assertRaises(ChangeMoveError): | |
self.board['A1'] = O | |
def test_place_illegal_chars(self): | |
with self.assertRaises(ValueError): | |
self.board['A1'] = 'Щ' | |
def test_two_moves_by_the_same_player(self): | |
self.board["A1"] = X | |
with self.assertRaises(WrongTurnError): | |
self.board["A2"] = X | |
def test_game_state_in_progress(self): | |
self.assertEqual(self.board.state, IN_PROGRESS) | |
def test_game_x_won(self): | |
self.board._grid = [ | |
[X, X, X], | |
[O, O, E], | |
[E, E, E], | |
] | |
self.assertEqual(self.board.state, X_WON) | |
self.board._grid = [ | |
[X, O, O], | |
[X, E, E], | |
[X, E, E], | |
] | |
self.assertEqual(self.board.state, X_WON) | |
self.board._grid = [ | |
[X, O, O], | |
[E, X, E], | |
[E, E, X], | |
] | |
self.assertEqual(self.board.state, X_WON) | |
def test_game_o_won(self): | |
self.board._grid = [ | |
[O, O, O], | |
[X, X, E], | |
[E, E, E], | |
] | |
self.assertEqual(self.board.state, O_WON) | |
self.board._grid = [ | |
[O, X, X], | |
[O, E, E], | |
[O, E, E], | |
] | |
self.assertEqual(self.board.state, O_WON) | |
self.board._grid = [ | |
[O, X, X], | |
[E, O, E], | |
[E, E, O], | |
] | |
self.assertEqual(self.board.state, O_WON) | |
def test_draw(self): | |
self.board._grid = [ | |
[O, X, X], | |
[X, X, O], | |
[O, O, X], | |
] | |
self.assertEqual(self.board.state, DRAW) | |
def test_move_on_ended_game(self): | |
self.board._grid = [ | |
[O, X, X], | |
[E, O, E], | |
[E, E, O], | |
] | |
with self.assertRaises(GameEndError): | |
self.board['B1'] = X | |
def test_to_string(self): | |
self.board._grid = [ | |
[O, E, O], | |
[E, X, E], | |
[O, E, O], | |
] | |
expected = """ | |
1 2 3 | |
A O | | O | |
---|---|--- | |
B | X | | |
---|---|--- | |
C O | | O | |
""" | |
self.assertEqual(str(self.board), expected) | |
def test_create_bigger(self): | |
test_board = [ | |
[E, E, E, E, E], | |
[E, E, E, E, E], | |
[E, E, E, E, E], | |
[E, E, E, E, E], | |
[E, E, E, E, E] | |
] | |
self.assertEqual(self.big_board._grid, test_board) | |
def test_import(self): | |
grid = [ | |
[O, E, O], | |
[E, X, E], | |
[O, E, O], | |
] | |
self.board.import_board(grid) | |
self.assertEqual(self.board._grid, grid) | |
def test_export(self): | |
grid = [ | |
[O, E, O], | |
[E, X, E], | |
[O, E, O], | |
] | |
self.board.import_board(grid) | |
self.assertEqual(self.board.export_board(), grid) | |
def test_invalid_board_size(self): | |
with self.assertRaises(ValueError): | |
Board(2) | |
if __name__ == '__main__': | |
unittest.main() |
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
from copy import deepcopy | |
X = 'X' | |
O = 'O' | |
EMPTY = ' ' | |
IN_PROGRESS = 0 | |
X_WON = 1 | |
O_WON = 2 | |
DRAW = 3 | |
class Board: | |
def __init__(self, size=3): | |
self._on_turn = X | |
self.__size = int(size) | |
if self.__size < 3 or self.__size > 9: | |
raise ValueError("Invalid game board size!") | |
self.__valid_fst = set(map(lambda a: chr(a+65), range(self.__size))) | |
self.__valid_snd = set(map(lambda a: a + 1, range(self.__size))) | |
self._grid = [[EMPTY] * self.__size for _ in range(self.__size)] | |
def import_board(self, board): | |
self._grid = board | |
def export_board(self): | |
return self._grid | |
@property | |
def state(self): | |
lines = deepcopy(self._grid) | |
columns = zip(*self._grid) | |
diagonals = ([(i, i) for i in range(self.__size)], | |
[(i, self.__size - i - 1) for i in range(self.__size)]) | |
for diagonal in diagonals: | |
lines.append([self._grid[x][y] for x, y in diagonal]) | |
lines.extend(columns) | |
for line in lines: | |
if set(line) == {X}: | |
return X_WON | |
elif set(line) == {O}: | |
return O_WON | |
if not any(map(lambda line: EMPTY in line, self._grid)): | |
return DRAW | |
return IN_PROGRESS | |
def _convert_to_coords(self, key): | |
if any([len(key) != 2, | |
key[0] not in self.__valid_fst, | |
int(key[1]) not in self.__valid_snd]): | |
raise IndexError("Invalid board coordinates!") | |
return ord(key[0]) - ord('A'), int(key[1]) - 1 | |
def __setitem__(self, key, value): | |
if self.state != IN_PROGRESS: | |
raise GameEndError("Game over!") | |
x, y = self._convert_to_coords(key) | |
if self._grid[x][y] != EMPTY: | |
raise ChangeMoveError("You can't change moves!") | |
if value not in {X, O}: | |
raise ValueError("You must place X or O!") | |
if value != self._on_turn: | |
raise WrongTurnError("{} is on turn!".format(self._on_turn)) | |
self._on_turn = X if self._on_turn == O else O | |
self._grid[x][y] = value | |
def __getitem__(self, key): | |
x, y = self._convert_to_coords(key) | |
return self._grid[x][y] | |
def __str__(self): | |
valid_fst = sorted(self.__valid_fst) | |
valid_snd = map(lambda a: str(a), sorted(self.__valid_snd)) | |
result = "\n {} \n\n".format(" ".join(valid_snd)) | |
ind = 0 | |
for row in self._grid[:-1]: | |
result += "{} {} \n".format(valid_fst[ind], " | ".join(row)) | |
result += " {}---\n".format("---|" * (self.__size - 1)) | |
ind += 1 | |
result += "{} {}\n".format(valid_fst[ind], " | ".join(self._grid[-1])) | |
return result | |
class IllegalMoveError(Exception): | |
pass | |
class WrongTurnError(IllegalMoveError): | |
pass | |
class ChangeMoveError(IllegalMoveError): | |
pass | |
class GameEndError(IllegalMoveError): | |
pass | |
def main(): | |
player = "one" | |
next_mark = X | |
board_size = input("Please enter the size of the board: ") | |
try: | |
game = Board(board_size) | |
except ValueError as error: | |
print("{}\nThe game will now end.".format(error)) | |
else: | |
while not game.state: | |
try: | |
print(game) | |
move_addr = input("Player {} enter your move: ".format(player)) | |
game[str(move_addr)] = next_mark | |
except ValueError: | |
print("Invalid input type or format!\nValid format is: A1") | |
except ChangeMoveError as error: | |
print("{}\nPlease try again.".format(error)) | |
except IndexError as error: | |
print("{}\nPlease try again.".format(error)) | |
else: | |
next_mark = X if next_mark == O else O | |
player = "one" if player == "two" else "two" | |
print(game) | |
if game.state == 1: | |
print("Player one won!") | |
elif game.state == 2: | |
print("Player two won!") | |
else: | |
print("The game ended in a draw!") | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment