forked from pybites/challenges
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtictactoe-atakume.py
145 lines (115 loc) · 4.1 KB
/
tictactoe-atakume.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
from operator import itemgetter
import os
import random
DEFAULT = '_'
VALID_POSITIONS = list(range(1, 10))
WINNING_COMBINATIONS = (
(7, 8, 9), (4, 5, 6), (1, 2, 3),
(7, 4, 1), (8, 5, 2), (9, 6, 3),
(1, 5, 9), (7, 5, 3),
)
PLAYER_X = "X"
PLAYER_O = "O"
AUTO_PLAYER = PLAYER_O
DRAW_GAME = 20
class TicTacToe:
def __init__(self, mode='multiplayer'):
self.board = [None] + len(VALID_POSITIONS) * [DEFAULT] # skip index 0
self.turns = 0
self.player = PLAYER_X
self.mode = mode
def __str__(self):
os.system('clear')
result = ""
for i in range(1, len(self.board), 3):
result += " ".join(self.board[i: i + 3]) + "\n"
return result
def is_win(self):
return self.player if TicTacToe.player_wins(self.player, self.board) else None
def is_end(self):
return TicTacToe.is_draw(self.board)
def take_turn(self):
if self.mode == 's' and self.player == AUTO_PLAYER:
move = self.compute_move()
else:
move = input("Take turn: ")
while not self.proper_move(move):
move = input("Wrong move try again: ")
self.make_move(move)
def proper_move(self, move):
return move.isdigit() and int(move) in TicTacToe.available_steps(self.board)
def make_move(self, move):
self.board[int(move)] = self.player
self.turns += 1
def compute_move(self):
return next(TicTacToe.available_steps(self.board), None)
def next_turn(self):
self.player = PLAYER_O if self.player == PLAYER_X else PLAYER_X
@staticmethod
def available_steps(board):
return [i for i in VALID_POSITIONS if board[i] == DEFAULT]
@staticmethod
def player_wins(player, board):
return any(all(player == i for i in itemgetter(a, b, c)(board)) for a, b, c in WINNING_COMBINATIONS)
@staticmethod
def is_draw(board):
return len(TicTacToe.available_steps(board)) == 0
class RandomAITicTacToe(TicTacToe):
def compute_move(self):
return random.choice(TicTacToe.available_steps(self.board))
class MinMaxAITicTacToe(TicTacToe):
def __init__(self, mode='multiplayer'):
super(MinMaxAITicTacToe, self).__init__(mode)
self.choice = 0
def compute_move(self):
self.minmax(self.board, 0)
return self.choice
def minmax(self, board, depth):
self.choice
depth += 1
scores = []
steps = []
def update_state(board, step, depth):
board = list(board)
board[step] = PLAYER_X if depth % 2 else PLAYER_O
return board
def unit_score(winner, depth):
if winner == DRAW_GAME:
return 0
else:
return 10 - depth if winner == PLAYER_X else depth - 10
def check_win_game(board):
for player in [PLAYER_X, PLAYER_O]:
if TicTacToe.player_wins(player, board):
return player
return DRAW_GAME if TicTacToe.is_draw(board) else 10
result = check_win_game(board)
if result != 10:
return unit_score(result, depth)
for step in TicTacToe.available_steps(board):
score = self.minmax(update_state(board, step, depth), depth)
scores.append(score)
steps.append(step)
if depth % 2 == 1:
max_value_index = scores.index(max(scores))
self.choice = steps[max_value_index]
return max(scores)
else:
min_value_index = scores.index(min(scores))
self.choice = steps[min_value_index]
return min(scores)
if __name__ == "__main__":
another = "y"
while True:
player_mode = input("For singleplayer mode type 's': ")
game = MinMaxAITicTacToe(player_mode)
print(game)
while not game.is_end():
game.take_turn()
print(game)
if game.is_win():
break
game.next_turn()
another = input("Game over. Play once again?")
if another.lower() not in ["y", "yes"]:
break