-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtictactoe_v5_immutable.py
129 lines (103 loc) · 3.62 KB
/
tictactoe_v5_immutable.py
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
import sys
from collections import Counter, namedtuple
from enum import Enum
from unittest import TestCase
class Player(Enum):
X = "X"
O = "O"
NA = " "
# REPLACE-START
def replace(tpl, idx, value):
return tpl[:idx] + (value, ) + tpl[idx+1:]
# REPLACE-END
class BoardState(namedtuple('_Board', ['board'])):
@property
def player(self):
plays = Counter(sum(self.board, ()))
if plays[Player.O] < plays[Player.X]:
return Player.O
else:
return Player.X
def __str__(self):
return "--+---+--\n".join(
" | ".join(play.value for play in row) + "\n"
for row in self.board
)
# ACTION-START
def do_move(self, x, y):
if self.board[x][y] == Player.NA:
new_row = replace(self.board[x], y, self.player)
new_board = replace(self.board, x, new_row)
return BoardState(new_board)
else:
return self
# ACTION-END
@property
def is_finished(self):
for row in self.board:
if row[0] != Player.NA and row[0] == row[1] == row[2]:
return True
for column in range(3):
if self.board[0][column] != Player.NA and self.board[0][column] == self.board[1][column] == self.board[2][column]:
return True
if self.board[0][0] != Player.NA and self.board[0][0] == self.board[1][1] == self.board[2][2]:
return True
if self.board[2][0] != Player.NA and self.board[2][0] == self.board[1][1] == self.board[0][2]:
return True
return False
def __sub__(self, other):
diff = set()
for x in range(3):
for y in range(3):
if self.board[x][y] != other.board[x][y]:
diff.add((x, y, self.board[x][y]))
return diff
BoardState.__new__.__defaults__ = (((Player.NA,)*3,)*3,)
class TestTicTacToe(TestCase):
# TEST-START
def test_moves_made(self):
# Store the state of the board before a move
before = BoardState()
# Store the state of the board after the move
after = before.do_move(0, 0)
# Compare the state before and after
self.assertEqual(after - before, {(0, 0, Player.X)})
self.assertEqual(before - after, {(0, 0, Player.NA)})
# TEST-END
def test_basic_play(self):
initial = BoardState()
all_moves = [(x, y) for x in range(3) for y in range(3)]
for (x0, y0) in all_moves:
with self.subTest(x0=x0, y0=y0):
after_first = initial.do_move(x0, y0)
self.assertEqual(
after_first - initial,
{(x0, y0, Player.X)}
)
# TEST-2-START
for (x1, y1) in all_moves:
with self.subTest(x1=x1, y1=y1):
after_second = after_first.do_move(x1, y1)
if x1 == x0 and y1 == y0:
self.assertEqual(after_second - after_first, set())
else:
self.assertEqual(
after_second - after_first,
{(x1, y1, Player.O)}
)
# TEST-2-END
# LOOP-START
def main():
state = BoardState()
while not state.is_finished:
print(state)
move = input(f"Player {state.player.value} (x y)? ")
x, y = move.split()
x = int(x)
y = int(y)
state = state.do_move(x, y)
# LOOP-END
print("Game Over!")
print(state)
if __name__ == "__main__":
sys.exit(main())