- 游戏规则:双方轮流放子,当某一方的三个子连成一线(行,列,对角)时,该方获胜。
程序设计思想
-
首先选择先手的玩家,如果选择“X”表示“玩家”先手,如果选择“O”表示“电脑”先手,如果输入错误就会默认“玩家”先手。
-
我们通过(0-8)的数字表示棋盘上的位置,“玩家”和“电脑”会轮流下棋,如果一方获得胜利,或者平局的时候(棋盘上没有空位置)就结束。当轮到“玩家”下棋,就让玩家自己输入(0-8)的数字,但是要求不能输入棋盘上已经有棋子的位置,否则就让玩家重新输入,如果轮到“电脑”下棋,那么就让“电脑”采用极大极小值算法自动选择最适合下棋的位置。
- 用以下的9个数字来表示棋盘的位置:
- 0 1 2
- 3 4 5
- 6 7 8
-
在该程序中,我们会用-1,1来表示具体的玩家,0来表示空位置,这样就会方便我们计算胜利或失败的情况。
- 我们用-1(绿色方块)来表示“玩家”,1(红色方块)来表示“电脑”,0(灰色方块)表示空位置。
极大极小值算法计算当前位置分数
-
在该游戏中,“电脑”和“玩家”都想找到对于各自来说最高的分数,同时也想让对手获得更低的分数,因为我们需要让“电脑”自动下棋,那么我们假想“玩家”是非常聪明的,总会找到让“电脑”分数更低的棋盘状态,因此只要“玩家”找到这个分值,那么对于“电脑”来说这就是他的最差分值,不会比这个分值更差了。
-
那么如何找到这个分值呢? 我们就需要搜索“玩家”和“电脑”对弈的所有情况,每一种情况都有一个棋盘状态,因此最后肯定有结果(输,赢,平局),我们规定,输=-1,赢=1,平局=0。 因此有结果(-1,1,0)。
-
我们观察下图第一排,此时轮到“玩家”下棋,可以看到生成了第二排3个棋盘,第二排第一个棋盘已经有玩家获胜了,因此不需要再进行分裂了,因此分裂后面两个状态,直到有玩家获胜或者平局,直到将所有的状态都生成之后,计算叶节点的状态值。
- 1.观察下图可以看到最后一排有4个状态值,前两个状态值分别为(0,1),第四排由“玩家”(符号“X”)更新第三排得到,因此这表示的是对于“玩家”来说的状态值,那么对于“电脑”来说状态值分别为(0,-1),由于第三排没有分状态,因此第三排的状态值对于“电脑”来说分别还是为(0,-1);
- 2.由于第三排由“电脑”(符号“O”)更新第二排得到,对于“电脑”来说状态值分别为(0,-1),因此对“电脑”会选择第三排的最大状态值0,那么第二排第二个状态值就为0,同理可以得出第二排第三个也为0,因此第二排状态值对于“玩家”来说分别为(1,0,0);
- 3.由于第二排由“玩家”(符号“X”)更新第一排得到,状态值分别为(1,0,0),那么对于“电脑”来说状态值为(-1,0,0)那么“电脑”会选最大状态值0,因此第一排的状态值就等于0。
- 我们规定,当最高的分数等于 -1 ,那么就肯定失败,如果最高的分数等于 0 ,那么就是平局,如果最高的分数等于 1 ,那么就胜利了,因此这里我们还可以使用剪枝的方式来剪掉一些多余的分支,也就是剪掉状态值大于等于2,或者小于等于-2的情况,因此我们用两个参数,alpha=-2, beta=2作为该算法的剪枝参数。
# -*- coding:utf-8 -*-
import random
from ipywidgets import Button, GridBox, Layout, ButtonStyle
HUMAN = 1
COMPUTER = 0
class alibaba(object):
def __init__(self):
# 图像布局
self.layout = Layout(
width='80%',
grid_template_columns='40px 40px 40px',
grid_template_rows='40px 40px 40px',
grid_gap='1px')
# 用一维列表表示棋盘:
self.SLOTS = (0, 1, 2, 3, 4, 5, 6, 7, 8)
# -1表示X玩家 0表示空位 1表示O玩家,在游戏时就会生成类似[-1, 1, -1, 0, 1, 0, 0, 0, 0]的数组
self.X_token = -1
self.None_token = 0
self.O_token = 1
# 设定获胜的组合方式(横、竖、斜)
self.WINNING_TRIADS = ((0, 1, 2), (3, 4, 5), (6, 7, 8),
(0, 3, 6), (1, 4, 7), (2, 5, 8),
(0, 4, 8), (2, 4, 6))
# 三种结果
self.result = ('平局', '胜利', '失败')
def legal_move_left(self, board):
for slot in self.SLOTS:
if board[slot] == self.None_token:
return True
return False
def winner(self, board):
for triad in self.WINNING_TRIADS:
triad_sum = board[triad[0]] + board[triad[1]] + board[triad[2]]
# 如果在获胜组合中每一个元素都等于1,那么相加就等于3,也就是说在获胜组合中都是“电脑”,因此“电脑”就赢了,返回 1
if triad_sum == 3:
return 1
# 如果在获胜组合中每一个元素都等于-1,那么相加就等于-3,也就是说在获胜组合中都是“玩家”,因此“玩家”就赢了,返回 -1
elif triad_sum == -3:
return -1
# 其它情况都返回 0 。
return 0
def alpha_beta_valuation(self, board, player, next_player, alpha, beta):
wnnr = self.winner(board)
if wnnr != self.None_token:
# 有玩家获胜
return wnnr
# 没有空位,平局,返回 0
elif not self.legal_move_left(board):
return 0
# 创建所有棋盘情况,并进行分值计算
for move in self.SLOTS:
# 是空位置才进行计算分值。
if board[move] == self.None_token:
# 默认玩家为X_token,把当前位置的值变为-1
board[move] = player
# 落子之后交换玩家,继续检验,通过递归的方法找出符合条件的 alpha和beta值。
val = self.alpha_beta_valuation(board, next_player, player, alpha, beta)
# 把空位置状态还原
board[move] = self.None_token
# 如果是“电脑”(记号是1),因此对于“电脑”来说需要找到最大值让自己获得更大的分数。
if player == self.O_token:
# 对于“电脑”来说,如果状态值比最小值alpha要大的话,就让alpha最小值等于这个分数val。
if val > alpha:
alpha = val
# 如果alpha大于等于beta值,因为规定的剪枝最大值为beta,因此直接返回beta,并且直接进行下一次循环。
if alpha >= beta:
return beta
# 如果是“玩家”(记号是-1),因此对于“玩家”来说需要让“电脑”获得更小的分数。
else:
# 对于“玩家”来说,如果状态值比最大值beta要小的话,就让beta最大值等于val。
if val < beta:
beta = val
# 如果beta小于等于alpha的值,因为规定的剪枝最小值为alpha,因此直接返回alpha,并且直接进行下一次循环。
if beta <= alpha:
return alpha
# 如果当前玩家是“电脑”,返回最小值alpha,也就是返回对于“电脑”来说最坏的情况,对于“玩家”来说最好的情况
if player == self.O_token:
retval = alpha
# 如果当前玩家是“玩家”,返回最大值beta,也就是返回对于“玩家”来说最坏的情况,对于“电脑”来说最好的情况
else:
retval = beta
return retval
def determine_move(self, board):
best_val = -2 # 本程序估值结果只在[-1,0,1]中,那么最小的分值只能为-1了,也就是大于-2的情况。
my_moves = []
print("开始思考")
for move in self.SLOTS:
# 判断当前move是否为0
if board[move] == self.None_token:
# 让“电脑”占用当前位置,预测玩家在当前位置的输赢。
board[move] = self.O_token
# 返回对于“电脑”来说最坏的情况。
val = self.alpha_beta_valuation(board, self.X_token, self.O_token, -2, 2)
# 让“电脑”归还当前位置
board[move] = self.None_token
# 如果当前玩家在当前位置的极大极小值等于-1,就认为会失败,等于0,就是平局,等于1,就是胜利。
print("Computer如果下在", move, ",将导致", self.result[val])
# 只要 val > best_val ,添加该move到my_moves列表中,并更新best_val,如果下次循环的val比这次best_val更大,那么重新给my_moves添加move元素。
if val > best_val:
best_val = val
my_moves = [move]
# 如果有val==best_val,那么就继续添加元素move。
if val == best_val:
my_moves.append(move)
# 如果my_moves有多个相同的val值的move,就随机挑选一个move作为电脑下棋的位置。
return random.choice(my_moves)
def plot(self, status):
color = ['LightGray', 'green', 'red']
color_dict = {}
tic_format = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
for i in range(3):
for j in range(3):
if status[tic_format[i][j]] == 0:
color_dict[i, j] = color[0]
elif status[tic_format[i][j]] == -1:
color_dict[i, j] = color[1]
elif status[tic_format[i][j]] == 1:
color_dict[i, j] = color[2]
button = {}
for m in range(3):
for n in range(3):
button[(m, n)] = Button(layout=Layout(width='auto', height='auto'),
style=ButtonStyle(button_color=str(color_dict[m, n])))
return button
def run(self):
next_move = HUMAN
opt = input("请选择先手方,输入X表示玩家先手,输入O表示电脑先手:")
if opt == "X":
next_move = HUMAN
elif opt == "O":
next_move = COMPUTER
else:
print("输入有误,默认玩家先手")
# 初始化空棋盘
board = [self.None_token for i in range(9)]
# 开始下棋
# 一直循环直到有一方获胜或者棋盘上没有空位置为止。
while True:
# if self.legal_move_left(board) and self.winner(board) == self.None_token:
if next_move == HUMAN and self.legal_move_left(board):
try:
humanmv = int(input("请输入你要落子的位置(0-8):"))
if board[humanmv] != self.None_token:
continue
board[humanmv] = self.X_token
next_move = COMPUTER
except:
print("输入有误,请重试")
continue
else:
break
# 如果轮到电脑下棋,并且棋盘上有空格并且玩家没有获得胜利的时候就让电脑选择下棋位置。
if next_move == COMPUTER and self.legal_move_left(board):
mymv = self.determine_move(board)
print("Computer最终决定下在", mymv)
board[mymv] = self.O_token
next_move = HUMAN
# 输出结果
# GridBox(children=list(self.plot(board).values()), layout=self.layout)
print(["平局", "Computer赢了", "你赢了"][self.winner(board)])
# while self.legal_move_left(board) and self.winner(board) == self.None_token:
# print("22222222222")
# # GridBox(children=list(self.plot(board).values()), layout=self.layout)
# # 如果轮到玩家下棋,并且棋盘上有空格并且对方没有获得胜利的时候就让玩家选择下棋位置。
# if next_move == HUMAN and self.legal_move_left(board):
# try:
# humanmv = int(input("请输入你要落子的位置(0-8):"))
# if board[humanmv] != self.None_token:
# continue
# board[humanmv] = self.X_token
# next_move = COMPUTER
# except:
# print("输入有误,请重试")
# continue
# # 如果轮到电脑下棋,并且棋盘上有空格并且玩家没有获得胜利的时候就让电脑选择下棋位置。
# if next_move == COMPUTER and self.legal_move_left(board):
# mymv = self.determine_move(board)
# print("Computer最终决定下在", mymv)
# board[mymv] = self.O_token
# next_move = HUMAN
# # 输出结果
# GridBox(children=list(self.plot(board).values()), layout=self.layout)
# print(["平局", "Computer赢了", "你赢了"][self.winner(board)])
if __name__ == '__main__':
fps = alibaba()
fps.run()