【中国象棋人机对战】引入了AI算法,学习低代码和高代码如何混编并互相调用
以低代码和高代码(原生JS代码)混编的方式引入了AI算法,学习如何使用表达式调用原生代码的。
整个过程在众触低代码应用平台进行,适合高阶学员。
AI智能级别演示
AI算法分三个等级,体现出来的智能水平不同。
切换皮肤
切换棋阵
各棋子的走法规则
先动手玩一玩:https://chinese-chess.zc-app.cn/z
详尽的的教学请移步哔哩哔哩视频:https://www.bilibili.com/video/BV1e44y1j7Ab
初始数据
onReady里:
$v.设置 = {AI搜索深度: 3, 外观: { cur: 1, arr: ["stype1", "stype2", "stype3"] }, 棋阵: { cur: 0, arr: ["初始阵", "八卦阵", "七星阵", "很二阵", "测试"] }}
$v.F5 = 1
$c.obj.棋子
大写字母的是对方黑子(my为-1,表示非我方),小写字母的是我方红子(my为1)。
【图】表示棋子图片URL的位置。因为所有图片都是放在一个文件夹整理好了整体上传的,位置都固定的。
$c.obj.棋阵
认真观察初始阵(标准棋盘)和图二里面棋子的布阵,不难发现它们的对应关系。
$c.obj.估值
各棋子在棋阵中不同位置的估值,是AI走棋的依据,AI用这个来评估一个棋子应该如何走才能得到更高的价值。
$c.obj.ui
里面预设了3种UI样式,根据其预设图片位置通过new $w.Image()
生成所有棋子及着点、外框、棋盘的图片。只有图片加载完成了才能进行桌布的绘制,所以在最后一个图片的onload函数里调用$c.exp.绘制
。
画布与刷新
一个空画布canvas放在挂载组件里,挂载组件的【重新挂载表达式】设为$v.F5
,当$v.F5
的值发生变化时会触发挂载事件,挂载事件里会进行初始化$c.exp.初始化.exc()
。
切换皮肤、棋阵、级别,重玩都是改变某个设置后改变$v.F5
的值卸载后重新初始化的。
初始化
...
$v.画布.addEventListener("click", func($c.exp.走棋))
...
$c.exp.摆棋子.exc()
// 黑子为红字估值位置的倒置
$l.黑子 = ["C","M","X","S","J","P","Z"]
$l.黑子.forEach('$c.obj.估值[$x] = clone($c.obj.估值[$x.toLowerCase()]).reverse()')
$w.AI = { 估值: $c.obj.估值 }
$w.eval($c.js.AI)
$w.eval($c.js.走法)
画布上添加有click事件监听器,点击时开始走棋。
$c.obj.估值
预设了红子的估值,把它位置颠倒一下就成了黑子的估值。颠倒前要记得clone一下才不会打乱原来的数据。
在AI走棋时会大量计算各种走法的估值,走法函数被调用的次数非常多。瞬间被海量调用的函数应该优先考虑原生代码而非表达式以提高速度,所以把它放在$c.js.走法
中。
我们把AI算法相关的放在window.AI下以方便调用。注意观察$c.js.AI
里面的原始JS代码有AI.走棋
、AI.AB搜索
和AI.评估棋阵
三个函数,通过$w.eval($c.js.AI)
执行一下,这三个函数就都放在$w.AI
里了($w就是window的简写)。
摆棋子
根据其在棋阵中的位置生成各个棋子。
$v.棋子 = {}
$v.棋阵.forEach('$x.forEach("$x ? $v.棋子[$x] = $w.Object.assign({x: $index, y: $ext.$index, key: $x, 名: $x.slice(0,1)}, $c.obj.棋子[$x.slice(0,1)]) : null")')
走棋
根据点击位置计算出其在棋盘中的坐标(x、y轴)。
$arg是click监听器传进来的参数,$arg[0]就是第一个参数event了。
如果坐标不在棋盘内就忽略点击动作。
stopIf($v.结束)
$l.x = $w.Math.round(($arg[0].pageX - $v.画布.offsetLeft() - $v.ui.pointStartX - 20) / $v.ui.spaceX)
$l.y = $w.Math.round(($arg[0].pageY - $v.画布.offsetTop() - $v.ui.pointStartY - 20) / $v.ui.spaceY)
stopIf($l.x < 0 || $l.x > 8 || $l.y < 0 || $l.y > 9)
$l.棋子 = $v.棋阵[$l.y][$l.x]
$l.棋子 ? $l.棋子 = $v.棋子[$l.棋子] : ""
$l.棋子 ? ($l.棋子.my == 1 ? $c.exp.选子.exc() : ($v.已选 && $v.已选.key !== $l.棋子.key ? $c.exp.吃子.exc() : "")) : ($v.着点.find('$x[0] == $l.x && $x[1] == $l.y') ? $c.exp.落子.exc() : "")
如果点击的是我方棋子则执行【选子】动作,给已选棋子添加外框,根据已选棋子在棋阵中的位置执行走法计算得出其所有着点。
选子
$v.已选 = $l.棋子
$v.外框 = { x: $v.已选.x, y: $v.已选.y }
$v.着点 = $w.走法[$v.已选.走法]($v.棋阵, $v.棋子, $v.已选.my, $v.已选.x, $v.已选.y)
$c.exp.绘制.exc()
$v.选子声.play()
如果点击的是对方棋子且在上次已选棋子的着点上则执行【吃子】动作
吃子
把走棋路径推入【历史】中,并把两个棋子的位置添加外框。
删除原棋子在棋阵中的位置并把新位置换成我方棋子。
删除被吃棋子并把原已选棋子的位置挪到新位置。
如果被吃的是【将】则结束。
稍等片刻后开始【AI走棋】(因为AI走得太快人反应不过来)。
stopIf(!$v.着点.find('$x[0] == $l.x && $x[1] == $l.y'), 'log("吃不到哦!")')
$v.历史.push([$v.已选.x, $v.已选.y, $l.x, $l.y])
$v.棋阵[$v.已选.y][$v.已选.x] = undefined
$v.棋阵[$l.y][$l.x] = $v.已选.key
$v.棋子[$l.棋子.key] = undefined
$v.外框 = { x: $v.已选.x, y: $v.已选.y, x2: $l.x, y2: $l.y }
$v.已选.x = $l.x
$v.已选.y = $l.y
$v.已选 = undefined
$v.着点 = []
$c.exp.绘制.exc()
$v.点击声.play()
$l.棋子.走法 == "j" ? nop(alert($l.棋子.my == 1 ? "你赢了!" : "你输了!"), $v.结束 = true) : ""
timeout(50)
$c.exp.AI走棋.exc()
如果点击的不是棋子但是在已选棋子的着点上则执行【落子】动作
落子
$v.棋阵[$v.已选.y][$v.已选.x] = undefined
$v.棋阵[$l.y][$l.x] = $v.已选.key
$v.历史.push([$v.已选.x, $v.已选.y, $l.x, $l.y])
$v.外框 = { x: $v.已选.x, y: $v.已选.y, x2: $l.x, y2: $l.y }
$v.已选.x = $l.x
$v.已选.y = $l.y
$v.已选 = undefined
$v.着点 = []
$c.exp.绘制.exc()
$v.点击声.play()
timeout(50)
$c.exp.AI走棋.exc()
类似【吃子】动作
悔棋
就是重置整个棋阵棋子,根据【历史】的足迹重走一遍,直到最后两步。
走到最后一步时添加外框。
$v.棋阵 = clone($c.obj.棋阵[$v.设置.棋阵.arr[$v.设置.棋阵.cur % 5]])
$c.exp.摆棋子.exc()
$v.历史.pop()
$v.历史.pop()
$v.历史.forEach('$l.走子 = $v.棋阵[$x[1]][$x[0]]; $l.吃子 = $v.棋阵[$x[3]][$x[2]]; $l.吃子 ? $v.棋子[$l.吃子] = undefined : ""; $v.棋子[$l.走子].x = $x[2]; $v.棋子[$l.走子].y = $x[3]; $v.棋阵[$x[3]][$x[2]] = $l.走子; $v.棋阵[$x[1]][$x[0]] = undefined; $v.历史.length - 1 ? $v.外框 = { x: $x[0], y: $x[1], x2: $x[2], y2: $x[3] } : ""')
$c.exp.绘制.exc()
AI走棋
$v.AI着 = $w.AI.走棋(clone($v.棋阵), clone($v.棋子), $v.设置.AI搜索深度, -1)
stopIf(!$v.AI着, 'alert("你赢了!"); $v.结束 = true')
$v.历史.push($v.AI着)
$l.已选 = $v.棋阵[$v.AI着[1]][$v.AI着[0]]
$l.已选 ? $l.已选 = $v.棋子[$l.已选] : ""
$l.被吃 = $v.棋阵[$v.AI着[3]][$v.AI着[2]]
$c.exp[$l.被吃 ? "AI吃子" : "AI落子"].exc()
$c.exp.绘制.exc()
$v.点击声.paused ? "" : timeout(300)
$v.点击声.paused ? "" : timeout(300)
$v.点击声.play()
前面初始化的时候我们有把原生代码【AI.走棋】$w.eval()以后放在$w里面了,这里直接执行原生代码的【$w.AI.走棋】得到AI认为的最佳着点。(总结:众触表达式和原生代码是通过window这个媒介来互相认识并调用的)
如果没得到着点则AI已被将死。
如果着点上是我方棋子则执行【AI吃子】,否则执行【AI落子】
AI吃子
$v.棋阵[$v.AI着[1]][$v.AI着[0]] = undefined
$v.棋阵[$v.AI着[3]][$v.AI着[2]] = $l.已选.key
$l.已选.x = $v.AI着[2]
$l.已选.y = $v.AI着[3]
$v.外框 = { x: $v.AI着[0], y: $v.AI着[1], x2: $v.AI着[2], y2: $v.AI着[3] }
$l.被吃 == "j0" || $l.被吃 == "J0" ? nop(alert($v.棋子[$l.被吃].my == -1 ? "你赢了!" : "你输了!"), $v.结束 = true) : ""
$v.棋子[$l.被吃] = undefined
AI落子
$v.棋阵[$v.AI着[1]][$v.AI着[0]] = undefined
$v.棋阵[$v.AI着[3]][$v.AI着[2]] = $l.已选.key
$v.外框 = { x: $v.AI着[0], y: $v.AI着[1], x2: $v.AI着[2], y2: $v.AI着[3] }
$l.已选.x = $v.AI着[2]
$l.已选.y = $v.AI着[3]
如果你打开原生代码学习AI搜索算法,估计很难明白它是如何实现人工智能的,下面摘抄了一点AI搜索算法原理供选学用。
极大极小搜索
圆形节点为红方走棋的局面,方形节点为黑方走棋局面,红色数字为局面估值(也就是红方的优势)。深度优先遍历这个搜索树。
初始局面为A,该红方走棋。红方有B1、B2、B3三种走法。
假设红方选择第一种走法,走到了局面B1。
在局面B1,该黑方走棋。黑方有C1、C2、C3三种走法。
C1、C2、C3是叶子节点,调用局面评估函数,算得局面估值(也就是红方优势)分别是8、10、15。
回到局面B1,此时该黑方走棋。黑方后完后,红方优势越小,对黑方越有利。黑方自然会走到对自己最有利的C1局面。此时我们认为,B1局面的估值是8。
同样的方法,B2、B3的估值分别是5、10。
回到初始局面A,红方走棋。现在已经知道,选择第一种走法,最终估值为8;选择第二种走法,最终估值为5;选择第三种走法,最终估值为10。估值就是红方的优势,红方自然会选择对自己优势最大的走法,也就是走到局面B3。
可知行棋路线为A -> B3 -> C7。
A点选择估值最大的局面,称为极大点;B1、B2、B3选择估值最小的局面,称为极小点。
Alpha-Beta搜索
叶子节点(depth == 0),调用评估函数并返回分值
当前局面全部走法
在根节点,Alpha取负无穷,Beta取正无穷。当函数递归时,Alpha和Beta不但取负数而且要交换位置。Alpha表示当前搜索节点走棋一方搜索到的最好值,任何比它小的值对当前节点走棋方都没有意义。Beta表示对手目前的劣势,这是对手所能承受的最坏结果。Beta值越大,表示对方劣势越明显;Beta值越小,表示对方劣势也越小。在对手看来,他总是希望找到一个对策不比Beta更坏。如果当前节点返回Beta或比Beta更好的值,作为父节点的对方就绝不会选择这种策略。
准备深入研究的同学请到https://www.zcappp.cn/course/chess页面后,点击右侧的【克隆】按钮,把整个游戏复制一份随意玩弄更改。
更多教学视频请移步哔哩哔哩空间:https://space.bilibili.com/475645807,里面不仅有各种前端可视化案例演示和讲解,还有多个完整功能的网站应用案例的开发过程演示和讲解。
【中国象棋人机对战】引入了AI算法,学习低代码和高代码如何混编并互相调用的更多相关文章
- 基于Qt Creator实现中国象棋人机对战, c++实现
GitHub地址: https://github.com/daleyzou/wobuku 这是自己大一学完c++后,在课程实践中写过的一个程序,实现象棋人机对战的算法还是有点难的, 自己当时差不多也是 ...
- Qt版本中国象棋开发(一)
开发目的:实现象棋人机对战简单AI,网络对战,移植到android中. 开发平台:windows10 + Qt5.4 for android 开发语言:C++ 开发过程:1.棋盘绘制: 方法一:重写 ...
- 中国象棋游戏Chess(3) - 实现走棋规则
棋盘的绘制和走棋参看博文:中国象棋游戏Chess(1) - 棋盘绘制以及棋子的绘制,中国象棋游戏Chess(2) - 走棋 现在重新整理之前写的代码,并且对于每个棋子的走棋规则都进行了限制,不像之前那 ...
- Java五子棋小游戏(控制台纯Ai算法)
Java五子棋小游戏(控制台纯Ai算法) 继续之前的那个五子棋程序 修复了一些已知的小Bug 这里是之前的五子棋程序 原文链接 修复了一些算法缺陷 本次增加了AI算法 可以人机对战 也可以Ai对Ai看 ...
- 中国象棋程序的设计与实现(五)--回答CSDN读者的一些问题
最近写了很多文章,同时,也上传了很多免积分的FansUnion原创的优质资源,有兴趣的同学可以看来我的CSDN博客瞧瞧 http://blog.csdn.net/FansUnion. 近期,收到了不少 ...
- "人机"对战:电脑太简单了,我是射手 skr~skr~skr
9月17日,2018 世界人工智能大会在上海拉开帷幕.在 SAIL 榜单入围项目中,我看到了小爱同学.小马智行.微软小冰.腾讯觅影等等,这不仅让我大开了眼界,也不禁让我感慨 AI 的发展神速.犹记得去 ...
- 基于QT的中国象棋
基于QT的中国象棋,可实现人人对战,人机对战,联网对战,可显示棋谱,可悔棋. 还有一些小毛病,我之后会找空把这个DEMO重新修改一下上传 链接:https://pan.baidu.com/s/1-eM ...
- Qt版本中国象棋开发(四)
内容:走法产生 中国象棋基础搜索AI, 极大值,极小值剪枝搜索, 静态估值函数 理论基础: (一)人机博弈走法产生: 先遍历某一方的所有棋子,再遍历整个棋盘,得到每个棋子的所有走棋情况(效率不高,可以 ...
- 中国象棋引擎的C#源代码
以前写的中国象棋引擎的C#源程序,可在VS2010中编译运行,由于个人精力有限,难以完成后续的开发工作,如果谁感兴趣,请关注微信公众号(“申龙斌的程序人生”,ID:slbGTD),发送后台消息“象棋引 ...
随机推荐
- Java 语言实现简易版扫码登录
基本介绍 相信大家对二维码都不陌生,生活中到处充斥着扫码登录的场景,如登录网页版微信.支付宝等.最近学习了一下扫码登录的原理,感觉蛮有趣的,于是自己实现了一个简易版扫码登录的 Demo,以此记录一下学 ...
- 使用 Swoole 加速你的 CMS 系统,并实现热更新 (基于 Laravel 框架)
主题:使用 Swoole 加速你的 CMS 系统,并实现热更新 关于 Swoole 的简介不再在此赘述,各位可以自行查看官网的文档进行详细的了解. 本文以 MyCms 为例,简要说明 Swoole 结 ...
- asp.net swagger的使用
最近要从其他系统获取一些数据,准备写个接口让别人把数据塞进来,顺便学习一下如何使用Swagger. 参考大神的教程:asp.net https://mp.weixin.qq.com/s/SHNNQo ...
- 移动端input的disabled属性对字体颜色影响
对于表单输入,input是很好的选择,这次记录主要是正对input的value值字体在Android和iOS(11)设备下显示不同问题: 如下图:1.2的区别主要是分别设置disabled.reado ...
- Fail2ban 术语
filter 过滤器,使用正则表达式定义一个过滤器,从日志中匹配到IP.端口等. action 动作,定义在指定时间段要执行的操作. jail 监禁,jail是一个filter和一个action或者多 ...
- Sass预处理器
CSS预处理器 less,sass和stylus sass:较早,缩进风格 scss:完全兼容css3,使用大括号 编写css的编程语言,引入变量,函数,重复代码等功能,如果编译成css文件 Sass ...
- 秋招如何抱佛脚?2022最新大厂Java面试真题合集(附答案
2022秋招眼看着就要来了,但是离谱的是,很多同学最近才想起来还有秋招这回事,所以纷纷临时抱佛脚,问我有没有什么快速磨枪的方法, 我的回答是:有! 说起来,临阵磨枪没有比背八股文更靠谱的了,很多人对这 ...
- 人体调优不完全指南「GitHub 热点速览 v.22.22」
本周特推又是一个人体调优项目,换而言之就是如何健康生活,同之前的 HowToLiveLonger研究全因死亡率不同,这个项目更容易在生活中实践,比如,早起晒太阳这么一件"小事"便有 ...
- STM32时钟系统配置程序源码深入分析
一.分析程序的目的 最近我在移植实时系统是遇到了一些问题,所以决定深入了解系统时钟的配置过程,当然想要学好stm32的小伙伴也有必要学习好时钟系统的配置,所以我将学习的过程再次记录,有写得不好的地方, ...
- script标签crossorigin属性及同源策略和跨域方法
首先介绍(同源策略) 同源策略是浏览器最核心且基本的安全约定,要求协议.域名.端口都相同为同源,如果非同源时请求数据浏览器会在控制台抛出跨域异常错误,同源策略是浏览器的行为,即使客户端请求发送了,服务 ...