Skip to content

Latest commit

 

History

History
1099 lines (604 loc) · 38.8 KB

报告.md

File metadata and controls

1099 lines (604 loc) · 38.8 KB

基于jQuery与Flask前后端框架,借助AJAX跨域通信

技术的人工智能井字棋小游戏 HTML5 APP 的开发

高清

关键词: Flask HTML5 CSS Javascript Max-Min Search AI

摘要

本次嵌入式作业,完成了井字棋HTML5 APP小游戏的开发,并实现了真机调试,真机安装。本APP前端借助HTML5+CSS+JavaScript,并采用了jQuery框架。

前端实现了及其细腻的动画,赏心悦目的用户界面,以及良好的异步操作性能。

游戏中与人对战的AI使用Min-Max搜索,由运行在阿里云服务器上的python程序实现。前后端通信采用了现代的AJAX CROD(ajax跨域)通信技术,利用HTTP协议将json格式的数据在前后端POST。利用Min-Max搜索的深度,可以调节难度。

实验中AI通信良好,一定难度下可以实现保证和棋或赢棋。

本实验利用HBuilderX, appUploader实现了IOS上APP真机联调与开发者协议下APP的安装。

本教程的脱水版即将发布CSDN与Github上的个人博客。


目录

[TOC]

基本概念

格式原因书写,脱水可以不看

HTML5 APP

HTML5历史

HTML(Hyper Text Markup Language ,超文本标记语言)是互联网发展的基石,目前几乎所有的网站都是基于HTML 进行开发的。HTML5 是HTML 的最新标准,经过近8年的艰辛努力,于2014 年10 月由W3C (万维网联盟) 发布为正式推荐标准。

十年前, Web 只是用来展示一个文档,而现在Web 则成为了应用程序的一个运行平台。互联网的不断发展对网站的功能提出了很多更高的要求,但由于HTML 没有及时地跟进这些需求,很多厂商或组织在HTML 上各自建立了自己的标准,如Flash 、Silv er Light 、Java Fx 等。因为商业上的原因,这些标准往往很难被广泛接受,于是造成了各种解决方案 互相竞争的局面。

当前,移动互联网时代Web 开发主要面临两种困境: (1)有些功能必须借助合适的插件才能实现,例如目前流行的页面上音频和视频的播放大多借助于Flash 插件。 (2) PC 端和移动端应用的多次开发,必须为微软、苹果、安卓等系统设计不同的方案,这意味着一个创业团队或公司必须

HTML5 标准的制定过程正值移动互联网崛起,标准组织成员中的Apple 、Google 、Opera 本身便有着对移动互联网的独立思考和见解,并最终影响HTML 5 的实际成果。设计之初, HTML5 便拥有桌面互联网、移动互联网兼容并蓄的想法,不仅统一了开发方式、网络内容,还做到了访问方式的体验统一化。

HTML5的新特性

相比原来的HTML,HTML5支持:

  • 用于绘画的 canvas 元素
  • 用于媒介回放的 video 和 audio 元素
  • 对本地离线存储的更好的支持
  • 新的特殊内容元素,比如 article、footer、header、nav、section
  • 新的表单控件,比如 calendar、date、time、email、url、search

等等。这些原来需要用Js,CSS实现的功能,可以现在用HTML5直接实现。

HTML5与Flash

Flash将退出历史舞台

2011 年11 月, Adobe 正式宣布停止为移动设备的浏览器开发F las h Pl ayer ,转而全面发展HTML5 技术,并表示“ HTML5 是各种移动平台浏览器中最佳的内容制作和发布解决方案”,正式宣告了Flash 退出移动端开发的舞台。

所以,目前大多数视频网站采用的是双重技术,如果在iOS 上欣赏网站视频,都会自动切换到HTML 5 播放模式上, PC 上则采用Flash 插件。

HTML5的优势

