实验要求
实验目的:
1.熟悉博弈树及博弈搜索;
2.了解minmax搜索算法和alpha-beta剪枝算法;
3.了解评估函数设计方法,运用博弈搜索解决益智游戏问题;
实验内容(三选一):
1.设计Minimax智能体;
2.利用Minimax算法的思想实现井字棋游戏;
3.自选对抗搜索游戏并进行实现。
原理
参考资料
https://blog.csdn.net/weixin_42165981/article/details/103263211
https://blog.csdn.net/xrying621/article/details/127254462
核心代码
程序使用time.monotonic()函数统计,输出电脑每一次决策需要的时间。最关键的部分是α-β剪枝优化,自己做的界面有点简单,可以调用pyQt或者pygame绘制更棒的图形界面。
import copy
import tkinter as tk
from tkinter import messagebox
import tkinter
import time
HEIGHT_OF_WINDOW = 500
WIDTH_OF_WINDOW = 500
HEIGHT_OF_GRID = 100
WIDTH_OF_GRID = 100
ENUM = ["O", "X"]
COLUMN_ROW = 3
MAX_VALUE = 2
ANCHOR = tk.CENTER
X_GRID = 100
Y_GRID = 50
X_CENTER = 0.3
Y_CENTER = 0.2
DEVIDE = 5
DELAY = 1000
COUNT = 0
class Node:
def __init__(
self,
board,
mark,
index=None,
) -> None:
self.board = board
self.mark = mark
self.value = MAX_VALUE if mark else -MAX_VALUE
# self.value=None
self.alpha = -MAX_VALUE
self.beta = MAX_VALUE
self.index = index
self.leaf = self.is_leaf()
self.next: list[Node] = []
def is_leaf(self):
if self.board[0][0] == self.board[1][1] == self.board[2][2] != "":
return self.board[0][0]
if self.board[0][2] == self.board[1][1] == self.board[2][0] != "":
return self.board[0][2]
for i in range(3):
if self.board[i][0] == self.board[i][1] == self.board[i][2] != "":
return self.board[i][0]
if self.board[0][i] == self.board[1][i] == self.board[2][i] != "":
return self.board[0][i]
if all([self.board[i][j] != "" for i in range(3) for j in range(3)]):
return "draw"
return False
def search_node(self):
if self.leaf:
self.evaluate_leaf()
else:
for i in range(3):
for j in range(3):
if self.board[i][j] in ENUM:
continue
board = copy.deepcopy(self.board)
board[i][j] = ENUM[self.mark]
temp = Node(board, not self.mark, (i, j))
temp.alpha = self.alpha
temp.beta = self.beta
temp.search_node()
if self.mark:
self.alpha = max(self.alpha, temp.value)
self.value = self.alpha
else:
self.beta = min(self.beta, temp.value)
self.value = self.beta
if self.beta <= self.alpha:
return
self.next.append(temp)
def evaluate_leaf(self):
if self.leaf == "X":
self.value = 1
elif self.leaf == "O":
self.value = -1
else:
self.value = 0
def search_next(self):
# global COUNT
# COUNT=0
clock = time.monotonic()
self.search_node()
if self.mark:
self.value = -MAX_VALUE
for n in self.next:
if n.value > self.value:
self.value = n.value
index = n.index
else:
self.value = MAX_VALUE
for n in self.next:
if n.value < self.value:
self.value = n.value
index = n.index
print(time.monotonic() - clock)
# print(COUNT)
return index
class Player:
def __init__(self, mark="X"):
self.mark = mark
self.mode = False
def switch(self):
if self.mark == "X":
self.mark = "O"
else:
self.mark = "X"
def winner(self, board):
for i in range(3):
if all([cell == self.mark for cell in board[i]]):
return self.mark
if all([cell == self.mark for cell in [row[i] for row in board]]):
return self.mark
if all([board[i][i] == self.mark for i in range(3)]):
return self.mark
if all([board[i][2 - i] == self.mark for i in range(3)]):
return self.mark
if all([cell != "" for row in board for cell in row]):
return "draw"
return False
class Button:
def __init__(
self,
master,
text,
command,
row=0,
column=0,
relx=0,
rely=0,
font=None,
width=3,
height=1,
):
if font == None:
self.button = tk.Button(master, text=text, command=command)
else:
self.button = tk.Button(
master,
text=text,
font=font,
width=width,
height=height,
command=command,
)
self.button.grid(row=row, column=column)
self.button.place(relx=relx, rely=rely, anchor=ANCHOR)
class Buttons:
def __init__(self, master: tkinter.Tk) -> None:
self.master = master
self.grids: list[tk.Button] = []
self.board = []
self.player = Player()
self.canvas = tk.Canvas(
self.master, width=WIDTH_OF_WINDOW, height=HEIGHT_OF_WINDOW
)
self.canvas.grid()
self.canvas.create_rectangle(
X_GRID,
Y_GRID,
X_GRID + WIDTH_OF_GRID * COLUMN_ROW,
Y_GRID + HEIGHT_OF_GRID * COLUMN_ROW,
outline="black",
)
self.tips = Button(master, "Tips", self.tip, 4, 1, 0.7, 0.9)
self.manMan = Button(master, "Man-\nMan", self.man_man, 4, 0, 0.3, 0.9)
self.manMechine = Button(
master, "Man-\nMechine", self.man_mechine, 4, 0, 0.5, 0.9
)
def grid(self, i, j):
self.canvas.delete("all")
if self.board[i][j] == "":
self.board[i][j] = self.player.mark
self.grids[i * 3 + j].config(text=self.player.mark)
self.grids[i * 3 + j].config(state=tk.DISABLED)
# print(self.board)
winner = self.player.winner(self.board)
if winner:
self.over(winner)
else:
self.player.switch()
if self.player.mode == "O" and self.player.mark == "X":
node = Node(self.board, True)
i, j = node.search_next()
self.grid(i, j)
elif self.player.mode == "X" and self.player.mark == "O":
node = Node(self.board, False)
i, j = node.search_next()
self.grid(i, j)
def start(self):
self.canvas.delete("all")
self.player.mark = "X"
for grid in self.grids:
grid.destroy()
self.grids = []
self.board = [["" for i in range(3)] for j in range(3)]
for row in range(3):
for column in range(3):
grid = tk.Button(
self.master,
text=self.board[row][column],
font=("Arial", 30),
width=3,
height=1,
command=lambda i=row, j=column: self.grid(i, j),
)
grid.grid(row=row, column=column)
grid.place(
relx=X_CENTER + row / DEVIDE,
rely=Y_CENTER + column / DEVIDE,
anchor=tk.CENTER,
)
self.grids.append(grid)
def tip(self):
# print(self.board,self.player.mark)
node = Node(self.board, True if self.player.mark == "X" else False)
row, column = node.search_next()
x1 = row * WIDTH_OF_GRID
y1 = column * HEIGHT_OF_GRID
x2 = x1 + WIDTH_OF_GRID
y2 = y1 + HEIGHT_OF_GRID
self.canvas.create_rectangle(
X_GRID + x1, Y_GRID + y1, X_GRID + x2, Y_GRID + y2, outline="red"
)
self.master.after(
DELAY,
lambda: self.canvas.create_rectangle(
X_GRID + x1, Y_GRID + y1, X_GRID + x2, Y_GRID + y2, outline="black"
),
)
def man_man(self):
self.start()
def man_mechine(self):
self.start()
if messagebox.askyesno(None, "Are you the Player 1?"):
self.player.mode = "X"
else:
self.player.mode = "O"
node = Node(self.board, True)
i, j = node.search_next()
self.grid(i, j)
def over(self, winner):
if winner == "draw":
messagebox.showinfo(None, "It's a draw!")
else:
messagebox.showinfo(None, winner + " wins!")
if messagebox.askyesno(None, "Do you want to play again?"):
self.reset()
else:
messagebox.showinfo("Game Over", "Thanks for playing!")
self.master.destroy()
def reset(self):
self.master.destroy()
tictactoe()
class GUI:
def __init__(self, height=HEIGHT_OF_WINDOW, width=WIDTH_OF_WINDOW):
self.root = tkinter.Tk()
self.root.geometry(f"{width}x{height}")
self.root.title("Tic Tac Toe")
def start(self):
self.root.mainloop()
def tictactoe():
gui = GUI()
Buttons(gui.root)
gui.start()
if __name__ == "__main__":
tictactoe()
效果演示
测得执行一步计算时间在0.2s以内