I want to change this 2-player game into a game against computer. I tried to implement this minimax algorithm that's in the first class in my code but I can't make it work. I tried a couple of options but I can't handle it.
I would appreciate some help!
Here's the old code version without my silly tries (XD) and class Robot with the minimax function that I'm trying to implement and adapt in the beginning of the code:
import tkinter as tk
#TRYING TO IMPLEMENT CLASS ROBOT
#START
class Robot:
def __init__(self, cube):
self.boards = cube.boards
def get_scores(self, board):
"""Get scores for all open squares on a board"""
def minimax(mark, square, alpha=-1000, beta=1000, depth=0):
"""minimax algorithm with alpha/beta pruning"""
# Place the mark and check for a win
square.mark = mark
if square.check_win():
# Give extra weight to earlier wins/losses
score = 10 - depth if mark is ROBOT else depth - 10
board.depth = min(board.depth, depth)
elif len(empty_squares := board.empty_squares()) == 0:
# No plays left. Draw.
score = 0
elif mark is PLAYER:
# Pick best move for robot
score = -1000
for s in empty_squares:
score = max(score, minimax(ROBOT, s, alpha, beta, depth+1))
alpha = max(alpha, score)
if alpha > beta:
break
else:
# Guess what move player will make
score = 1000
for s in empty_squares:
score = min(score, minimax(PLAYER, s, alpha, beta, depth+1))
beta = min(beta, score)
if alpha > beta:
break
# Remove mark and return score for the square
square.mark = EMPTY
return score
# Collect scores for empty squares. If board is empty,
# minimax will return 0 for all squares
board.depth = 10
empty_squares = board.empty_squares()
if len(empty_squares) == 9:
board.scores = [[0, s] for s in empty_squares]
else:
# Calling minimax twice. The first is to find the square
# giving us best chance to win board. Second is to prevent
# abandoning board early and giving player an easy win.
board.scores = [[minimax("O", s), s] for s in empty_squares]
[minimax("X", s, depth=1) for s in empty_squares]
def play(self):
"""Place robot mark."""
# Get scores for all empty squares
depth = 10
for board in self.boards:
self.get_scores(board)
depth = min(depth, board.depth)
# Select board with minimum depth. This is the board that
# will win/lose in the least number of moves.
scores = []
for board in self.boards:
if board.depth <= depth:
scores.extend(board.scores)
# Randomly select from best scores on the selected board.
max_score = max(score[0] for score in scores)
squares = [score[1] for score in scores if score[0] >= max_score]
return random.choice(squares)
#END
class Square(tk.Button):
"""Special button for playing tic-tac-toe."""
colors = {"O": "blue", "X": "red"}
def __init__(self, parent, index):
super().__init__(parent, width=3, font=('Comic Sans MS', 20, 'bold'))
self.index = index
self._mark = None
@property
def mark(self):
"""What marker appears in square."""
return self._mark
@mark.setter
def mark(self, mark):
self._mark = mark
self["fg"] = self.colors.get(mark, 'black')
self["text"] = '' if mark is None else mark
class TicTacToe(tk.Tk):
"""Play tic-tac-toe. Players take turn clicking on empty buttons
to place their marker. The first player to get three markers in
a row, up/down, sideways or diagonally, wins
"""
# All possible winning conbinations
wins = ((0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8),
(9, 10, 11), (12, 13, 14), (15, 16, 17), (9, 12, 15), (10, 13, 16), (11, 14, 17),
(18, 19, 20), (21, 22, 23), (24, 25, 26), (18, 21, 24), (19, 22, 25), (20, 23, 26),
(27, 28, 29), (30, 31, 32), (33, 34, 35), (27, 30, 33), (28, 31, 34), (29, 32, 35),
(36, 37, 38), (39, 40, 41), (42, 43, 44), (36, 39, 42), (37, 40, 43), (38, 41, 44),
(45, 46, 47), (48, 49, 50), (51, 52, 53), (45, 48, 51), (46, 49, 52), (47, 50, 53),
(3, 6, 36, 39), (4, 7, 37, 40), (5, 8, 38, 41),
(18, 21, 39, 42), (19, 22, 40, 43), (20, 23, 41, 44),
(28, 29, 36, 37), (31, 32, 39, 40), (34, 35, 42, 43),
(37, 38, 45, 46), (40, 41, 48, 49), (43, 44, 51, 52),
(9, 12, 21, 24), (10, 13, 22, 25), (11, 14, 23, 26),
(0, 3, 12, 15), (1, 4, 13, 16), (2, 5, 14, 17),
(27, 28, 46, 47), (30, 31, 49, 50), (33, 34, 52, 53),
(0, 1, 27, 30), (3, 4, 28, 31), (6, 7, 29, 32),
(1, 2, 47, 50), (4, 5, 46, 49), (7, 8, 45, 48),
(18, 19, 32, 35), (21, 22, 31, 34), (24, 25, 30, 33),
(19, 20, 48, 51), (22, 23, 49, 52), (25, 26, 50, 53),
(15, 16, 27, 28), (12, 13, 30, 31), (9, 10, 33, 34),
(16, 17, 46, 47), (13, 14, 49, 50), (10, 11, 52, 53),
(6, 37, 41, 51), (7, 38, 48, 52), (7, 32, 34, 36), (8, 35, 37, 39),
(19, 28, 32, 42), (20, 29, 39, 43), (18, 41, 43, 45), (19, 44, 46, 48),
(19, 23, 32, 42), (19, 21, 44, 48)) #last one example of a diagonal combo over 2 boards
def __init__(self):
super().__init__()
self.title('Tic Tac Toe')
# create a frame to hold all four boards
self.boards_frame = tk.Frame(self)
self.boards_frame.pack(side="top", padx=5, pady=5)
# create the left board
self.frame1 = tk.Frame(self.boards_frame)
self.frame1.pack(side="left")
self.squares1 = []
for i in range(9):
self.squares1.append(Square(self.frame1, i))
for square in self.squares1:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares1))
self.frame1.pack(side="top")
self.frame2 = tk.Frame(self.boards_frame)
self.frame2.pack(side="left")
for i in range(9, 18):
self.squares1.append(Square(self.frame2, i))
for square in self.squares1:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares1))
self.frame3 = tk.Frame(self.boards_frame)
self.frame3.pack(side="left")
for i in range(18, 27):
self.squares1.append(Square(self.frame3, i))
for square in self.squares1:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares1))
self.frame4 = tk.Frame(self.boards_frame)
self.frame4.pack(side="left")
for i in range(27, 36):
self.squares1.append(Square(self.frame4, i))
for square in self.squares1:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares1))
self.frame5 = tk.Frame(self.boards_frame)
self.frame5.pack(side="left")
for i in range(36, 45):
self.squares1.append(Square(self.frame5, i))
for square in self.squares1:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares1))
self.frame6 = tk.Frame(self.boards_frame)
self.frame6.pack(side="left")
for i in range(45, 54):
self.squares1.append(Square(self.frame6, i))
for square in self.squares1:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares1))
self.frame2.pack(side="bottom")
self.frame3.pack(side="bottom")
'''
# create the middle-left board
self.frame2 = tk.Frame(self.boards_frame)
self.frame2.pack(side="left")
self.squares2 = [Square(self.frame2, i) for i in range(9)]
for square in self.squares2:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares2, self.squares1))
# create the middle-right board
self.frame3 = tk.Frame(self.boards_frame)
self.frame3.pack(side="left")
self.squares3 = [Square(self.frame3, i) for i in range(9)]
for square in self.squares3:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares3, self.squares1, self.squares2, self.squares4, self.squares5, self.squares6))
self.frame4 = tk.Frame(self.boards_frame)
self.frame4.pack(side="left")
self.squares4 = [Square(self.frame4, i) for i in range(9)]
for square in self.squares4:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares4, self.squares1, self.squares2, self.squares3, self.squares5, self.squares6))
self.frame5 = tk.Frame(self.boards_frame)
self.frame5.pack(side="left")
self.squares5 = [Square(self.frame5, i) for i in range(9)]
for square in self.squares5:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares5, self.squares1, self.squares2, self.squares3, self.squares4, self.squares6))
self.frame1.pack(side="bottom")
self.frame2.pack(side="top")
self.frame6 = tk.Frame(self.boards_frame)
self.frame6.pack(side="left")
self.squares6 = [Square(self.frame6, i) for i in range(9)]
for square in self.squares6:
row = square.index // 3
column = square.index % 3
square.grid(row=row, column=column, padx=5, pady=5)
square.configure(command=lambda arg=square: self.play(arg, self.squares6, self.squares1, self.squares2, self.squares3, self.squares4, self.squares5))
self.frame3.pack(side="bottom")
'''
self.message = tk.Label(self, text=' ', width=30)
self.message.pack(side="bottom")
self.new_game = False
self.player = "X"
self.reset(self.squares1)
def reset(self, squares1):
"""Reset board to empty"""
self.open_squares = 54
self.new_game = False
for square in squares1:
square.mark = None
'''
for square in squares2:
square.mark = None
for square in squares3:
square.mark = None
for square in squares4:
square.mark = None
for square in squares5:
square.mark = None
for square in squares6:
square.mark = None
'''
self.message['text'] = f"{self.player}'s turn"
def play(self, square, squares1):
"""Put player marker in slot if open. Check for win or tie"""
if self.new_game:
return
if square.mark is not None:
self.message['text'] = "Invalid move"
return
square.mark = self.player
self.open_squares -= 1
# check for a win
for win in self.wins:
if all(squares1[i].mark == self.player for i in win):
self.message['text'] = f"{self.player} wins!"
self.new_game = True
return
'''
if all(squares2[i].mark == self.player for i in win):
self.message['text'] = f"{self.player} wins!"
self.new_game = True
return
if all(squares3[i].mark == self.player for i in win):
self.message['text'] = f"{self.player} wins!"
self.new_game = True
return
if all(squares4[i].mark == self.player for i in win):
self.message['text'] = f"{self.player} wins!"
self.new_game = True
return
if all(squares5[i].mark == self.player for i in win):
self.message['text'] = f"{self.player} wins!"
self.new_game = True
return
if all(squares6[i].mark == self.player for i in win):
self.message['text'] = f"{self.player} wins!"
self.new_game = True
return
'''
# check for a tie
if self.open_squares == 0:
self.message['text'] = "Tie game"
self.new_game = True
return
# switch players
self.player = "O" if self.player == "X" else "X"
self.message['text'] = f"{self.player}'s turn"
def game_over(self, squares1):
"""Check for winner or tie"""
# Check all winning combinations
for win in self.wins:
if all(squares1[i].mark == self.player for i in win):
for i in win:
squares1[i]["fg"] = "green"
self.message["text"] = f"{self.player} wins!"
return True
'''
if all(squares2[i].mark == self.player for i in win):
for i in win:
squares2[i]["fg"] = "green"
self.message["text"] = f"{self.player} wins!"
return True
if all(squares3[i].mark == self.player for i in win):
for i in win:
squares3[i]["fg"] = "green"
self.message["text"] = f"{self.player} wins!"
return True
if all(squares4[i].mark == self.player for i in win):
for i in win:
squares4[i]["fg"] = "green"
self.message["text"] = f"{self.player} wins!"
return True
if all(squares5[i].mark == self.player for i in win):
for i in win:
squares5[i]["fg"] = "green"
self.message["text"] = f"{self.player} wins!"
return True
if all(squares6[i].mark == self.player for i in win):
for i in win:
squares6[i]["fg"] = "green"
self.message["text"] = f"{self.player} wins!"
return True
'''
# Check for a tie
if self.open_squares <= 0:
self.message["text"] = "Game ends in a tie"
return True
return False
TicTacToe().mainloop()