-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMiniMaxTicTacToe.py
257 lines (215 loc) · 6.31 KB
/
MiniMaxTicTacToe.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
#!/usr/bin/env python3
# -----imports-----
import time
import platform
from os import system
from random import choice
# -----notes-----
# the terminal output window is 13x7
#
#
# -----global variables-----
HUMAN = -1
AI = +1
board = [
[0,0,0],
[0,0,0],
[0,0,0],
]
# -----TicTacToe Functions-----
# takes current board state and displays it
def render(state, a_choice, p_choice):
chars = { #char dict
-1: p_choice,
+1: a_choice,
0: ''
}
str_line = '------------'
print('\n' + str_line)
for row in state:
for cell in row:
symbol = chars[cell]
print(f'| {symbol} |', end='')
print('\n' + str_line)
# clears the screen
def clean():
os_name = platform.system().lower()
if 'windows' in os_name:
system('cls')
else:
system('clear')
# creates a list of empty cells??
def empty_cells(state):
cells = []
for x, row in enumerate(state):
for y, cell in enumerate(row):
if cell == 0:
cells.append([x,y])
return cells
# using our empty cell list, checks to see if x,y is in
def check_if_valid(x,y):
if [x, y] in empty_cells(board):
return True
else:
return False
# checks validity and then applies to board
def make_move(x, y, human):
if check_if_valid(x, y):
board[x][y] = human
return True
else:
return False
# takes in the current state + player variable and checks all win configurations for player value
# RETURN: BOOL
def wins(state, player):
win_state = [
[state[0][0], state[0][1], state[0][2]], #top straight
[state[1][0], state[1][1], state[1][2]], #middle straight
[state[2][0], state[2][1], state[2][2]], #bottom straight
[state[0][0], state[1][0], state[2][0]], #left vertical
[state[0][1], state[1][1], state[2][1]], #middle vertical
[state[0][2], state[1][2], state[2][2]], #right vertical
[state[0][0], state[1][1], state[2][2]], #l-r diagonal
[state[2][0], state[1][1], state[0][2]], #r-l diagonal
]
if [player, player, player] in win_state:
return True
else:
return False
# ensures the game has not been won
# RETURN: BOOL
def game_ends(state):
depth = len(empty_cells(board))
return wins(state,HUMAN) or wins(state, AI) or depth==0
# player turn code
# *look inside for further info*
#question does triggering not can_move will cause AI to take turn after
def player_turn(a_choice, p_choice):
# determine depth of game tree from board state
depth = len(empty_cells(board))
if depth == 0 or game_ends(board):
return
#default the moves value and provide a dict of numpad -> coords
move = -1
moves = {
1: [0, 0],
2: [0, 1],
3: [0, 2],
4: [1, 0],
5: [1, 1],
6: [1, 2],
7: [2, 0],
8: [2, 1],
9: [2, 2],
}
#Player input
clean()
print(f'Human turn [{p_choice}]')
render(board, a_choice, p_choice)
while move < 1 or move > 9:
try: #error checking
move = int(input('Use numpad (1..9): ')) #ask for input
coord = moves[move]
can_move = make_move(coord[0], coord[1], HUMAN)
if not can_move: #check if valid move
print('Bad move')
move = -1
#error output
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')
# -----AI functions-----
# computer turn code
# *look inside for further info*
def comp_turn(a_choice, p_choice):
#check depth of game tree
depth = len(empty_cells(board))
if depth == 0 or game_ends(board):
return
clean()
print(f'Computer turn [{a_choice}]')
render(board, a_choice, p_choice)
# random start
if depth == 9:
x = choice([0, 1, 2])
y = choice([0, 1, 2])
else: # subsequent turns will be determined with minimax
time_minimax = 0
minimax_start = time.time()
move = minimax(board, depth, AI)
x, y = move[0], move[1]
print('Minimax time:')
print(time.time() - minimax_start)
make_move(x, y, AI)
print()
time.sleep(1)
# returns scoring for the current board
# returns +1, -1, 0
def evaluate(state):
if wins(state, AI):
score = +1
elif wins(state, HUMAN):
score = -1
else:
score = 0
return score
# minimax code
# minimax is a decision rule for:
# minimizing the loss for a maximum loss scenario
# or
# maximizing the gain for a minimum gain scenario
def minimax(state, depth, player):
#best move by default is an invalid move because it is TBD
if player == AI: #if player is MAX
best = [-1, -1, -99]
else:
best = [-1, -1, +99]
#check if game has been won
if depth == 0 or game_ends(state):
score = evaluate(state)
return [-1, -1, score]
for cell in empty_cells(state):
# get coords for next available space
x, y = cell[0], cell[1]
# insert player to the grid
state[x][y] = player
# get the state to play, next turn, and alternate min/max player
score = minimax(state, depth -1 , -player)
#reset board position
state[x][y] = 0
#save result of minimax
score[0], score[1] = x, y
if player == AI:
if score[2] > best[2]:
best = score # highest value
else:
if score[2] < best[2]:
best = score # min value
return best
# -----driver code-----
def main():
a_choice = 'X'
p_choice = 'O'
#gameloop
test_time = 0
while(not game_ends(board)):
#game loop outline:
# 1)render the current board
clean()
print('Computer turn total time:')
print(test_time)
render(board, a_choice, p_choice)
# 2)get player input
player_turn(a_choice,p_choice)
# 3)get AI input
start = time.time()
comp_turn(a_choice,p_choice)
end = time.time()
test_time = end - start
# 4)repeat until win/draw/lose
# render final board
render(board,a_choice,p_choice)
if __name__ == "__main__":
main()