Skip to content

Instantly share code, notes, and snippets.

@cyvax
Created March 21, 2021 19:10
Show Gist options
  • Save cyvax/467a36eea73ea6ea6f266594c58af5eb to your computer and use it in GitHub Desktop.
Save cyvax/467a36eea73ea6ea6f266594c58af5eb to your computer and use it in GitHub Desktop.
tictactoe_ai.py
# 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