HTML5在很多功能上与Flash存在重叠,但又有显著优势:

  • Flash不开放,是一个完全封闭的系统, Adobe 公司拥有100 % 的技术专利, Adobe 想通过Flash Player 授权来收费,每台移动设备嵌入Flash Player ,预收1 美元。

    而HTML5是开放的标准,网页开发者利用HTML5 就能做出高级的图像、字体、动画以及过渡效果,而不必依赖Flas h 插件。

  • Flash 的安全性漏洞较多,黑客经常利用这些漏洞。2009 年,赛门铁克公司曾经指出,Flash 是最不安全的系统之一。

  • Flash对触屏支持较差,不适用于当前的移动互联网时代。

利用HTML5开发APP的优势

成本比较

HTML5 App 原生App
开发成本 低, 只写一套代码就可以导出不同‘’ 开发成本平台的App 高,不同的移动平台需要完全不同的代码
测试成本 bug少,一个工程代码行数有限, bug多,不同的移动平各自需要各自测试有限
招聘成本 招聘成本低,人门门槛低,人才基数大 人才少,需要招聘不同平台
复用 可与PC及手机浏览器端代码复用 无法复用

难度比较

HTML5 App 原生App
学习难度 低,脚本语言,动态执行 高,需要很多与业务无关的底层逻辑
开源资源 多,开源库多,且所有HTML5网页都可以直接作为参考。不用重复造轮子 相对少

市场推广比较

HTML5 App 原生App
用户获取 多,超级App 如微信朋友周)、搜索引擎 少,主要是应用市场
导流效率 高,广告变用户的转化率明显较高 用户从看到应用的广告到装好该应的门槛和时间都很长,导致折损率高

可见,HTML5 App 的开发效率更高,开发快,更新快,更能适应“快鱼吃慢鱼”的移动互联网市场变化;成本更低,从技术到管理,各项成本大大降低,便于创业,也降低了失败的机率和风险;对于产品的推广也更容易,导流入口多,效率高,流量大。对于用户而言, App 应用的获取,更快速方便, 更省流量; App 的使用更省电量,更省空间。

然而,并不是说HTML5 APP就万五一缺了。相比原生APP,在API的调用,底层硬件实现,还有效率上,HTML5 APP要打折扣。尤其是当设计复杂算法的时候,JS语言的劣势就毕现无疑。这就需要用到之后一节讲到的前后端结合。

前端与后端

前端

所谓前端,主要指的是Web 移动应用三剑客:HTML5 、css 和JavaScript。基于HTML5,CSS 和JavaScript 的移动应用程序将会是未来的趋势。HTML5 移动应用开发并不是单指HTML5 标准,而是指HTM5+CSS3+JavaScript 三项技术:HTML5 负责内容, CSS3 负责外观,JavaScript 负责行为。

“HTML5+CSS+Javascript”的图片搜索结果

后端

网页的内容,需要从远程服务器下载;用户提交的信息,需要在服务器上处理;网页上需要执行的复杂运算,也可以在云服务器上实现。

这些在服务器上实现的功能可以称之为后端。

后端框架

“框架”,可以粗浅的理解为程序运行时调用的库,代码的写作模式。

服务器上的后端程序需要由可以执行HTTP通信的程序运行。常见的框架由:

  • DJango

  • Flask

  • react

  • 等等

前两个由Python实现,最后一个由Node.js实现。本文用简单易学的Flask框架。

Min-Max搜索

本APP的后端AI算法是Min-Max搜索。这是一种棋类游戏中常用的人工智能算法,且有Alpha-Beta剪枝,基于深度学习启发式函数等各种改进。

本APP实现的是井字棋,搜索深度较小,直接采用原始的A-B剪枝就可以实现性能要求。更多算法细节可以参考参考书,本问不做过多解释。

设计流程

需求分析

系统名称 井字棋小游戏(Tic-Tac-Toe)
运行环境 移动端Web浏览器,ios APP(目前针对iphone Xr上)
输入输出 输入用户落子,输出AI落子或输赢判断,网络错误等等
功能描述 参见之后“功能描述”一节
设计目的 自娱自乐的小游戏

详细说明

完成了井字棋HTML5 APP小游戏的开发,并实现了真机调试,真机安装。本APP前端借助HTML5+CSS+JavaScript,并采用了jQuery框架。

前端实现了及其细腻的动画,赏心悦目的用户界面,以及良好的异步操作性能。

游戏中与人对战的AI使用Min-Max搜索,由运行在阿里云服务器上的python程序实现。前后端通信采用了现代的AJAX CROD(ajax跨域)通信技术,利用HTTP协议将json格式的数据在前后端POST。利用Min-Max搜索的深度,可以调节难度。

实验中AI通信良好,一定难度下可以实现保证和棋或赢棋。

本实验利用HBuilderX, appUploader实现了IOS上APP真机联调与开发者协议下APP的安装。

更多说明参见参见之后“功能描述”一节。

结构设计与系统集成

前端设计

前端

后端设计

后端

瀑布模型

本次设计使用瀑布模型,从需求分析开始,先前端,再后端,最后前后端联调,实现功能。

ATTENTION

以下开始干货

  • 描述APP功能,具体可以参考视频

  • APP设计教程。

本教程较为详细

有一定HTML+CSS+JS python基础的人可以依照教程重新实现这个教程重新实现APP

APP功能描述

pdf大小原因,图片质量有所压缩。

Icon与启动画面

![WeChat Image_20190429130436](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429130436.png)

![WeChat Image_20190429130441](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429130441.png)

设计了赏心悦目的Icon与启动界面。来自开源图库icon8与Pinterest某位画师作品。

开始界面

注意使用了ios自己的API,才实现刘海屏沉浸式状态栏体验。

![WeChat Image_20190429114705](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114705.png)

游戏开始界面如图,配色采用Bootstrap前端框架推荐的Flat UI配色。

游戏开始时会有好看的开启动画,参见视频。

  • 标题栏是游戏名字 Tic-Tac-Toe
  • 首部有当前游戏回合,输赢统计
  • 粗体表示当前难度,分为
难度名称 含义
Settler(开拓者) Min-Max搜索深度为0,AI进入傻瓜模式,容易打败
Warlord(军阀) Min-Max搜索深度为1,需要技巧和运气,偶尔可以赢一局
Emperor(皇帝) Min-Max搜索深度为3,无法打败,最多只能平局
God(神) Min-Max搜索深度为9,无法打败; 第一次落子相应速度慢,可以演示网络延时下的动画

God相比Emperor,由于深度大,在可以当即获胜和再允许玩家下一步后获胜会选择后者,体现了AI傲慢的态度,所以叫做God。

按下NewGame按钮,可以重新开始一局,当前局抛弃。

短按长按落子

  • 短按棋盘,棋盘有凹陷动画,提醒用户要长按落子,如下图(1,2)

![WeChat Image_20190429114725](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114725.png)

  • 长按棋盘,棋盘落子

![WeChat Image_20190429114636](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114636.png)

  • 如果按到棋盘而不是棋格,则棋盘边缘亮度稍微增加,提醒用户长按位置错误:

    ![WeChat Image_20190429114646](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114646.png)

设置界面

  • 按下右下小齿轮,小齿轮旋转,并用流畅动画弹出磨砂效果的选择界面,可以选择难度

![WeChat Image_20190429114731](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114731.png)

游戏结束

  • 弹出游戏结果

loss

网络错误

  • 如果有网络错误,手机振动,并弹出警告

![WeChat Image_20190429130456](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429130456.jpg)

网络延时

AI落子时,要保证玩家不能落子,也就是说触摸回调函数要能够根据当前状态变量拒绝落子,并用一个小时钟动画提醒用户耐心等待:

![WeChat Image_20190429114625](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114625.png)

设计教程

IOS开发者证书,APP描述文件获得

苹果开发者账号

该教程主要参考www.applicationloader.net/blog/zh/1073.html,但是教程中已有一些落伍,则合理根据APP情况再做一些专门说明。

参见:苹果开发者账号申请教程

img

注意,如果只是测试的话,到了付款那步不用付钱

不付款就可以使用调试三台及其的描述文件。但苹果开发者网站不会明确说明这一点(可能时希望你买个¥688的账号)

申请p12证书与描述文件

如果还没安装Appuploader先安装好

Appuploader安装教程

1、打开Appuploader,用苹果开发者账号登录。

img

如果登录报错,先登录下https://developer.apple.com/account,同意下协议再登录Appuploader软件申请证书

img

如果登录提示以下错误,说明没有同意苹果的隐私协议,请看这个教程同意下即可登录。apple id同意隐私新协议教程

imgimg

正常登录会出现这个下图提示!

意思是只能申请iOS开发证书用于测试,不能上传ipa不能上架,上架需要付费688的开发账号。

img

2、选择证书选项

img

3、点击右下角+ADD选择,下拉选择iOS开发证书!

输入证书名称:不要中文、随意设置

邮箱:(随意)

密码:证书的密码,不是开发者账号密码,如123这样不用很复杂,记好、打包时要用、很重要。

应用id:这里不用选!

点击ok创建。

img

如果账号已经有一个iOS开发证书了,将申请报错(如下图)免费开发者账号只能申请一个开发证书p12,可以删除掉再申请,或者直接用这个已经申请的。

img

4、申请到了,点击p12文件下载保存.p12 证书文件到电脑。

img

三、申请ios描述文件(mobileprovision)

1、返回软件,选择描述文件

img

2、点击右下角+ADD,先选择添加应用id

img

应用id:三段式格式、如app名称是淘宝,可以编写为com.app.taobao,自由编写!不能重复!具有唯一性@

名称:数字或者字母,自由编写,不要中文,不能重复。

如果添加报错(重复添加或者别人已用这个应用id),解决办法就是修改下应用id,重新编下。

img

点击ok只要没弹出报错就是添加成功了,注意先关掉窗口,重新点右下角+ADD进入下拉应用id可查看刚添加的应用id是否存在。

img

填加好应用id下步添加设置udid

3、加好了应用id下一步添加用来测试的苹果手机,先获取UUID。

苹果手机助手获取UDID

如爱思助手,电脑下载爱思助手,连上苹果手机,设备信息里面那个设备标识就是udid。

img

获取到UUID、点击添加测试设备,复制到UDID框,输入设备名称(随意,不要中文),点击ok。

免费账号7天内最多只能添加3个手机进行测试.

如果报错下图,可能这个udid已经添加过,或者别人添加过,先关掉申请窗口,重新点右下角+ADD,选择开发版看有没有出现设备。

img

输入刚获取的udid(如果你的苹果手机链接了电脑,Appuploader会自动获取udid)

name:这个名称不用要中文,数字或者字母随意编写,不要跟之前添加过的名称一样就行。

img

添加成功后选择开发版profile在设备栏就会出现刚添加的设备!

img

重新点击右下角的+ADD进入(才能同步到刚申请的appid和设备),选择开发版profile

选择刚创建的appid 应用id 如com.app.taobao,勾选关联第一步创建的ios证书p12,选择刚添加要测试的设备。

输入名称(随意,123、abc之类的不要中文,因为不要跟之前的重复)

点击ok创建。

img

5、点击下载保存.mobileprovision,描述文件。

苹果那边规定,没有付费688的苹果账号申请的描述文件只有7天有效期,付费苹果开发者账号的证书是1年有效期,到期可以重新申请打包,当然测试的话几天时间也足够了

img

HBuilderX打包HTML5APP应用

1、打开HBuilder工具,选择完工的项目,点击发行,选择发行为原生安装包。

img

2、选择iOS打包,支持的设备类型(可以选择支持iPhone和支持ipad),选择使用苹果证书

AppID,自己编的那个,如com.app.taobao

profile文件,选择上传配置文件.mobileprovision

私钥证书,上传.p12文件

私钥密码,输入创建p12设置的密码。

然后点击打包。

img

3、打包成功后,下载保存ipa,这个ipa包就能安装到手机测试了。

img

免费开发者账号打包的app只能通过爱思助手安装!

如果需要上传蒲公英 fir等分发平台扫码安装请看这个教程、需要付费的开发者账号。

iOS APP真机调试图文介绍

1、普通账号申请的ios证书打包的ipa、经测试,苹果官方的iTunes助手安装不了,不要用这个。、

用爱思苹果助手可以成功安装

https://www.i4.cn/

连接上手机、点击应用游戏,点击导入安装,选择刚打包的ipa包,或者直接选择ipa包右键通过爱思助手安装。

img

2、ipa将自动安装,类型是越狱版,安装成功后显示个人正版,因为是个人ios证书打包,没上架App Store。

img

