-
-
Save cyvax/467a36eea73ea6ea6f266594c58af5eb to your computer and use it in GitHub Desktop.
tictactoe_ai.py
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
# write your code here | |
import random | |
from math import inf | |
class Move: | |
def __init__(self): | |
self._index = None | |
self._score = None | |
@property | |
def index(self): | |
return self._index | |
@property | |
def score(self): | |
return self._score | |
@index.setter | |
def index(self, value): | |
self._index = value | |
@score.setter | |
def score(self, value): | |
self._score = value | |
class Minimax: | |
def __init__(self): | |
self.player = "X" | |
self.ia = "O" | |
self.iter = 0 | |
@staticmethod | |
def available(board): | |
return [i for i, _ in enumerate(board) if _ == " "] | |
@staticmethod | |
def winning(board, player): | |
if any([(board[0] == player and board[1] == player and board[2] == player) or | |
(board[3] == player and board[4] == player and board[5] == player) or | |
(board[6] == player and board[7] == player and board[8] == player) or | |
(board[0] == player and board[3] == player and board[6] == player) or | |
(board[1] == player and board[4] == player and board[7] == player) or | |
(board[2] == player and board[5] == player and board[8] == player) or | |
(board[0] == player and board[4] == player and board[8] == player) or | |
(board[2] == player and board[4] == player and board[6] == player)]): | |
return True | |
else: | |
return False | |
def minimax(self, board, player): | |
available = self.available(board) | |
if self.winning(board, "X"): | |
return -10 | |
elif self.winning(board, "O"): | |
return 10 | |
elif len(available) == 0: | |
return 0 | |
moves = [] | |
for i in range(len(available)): | |
move = Move() | |
move.index = available[i] | |
board[available[i]] = player | |
if player == self.ia: | |
g = self.minimax(board, self.player) | |
else: | |
g = self.minimax(board, self.ia) | |
move.score = g if type(g) == int else g.score | |
board[available[i]] = move.index | |
moves.append(move) | |
best_move = None | |
if player == self.ia: | |
best_score = -inf | |
for i in range(len(moves)): | |
if moves[i].score > best_score: | |
best_score = moves[i].score | |
best_move = i | |
else: | |
best_score = +inf | |
for i in range(len(moves)): | |
if moves[i].score < best_score: | |
best_score = moves[i].score | |
best_move = i | |
return moves[best_move] | |
class TicTacToe: | |
def __init__(self): | |
self.raw_cells = " " | |
self.char = {0: "X", 1: "O"} | |
self.players = {} | |
self.turn = 0 | |
self.cells = [self.raw_cells[i:i + 3] for i in range(0, len(self.raw_cells), 3)] | |
self.mini_cells = list(self.raw_cells) | |
self.vertical = [[self.cells[i][r] for i in range(3)] for r in range(3)] | |
self.cells_dict = {k: v for k, *v in zip([3, 2, 1], *self.vertical)} | |
self.bot_cells = {0: 3, 1: 2, 2: 1} | |
self.bot_diag_cells = {3: 1, 2: 2, 1: 3} | |
self.numbers = [0, 1, 2], [2, 1, 0] | |
self.minimax = Minimax().minimax | |
for _ in self.cells_dict: | |
self.cells_dict[_] = dict(zip(range(1, len(self.cells_dict[_]) + 1), self.cells_dict[_])) | |
def update(self, data, bot=False): | |
if bot: | |
self.raw_cells = data | |
else: | |
self.raw_cells = ''.join([data[i][x] for i in data for x in data[i]]) | |
self.cells = [self.raw_cells[i:i + 3] for i in range(0, len(self.raw_cells), 3)] | |
self.vertical = [[self.cells[i][r] for i in range(3)] for r in range(3)] | |
self.cells_dict = {k: v for k, *v in zip([3, 2, 1], *self.vertical)} | |
self.mini_cells = list(self.raw_cells) | |
for _ in self.cells_dict: | |
self.cells_dict[_] = dict(zip(range(1, len(self.cells_dict[_]) + 1), self.cells_dict[_])) | |
@staticmethod | |
def check(data): | |
check = [list(x)[0] for x in [set(data[i]) for i in range(3)] if len(x) == 1 and list(x)[0] != " "] | |
if len(check) == 0: | |
return False | |
if len(check) == 1: | |
return check[0] | |
return False | |
def check_horizontal(self): | |
return self.check(self.cells) | |
def check_vertical(self): | |
return self.check(self.vertical) | |
def check_diagonal(self): | |
diagonal_cells = [[self.cells[i][i] for i in self.numbers[0]], | |
[self.cells[i][self.numbers[1][i]] for i in self.numbers[0]]] | |
check = [set(diagonal_cells[i]) for i in range(2)] | |
if any(True if i == {' '} else False for i in list(check)): | |
return False | |
if any([True if len(set(diagonal_cells[i])) == 1 else False for i in range(2)]): | |
return [r.pop() for r in check if len(r) == 1] | |
return False | |
def check_victory(self): | |
hor_check = self.check_horizontal() | |
if hor_check and len(hor_check) == 1 and hor_check[0] != "_": | |
return f"{hor_check} wins" | |
ver_check = self.check_vertical() | |
if ver_check: | |
return f"{ver_check} wins" | |
dia_check = self.check_diagonal() | |
if dia_check and len(dia_check) == 1 and dia_check[0] != "_": | |
return f"{dia_check[0]} wins" | |
if all([set(self.raw_cells) == {'X', 'O'}, | |
abs(self.raw_cells.count("X") - self.raw_cells.count("O")) == 1]): | |
return "Draw" | |
if any([len(hor_check) > 1 if type(hor_check) is not bool else False, | |
len(ver_check) > 1 if type(ver_check) is not bool else False, | |
len(dia_check) > 1 if type(dia_check) is not bool else False]): | |
return "Impossible" | |
if abs(self.raw_cells.count("X") - self.raw_cells.count("O")) > 1: | |
return "Impossible" | |
return False | |
def show_table(self): | |
print("---------\n" | |
"| {0} {1} {2} |\n" | |
"| {3} {4} {5} |\n" | |
"| {6} {7} {8} |\n" | |
"---------".format(*self.raw_cells)) | |
def ask_player(self): | |
raw_coordinates = input("Enter the coordinates: ").split(" ") | |
if len(raw_coordinates) < 2 or not raw_coordinates[0].isdigit() and not raw_coordinates[1].isdigit(): | |
print("You should enter numbers!") | |
return self.ask_player() | |
if not all([1 <= int(raw_coordinates[0]) <= 3, 1 <= int(raw_coordinates[1]) <= 3]): | |
print("Coordinates should be from 1 to 3!") | |
return self.ask_player() | |
coordinates = [int(raw_coordinates[0]), int(raw_coordinates[1])] | |
self.add_entry(*coordinates) | |
def add_entry(self, x, y): | |
if self.cells_dict[y][x] != " ": | |
print("This cell is occupied! Choose another one!") | |
return self.ask_player() | |
self.cells_dict[y][x] = self.char[self.turn] | |
self.turn = int(not self.turn) | |
self.update(self.cells_dict) | |
def bot_easy_play(self): | |
bot_x = random.choice([i for i, x in enumerate(self.raw_cells) if x == " "]) | |
data = list(self.raw_cells) | |
data[bot_x] = self.char[self.turn] | |
self.turn = int(not self.turn) | |
self.update(data, True) | |
def bot_medium_play(self): | |
horizontal = [x for x in range(len(self.cells)) if " " in set(self.cells[x]) and | |
(self.cells[x].count("X") == 2 or self.cells[x].count("O") == 2)] | |
verticals_cells = [[self.vertical[i][0], self.vertical[i][1], self.vertical[i][2]] for i in range(3)] | |
vertical = [x for x in range(len(verticals_cells)) if " " in set(verticals_cells[x]) and | |
(verticals_cells[x].count("X") == 2 or verticals_cells[x].count("O") == 2)] | |
diagonal_cells = ["".join([self.cells[i][i] for i in self.numbers[0]]), | |
"".join([self.cells[i][self.numbers[1][i]] for i in self.numbers[0]])] | |
diagonal = [x for x in range(len(diagonal_cells)) if " " in set(diagonal_cells[x]) and | |
(diagonal_cells[x].count("X") == 2 or diagonal_cells[x].count("O") == 2)] | |
if horizontal: | |
x = self.bot_cells[horizontal[0]] | |
y = list(self.cells_dict[x].values()).index(" ") + 1 | |
self.cells_dict[x][y] = self.char[self.turn] | |
self.turn = int(not self.turn) | |
self.update(self.cells_dict) | |
elif vertical: | |
cell = ["".join([self.vertical[i][0], self.vertical[i][1], self.vertical[i][2]]) for i in range(3)] | |
cells = [[self.vertical[i][0], self.vertical[i][1], self.vertical[i][2]] for i in range(3)] | |
x = vertical[0] | |
if len(vertical) > 1: | |
for i in vertical: | |
if self.char[self.turn] in list(set(cell[i])): | |
x = i | |
y = cell[x].index(" ") | |
cells[x][y] = self.char[self.turn] | |
cells = "".join(["".join([cells[i][r] for i in range(3)]) for r in range(3)]) | |
self.turn = int(not self.turn) | |
self.update(cells, True) | |
elif diagonal: | |
y = diagonal_cells[diagonal[0]].index(" ") + 1 | |
x = self.bot_diag_cells[y] | |
if diagonal[0] == 0: | |
self.cells_dict[x][y] = self.char[self.turn] | |
self.turn = int(not self.turn) | |
self.update(self.cells_dict) | |
elif diagonal[0] == 1: | |
y = self.bot_diag_cells[y] | |
self.cells_dict[x][y] = self.char[self.turn] | |
self.turn = int(not self.turn) | |
self.update(self.cells_dict) | |
else: | |
self.bot_easy_play() | |
def bot_hard_play(self): | |
index = self.minimax(self.mini_cells, self.char[self.turn]).index | |
self.mini_cells = list(self.raw_cells) | |
self.mini_cells[index] = self.char[self.turn] | |
self.turn = int(not self.turn) | |
self.update(''.join(self.mini_cells), True) | |
def game(self): | |
self.update(list(" " * 9), True) | |
self.turn = 0 | |
while True: | |
self.show_table() | |
victory = self.check_victory() | |
if victory: | |
print(victory) | |
break | |
if self.players["player1"] == "user": | |
self.ask_player() | |
elif self.players["player1"] == "easy": | |
print('Making move level "easy"') | |
self.bot_easy_play() | |
elif self.players["player1"] == "medium": | |
print('Making move level "medium"') | |
self.bot_medium_play() | |
elif self.players["player1"] == "hard": | |
print('Making move level "hard"') | |
self.bot_hard_play() | |
self.show_table() | |
victory = self.check_victory() | |
if victory: | |
print(victory) | |
break | |
if self.players["player2"] == "user": | |
self.ask_player() | |
elif self.players["player2"] == "easy": | |
print('Making move level "easy"') | |
self.bot_easy_play() | |
elif self.players["player2"] == "medium": | |
print('Making move level "medium"') | |
self.bot_medium_play() | |
elif self.players["player2"] == "hard": | |
print('Making move level "hard"') | |
self.bot_hard_play() | |
def run(self): | |
while True: | |
choice = input("Input command (help): ").split(" ") | |
if len(choice) < 3: | |
if choice[0] == "exit": | |
exit(0) | |
elif choice[0] == "help": | |
print("help - bring this panel") | |
print("exit - close the game") | |
print("start {player1} {player2} - start a game with player1 or player2") | |
print("start {player1} {player2} - start a game with player1 or player2") | |
print("You can choose between several different players :") | |
print("user -- a human player") | |
print("easy -- a easy bot") | |
print("medium -- a medium bot") | |
print("hard -- a hard bot") | |
elif choice[0] == "start": | |
if choice[1] not in ["user", "easy", "medium", "hard"] or \ | |
choice[2] not in ["user", "easy", "medium", "hard"]: | |
print("ERROR: Unknown player type, please refer to `help`") | |
else: | |
self.players["player1"] = choice[1] | |
self.players["player2"] = choice[2] | |
self.game() | |
else: | |
print("Bad parameters!") | |
TicTacToe().run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment