Qt稍玩玩人机对弈井字棋(五子棋) 您所在的位置:网站首页 我想看五子棋的字 Qt稍玩玩人机对弈井字棋(五子棋)

Qt稍玩玩人机对弈井字棋(五子棋)

2024-07-01 21:52| 来源: 网络整理| 查看: 265

        还记得高中时候,有个女生说她语文方格作业纸上玩五子棋还挺厉害的。咱也是真玩不过,还是推给同桌,让他玩去了。而现在也是想起来才有了灵感做一个井字棋的。

近期使用Qt

        说起来,我去年装Ubuntu玩双系统玩了半年到现在不觉已经有一年了,之前也在Windows用Qt做了扫雷,还有前几天通过adb命令直接每隔会儿发手机截图到电脑上“模拟远程同步”:

这是我的手机画面

        只是玩玩,所以看完效果代码删了。adb工具去谷歌官网下载,手机打开设置的开发者选项,把这四个打开

        第一次需要USB线,后面安卓11设置里打开无线调试就可以在同一个局域网下联上了。

在adb工具的platform-tools文件夹下,打开控制台:

在这输入并回车

        输入.\adb.exe connect 主机ip和端口号,在手机设置无线调试里有这个信息,注意英文冒号隔开。

        因为代码删了,大概的adb shell命令逻辑说一下就是:

这里电脑上adb工具路径要换成对应正确的位置。

        借助C语言的system函数可以加上循环语句,不断截图保存上传电脑。当然也有人借这个截图分析像素信息,调用GetPixel()自己写函数分析,自动点击和滑动屏幕的。

        顺便把模拟点击和滑动的命令也简单封装一下公布:

        安卓手机屏幕上的点坐标可以设置的开发者选项里打开显示指针来获取。

这是我在Ubuntu上用的代码:

正式说明

        我在VM虚拟机里装上Ubuntu(还不是专业版自带的Hpyer-v里的linux虽然效率高,但是没有声音),版本是官网下载的ubuntu-21.10-desktop-amd64.iso,短期支持版,比较新适合玩玩尝鲜。然后安排好之后,https://download.qt.io下载的qt5.14.2。简单说就是chmod +x加权之后运行就安装上的。后面通过vim /etc/environment加入qt的bin和好像是tools目录,虽然现在不建议这种直接加环境变量,之后随便写一个文件报错还缺一个opengl的依,通过终端sudo apt install 给安装上就好。

        这时候我就想随便写点什么吧。原本就想写个小孩玩的井字棋,九宫格画×和O的那种,所以项目名英文就叫九格棋。很快就想好写完了。于是一想,这种平局多,没意思。

        调整了一下,改成可以改棋盘行列数/棋盘大小的版本,相应的大于五行五列不能再要求人满足六七个棋子连线这种了,有困难。于是就变成了五子棋。

        最后,又一想这没意思,难道自己和自己玩吗?简单写个低劣的算法来实现人机对弈,于是随着需求的不断升级,最后就是现在看到的这样子能人机对弈了。

没去找图片,所以界面很朴实无华

        就几百行代码,不需要过多的解释,我用PPT画了类和流程图,仔细看这个就知道函数调用关系了,源代码上有简洁有力的注释。后面我介绍一下我低劣的井字棋算法就好。

我的井字棋运作流程

        

        这个虚拟棋手类不计从QObject继承的话,其实只有上述3个成员。这是C语言函数的封装,也便于管理。

        1.autoPlay()是对外公有接口。先从人的思路出发,实现对弈的原理就在于对所有能落棋子的空白点进行评估,然后选出最合适的位置下棋,那么设计算法也应该是这个流程。

代码中我声明了一个数组,用来储存所有点的评估价值分数。接着循环遍历全部的非空白点,调用评估函数(当下一步考虑当下一步的事,这个后续再实现,就当它已经存在了,老C语言思维)。接着找出评估后的最大值,可以看到又是循环遍历数组,至于我写的随机数qrand()其实没有必要。但是玩嘛,顺手写上去了,也可以删了。最后自然实在价值最高的点落棋子。

        2.下面实现评估函数beginAssess()。人下棋要讲究知己知彼,所以这里的思想是先对这个空白点周围的对方棋子做分析,再对我方棋子做分析,可以看到我最后return score1 + score2;是一个综合评定结果。

        可能让棋子连成线的方向可以定为四个,分别是上下、左右、左斜、右斜。因此创建一个用来统计周围对方棋子连续成直线的棋子数的数组,另一个存储可落子端点数的数组,是我个人思维想连续的棋子两端如果没有被堵住或者在棋盘边缘无法延伸,会相对更有下棋落子的潜力。

        在此如果写上统计各个方向连续棋子数和端点可用数的代码,那这个函数太臃肿,可读性会比较差。所以,还是模块化编程思维,把大事化成一步一步有序的小事来处理,这里仍然假定getSeriesSituation()已经被我实现出来了。

        接着肯定是要“抓大头”,也就是抓住事物的主要方面,稍微考虑事物的次要方面。四个直线方向,那边连续棋子最多,那就主要考虑哪边,其他方向只要知道有没有发展可能(表明潜力)就可以了。又是一个循环遍历寻找最大值的过程,代码中allSeries就是用来记录四个方向上有几个方向能有机会连成线(其实也就是旁边有没有紧挨着的点)。

        最后,可以看到我给已经有连续棋子数是1~4的分别评分情况,这个计算公式是我自己不违背常理的情况下瞎写的,没有事实统计依据。道理就是对方已经有1个棋子,那我在相邻位置下棋意义是就可以阻止其变成2连子……可以看到代码上下对称的,同样运作逻辑。

        当然这种简单的只考虑单一连续棋子、并且评分计算公式的形式和系数也没有基于大数据统计,最后下棋一定会有一些思维漏洞,一定会在关键时候下到非最佳的点(甚至毫无意义的点)。况且是只能静态分析残局利益,而整体最优解和局部最优解不一定相等。当然要不依赖大数据统计下的机器学习,想写出优质棋局算法,那作者的五子棋水平一定不能太低。恰好我的棋技异常菜鸡,可能在经常下棋的人眼里:“这什么玩意,这么设计人机分分钟被拿下”,所以这个算法比较适合小白自己练手。

        3.最后来实现一下统计棋子连线情况和端点情况的getSeriesSituation()。

以左右为例,先考虑右边,左边同理。循环里x + i < line先判断没有越界,对应汇编入栈顺序是先右边的表达式、后左边的表达式入栈,亦或是只进寄存器,最后执行对比都是先左后右。GCC下反汇编证明:

调试代码反汇编证明

这个rsp+0x2c,栈顶下方2C就是变量b的内存地址。b移入edx再test进行按位与操作标志位判断是否>0,再cmp对比变量a(即0xA)。

右边的chessArray[x+i][y] == player在循环下每次右移棋盘一格,连续点计数+1,直到遇到别的棋子或空白,此时执行else内语句判断没有越界且是空白,则端点空白点的计数+1。

后续都是一样的逻辑,Ctrl+CV微改一下,就好了。

        这个低级的棋局算法明显的局限性前已叙述,评分的算式系数也有待不断的实战数据调整,但是玩嘛,代码量也很小,更重要的是练习编程思想。

        最后我在linux上使用linuxdeployqt-continuous-x86_64.AppImage打包发布时,终端提醒说系统版本太高,要求换较老的版本。所以,我换到Windows11上,用以前自己编译的qmake静态编译了一下。Qt的库还是有点大的,静态编译完都大约17M。

Windows静态编译单文件:https://www.jianguoyun.com/p/DQX-1fUQp6azChiG4rYEIAA

源代码:https://www.jianguoyun.com/p/DcK6HlwQp6azChiK4rYEIAA



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有