这算是我编写的第一个带点智能的程序了,虽然学编程这么久了。。。。。。

本篇文章就是记录一个三连子游戏的理论、设计、实现的全部过程和思路,(五子棋 四连子 井字棋都差不多,三连子更简单一点,方便理解)

理论

我是最开始是通过人工智能 一种现代的方法第三版 这本书接触到这方面的知识的,然而书上讲解的不甚详细,还参考了网络上其他讲解这部分理论的文章
其他写的比较好的文章汇总:五子棋入门级AI的设计与实现
博弈游戏的AI设计(一):极大极小树
五子棋AI算法第四篇-启发式搜索函数

我总结下来就是极大极小值算法通过深搜来搜索双方的落子的情况,来得出最佳的落子位置,然后使用α-β剪枝加快速度,但是就算是使用α-β剪枝 剪枝了,时间上还是不可以接受

所以我们想到这样一种方法,我们不一定非要深搜到谁赢谁输才算到头(这样太慢了),我们确定一种对棋局分析的策略(评估函数),这个评估函数可以在没有确定谁输谁赢的时候给我们一个当前棋局的评估分数(更加符合人类的思考方式),我们根据评估分数作为落子的参考。

关于MAX和MIN的剪支不理解的话,可以参考下面的两个图
下面是MAX层的剪支

下面是MIN层的剪支,可以看到因为第一层MIN确定了比3小了,第二层的第二个MAX只要确定了比4大,则剩下的全部会被剪支

三连子游戏的设计

评估函数的策略应该如何设计

首先我们统计棋盘的情况,应该对两类棋子分开统计,计算机的棋子类别设为1,人类下的棋子设为2,记当前棋局1类棋子的得分为maxscore,2类棋子的得分为minscore,函数返回maxscore-minscore。 这样1类棋子得分越高,2类棋子得分越低,1类棋子赢的概率越大,也就是计算机赢的概率越大

如何计算某一类棋子的得分

本次策略是首先把棋盘上所有可以获胜的情况(3个子相连了,横竖斜均可)列举出来,比如棋盘上(0,0) (0,1) (0,2) 是三个相连的点 ,然后对这三个相连的点进行统计,看这三个点上 有人类下的棋子几个,电脑下的棋子几个,空了几个。

  • 如果有人类棋子3个了,很明确已经胜利了,权重最大,记为3000
  • 如果有2个人类棋子并且另一个是空的,那么人类很可能胜利,权重小一点,记为100
  • 如果有2个人类棋子,1个电脑的棋子,那么人类的局势还行,权重更小,记为10
  • 如果有1个人类棋子,2个空闲位置,那么人类很可能胜利,权重要再小一点,记为50
  • 如果有1个人类棋子,1个电脑的棋子,那么人类的局势还行,权重更小,记为10
    然后把对人类下的棋子的权重进行相加,记为maxscore

电脑的棋子也是同样的情况,所有权重相加记为minscore

python实现代码

注释量快赶上代码量了,希望都能看懂吧

import numpy as np
from tkinter import *
#导入 Tkinter 库  ,python的标准GUIclass Game(object):def __init__(self):#table_num 记录每个列当前能哪一行能放旗子,最初能放旗子的位置是(3,0)(3,1)(3,2)(3,3)#三连子的规则是下棋的时候都是从每列自底向上开始的self.table_num = [3, 3, 3, 3]#win_sn保存着4*4棋盘的所有赢法self.win_sn=self.getSn()#生成棋盘self.chess = np.zeros((4, 4), dtype=int)  # 棋盘状态数组  0---空格  1---叉电脑  2---圈玩家#iscircle  表示本次落子是不是落的圆圈,即玩家#当iscircle为False时候,落子的是×  即电脑self.iscircle = True  # 当前圈下-,默认玩家先手## 实例化Tk类(窗口对象)。Tk类是顶层的控件类,完成窗口的一系列初始化self.root = Tk()self.root.title('三连子')#调用Tkinter 库 画布控件,生成一个白色背景的 长宽指定的画布self.canvas = Canvas(self.root, width=290, height=290, bg="white")#使用pack布局,从上到下,从左到右的摆放控件self.canvas.pack()#在画布上创建一个矩形,使用CA9762颜色填充#另外Tkinter中的坐标系也是从左上角开始的self.canvas.create_rectangle(25, 25, 265, 265, fill="#CA9762")for i in range(1, 6):self.canvas.create_line(25, 25 + 60 * (i - 1), 265, 25 + 60 * (i - 1))  # 横线self.canvas.create_line(25 + 60 * (i - 1), 25, 25 + 60 * (i - 1), 265)  # 竖线#<Button-1>表示鼠标左键单击   绑定到player 函数,表示玩家点击了棋盘某一点后触发函数self.canvas.bind("<Button-1>", self.player)#主窗口循环self.root.mainloop()#生成4*4棋盘所有的赢法def getSn(self):out = []for i in range(4):temp_list = [(i, 0), (i, 1), (i, 2)]out.append(temp_list)temp_list = [(i, 1), (i, 2), (i, 3)]out.append(temp_list)temp_list = [(0, i), (1, i), (2, i)]out.append(temp_list)temp_list = [(1, i), (2, i), (3, i)]out.append(temp_list)# 主对角线# 左上到右下temp_list = [(0, 0), (1, 1), (2, 2)]out.append(temp_list)temp_list = [(1, 1), (2, 2), (3, 3)]out.append(temp_list)# 右上到左下temp_list = [(0, 3), (1, 2), (2, 1)]out.append(temp_list)temp_list = [(1, 2), (2, 1), (3, 0)]out.append(temp_list)# 上侧的副对角线temp_list = [(0, 1), (1, 2), (2, 3)]out.append(temp_list)temp_list = [(0, 2), (1, 1), (2, 0)]out.append(temp_list)# 下面对角线temp_list = [(1, 0), (2, 1), (3, 2)]out.append(temp_list)temp_list = [(1, 3), (2, 2), (3, 1)]out.append(temp_list)return out#判断是不是赢了,chesstemp 是棋盘数据,who保存1 还是2 表示棋盘中1赢了还是棋盘的2赢了def win(self, chesstemp, who):t = chesstemp.copy()for list in self.win_sn:if t[list[0][0]][list[0][1]] == who and t[list[1][0]][list[1][1]] == who and t[list[2][0]][list[2][1]] == who:return Truereturn Falsedef isGameOver(self, who):# 游戏结束 的三个情况 1、满格平局  2、电脑胜,棋子1赢了   3、玩家胜 棋子2赢了t = self.chess.copy()#判断是不是赢了if self.win(t, who):return Trueelse:#判断棋盘还有哪些位置空闲empty_cells = [(i, j) for i in range(4) for j in range(4) if t[i][j] == 0]if len(empty_cells) == 0:  # 没有人赢,但是没有空格了,游戏结束return Trueelse:  # 游戏没有结束return False#画圈,i j表示在4*4 棋盘的那个位置画,函数内部完成坐标转换def drawcircle(self, i, j):x = 25 + 60 * jy = 25 + 60 * iself.canvas.create_oval(x + 30 - 20, y + 30 - 20, x + 30 + 20, y + 30 + 20)#画×,i j表示在4*4 棋盘的那个位置画,函数内部完成坐标转换def drawcha(self, i, j):x = 25 + 60 * jy = 25 + 60 * iself.canvas.create_line(x, y, x + 60, y + 60)self.canvas.create_line(x + 60, y, x, y + 60)# 通过按钮事件来调用play,表示玩家下棋,玩家下完后需要电脑落子了# 所以play函数里面还会再调用电脑的下棋函数 self.computer()def player(self, event):y = event.yx = event.xif self.iscircle and (25 <= x <= 265) and (25 <= y <= 265):#确定鼠标左键单击时的坐标对应棋盘几行 几列i = int((y - 25) / 60)j = int((x - 25) / 60)#由于三连子的规则,我们确定了列其实就是说在该列最低处落子#所以对于鼠标左击的坐标只用到X轴的值 即J,帮助确定那一列#而table_num保存着每一列的哪一行当前可以落子i = self.table_num[j]#这样我们在棋盘上的落子就确定下来了 (i,j)if i >= 0:#棋盘上 落子,玩家的棋子用2来表示self.chess[i][j] = 2#table_num 储存着j列的的落子行下标-1self.table_num[j] -= 1# 画圈self.drawcircle(i, j)# 画完,判断是否结束游戏get = self.isGameOver(2)if get:print("--------你竟然赢了-------")else:# 轮到电脑下self.computer()else:print("--------该列已经满了,换个列吧-------")def computer(self):#计算机的下棋函数,计算机下1这类棋子,该函数的目的是确保棋盘中1的棋子能够胜利# 进行 a-b剪枝 ,返回下一步该下的坐标  程序的关键之一i, j, score = self.alphabeta(self.chess, -3000, 3000, True,6)  # 树的深度限制print('Computer下棋的位置 ,i ,j ', i, j, score)#画×self.drawcha(i, j)#棋盘落子self.chess[i][j] = 1#table_num 储存着j列的落子行下标需要减去1self.table_num[j] -= 1isGameOver = self.isGameOver(1)if isGameOver:print("--------你果然输了-------")else:self.iscircle = True#对于alpha beta 剪支算法,我的理解是对极小极大算法的优化# 在MAX层主要更新alpha ,在MIN层主要更新Beta#然后不论是MAX层还是MIN层,除了自己本身需要不断优化alpha或者beta (初始值负无穷和正无穷)#我这里的程序是把-2000 2000看成正负无穷# 还需要保留上一层的 alpha beta用于剪支def alphabeta(self, chessborad, alpha, beta, isMax, depth):#复制棋局tempChess = chessborad.copy()if isMax:  # MAX   计算机 下1这种棋子#parent记录从上一级过来的alpha  beta 值,用于剪支alpha_parent = alphabeta_parent = betaalpha=-3000beta=3000#棋盘哪些位置可以落子,因为三连子的规则,每一步都只能在当前列的最后一个empty_cells=[]for j in range(4):if(self.table_num[j]>=0):i=self.table_num[j]empty_cells.append((i,j))#如果还是有空位的话if len(empty_cells) != 0:#最后输出本次max层最有利  的下棋位置,先默认是第一个下棋点#change_i 保存着当前MAX层 能确保score最大的放置棋子的地方change_i, change_j = empty_cells[0]for index in range(len(empty_cells)):#获取本次循环确定的落子地点i, j = empty_cells[index]#落子tempChess[i][j] = 1#落子的j列对应的行标需要减去1self.table_num[j] -= 1#评估落子后的局势score = self.evaluate(tempChess)#当还有深度或者当前没有赢,因为大于2000就认为棋子1已经胜利了if depth>0 and score<2000:# 深搜,注意这里传递是alpha beta# 因为alpha可能会在完成一次深搜之后更新#可以看看人工智能:一种现代的方法(第3版)图片5-5的示例temp_i, temp_j, score = self.alphabeta(tempChess, alpha, beta, not isMax, depth - 1)#之前落子的列,把位置再空值出来self.table_num[j] += 1tempChess[i][j] = 0#当递归获得或者是直接计算当前局势的评分比alpha大,就更新alphaif score > alpha:alpha = scorechange_i, change_j = i, jprint("MAX更新alpha为:", alpha)print("在",depth,'轮MAX下棋位置为 (', i,",", j,")时候的评分:", score)# 输出当前的棋盘情况print(tempChess)if alpha > beta_parent:print('剪枝了')breakprint("在",depth, '轮MAX下棋最终位置和评分为',change_i, change_j,' score为',  alpha)return change_i, change_j, alphaelse:# 深度不为0,但是没有空位了,不再继续向下搜索,将当前节点当做子节点进行评价score = self.evaluate(tempChess)if score > alpha:alpha = scoreprint("在",depth, '轮MAX,无空位时评分为 score为',  alpha)return -1, -1, alphaelse:  # MIN# MIN结点剪支需要的MAX父节点的 beta值alpha_parent = alphabeta_parent = betaempty_cells = []for j in range(4):if (self.table_num[j] >= 0):i = self.table_num[j]empty_cells.append((i, j))if len(empty_cells) != 0:  # 有MIN节点change_i, change_j = empty_cells[0]for index in range(len(empty_cells)):i, j = empty_cells[index]tempChess[i][j] = 2self.table_num[j] -= 1score = self.evaluate(tempChess)if depth > 0 and score > -2000:# 进一步递归下一层的MAXtemp_i, temp_j, score = self.alphabeta(tempChess, alpha, beta, not isMax, depth - 1)self.table_num[j] += 1tempChess[i][j] = 0if score < beta:beta = scorechange_i, change_j = i, jprint("在",depth,'轮MIN下棋位置为 (', i,",", j,")时候的评分:", score)print(tempChess)if beta < alpha_parent:print('剪枝了')breakprint("在",depth, '轮MIN下棋最终位置和评分为',change_i, change_j,' score为',  beta)return change_i, change_j, betaelse:# 深度不为0,但是没有子节点,不再继续向下搜索,将当前节点当做子节点进行评价score = self.evaluate(tempChess)if score < beta:beta = scoreprint("在",depth, '轮MIN,无空位时评分为 score为',  beta)return -1, -1, beta# 评估函数,计算机有点聪明劲头儿的精髓#首先这个评估函数就是分析出,当前的棋盘的局势对双方的胜率有多大#因为我们这里是编写一个尽量智能的计算机下棋程序,所欲评估函数对于#计算机的棋子1 胜利的情况要尽可能返回最大值,对于人下的棋子2胜利的情况要尽可能#返回最小值,为了满足这样的要求,我们对 棋子1评估取胜分数为maxscore,# 对棋子2评估取胜分数minscore,然后让函数返回maxscore-minscoredef evaluate(self, chessborad):#默认 棋子1取胜的分数maxscore是0, 棋子2取胜的分数minscore是0maxscore = 0minscore=0# win_sn 保存着所有可能的取胜路径,对所有路径进行遍历for list in self.win_sn:#保存取胜路径上棋子的数目,比如signNum[0] 就是在取胜路径上0棋子的个数signNum=[0,0,0]#这个sign主要是对取胜路径进行判断,比如我们当前棋盘是这样婶儿的# 2 2 0 0# 1 1 0 0# 2 2 0 0# 1 1 0 0#这个时候大概率取胜应该是1,因为根据三连子的规则,下一个棋子只能放在(3,2)这个位置sign = 1for tuple in list:#tuple[0] 是当前遍历的取胜路径的行下标#还是上面那个例子,如果遍历到的取胜路径是 (0,0) (0,1) (0,2)# 而棋盘上0 1 2 列对应可以放置棋子的行数是 -1 -1 3#所以取胜路径上行下标小于  可以防止棋子的行下标的时候,本次取胜路径就不可取if tuple[0] <self.table_num[tuple[1]]:sign=-1break#统计取胜路径上 0 1 2棋子的个数signNum[chessborad[tuple[0]][tuple[1]]]+=1if sign==1:#已经取胜了,取胜设置权重为3000if signNum[1] == 3:maxscore += 3000# 2缺1#2个1棋子,另一个是空白位置,有很大可能赢,所以设置权重为100elif signNum[1] == 2 and signNum[0] == 1 :maxscore += 100 * signNum[1]# 2个1棋子,1个2棋子,赢得可能再小一点elif signNum[1] == 2 and signNum[2] == 1:maxscore += 10 * signNum[1]# 3缺2#只有一个1棋子,其他两个位置是空白的if signNum[1] == 1 and signNum[0] == 2:maxscore += 50 * signNum[1]#1个1棋子  2个2棋子elif signNum[1] == 1 and signNum[2] == 2:maxscore += 10 * signNum[1]# min的# 二缺1if signNum[2] == 3:minscore += 3000if signNum[2] == 2 and signNum[0] == 1 :minscore += 100 * signNum[2]elif signNum[2] == 2 and signNum[1] == 1:minscore += 10 * signNum[2]# 3缺2elif signNum[2] == 1 and signNum[0] == 2:minscore += 50 * signNum[2]elif signNum[2] == 1 and signNum[1] == 2:minscore += 10 * signNum[2]return maxscore - minscoreif __name__ == '__main__':Game()