img

3、安装成功了第一次启动应用会出现如下提示,用测试证书或者企业证书打包的ipa都会这样,需要设置一下。

点击设置、进入通用,下拉选择描述文件和设备管理。

imgimg

4、点击开发者应用下面出现的账号,信任,然后就能启动应用,不在出现提示。

imgimg

接下来为纯原创内容

用Chrome在PC上调试应用

调试的时候,HBuilderX可以实现真机联调,但是非常不方便。用chrome再PC上调试相比就功能强大的多:

先打开chrome调试

(用VScode打代码方便一些,也可以把index.html放到chrome里面,效果一样)

![1556517182588](C:\Users\Xiaobo Yang\AppData\Roaming\Typora\typora-user-images\1556517182588.png)

Ctlr-Shift-I,打开开发者工具:

![1556517313958](C:\Users\Xiaobo Yang\AppData\Roaming\Typora\typora-user-images\1556517313958.png)

Elements功能

![1556517416815](C:\Users\Xiaobo Yang\AppData\Roaming\Typora\typora-user-images\1556517416815.png)

选中左边的某个元素,elements窗口会跳出对应HTML内容,Style窗口会跳出层叠再元素上的CSS。

调试JS代码

![1556517598635](C:\Users\Xiaobo Yang\AppData\Roaming\Typora\typora-user-images\1556517598635.png)

Source功能可以设置JS断点,观察变量。

下面console可以作为控制台打入命令。

Animations界面可以将鼠标移上去,就可以追踪CSS动画。这个功能再调试中非常重要。

抓包

![1556517729927](C:\Users\Xiaobo Yang\AppData\Roaming\Typora\typora-user-images\1556517729927.png)

NetWork可以抓包,这里抓到了界面初始化前测试服务器是否在线的GET包。

调试AJAX,前后端联调,这个功能必须用到。

用JQuery实现开场动画

本APP实现了很多动画,思路大同小异。这里先介绍开场动画的实现:

HTML内容

DOM树的结构,应该是把棋盘作为一个div,9个棋盘格子作为子div,用float方法从左到右排列:

<div id="chess-board">
    <div id="chess-grid-0-0" class="chess-grid">
      <div class="piece local-piece-on">X</div>
    </div>
    <div id="chess-grid-0-1" class="chess-grid"></div>
    <div id="chess-grid-0-2" class="chess-grid"></div>
    <div id="chess-grid-1-0" class="chess-grid"></div>
    <div id="chess-grid-1-1" class="chess-grid"></div>
    <div id="chess-grid-1-2" class="chess-grid"></div>
    <div id="chess-grid-2-0" class="chess-grid"></div>
    <div id="chess-grid-2-2" class="chess-grid"></div>
    <div id="chess-grid-2-3" class="chess-grid"></div>
  </div>

然而,每次开始新游戏,都需要重新生成新的棋盘,所以这种静态写法是不实际的。正确方法是每次newGame,都用JS动态生成DOM树。

//init chess_board
    chess_board = $('#chess-board');
    chess_board.empty();
    for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
            var new_chess_grid = $("<div id='chess-grid-" + i + "-" + j + "' class='chess-grid'></div>");
            var new_piece = $("<div id='piece-" + i + "-" + j + "' class='piece piece-off'></div>");
            new_chess_grid.append(new_piece);
            chess_board.append(new_chess_grid);
            board[i][j] = 0;
        }
    }

这里利用jQuery这个库,很方便就能生成九个棋盘格子,作为棋盘的子DOM节点。

设置CSS

首先要进制用户长按选中内容,其次要保证用户无法滚屏,这样APP才更像APP而不是网页。

body {
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

html,body {
	margin: 0;
	background: #2980b9;
	height: 100%;
	overflow: hidden;
	font-family: helvetica;
}

接下来,

  • 每个棋盘格子设置定位为float,长宽是棋盘的1/3,就可以自动归位
.chess-grid {
	box-sizing: border-box;
	margin: 1%;
	width: 31%;
	height: 31%;
	border-radius: 0.5rem;
	float: left;
	background: #16a085;
	transition: 0.5s;
}
  • 棋盘的长宽一致,是一个正方形,则需要JS帮助来让高度等于宽度。纯粹CSS实现相当困难:
var chessBoard = $('#chess-board');
var chessBoardWidth = chessBoard.outerWidth();
chessBoard.css('height', String(chessBoardWidth));

设置动画效果

我们希望每个棋盘格子开场出现的时候都有一个从小到大,颜色先变亮再回到变深的动画过程,像是花朵开放一样,十分好看。

最直接的方法使用jQuery的animate函数。但是这个函数不能控制颜色,局限很大。

另一种方法使用CSS的keyframe动画,但是注意,不能把动画直接写道期盼格子的CSS里面,这样它只会运行一次。

正确做法是要把动画封装称一个类,使用的时候就用addClass把动画类加上去:

.chess-grid-on {
	animation: chess-grid-on 1.5s;
}

@keyframes chess-grid-on {
	0% {
		transform: scale(.1);
		background: #16a085;
	}

	50% {
		transform: scale(1);
		background: #1abc9c;
	}

	100% {
		background: #16a085;
	}
}
for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
            $('#chess-grid-' + i + '-' + j).addClass('chess-grid-on');
            setTimeout(() => {
                $('#chess-grid-' + i + '-' + j).removeClass('chess-grid-on');
            }, 1010);
        }
    }

这里加动画的JS代码很奇怪,需要用setTimeout再1010ms后再删掉。那是因为这个动画要运行多次,所以每次运行后都要等待一会把动画类去掉,下次才能运行。

注意,所有动画类都因该封装为class,除非你希望每次元素的class变动,都执行一次这个动画。(每次加减class所有的属性都会重新执行一遍)

到这里为止,开场动画就实现了。

用JQuery实现用户触发的动画(以落子为例)

要用户触发,首先要添加监听函数,监听用户的动作。这里监听长按动作:

chess_board.on('taphold', function (event) {
        var trigger_id = event.target.id;
        re = /[a-zA-Z\-]+(\d)\-(\d)/;
        if (!re.test(trigger_id)) {
            $('#chess-board').addClass('none-response-area');
            setTimeout(() => {
                $('#chess-board').removeClass('none-response-area');
            }, 2000);
            return;
        }
        local_click(parseInt(re.exec(trigger_id)[1]), parseInt(re.exec(trigger_id)[2]));
    });

这里务必吐槽以下JS代码语法颇为脏乱,需要回调函数相互嵌套。

但考虑到JS是面向用户交互的语言,大量回调函数的使用也是不得已而为之,否则无法实现各种异步操作。只能姑且忍受。如果是复杂算法,最好还是后端用其他语言实现。

我们要监听的是棋盘格子,但是由于DOM事件的捕捉,在jQuery中默认是Bubble模式,所以我们只要把监听器放在棋盘这个父DOM上就可以了。

侦听到事件后,我们用正则表达式匹配出是哪一个棋盘格子被按下(如果都不是,进入其他操作,这里不表),随后调用相应落子函数:

function local_click(x, y) {
    if (DEBUG) {
        console.log("Get Click on" + x + "," + y);
        console.log("Current Player is" + current_player);
    }
    if (board[x][y] === 0) {
        if (current_player === LOCAL_PLAYER) {
            if (DEBUG) console.log(x + "," + y + " is ready to down chess");
            // Update vars
            board[x][y] = LOCAL_PIECE;
            current_step++;
            //Update chess board
            $('#piece-' + x + '-' + y).text('X')
                .removeClass('piece-off')
                .addClass('local-piece-on');
            //Test Is GameOver
            if (isGameOver(x, y)) {
                return 0;
            }
            else {
                current_player *= -1;
                setTimeout(ForeignMove, 100);
            }
        }
        else {
            $('#chess-grid-' + x + '-' + y).addClass('wait-clock');
            setTimeout(() => {
                $('#chess-grid-' + x + '-' + y).removeClass('wait-clock');
            }, 1210);
        }
    }
    else {
        $('#piece-' + x + '-' + y).animate({ 'opacity': 0 }, 120)
            .animate({ 'opacity': 100 }, 120)
            .animate({ 'opacity': 0 }, 120)
            .animate({ 'opacity': 100 }, 120);
    }
    return 0;
}

