-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_TicTacToe.py
338 lines (288 loc) · 13.7 KB
/
test_TicTacToe.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
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
"""Contains tests for the TicTacToe.py module.
- test_returns_correct_game_name: Checks that the name of the game is returned as expected.
- test_empty_board: Tests that empty board initializes as expected.
- test_checkValidMove_correctly_evaluates_valid_moves: Tests that the checkValidMove function correctly identifies
empty spaces as valid moves, and filled spaces as invalid moves.
- test_updateBoard_updates_board_correctly: Tests that updateBoard function correctly assigns player icons to spaces.
- test_checkBoard_correctly_identifies_all_endgame_scenarios: Tests that the checkBoard function correctly
identifies win, draw, and GAME_IN_PROGRESS states.
- test_bot_takes_wins: Tests that the bot will take wins when possible.
- test_bot_blocks_wins: Tests that the bot will block opponent wins when possible.
- test_resetGame: Tests that the resetGame function properly resets the game.
- test_updatePlayerIcons_assigns_icons: Tests that the updatePlayerIcons function correctly assigns selected icons.
"""
import TicTacToe
import pytest
from string import printable as printable_chars
@pytest.fixture
def tic_tac_toe():
"""PyTest Fixture allows for easy initialization of class object in each test.
:return: clean TicTacToe object to be used in each test.
"""
return TicTacToe.TicTacTerminal()
def test_returns_correct_game_name(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Checks that the name of the game is returned as expected.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
assert tic_tac_toe.gameName() == "Tic-Tac-Toe"
def test_empty_board(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that empty board initializes as expected.
:param tic_tac_toe: :param tic_tac_toe: the TicTacToe object to be used in the test
:return:
"""
assert tic_tac_toe.emptyBoard() == [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
def test_checkValidMove_correctly_evaluates_valid_moves(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that the checkValidMove function correctly identifies empty spaces as valid moves,
and filled spaces as invalid moves.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Initialize an empty board for the test
tic_tac_toe.board = [
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS]
]
# check that each space is a valid move
for i in range(0, 3):
for j in range(0, 3):
assert tic_tac_toe.checkValidMove(i, j) is True
# Initialize a full board of PLAYER_0 for the test
tic_tac_toe.board = [
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0],
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0],
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0]
]
# check that each space is not a valid move
for i in range(0, 3):
for j in range(0, 3):
assert tic_tac_toe.checkValidMove(i, j) is False
# Initialize a full board of PLAYER_1 for the test
tic_tac_toe.board = [
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1],
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1],
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1]
]
# check that each space is not a valid move
for i in range(0, 3):
for j in range(0, 3):
assert tic_tac_toe.checkValidMove(i, j) is False
def test_updateBoard_updates_board_correctly(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that updateBoard function correctly assigns player icons to spaces.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Initialize an empty board for the test
tic_tac_toe.board = [
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS]
]
# Model board is manually updated in the test to make comparisons for expected results
model_board = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
# Check each row and column
for i in range(0, 3):
for j in range(0, 3):
# Update model board manually to check results
model_board[i][j] = tic_tac_toe.PLAYER_0
# Update using tested function
tic_tac_toe.updateBoard(i, j, tic_tac_toe.PLAYER_0)
assert tic_tac_toe.board == model_board
def test_checkBoard_correctly_identifies_all_endgame_scenarios(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that the checkBoard function correctly identifies win, draw, and GAME_IN_PROGRESS states.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Win scenarios to be tested
WIN_OPTIONS = [
[(0, 0), (0, 1), (0, 2)],
[(1, 0), (1, 1), (1, 2)],
[(2, 0), (2, 1), (2, 2)],
[(0, 0), (1, 0), (2, 0)],
[(0, 1), (1, 1), (2, 1)],
[(0, 2), (1, 2), (2, 2)],
[(0, 0), (1, 1), (2, 2)],
[(0, 2), (1, 1), (2, 0)]
]
# Test for each player icon
game_states_to_check = (
(tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0_WINNER),
(tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1_WINNER)
)
# Test each icon in each win scenario
for icon, expected_winner in game_states_to_check:
for scenario in WIN_OPTIONS:
tic_tac_toe.board = [
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS]
]
for space in scenario:
tic_tac_toe.board[space[0]][space[1]] = icon
tic_tac_toe.checkBoard()
assert tic_tac_toe.game_state == expected_winner
# Test a draw game state
tic_tac_toe.board = [
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_1],
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_0],
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_1]
]
tic_tac_toe.checkBoard()
assert tic_tac_toe.game_state == tic_tac_toe.DRAW_GAME
# Test a GAME_IN_PROGRESS state
tic_tac_toe.board = tic_tac_toe.board = [
[tic_tac_toe.PLAYER_0, tic_tac_toe.BLANK_POS, tic_tac_toe.PLAYER_1],
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_0],
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_1]
]
tic_tac_toe.checkBoard()
assert tic_tac_toe.game_state == tic_tac_toe.GAME_IN_PROGRESS
def test_bot_takes_wins(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that the bot will take wins when possible.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Scenarios to be checked
WIN_OPTIONS = [
[(0, 0), (0, 1), (0, 2)],
[(1, 0), (1, 1), (1, 2)],
[(2, 0), (2, 1), (2, 2)],
[(0, 0), (1, 0), (2, 0)],
[(0, 1), (1, 1), (2, 1)],
[(0, 2), (1, 2), (2, 2)],
[(0, 0), (1, 1), (2, 2)],
[(0, 2), (1, 1), (2, 0)]
]
# For each scenario, reset board and test
for scenario in WIN_OPTIONS:
tic_tac_toe.board = [
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS]
]
# Set up a win scenario for the bot and let it move once
for space in (scenario[0], scenario[1]):
tic_tac_toe.board[space[0]][space[1]] = tic_tac_toe.PLAYER_1
bot_move = tic_tac_toe.botMove(tic_tac_toe.PLAYER_1)
tic_tac_toe.updateBoard(bot_move[0], bot_move[1], tic_tac_toe.PLAYER_1)
assert tic_tac_toe.board[scenario[2][0]][scenario[2][1]] == tic_tac_toe.PLAYER_1
# Give the bot some more complex scenarios -- block opponent or win? (Should choose win)
# Check a scenario involving rows
tic_tac_toe.board = [
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.BLANK_POS],
[tic_tac_toe.PLAYER_0, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.BLANK_POS]
]
bot_move = tic_tac_toe.botMove(tic_tac_toe.PLAYER_1)
tic_tac_toe.updateBoard(bot_move[0], bot_move[1], tic_tac_toe.PLAYER_1)
tic_tac_toe.checkBoard()
assert tic_tac_toe.game_state == tic_tac_toe.PLAYER_1_WINNER
# Check a scenario involving columns
tic_tac_toe.board = [
[tic_tac_toe.PLAYER_0, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.PLAYER_0, tic_tac_toe.BLANK_POS, tic_tac_toe.PLAYER_1],
[tic_tac_toe.BLANK_POS, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_1]
]
bot_move = tic_tac_toe.botMove(tic_tac_toe.PLAYER_1)
tic_tac_toe.updateBoard(bot_move[0], bot_move[1], tic_tac_toe.PLAYER_1)
tic_tac_toe.checkBoard()
assert tic_tac_toe.game_state == tic_tac_toe.PLAYER_1_WINNER
def test_bot_blocks_wins(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that the bot will block opponent wins when possible.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Scenarios to be checked
WIN_OPTIONS = [
[(0, 0), (0, 1), (0, 2)],
[(1, 0), (1, 1), (1, 2)],
[(2, 0), (2, 1), (2, 2)],
[(0, 0), (1, 0), (2, 0)],
[(0, 1), (1, 1), (2, 1)],
[(0, 2), (1, 2), (2, 2)],
[(0, 0), (1, 1), (2, 2)],
[(0, 2), (1, 1), (2, 0)]
]
# For each scenario, reset the board and test
for scenario in WIN_OPTIONS:
tic_tac_toe.board = [
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS],
[tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS, tic_tac_toe.BLANK_POS]
]
# Set up a win scenario for the opponent and let the bot move once
for space in (scenario[0], scenario[1]):
tic_tac_toe.board[space[0]][space[1]] = tic_tac_toe.PLAYER_0
bot_move = tic_tac_toe.botMove(tic_tac_toe.PLAYER_1)
tic_tac_toe.updateBoard(bot_move[0], bot_move[1], tic_tac_toe.PLAYER_1)
assert tic_tac_toe.board[scenario[2][0]][scenario[2][1]] == tic_tac_toe.PLAYER_1
def test_resetGame(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that the resetGame function properly resets the game.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Initialize a filled board and game state
tic_tac_toe.board = [
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_1],
[tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_1, tic_tac_toe.PLAYER_0],
[tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_0, tic_tac_toe.PLAYER_1]
]
tic_tac_toe.game_state = tic_tac_toe.PLAYER_0_WINNER
# Run tested function
tic_tac_toe.resetGame()
# Test board and game state
assert tic_tac_toe.emptyBoard() == [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
assert tic_tac_toe.game_state == tic_tac_toe.GAME_IN_PROGRESS
def test_updatePlayerIcons_assigns_icons(tic_tac_toe: TicTacToe.TicTacTerminal):
"""Tests that the updatePlayerIcons function correctly assigns selected icons.
Yup, we really be testing everything here.
:param tic_tac_toe: the TicTacToe object to be used in the test
"""
# Iterate through all printable characters, two at a time
for i in range(0, len(printable_chars), 2):
# Assign odd indexed characters to j, evens are assigned to i
j = i + 1
icon1 = printable_chars[i]
icon2 = printable_chars[j]
# Run the tested function and check result
tic_tac_toe.updatePlayerIcons(icon1, icon2)
assert tic_tac_toe.PLAYER_0_ICON == icon1
assert tic_tac_toe.PLAYER_1_ICON == icon2
def test_bot_avoids_double_middle_trap(tic_tac_toe):
# cases that can trap the bot into a loss,
# in the format (move1, move2, move3, move_to_avoid)
scenarios_to_check = [
[(0, 1), (1, 1), (1, 0), (2, 2)],
[(0, 1), (2, 2), (1, 0), (1, 1)],
[(0, 1), (1, 1), (1, 2), (2, 0)],
[(0, 1), (2, 0), (1, 2), (1, 1)],
[(2, 1), (1, 1), (1, 2), (0, 0)],
[(2, 1), (0, 0), (1, 2), (1, 1)],
[(2, 1), (1, 1), (1, 0), (0, 2)],
[(2, 1), (0, 2), (1, 0), (1, 1)]
]
# Run several times to account for randomness in bot decisions
for _ in range(0, 10):
for scenario in scenarios_to_check:
# Initialize empty board
tic_tac_toe.board = tic_tac_toe.emptyBoard()
# simulate the scenario
tic_tac_toe.updateBoard(scenario[0][0], scenario[0][1], tic_tac_toe.PLAYER_0)
tic_tac_toe.updateBoard(scenario[1][0], scenario[1][1], tic_tac_toe.PLAYER_1)
tic_tac_toe.updateBoard(scenario[2][0], scenario[2][1], tic_tac_toe.PLAYER_0)
# let the bot move once
move = tic_tac_toe.botMove(tic_tac_toe.PLAYER_1)
# move should NOT be the center
assert move != (1, 1)
# move SHOULD be a corner (besides the trap corner, see next assert)
assert (move[0] + move[1]) % 2 == 0
tic_tac_toe.updateBoard(move[0], move[1], tic_tac_toe.PLAYER_1)
# check that the bot successfully avoided the trap
assert tic_tac_toe.board[scenario[3][0]][scenario[3][1]] != tic_tac_toe.PLAYER_1
tic_tac_toe.move_history = []