关键词: 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]
格式原因书写,脱水可以不看
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 便拥有桌面互联网、移动互联网兼容并蓄的想法,不仅统一了开发方式、网络内容,还做到了访问方式的体验统一化。
相比原来的HTML,HTML5支持:
- 用于绘画的 canvas 元素
- 用于媒介回放的 video 和 audio 元素
- 对本地离线存储的更好的支持
- 新的特殊内容元素,比如 article、footer、header、nav、section
- 新的表单控件,比如 calendar、date、time、email、url、search
等等。这些原来需要用Js,CSS实现的功能,可以现在用HTML5直接实现。
2011 年11 月, Adobe 正式宣布停止为移动设备的浏览器开发F las h Pl ayer ,转而全面发展HTML5 技术,并表示“ HTML5 是各种移动平台浏览器中最佳的内容制作和发布解决方案”,正式宣告了Flash 退出移动端开发的舞台。
所以,目前大多数视频网站采用的是双重技术,如果在iOS 上欣赏网站视频,都会自动切换到HTML 5 播放模式上, PC 上则采用Flash 插件。
HTML5在很多功能上与Flash存在重叠,但又有显著优势:
-
Flash不开放,是一个完全封闭的系统, Adobe 公司拥有100 % 的技术专利, Adobe 想通过Flash Player 授权来收费,每台移动设备嵌入Flash Player ,预收1 美元。
而HTML5是开放的标准,网页开发者利用HTML5 就能做出高级的图像、字体、动画以及过渡效果,而不必依赖Flas h 插件。
-
Flash 的安全性漏洞较多,黑客经常利用这些漏洞。2009 年,赛门铁克公司曾经指出,Flash 是最不安全的系统之一。
-
Flash对触屏支持较差,不适用于当前的移动互联网时代。
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 负责行为。
网页的内容,需要从远程服务器下载;用户提交的信息,需要在服务器上处理;网页上需要执行的复杂运算,也可以在云服务器上实现。
这些在服务器上实现的功能可以称之为后端。
“框架”,可以粗浅的理解为程序运行时调用的库,代码的写作模式。
服务器上的后端程序需要由可以执行HTTP通信的程序运行。常见的框架由:
-
DJango
-
Flask
-
react
-
等等
前两个由Python实现,最后一个由Node.js实现。本文用简单易学的Flask框架。
本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
pdf大小原因,图片质量有所压缩。
![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)
- 弹出游戏结果
- 如果有网络错误,手机振动,并弹出警告
![WeChat Image_20190429130456](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429130456.jpg)
AI落子时,要保证玩家不能落子,也就是说触摸回调函数要能够根据当前状态变量拒绝落子,并用一个小时钟动画提醒用户耐心等待:
![WeChat Image_20190429114625](D:\3160101465-杨笑波-嵌入式系统-HTML5应用\img\WeChat Image_20190429114625.png)
该教程主要参考www.applicationloader.net/blog/zh/1073.html,但是教程中已有一些落伍,则合理根据APP情况再做一些专门说明。
参见:苹果开发者账号申请教程
注意,如果只是测试的话,到了付款那步不用付钱。
不付款就可以使用调试三台及其的描述文件。但苹果开发者网站不会明确说明这一点(可能时希望你买个¥688的账号)
如果还没安装Appuploader先安装好
1、打开Appuploader,用苹果开发者账号登录。
如果登录报错,先登录下https://developer.apple.com/account,同意下协议再登录Appuploader软件申请证书
如果登录提示以下错误,说明没有同意苹果的隐私协议,请看这个教程同意下即可登录。apple id同意隐私新协议教程
正常登录会出现这个下图提示!
意思是只能申请iOS开发证书用于测试,不能上传ipa不能上架,上架需要付费688的开发账号。
2、选择证书选项
3、点击右下角+ADD选择,下拉选择iOS开发证书!
输入证书名称:不要中文、随意设置
邮箱:(随意)
密码:证书的密码,不是开发者账号密码,如123这样不用很复杂,记好、打包时要用、很重要。
应用id:这里不用选!
点击ok创建。
如果账号已经有一个iOS开发证书了,将申请报错(如下图)免费开发者账号只能申请一个开发证书p12,可以删除掉再申请,或者直接用这个已经申请的。
4、申请到了,点击p12文件下载保存.p12 证书文件到电脑。
1、返回软件,选择描述文件
2、点击右下角+ADD,先选择添加应用id
应用id:三段式格式、如app名称是淘宝,可以编写为com.app.taobao,自由编写!不能重复!具有唯一性@
名称:数字或者字母,自由编写,不要中文,不能重复。
如果添加报错(重复添加或者别人已用这个应用id),解决办法就是修改下应用id,重新编下。
点击ok只要没弹出报错就是添加成功了,注意先关掉窗口,重新点右下角+ADD进入下拉应用id可查看刚添加的应用id是否存在。
填加好应用id下步添加设置udid
3、加好了应用id下一步添加用来测试的苹果手机,先获取UUID。
苹果手机助手获取UDID
如爱思助手,电脑下载爱思助手,连上苹果手机,设备信息里面那个设备标识就是udid。
获取到UUID、点击添加测试设备,复制到UDID框,输入设备名称(随意,不要中文),点击ok。
免费账号7天内最多只能添加3个手机进行测试.
如果报错下图,可能这个udid已经添加过,或者别人添加过,先关掉申请窗口,重新点右下角+ADD,选择开发版看有没有出现设备。
输入刚获取的udid(如果你的苹果手机链接了电脑,Appuploader会自动获取udid)
name:这个名称不用要中文,数字或者字母随意编写,不要跟之前添加过的名称一样就行。
添加成功后选择开发版profile在设备栏就会出现刚添加的设备!
重新点击右下角的+ADD进入(才能同步到刚申请的appid和设备),选择开发版profile、
选择刚创建的appid 应用id 如com.app.taobao,勾选关联第一步创建的ios证书p12,选择刚添加要测试的设备。
输入名称(随意,123、abc之类的不要中文,因为不要跟之前的重复)
点击ok创建。
5、点击下载保存.mobileprovision,描述文件。
苹果那边规定,没有付费688的苹果账号申请的描述文件只有7天有效期,付费苹果开发者账号的证书是1年有效期,到期可以重新申请打包,当然测试的话几天时间也足够了
1、打开HBuilder工具,选择完工的项目,点击发行,选择发行为原生安装包。
2、选择iOS打包,支持的设备类型(可以选择支持iPhone和支持ipad),选择使用苹果证书
AppID,自己编的那个,如com.app.taobao
profile文件,选择上传配置文件.mobileprovision
私钥证书,上传.p12文件
私钥密码,输入创建p12设置的密码。
然后点击打包。
3、打包成功后,下载保存ipa,这个ipa包就能安装到手机测试了。
如果需要上传蒲公英 fir等分发平台扫码安装请看这个教程、需要付费的开发者账号。
1、普通账号申请的ios证书打包的ipa、经测试,苹果官方的iTunes助手安装不了,不要用这个。、
用爱思苹果助手可以成功安装
连接上手机、点击应用游戏,点击导入安装,选择刚打包的ipa包,或者直接选择ipa包右键通过爱思助手安装。
2、ipa将自动安装,类型是越狱版,安装成功后显示个人正版,因为是个人ios证书打包,没上架App Store。
3、安装成功了第一次启动应用会出现如下提示,用测试证书或者企业证书打包的ipa都会这样,需要设置一下。
点击设置、进入通用,下拉选择描述文件和设备管理。
4、点击开发者应用下面出现的账号,信任,然后就能启动应用,不在出现提示。
接下来为纯原创内容
调试的时候,HBuilderX可以实现真机联调,但是非常不方便。用chrome再PC上调试相比就功能强大的多:
(用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)
![1556517416815](C:\Users\Xiaobo Yang\AppData\Roaming\Typora\typora-user-images\1556517416815.png)
选中左边的某个元素,elements窗口会跳出对应HTML内容,Style窗口会跳出层叠再元素上的CSS。
![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,前后端联调,这个功能必须用到。
本APP实现了很多动画,思路大同小异。这里先介绍开场动画的实现:
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节点。
首先要进制用户长按选中内容,其次要保证用户无法滚屏,这样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所有的属性都会重新执行一遍)
到这里为止,开场动画就实现了。
要用户触发,首先要添加监听函数,监听用户的动作。这里监听长按动作:
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落子函数。
-
每次落子结束后都检查游戏是否结束,结束就重开一局,记录输赢。
为了获得远程落子,需要把当前棋盘状态用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收到前端发出的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():
#函数主体
具体内容请参见本人另外一个Github项目Tic-Tac-Toe。这里不详细说明算法。
- 这个作业起始难度不大,前端三剑客还是很好写的,只是学习的量有点大。
需要一个考试周自学HTML+CSS+JavaScript+JQuery+Flask框架。基本五天在学习基础知识,代码和调试,部署服务器只花了两天左右就完成了。
-
注意,后端代码和前端一样,也是”响应式“的,每次请求过来,都是一个新的线程。这就导致以前python的编码习惯会带来bug,
比如,以前的difficulty(或者搜索深度DepthLimit)是一个全局变量
Depthlimit = 3 def foo(): global Depthlimit Depthlimit = #json反序列化得到的数据
在服务器最好不这样写,如果一个用户还好。压力测试的时候,我让室友一起玩,就出现一个玩家修改了难度,所有玩家的难度都被修改了这种情况。
所以difficulty/Depthlimit要确保设置为每个响应线程独立拥有的变量
我还没来得及修改阿里云服务器上的代码,所以问题依旧存在,可以取体会以下在网页端修改难度,手机端也会跟随变动
-
ajax的跨域通信十分难以处理
要注意上面提到的种种安全限制,比如设置jsonp句段。服务器返回的header要加上各种语句才能允许通讯等等。关键是没有特定文档说明这些,只能网上东拼西凑解决。
- JS异步的特性要熟悉
不熟悉JS的时候,总是开启一个异步线程,随后就使用异步动作的结果。现在知道要用Promiss对象,确保对异步操作的结果在异步线程完成后执行。
响应操作避免同步,以防卡死
-
firefox相比chrome
firefox调试界面好看,历史上firebugger更强大。
chorme的devtool启动起来比firefox快好多,js调试,断点效率更高。
更推荐用chrome
-
CSS动画
CSS动画能够真的有模板的很少,还是要重复造轮子。
这次作业代码里面,调整各种CSS模板就花了大半天。但做出流畅好看的动画还是很开兴的。
- 重复执行的动画
所有动画类都因该封装为class,除非你希望每次元素的class变动,都执行一次这个动画。(每次加减class所有的属性都会重新执行一遍)
这种方式很不优雅,我相信有更好的办法,但似乎没能在网上找到。
- ios刘海屏适配
iphoneXr的刘海屏和下巴很难适配,总是会空出一段,后来查了开发者文档才实现了沉浸式状态栏,通屏显示:
body { padding-top: env(safe-area-inset-top); /* 在 iphone x + 中本句才会生效 */ }
HBuilder中设置:
plus" : { "statusbar" : { "immersed" : true /*开启沉浸式状态栏*/ }, "launchwebview" : { "statusbar" : { "background" : "#2980b9" /*设置状态栏的颜色,一般是跟头部的颜色一样*/ } }
- jQuery要使用离线版本
HTML5 APP 可能要离线使用,这时候再用在线的CDN jquery源就不合适了,如果加载失败,界面会变得十分丑陋。
jQuery官网下载特定版本的jQuery.min.js就可以了。
-