人工智能-三连子游戏设计和实现相关推荐

  1. 北京工业大学 大一C语言课程设计--四子棋(Bingo)(连子游戏)in TurboC 3.0

          连子游戏设计报告书                               2009年12月 目录页 目录 1 需求分析... 3 1.1功能与数据需求... 3 1.1.1题目要求的 ...

  2. 人工智能在游戏设计中的应用

    人工智能游戏的快速发展,为计算机游戏产业提供了新的机遇,目前人工智能技术已经成为优秀计算机游戏开发中不可缺少的部分. 这里首先介绍人工智能游戏的概念以及基本的游戏人工智能技术,然后介绍游戏角色的指导与 ...

  3. 51单片机系列(三)51 单片机游戏设计 —— 双人对战小游戏(石头剪刀布)

    本博客51单片机实训系列,旨在记录本人在大学上单片机技术这门课时所做的课程实训内容,并与大家分享基于51单片的课程作业,如果作业中的某些细节和代码能给大家一点启发那就更好了,希望大家能用51单片机做出 ...

  4. # 使用Scratch 3.0制作弹球游戏(三)——游戏关卡及难度设计

    目录 使用Scratch 3.0制作弹球游戏(三)--游戏关卡及难度设计 1.第一关 1.1第一关游戏角色需求 1.2第一关游戏玩法设计 1.3角色设计--球 1.4角色设计--接球平台 1.5背景设 ...

  5. 游戏人工智能 读书笔记 (三) 游戏和人工智能的相互影响

    作者:苏博览,腾讯互动娱乐高级研究员 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. 原文链接:https://wetest.qq.com/lab/view/412.html 本文内容 ...

  6. 《游戏设计艺术(第二版)》第三章个人学习

    目录 第三章 体验发生于场景 流沙般的平台 私人场景 炉边 工作台 读书角 公共场合 剧场 竞技场 博物馆 半公开/半私人场景 游戏桌 操场 随时随地 场景之间的混合与搭配 3号透镜:场景 第三章 体 ...

  7. Silverlight游戏设计(Game Design):(八)三国策(Demo) 之 “江山一统”①

    教程中无数次提到<三国>系列,那段荡气回肠的过去一直深刻烙印于心.我深爱中国的历史,因此我从不去公开评论政治,因为它是我的母亲:我执着于策略游戏,闲暇时爱不离手的依旧是NDS中的<三 ...

  8. 游戏设计梦工厂读书笔记(三)

    玩家 游戏是一种体验式设计,玩家在参与游戏后就进入了魔法圈.在魔法圈中,游戏的规则产生了一定的权力并给予玩家一些可能性. 在游戏规则的约束下,我们可以做一些我们从来不会想到的事,例如:射击.杀戮.背叛 ...

  9. 【游戏设计】游戏设计师修炼秘籍 读书笔记三(说说IOS游戏水果忍者和捕鱼达人的事情)

    1.游戏设计的基本 笔记: [从物理或者化学的角度来说是原子,那我们从游戏设计讲的话就是游戏元素,我们定位的游戏的主角,我们通过什么东西诠释我们游戏的主体.针对IOS的游戏,比如风靡全球的水果忍者,他 ...

最新文章

  1. [UWP]了解模板化控件(5):VisualState
  2. Nginx配置https,反向代理多实例tomcat的操作记录
  3. 正在研究d2010的dcu格式
  4. opencv for linux mac,opencv for Java在MacOS 10.10安装
  5. web前端培训分享Electron之Main Process API
  6. Visual Prolog 的 Web 专家系统 (10)
  7. UE4 在C++ 动态生成几何、BSP体、BRUSH ---- MESH_GENERATION
  8. 基于Spring Security的认证方式_SpringBoot认识_Spring Security OAuth2.0认证授权---springcloud工作笔记121
  9. Linux 命令(77)—— killall 命令
  10. Python学习入门基础教程(learning Python)--5 Python文件处理
  11. 操作系统实验二、进程通信实验——f(x,y) = f(x) + f(y)
  12. 《The Django Book 2.0》中文版笔记
  13. 计算机软件著作权可以同时寄多份,软件著作权可以挂几个人,最多几个作者?...
  14. Office文档忘保存了?办公必学技能:快速找回未保存的文档
  15. 如何在同一台电脑上打开多个iPhone模拟器
  16. php干货网,php高手干货【必看】
  17. 【读论文0628】Does Learning Require Memorization? A Short Tale about a Long Tail∗
  18. null id in entry (don‘t flush the Session after an exception occurs)解决思路
  19. nginx反向代理实践:将某个指定的域名代理到指定的服务
  20. python实现连环阵

热门文章

  1. 如何将Mac磁盘映像转换为其他格式?
  2. ltrim and rtrim函数使用例子
  3. 电子书PDF裁减、合并工具及脚本
  4. java飘动_OpenGL -- 飘动的旗帜 (java)
  5. ESP8266开发之旅 应用篇⑤ WiFi探针
  6. 汉诺塔问题(+递推公式)
  7. 来看看中国计算机视觉行业发展有什么动态?
  8. 解决办法:E: 仓库 “......” 没有 Release 文件。
  9. IntArray和Array<Int>
  10. 天池算法大赛思路和代码分享