上一节,我们实现了简单版的人机对战,只不过电脑的水平太弱鸡了,下面接下来,我们需要做的就是升级电脑的落子水平。
首先,要先了解一下五子棋的棋型知识。
棋型知识库主要包括各种既定的棋盘形式,有如下几种:
活二:能够形成活三的二,如下图,是三种基本的活二棋型。图中白点为活三点。
眠二:能够形成眠三的二。图中四个为最基本的眠二棋型。图中白点为眠三点。
活三:可以形成活四的三,如下图,代表两种最基本的活三棋型。图中白点为活四点。活三棋型是进攻中最常见的一种,因为活三之后,如果对方不以理会,将可以下一手将活三变成活四,而活四是无法防守的。所以,面对活三的时候,需要非常谨慎对待。在没有更好的进攻手段的情况下,必须对其进行防守,以防止其形成可怕的活四棋型。
眠三:只能够形成冲四的三,如下各图,分别代表最基础的六种眠三形状。图中白点代表冲四点。眠三的棋型与活三的棋型相比,危险系数下降不少,因为眠三棋型即使不去防守,下一手它也只能形成冲四,而对于单纯的冲四棋型,是可以很简单的防守住的。
活四:有两个连五点(即有两个点可以形成五),图中白点即为连五点。当活四出现的时候,整个局势已经无法阻止连五了,活四的归属方一定能取得胜利。
冲四:有一个连五点,如下面三图,均为冲四棋型。图中白点为连五点。 相对比活四来说,冲四的威胁性就小了很多,因为这个时候,只要跟着防守在那个唯一的连五点上,冲四就没法形成连五。
对于上述的棋型,我们主要考虑的是活三、眠三、活四、冲四这几种主要的进攻棋型的防守与构成,整体棋型遵从以下原则:优先考虑数目,同等数目的情况下考虑是活是眠。评分表算法的设计整体偏向于防守。
其次,要考虑人机博弈过程。
当下棋型的评估分析,算法严格遵从以下流程:
当人类方落下一子,算法启动,扫描全局,得到人类棋子的集合和电脑棋子的集合。全局扫描之后,对当前局势进行排序、计算。对每个集合的每个空白点位置打分,打分依据是根据这个点周围四个方向上的同色连续棋子的数量。按照这些最后得到的评分,得出最大值。得到人类方和电脑方的两个最大值之后,进行比较,如果人类方局势较好(分数较高),则算法将下一次落子位置设置为人类方得分最高的点,尽力降低人类方的下一步得分;如果电脑方的分数较高,那么则直接在使得分数最高的点落子即可。
完整的代码如下:
# -*- coding: utf-8 -*-
import random
from random import *
'''
Created on 2020年1月2日
@author: Fan Xiaoxin
'''
# 五子棋类的定义
class Gomoku(object):
level = 15
grade = 10
MAX = 1000111
def __init__(self, size=15):
"""初始化"""
# 棋盘横纵向变量
self.size = size
# 定义一个存储棋子的位置的矩阵
self.go_map = [[0] * self.size for _ in range(self.size)]
# 存储棋盘界面
self.str = ''
# 步数
self.cur_step = 0
def Scan(self, go_map, player_mode):
shape = [[[0 for _ in range(5)] for _ in range(Gomoku.level)] for _ in range(Gomoku.level)]
# 扫描每一个点,然后在空白的点每一个方向上做出价值评估!!
for i in range(Gomoku.level):
for j in range(Gomoku.level):
# 如果此处为空 那么就可以开始扫描周边
if go_map[i][j] == 0:
m = i
n = j
# 如果上方跟当前传入的颜色参数一致,那么加分到0位!
if player_mode:
while n - 1 >= 0 and go_map[m][n - 1] == 1:
n -= 1
shape[i][j][0] += Gomoku.grade
if n - 1 >= 0 and go_map[m][n - 1] == 0:
shape[i][j][0] += 1
if n - 1 >= 0 and go_map[m][n - 1] == 2:
shape[i][j][0] -= 2
else:
while n - 1 >= 0 and go_map[m][n - 1] == 2:
n -= 1
shape[i][j][0] += Gomoku.grade
if n - 1 >= 0 and go_map[m][n - 1] == 0:
shape[i][j][0] += 1
if n - 1 >= 0 and go_map[m][n - 1] == 1:
shape[i][j][0] -= 2
m = i
n = j
# 如果下方跟当前传入的颜色参数一致,那么加分到0位!
if player_mode:
while n + 1 < Gomoku.level and go_map[m][n + 1] == 1:
n += 1
shape[i][j][0] += Gomoku.grade
if n + 1 < Gomoku.level and go_map[m][n + 1] == 0:
shape[i][j][0] += 1
if n + 1 < Gomoku.level and go_map[m][n + 1] == 2:
shape[i][j][0] -= 2
else:
while n + 1 < Gomoku.level and go_map[m][n + 1] == 2:
n += 1
shape[i][j][0] += Gomoku.grade
if n + 1 < Gomoku.level and go_map[m][n + 1] == 0:
shape[i][j][0] += 1
if n + 1 < Gomoku.level and go_map[m][n + 1] == 1:
shape[i][j][0] -= 2
m = i
n = j
# 如果左边跟当前传入的颜色参数一致,那么加分到1位!
if player_mode:
while m - 1 >= 0 and go_map[m - 1][n] == 1:
m -= 1
shape[i][j][1] += Gomoku.grade
if m - 1 >= 0 and go_map[m - 1][n] == 0:
shape[i][j][1] += 1
if m - 1 >= 0 and go_map[m - 1][n] == 2:
shape[i][j][1] -= 2
else:
while m - 1 >= 0 and go_map[m - 1][n] == 2:
m -= 1
shape[i][j][1] += Gomoku.grade
if m - 1 >= 0 and go_map[m - 1][n] == 0:
shape[i][j][1] += 1
if m - 1 >= 0 and go_map[m - 1][n] == 1:
shape[i][j][1] -= 2
m = i
n = j
# 如果右边跟当前传入的颜色参数一致,那么加分到1位!
if player_mode:
while m + 1 < Gomoku.level and go_map[m + 1][n] == 1:
m += 1
shape[i][j][1] += Gomoku.grade
if m + 1 < Gomoku.level and go_map[m + 1][n] == 0:
shape[i][j][1] += 1
if m + 1 < Gomoku.level and go_map[m + 1][n] == 2:
shape[i][j][1] -= 2
else:
while m + 1 < Gomoku.level and go_map[m + 1][n] == 2:
m += 1
shape[i][j][1] += Gomoku.grade
if m + 1 < Gomoku.level and go_map[m + 1][n] == 0:
shape[i][j][1] += 1
if m + 1 < Gomoku.level and go_map[m + 1][n] == 1:
shape[i][j][1] -= 2
m = i
n = j
# 如果左下方跟当前传入的颜色参数一致,那么加分到2位!
if player_mode:
while m - 1 >= 0 and n + 1 < Gomoku.level and go_map[m - 1][n + 1] == 1:
m -= 1
n += 1
shape[i][j][2] += Gomoku.grade
if m - 1 >= 0 and n + 1 < Gomoku.level and go_map[m - 1][n + 1] == 0:
shape[i][j][2] += 1
if m - 1 >= 0 and n + 1 < Gomoku.level and go_map[m - 1][n + 1] == 2:
shape[i][j][2] -= 2
else:
while m - 1 >= 0 and n + 1 < Gomoku.level and go_map[m - 1][n + 1] == 2:
m -= 1
n += 1
shape[i][j][2] += Gomoku.grade
if m - 1 >= 0 and n + 1 < Gomoku.level and go_map[m - 1][n + 1] == 0:
shape[i][j][2] += 1
if m - 1 >= 0 and n + 1 < Gomoku.level and go_map[m - 1][n + 1] == 1:
shape[i][j][2] -= 2
m = i
n = j
# 如果右上方跟当前传入的颜色参数一致,那么加分到2位!
if player_mode:
while m + 1 < Gomoku.level and n - 1 >= 0 and go_map[m + 1][n - 1] == 1:
m += 1
n -= 1
shape[i][j][2] += Gomoku.grade
if m + 1 < Gomoku.level and n - 1 >= 0 and go_map[m + 1][n - 1] == 0:
shape[i][j][2] += 1
if m + 1 < Gomoku.level and n - 1 >= 0 and go_map[m + 1][n - 1] == 2:
shape[i][j][2] -= 2
else:
while m + 1 < Gomoku.level and n - 1 >= 0 and go_map[m + 1][n - 1] == 2:
m += 1
n -= 1
shape[i][j][2] += Gomoku.grade
if m + 1 < Gomoku.level and n - 1 >= 0 and go_map[m + 1][n - 1] == 0:
shape[i][j][2] += 1
if m + 1 < Gomoku.level and n - 1 >= 0 and go_map[m + 1][n - 1] == 1:
shape[i][j][2] -= 2
m = i
n = j
# 如果左上方跟当前传入的颜色参数一致,那么加分到3位!
if player_mode:
while m - 1 >= 0 and n - 1 >= 0 and go_map[m - 1][n - 1] == 1:
m -= 1
n -= 1
shape[i][j][3] += Gomoku.grade
if m - 1 >= 0 and n - 1 >= 0 and go_map[m - 1][n - 1] == 0:
shape[i][j][3] += 1
if m - 1 >= 0 and n - 1 >= 0 and go_map[m - 1][n - 1] == 2:
shape[i][j][3] -= 2
else:
while m - 1 >= 0 and n - 1 >= 0 and go_map[m - 1][n - 1] == 2:
m -= 1
n -= 1
shape[i][j][3] += Gomoku.grade
if m - 1 >= 0 and n - 1 >= 0 and go_map[m - 1][n - 1] == 0:
shape[i][j][3] += 1
if m - 1 >= 0 and n - 1 >= 0 and go_map[m - 1][n - 1] == 1:
shape[i][j][3] -= 2
m = i
n = j
# 如果右下方跟当前传入的颜色参数一致,那么加分到3位!
if player_mode:
while m + 1 < Gomoku.level and n + 1 < Gomoku.level and go_map[m + 1][n + 1] == 1:
m += 1
n += 1
shape[i][j][3] += Gomoku.grade
if m + 1 < Gomoku.level and n + 1 < Gomoku.level and go_map[m + 1][n + 1] == 0:
shape[i][j][3] += 1
if m + 1 < Gomoku.level and n + 1 < Gomoku.level and go_map[m + 1][n + 1] == 2:
shape[i][j][3] -= 2
else:
while m + 1 < Gomoku.level and n + 1 < Gomoku.level and go_map[m + 1][n + 1] == 2:
m += 1
n += 1
shape[i][j][3] += Gomoku.grade
if m + 1 < Gomoku.level and n + 1 < Gomoku.level and go_map[m + 1][n + 1] == 0:
shape[i][j][3] += 1
if m + 1 < Gomoku.level and n + 1 < Gomoku.level and go_map[m + 1][n + 1] == 1:
shape[i][j][3] -= 2
return shape
def Sort(self, shape):
for i in shape:
for j in i:
for x in range(5):
for w in range(3, x - 1, -1):
if j[w - 1] < j[w]:
temp = j[w]
j[w - 1] = j[w]
j[w] = temp
print("This Time Sort Done !")
return shape
def Evaluate(self, shape):
for i in range(Gomoku.level):
for j in range(Gomoku.level):
if shape[i][j][0] == 4:
return i, j, Gomoku.MAX
shape[i][j][4] = shape[i][j][0] * 1000 + shape[i][j][1] * 100 + shape[i][j][2] * 10 + shape[i][j][3]
max_x = 0
max_y = 0
max = 0
for i in range(Gomoku.level):
for j in range(Gomoku.level):
if max < shape[i][j][4]:
max = shape[i][j][4]
max_x = i
max_y = j
print("the max is " + str(max) + " at ( " + str(max_x) + " , " + str(max_y) + " )")
return max_x, max_y, max
def Autoplay(self, go_map):
a1 = [1, -1, 1, -1, 1, -1, 0, 0]
b1 = [1, -1, -1, 1, 0, 0, 1, -1]
m = Gomoku.level / 2
n = Gomoku.level / 2
rand = randint(0, 7)
while m + a1[rand] >= 0 and m + a1[rand] < Gomoku.level and n + b1[rand] >= 0 and \
n + b1[rand] < Gomoku.level and go_map[m + a1[rand]][n + b1[rand]] != 0:
rand = randint(0, 7)
return m + a1[rand], n + b1[rand]
def player_drop(self):
"""
玩家落子
:param pos_x: 从图形界面输入时,输入的x坐标为多少
:param pos_y: 从图形界面输入时,输入的y坐标为多少
"""
while True:
try:
# 接受玩家的输入
pos_x = int(input('x: '))
pos_y = int(input('y: '))
if 0 <= pos_x <= self.size - 1 and 0 <= pos_y <= self.size - 1:
if self.go_map[pos_x][pos_y] == 0:
self.go_map[pos_x][pos_y] = 1
self.cur_step += 1
return
else:
print "该位置已经被占据,请选择其他位置"
continue
except ValueError: # 玩家输入不正确的情况(例如输入了‘A’)
continue
def computer_drop(self):
"""
电脑落子
: param pos_x: 从图形界面输入时,输入的x坐标为多少
: param pos_y: 从图形界面输入时,输入的y坐标为多少
"""
while True:
print "cur_step = {}" .format(self.cur_step)
try:
if self.cur_step < 2:
pos_x, pos_y = self.Autoplay(self.go_map)
else:
# 接受电脑的输入
player_x, player_y, player_max = self.Evaluate(self.Sort(self.Scan(self.go_map, player_mode=True)))
computer_x, computer_y, computer_max = self.Evaluate(self.Sort(self.Scan(self.go_map, player_mode=False)))
if player_max > computer_max and player_max < Gomoku.MAX:
pos_x, pos_y = player_x, player_y
else:
pos_x, pos_y = computer_x, computer_y
if 0 <= pos_x <= self.size - 1 and 0 <= pos_y <= self.size - 1:
if self.go_map[pos_x][pos_y] == 0:
self.go_map[pos_x][pos_y] = 2
self.cur_step += 1
return
else:
print "该位置已经被占据,请选择其他位置"
continue
# 玩家输入不正确的情况(例如输入了‘A’)
except ValueError:
continue
def go_result(self):
"""判断游戏的结局。0为游戏进行中,1为玩家获胜,2为电脑获胜,3为平局"""
# 1. 判断是否横向连续五子
for x in range(self.size - 4):
for y in range(self.size):
if self.go_map[x][y] == 1 and self.go_map[x + 1][y] == 1 and self.go_map[x + 2][y] == 1 and self.go_map[x + 3][y] == 1 and self.go_map[x + 4][y] == 1:
return 1
if self.go_map[x][y] == 2 and self.go_map[x + 1][y] == 2 and self.go_map[x + 2][y] == 2 and self.go_map[x + 3][y] == 2 and self.go_map[x + 4][y] == 2:
return 2
# 2. 判断是否纵向连续五子
for x in range(self.size):
for y in range(self.size - 4):
if self.go_map[x][y] == 1 and self.go_map[x][y + 1] == 1 and self.go_map[x][y + 2] == 1 and self.go_map[x][y + 3] == 1 and self.go_map[x][y + 4] == 1:
return 1
if self.go_map[x][y] == 2 and self.go_map[x][y + 1] == 2 and self.go_map[x][y + 2] == 2 and self.go_map[x][y + 3] == 2 and self.go_map[x][y + 4] == 2:
return 2
# 3. 判断是否有左上-右下的连续五子
for x in range(self.size - 4):
for y in range(self.size -4):
if self.go_map[x][y] == 1 and self.go_map[x + 1][y + 1] == 1 and self.go_map[x + 2][y + 2] == 1 and self.go_map[x + 3][y + 3] == 1 and self.go_map[x + 4][y + 4] == 1:
return 1
if self.go_map[x][y] == 2 and self.go_map[x + 1][y + 1] == 2 and self.go_map[x + 2][y + 2] == 2 and self.go_map[x + 3][y + 3] == 2 and self.go_map[x + 4][y + 4] == 2:
return 2
# 4. 判断是否有右上-左下的连续五子
for x in range(self.size - 4):
for y in range(self.size -4):
if self.go_map[x + 4][y] == 1 and self.go_map[x + 3][y + 1] == 1 and self.go_map[x + 2][y + 2] == 1 and self.go_map[x + 1][y + 3] == 1 and self.go_map[x][y + 4] == 1:
return 1
if self.go_map[x + 4][y] == 2 and self.go_map[x + 3][y + 1] == 2 and self.go_map[x + 2][y + 2] == 2 and self.go_map[x + 1][y + 3] == 2 and self.go_map[x][y + 4] == 2:
return 2
# 5. 判断是否为平局
for x in range(self.size):
for y in range(self.size):
# 棋盘中还有剩余的格子,不能判断为平局
if self.go_map[x][y] == 0:
return 0
return 3
def gomoku_board(self, res):
"""画出棋盘"""
self.str = ''
for y in xrange(self.size):
for x in xrange(self.size-1):
# 该位置没有棋子
if self.go_map[x][y]==0:
self.str += ' '
# 该位置已被我方占据
elif self.go_map[x][y]==1:
self.str += 'O'
# 该位置已被对方占据
elif self.go_map[x][y]==2:
self.str += 'X'
self.str += '-'
self.str += '\n'
if y != (self.size-1):
for _ in xrange(self.size):
self.str += '| '
self.str += '\n'
print self.str
if res == 0:
print u'游戏正在进行中!'
elif res == 1:
print u'玩家获胜!'
elif res == 2:
print u'电脑获胜!'
elif res == 3:
print u'平局!'
return self.str
# 主函数
if __name__ == '__main__':
gomoku = Gomoku()
while True:
gomoku.player_drop()
res = gomoku.go_result()
gomoku.gomoku_board(res)
gomoku.computer_drop()
res = gomoku.go_result()
gomoku.gomoku_board(res)