函数很长,但是可以分段来看:

  • 函数首先判断当前落子的位置是否已经有子,有子则用jQeury的animate函数实现“眨眼”的效果,警告用户。

  • 如果无子,则判断是否是AI落子回合,如果是,则播放“时钟闪现”动画

     $('#chess-grid-' + x + '-' + y).addClass('wait-clock');
                setTimeout(() => {
                    $('#chess-grid-' + x + '-' + y).removeClass('wait-clock');
                }, 1210);

    这里加上动画的方法和上一节提到的方法一样,CSS封装+setTimeout函数。这种方法起始显得有点繁琐,但遍历网上所有教程,似乎是当前最好的解决方案了。

  • 如果可以落子,更新棋盘记录变量board,并落子。随后改变玩家,触发AI落子函数。

  • 每次落子结束后都检查游戏是否结束,结束就重开一局,记录输赢。

用JQuery的AJAX实现HTTP通信——在保持异步回调的前提下保证通信结果获得处理

为了获得远程落子,需要把当前棋盘状态用json发到服务器上。

  • 首先要准备好发送的json数据:

    var ajax_board = {
            _board: board,
            _current_player: current_player,
            _difficulty: difficulty,
        }
        var ajax_board_json = JSON.stringify(ajax_board);

    注意数据左后要序列化为字符串格式。

  • 随后用AJAX发送数据:

    ajax_response = $.ajax({
            url: 'http://47.106.118.102:80/GetMove',
            type: 'POST',
            contentType: 'application/json',
            data: ajax_board_json,
            dataType: 'json',
            jsonp: 'callback',
            success: (response_data) => {
                //为突出重点,略去
            },
            error: () => {
               //为突出重点,略去
            },
            complete: ()=>{
                callBack(_x, _y);
            }
        })

    可以看到AJAX发送的都是http请求中所需要的常规要求。

    注意的一点是,我们这里是跨域通信(AJAX CROD),传统AJAX处于安全考虑,非同域名下的两个网页时不可以用AJAX通信的。jQuery用jsonp掩盖了这个限制,但是要设置jsonp: 'callback',

  • 最后,收到数据(或者网络错误后随即落子)得到落子位置后,我们回调callBack(_x,_y)这个函数。在界面上落子。

这里一个常见的疑惑时为什么要把落子函数放在ajax的回调里面,显得很不舒服,不能先AJAX请求,返回落子位置,再落子吗?

这是一个常见误区,是对js异步性不熟悉的体现。ajax发送数据后就开始一个异步线程,如果接下来紧接着落子,会发现ajax的返回值是一个undefined变量,那是应为网络通信没有完成。

相反,ajax自己时一个promise变量,他的回调函数一定能够再通信完成后被执行。

BTW:

我们也可以设置ajax为async(同步)模式,就可以如同传统程序那样编程了。但这种编程方式很容易造成浏览器卡死(chrome,firefox的devtool都会发出警告),所以用户交互,尽量还是要用异步的回调处理问题。

Flask Web框架的使用——AJAX跨域通信JSON需要的特殊处理

后端flask收到前端发出的http请求后,根据请求类型,做出特定相应:

@app.route('/GetMove',methods=['GET', 'POST', 'OPTIONS'])
@allowCROD
def GetMove():
    resp = make_response()
    if request.method == 'GET':
        app.logger.info('Get an GET request')

        resp = make_response('You Sent an Get Request')
        return resp
    elif request.method == 'POST':
        app.logger.info('Get an Post request')

        req_data = request.get_json()
        app.logger.info(req_data)


        resp = Solve(req_data)
        # resp.headers['Access-Control-Allow-Origin'] = '*'
        return resp
    elif request.method == 'OPTIONS':
        app.logger.info('Get an OPTION request')
        return resp
    else:
        app.logger.error('Unrecognized Request')

        resp = make_response("Unrecognized Request")
        return resp

后端

注意,这里有一个OPTIONS请求,很不常见,但是十分重要:

AJAX跨域通信本来是不安全的被禁止的,即使使用jsonp,也只能直接post例如form/application内容的数据。如果要POST json格式数据的话,就要求先发送一个OPTIONS请求。服务器再返回包的Header,要注明一些列数据,说明服务器允许的跨域通信范围,允许的数据类型,如下:

resp = make_response(func(*arg, **kwargs))
def allowCROD(func):
     def wrapper(*arg, **kwargs):
         resp = make_response(func(*arg, **kwargs))
         resp.headers['Access-Control-Allow-Origin'] = '*'
         resp.headers['Access-Control-Allow-Methods'] = 'PUT,GET,POST,DELETE'
         resp.headers['Access-Control-Allow-Headers'] = '*'
         return resp
     return wrapper

这样浏览器才认为AJAX通信时安全的,可以传输json数据。

注意我们把这个包封装成了python的装饰器,那么我们之后只要用@命令,就可以实现每次返回http响应,都有这样子的Header:

@app.route('/GetMove',methods=['GET', 'POST', 'OPTIONS'])
@allowCROD
def GetMove():
#函数主体

Min-Max搜索的实现

具体内容请参见本人另外一个Github项目Tic-Tac-Toe。这里不详细说明算法。

感想与心得


  1. 这个作业起始难度不大,前端三剑客还是很好写的,只是学习的量有点大。

需要一个考试周自学HTML+CSS+JavaScript+JQuery+Flask框架。基本五天在学习基础知识,代码和调试,部署服务器只花了两天左右就完成了。


  1. 注意,后端代码和前端一样,也是”响应式“的,每次请求过来,都是一个新的线程。这就导致以前python的编码习惯会带来bug,

    比如,以前的difficulty(或者搜索深度DepthLimit)是一个全局变量

    Depthlimit = 3
    
    def foo():
        global Depthlimit
        Depthlimit = #json反序列化得到的数据
    	

    在服务器最好不这样写,如果一个用户还好。压力测试的时候,我让室友一起玩,就出现一个玩家修改了难度,所有玩家的难度都被修改了这种情况。

    所以difficulty/Depthlimit要确保设置为每个响应线程独立拥有的变量

    我还没来得及修改阿里云服务器上的代码,所以问题依旧存在,可以取体会以下在网页端修改难度,手机端也会跟随变动


    1. ajax的跨域通信十分难以处理

      要注意上面提到的种种安全限制,比如设置jsonp句段。服务器返回的header要加上各种语句才能允许通讯等等。关键是没有特定文档说明这些,只能网上东拼西凑解决。


    1. JS异步的特性要熟悉

    不熟悉JS的时候,总是开启一个异步线程,随后就使用异步动作的结果。现在知道要用Promiss对象,确保对异步操作的结果在异步线程完成后执行。

    响应操作避免同步,以防卡死


    1. firefox相比chrome

      firefox调试界面好看,历史上firebugger更强大。

      chorme的devtool启动起来比firefox快好多,js调试,断点效率更高。

      更推荐用chrome


    1. CSS动画

      CSS动画能够真的有模板的很少,还是要重复造轮子。

      这次作业代码里面,调整各种CSS模板就花了大半天。但做出流畅好看的动画还是很开兴的。


    1. 重复执行的动画

    所有动画类都因该封装为class,除非你希望每次元素的class变动,都执行一次这个动画。(每次加减class所有的属性都会重新执行一遍)

    这种方式很不优雅,我相信有更好的办法,但似乎没能在网上找到。


    1. ios刘海屏适配

    iphoneXr的刘海屏和下巴很难适配,总是会空出一段,后来查了开发者文档才实现了沉浸式状态栏,通屏显示:

    body {
    	padding-top: env(safe-area-inset-top); /* 在 iphone x + 中本句才会生效 */
    }

    HBuilder中设置:

    plus" : {
                "statusbar" : {
                    "immersed" : true /*开启沉浸式状态栏*/
                },
                "launchwebview" : {
                    "statusbar" : {
                        "background" : "#2980b9" /*设置状态栏的颜色,一般是跟头部的颜色一样*/
                    }
                }

    1. jQuery要使用离线版本

    HTML5 APP 可能要离线使用,这时候再用在线的CDN jquery源就不合适了,如果加载失败,界面会变得十分丑陋。

    jQuery官网下载特定版本的jQuery.min.js就可以了。