以低代码和高代码(原生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搜索算法原理供选学用。

极大极小搜索

参考


圆形节点为红方走棋的局面,方形节点为黑方走棋局面,红色数字为局面估值(也就是红方的优势)。深度优先遍历这个搜索树。

  1. 初始局面为A,该红方走棋。红方有B1、B2、B3三种走法。

  2. 假设红方选择第一种走法,走到了局面B1。

  3. 在局面B1,该黑方走棋。黑方有C1、C2、C3三种走法。

  4. C1、C2、C3是叶子节点,调用局面评估函数,算得局面估值(也就是红方优势)分别是8、10、15。

  5. 回到局面B1,此时该黑方走棋。黑方后完后,红方优势越小,对黑方越有利。黑方自然会走到对自己最有利的C1局面。此时我们认为,B1局面的估值是8。

  6. 同样的方法,B2、B3的估值分别是5、10。

  7. 回到初始局面A,红方走棋。现在已经知道,选择第一种走法,最终估值为8;选择第二种走法,最终估值为5;选择第三种走法,最终估值为10。估值就是红方的优势,红方自然会选择对自己优势最大的走法,也就是走到局面B3。

  8. 可知行棋路线为A -> B3 -> C7。

A点选择估值最大的局面,称为极大点;B1、B2、B3选择估值最小的局面,称为极小点。

Alpha-Beta搜索

参考

  1. 叶子节点(depth == 0),调用评估函数并返回分值

  2. 当前局面全部走法

  3. 在根节点,Alpha取负无穷,Beta取正无穷。当函数递归时,Alpha和Beta不但取负数而且要交换位置。Alpha表示当前搜索节点走棋一方搜索到的最好值,任何比它小的值对当前节点走棋方都没有意义。Beta表示对手目前的劣势,这是对手所能承受的最坏结果。Beta值越大,表示对方劣势越明显;Beta值越小,表示对方劣势也越小。在对手看来,他总是希望找到一个对策不比Beta更坏。如果当前节点返回Beta或比Beta更好的值,作为父节点的对方就绝不会选择这种策略。

准备深入研究的同学请到https://www.zcappp.cn/course/chess页面后,点击右侧的【克隆】按钮,把整个游戏复制一份随意玩弄更改。

更多教学视频请移步哔哩哔哩空间:https://space.bilibili.com/475645807,里面不仅有各种前端可视化案例演示和讲解,还有多个完整功能的网站应用案例的开发过程演示和讲解。

【中国象棋人机对战】引入了AI算法,学习低代码和高代码如何混编并互相调用的更多相关文章

  1. 基于Qt Creator实现中国象棋人机对战, c++实现

    GitHub地址: https://github.com/daleyzou/wobuku 这是自己大一学完c++后,在课程实践中写过的一个程序,实现象棋人机对战的算法还是有点难的, 自己当时差不多也是 ...

  2. Qt版本中国象棋开发(一)

    开发目的:实现象棋人机对战简单AI,网络对战,移植到android中. 开发平台:windows10 + Qt5.4 for android 开发语言:C++ 开发过程:1.棋盘绘制: 方法一:重写  ...

  3. 中国象棋游戏Chess(3) - 实现走棋规则

    棋盘的绘制和走棋参看博文:中国象棋游戏Chess(1) - 棋盘绘制以及棋子的绘制,中国象棋游戏Chess(2) - 走棋 现在重新整理之前写的代码,并且对于每个棋子的走棋规则都进行了限制,不像之前那 ...

  4. Java五子棋小游戏(控制台纯Ai算法)

    Java五子棋小游戏(控制台纯Ai算法) 继续之前的那个五子棋程序 修复了一些已知的小Bug 这里是之前的五子棋程序 原文链接 修复了一些算法缺陷 本次增加了AI算法 可以人机对战 也可以Ai对Ai看 ...

  5. 中国象棋程序的设计与实现(五)--回答CSDN读者的一些问题

    最近写了很多文章,同时,也上传了很多免积分的FansUnion原创的优质资源,有兴趣的同学可以看来我的CSDN博客瞧瞧 http://blog.csdn.net/FansUnion. 近期,收到了不少 ...

  6. "人机"对战:电脑太简单了,我是射手 skr~skr~skr

    9月17日,2018 世界人工智能大会在上海拉开帷幕.在 SAIL 榜单入围项目中,我看到了小爱同学.小马智行.微软小冰.腾讯觅影等等,这不仅让我大开了眼界,也不禁让我感慨 AI 的发展神速.犹记得去 ...

  7. 基于QT的中国象棋

    基于QT的中国象棋,可实现人人对战,人机对战,联网对战,可显示棋谱,可悔棋. 还有一些小毛病,我之后会找空把这个DEMO重新修改一下上传 链接:https://pan.baidu.com/s/1-eM ...

  8. Qt版本中国象棋开发(四)

    内容:走法产生 中国象棋基础搜索AI, 极大值,极小值剪枝搜索, 静态估值函数 理论基础: (一)人机博弈走法产生: 先遍历某一方的所有棋子,再遍历整个棋盘,得到每个棋子的所有走棋情况(效率不高,可以 ...

  9. 中国象棋引擎的C#源代码

    以前写的中国象棋引擎的C#源程序,可在VS2010中编译运行,由于个人精力有限,难以完成后续的开发工作,如果谁感兴趣,请关注微信公众号(“申龙斌的程序人生”,ID:slbGTD),发送后台消息“象棋引 ...

随机推荐

  1. 云厂商 RDS MySQL 怎么选

    1. 摘要 为了让大家更好的了解各云厂商在RDS MySQL数据库功能上的差异,也为给准备上云的同学做个参考,本文将对阿里云.腾讯云.华为云和AWS 的 RDS MySQL数据库进行对比说明. 从一个 ...

  2. 伪元素选择器,选择器优先级,CSS修改文字属性,CSS修改字体属性,CSS修改其他属性

    伪元素选择器 未使用元素选择器的效果 第一行:伪元素选择器:选择部分内容 第二行:伪元素选择器:选择部分内容 伪元素选择器:选择部分内容 伪元素选择器:选择部分内容 ::selection:选择指定元 ...

  3. HMS Core使能AI智慧体验,共建创新应用生态

    5月17日,2022年搜狐科技峰会成功举办,峰会汇聚各界大咖,共同探讨AI 技术的深入应用以及行业数字化的发展趋势.华为终端云服务应用生态BU总裁望岳发表题为<使能AI智慧体验,共建创新应用生态 ...

  4. 665. Non-decreasing Array - LeetCode

    Question 665. Non-decreasing Array Solution 题目大意: 思路:当前判断2的时候可以将当前元素2变为4,也可以将上一个元素4变为2,再判断两变化后是否满足要求 ...

  5. OAuth2.0笔记

    OAuth2.0笔记 角色 一般资源服务器和授权服务器是一个 资源拥有者 客户端应用 资源服务器 授权服务器 客户端类型 OAuth 2.0规范定义了两种客户端类型: 保密的:web应用 公有的:用户 ...

  6. Clash 规则的写法

    这篇博文是针对 CFW 写的. 最近尝试从 v2 转向使用 Clash.基于一个简单的需求:用 Spotify 听专的时候用代理,用 AM 听专的时候直连,我参考了以下完成了我的规则: CFW 官网的 ...

  7. 【原创】eNSP路由器启动#号问题排查

    1.删除拖出来的设备,重新拖出来一台---我用过[有时候好使] 2.确保Ensp的设置-工具-Virtual Box安装目录是否正确--我也遇到过[尤其是卸载掉Virtual Box重装之后] 3.确 ...

  8. Java - happens-before

    Java - happens-before JSR-133对 happens-before 关系的定义如下: 如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个 ...

  9. go-zero 微服务实战系列(二、服务拆分)

    微服务概述 微服务架构是一种架构风格,它将一个大的系统构建为多个微服务的集合,这些微服务是围绕业务功能构建的,服务关注单一的业务功能,这些服务具有以下特点: 高度可维护和可测试 松散的耦合 可独立部署 ...

  10. JavaScript中的??和?.和??=操作符

    JS中两种不常使用但挺实用的操作符:??和?. 一起来了解并学会使用它们吧: 空值合并操作符:?? 只有当操作符左侧为null或undefined时才会返回操作符右侧的值,否则返回左侧的值. eg: ...