5.1 井字棋游戏介绍 井字棋游戏在九宫方格内进行,如果一方首先沿某方向(横、竖、斜)连成 3 子,则获 取胜利。本游戏有人人对战和人机对战两种模式。游戏开始时从图 5-1(a)中选择对战模式,如 果是人人对战模式,两个玩家轮流下棋。如果是人机对战模式,游戏开始玩家(X 方)先走, 计算机(O 方)智能对弈下棋。游戏运行界面如图 5-1(b)所示。 图 5-1 井字棋游戏运行效果 5.2 程序设计的思路 5.2.1 计算机智能下棋 在游戏中,pos 数组存储玩家、计算机的落子信息,未落子处存储 0。X 方落子存储 1, 145 第 5 章 井字棋游戏 O 方落子存储 2。 由于人机对战,需要实现计算机的智能性,下面是为计算机设计的简单策略。 (1)如果有一步棋可以让计算机在本轮获胜,就选那一步。 (2)否则,如果有一步棋可以让玩家在本轮获胜,就选那一步。 (3)否则,计算机应该选择最佳空位置来走。最优位置就是中间那个,次优位置是四个 角,剩下的就都算第三优。 假设游戏中方格位置代号形式如图 5-2 所示。 0 1 2 3 4 5 6 7 8 图 5-2 方格位置 在程序中定义一个数组 BEST_MOVES 存储最佳方格位置,代码如下。 #按优劣顺序排序的下棋位置 var BEST_MOVES = [4, 0, 2, 6, 8, 1, 3, 5, 7]; 按上述规则设计程序,就可以实现计算机的智能性。 5.2.2 井字棋输赢判断 井字棋输赢判断比较简单,这里横斜竖赢(即三颗同色的棋子排成一条直线)的情况只 有 8 种。通过遍历,就可以判断哪一方是否获胜。 //输赢判断 Iswin: function() { //判定(纵) for (var i = 0; i < 3; i++) { if (pos[i][0] == pos[i][1] && pos[i][1] == pos[i][2] && pos[i][1] != 0) return pos[i][1]; } //判定(横) for (var i = 0; i < 3; i++) { if (pos[0][i] == pos[1][i] && pos[1][i] == pos[2][i] && pos[1][i] != 0) return pos[1][i]; } //判定(斜) if (pos[0][0] == pos[1][1] && pos[1][1] == pos[2][2] && pos[1][1] != 0) { return pos[1][1]; } if (pos[0][2] == pos[1][1] && pos[1][1] == pos[2][0] && 146 pos[1][1] != 0) { return pos[1][1]; } return 0; }, 5.3 关 键 技 术 5.3.1 画布 canvas 微信小程序画布 canvas 组件的属性如表 5-1 所示。 表 5-1 canvas 的属性 属 性 名 类 型 默 认 值 说 明 canvas-id String canvas 组件的唯一标识符 disable-scroll Boolean false 当在 canvas 中移动时且有绑定手势事件时,禁止屏幕 滚动以及下拉刷新 bindtouchstart EventHandle 手指触摸动作开始 bindtouchmove EventHandle 手指触摸后移动 bindtouchend EventHandle 手指触摸动作结束 bindtouchcancel EventHandle 手指触摸动作被打断,如来电提醒、弹窗 bindlongtap EventHandle 手指长按 500ms 之后触发,触发了长按事件后进行移 动不会触发屏幕的滚动 binderror EventHandle 当发生错误时触发 error 事件,detail={errMsg:'something wrong'} 注意:canvas 标签默认宽度 300px、高度 225px。在同一页面中的 canvas-id 不可重复, 如果使用一个已经出现过的 canvas-id,该 canvas 标签对应的画布将被隐藏并不再正常工作。 示例代码: <!-- canvas.wxml --> <canvas style="width: 300px; height: 200px;" canvas-id="firstCanvas"> </canvas> <!--当使用绝对定位时,文档流后边的canvas的显示层级高于前边的canvas--> <canvas style="width: 400px;height: 500px;" canvas-id="secondCanvas"> </canvas> // canvas.js Page({ canvasIdErrorCallback: function (e) { console.error(e.detail.errMsg) }, onReady: function (e) { //使用wx.createContext获取绘图上下文context 147 第 5 章 井字棋游戏 var context = wx.createCanvasContext('firstCanvas') context.setStrokeStyle("#00ff00") context.setLineWidth(5) context.rect(0,0,200,200) context.stroke() context.setStrokeStyle ("#ff0000") context.setLineWidth(2) context.moveTo(160,100) context.arc(100,100,60,0,2*Math.PI,true) context.moveTo(140,100) context.arc(100,100,40,0,Math.PI,false) context.moveTo(85,80) context.arc(80,80,5,0,2*Math.PI,true) context.moveTo(125,80) context.arc(120,80,5,0,2*Math.PI,true) context.stroke() context.draw() } }) 5.3.2 响应 canvas 组件事件 canvas 组件可以响应手指触摸动作。可以在<canvas>中加上一些事件,观测手指的坐标。 【例 5-1】 观测手指触摸的坐标。 WXML 代码如下。 //index.wxml <canvas canvas-id="myCanvas" style="margin: 5px; border:1px solid #d3d3d3;" bindtouchstart="start" bindtouchmove="move" bindtouchend="end"/> <view hidden="{{hidden}}"> Coordinates: ({{x}}, {{y}}) </view> 其中,canvas-id 为当前画布的名称。bindtouchstart 是单击后触发,bindtouchend 是手指触摸动 作结束后触发, bindtouchmove 是手指触摸后移动时触发,并且可以传过来目前移动的参数 坐标。例如: move: function( event ) { var xx=event.touches[0].x; var yy=event.touches[0].y; console.log(xx+","+yy) }, 实现了手指触摸后移动时打印坐标。 Index.js 文件完整代码如下。 Page({ data: { x: 0,y: 0, 148 hidden: true }, start: function(e) { this.setData({ hidden: false, x: e.touches[0].x, y: e.touches[0].y }) }, move: function(e) { this.setData({ x: e.touches[0].x, y: e.touches[0].y }) }, end: function(e) { this.setData({ hidden: true }) } }) 当把手指放到 canvas 中移动,就会在下边显示出触碰点的坐标,如图 5-3 所示。 图 5-3 显示手指触碰点的坐标 在游戏开发中往往需要根据手指触摸、单击动作下棋、移动物体等,都是利用这些 bindtouchstart、bindtouchmove 和 bindtouchend 事件实现的。 5.4 程序设计的步骤 5.4.1 选择对战模式页面 新建一个微信小程序后,在 app.json 中修改原有的 pages 值,增加两个对战页面路径: "pages/Three/Three" , "pages/computerThree/computerThree" 同时修改导航条标题文字为“井字棋夏敏捷开发”,结果如下。 149 第 5 章 井字棋游戏 { "pages": [ "pages/index/index", "pages/logs/logs", "pages/Three/Three" , "pages/computerThree/computerThree" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "井字棋夏敏捷开发", "navigationBarTextStyle": "black" }, "style": "v2", "sitemapLocation": "sitemap.json" } 修改原有 index 页面,在 index.js 中增加事件处理函数。 //事件处理函数 drawComputerThree:function() { wx.navigateTo({ url: '../computerThree/computerThree' //跳转到人机对战游戏页面 }) }, drawThree: function () { wx.navigateTo({ url: '../Three/Three' //跳转到人人对战游戏页面 }) } 在原有 index.wxml 视图文件中,增加两个按钮,同时绑定 tap 单击事件。 <button bindtap='drawThree'>人人对战井字棋</button> <button bindtap='drawComputerThree'>人机对战井字棋</button> 结果如下。 <!--index.wxml--> <view class="container"> <view class="userinfo"> <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo">获取头像昵称</button> <block wx:else> <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> <button bindtap='drawThree'>人人对战井字棋</button> <button bindtap='drawComputerThree'>人机对战井字棋游戏</button> </view> </view> 150 至此,可以实现跳转到不同游戏页面。 5.4.2 人人对战游戏页面 在微信小程序 pages 下新建文件夹 Three,在其下新建 page,命名为 Three,用来实现人 人对战游戏页面。同时新建文件夹\images\png,其中存储 O.png 和 X.png 棋子图片。 1. Three.wxml 视图文件 <!--pages/Three/Three.wxml--> <view class='title'>人人对战</view> <canvas canvas-id='myCanvas' style='border:1rpx solid' bindtouchstart= "touchStart"></canvas> <text>{{info}}</text> Three.wxml 文件内部仅添加画布,并设置触屏事件函数。<text>组件显示游戏输赢信息。 2. Three.js 文件 人人对战需要记录哪方走棋,这里使用 role 记录。游戏中使用值 1 代表 X 方,值 2 代表 O 方。二维数组 pos 存储落子情况。 // pages/Three/Three.js var role = 1; //X方 var pos = new Array(); //存储落子情况 var isOver = false; //游戏是否结束 Page({ /** * 页面的初始数据 */ data: { info: "", }, 游戏开始时,棋盘上没有棋子,所以 pos[i][j]元素值存储 0,代表此处无棋子。同时在画 布上绘制九宫格棋盘。 /** * 生命周期函数——监听页面加载 */ onLoad: function(options) { //创建画布上下文 this.ctx = wx.createCanvasContext('myCanvas') this.init(); this.drawQipan();//画棋盘 this.ctx.draw(); }, init: function() { for (var i = 0; i < 3; i++) { 151 第 5 章 井字棋游戏 pos[i] = new Array(); for (var j = 0; j < 3; j++) { pos[i][j] = 0; //0表示空的 } } }, //画九宫格棋盘 drawQipan: function() { let ctx = this.ctx ctx.beginPath(); for (var i = 0; i <= 3; i++) { ctx.moveTo(i * 50, 0); ctx.lineTo(i * 50, 150); ctx.moveTo(0, i * 50); ctx.lineTo(150, i * 50); } ctx.stroke(); }, 以下是触屏事件函数 touchStart (e),处理用户下棋落子。触屏事件中首先获取触屏位置 后换算成棋盘坐标(startx,starty),如果触摸位置已有棋子,则修改 info 变量,更新页面显 示“此位置已有棋子!”提示。修改落子位置 pos[startx][starty]元素值后重新绘制棋盘和棋子, 最后调用 Iswin()判断输赢。 touchStart: function(e) { var startx = Math.floor(e.touches[0].x / 50); //获取触屏位置 var starty = Math.floor(e.touches[0].y / 50); if (isOver) { console.log("游戏已经结束!"); this.setData({ info: "游戏已经结束!", }) return; } //此位置已有棋子 if (pos[startx][starty] != 0) { console.log("此位置已有棋子!"); this.setData({ info: "此位置已有棋子!", }) return; } console.log("玩家走" + startx + ";" + starty); pos[startx][starty] = role; //修改落子位置元素值 this.changeRole(); //改变角色 this.drawQipan(); this.drawQi(); var info = "未赢"; 152 if (this.Iswin() == 1) { info = "X方赢!!!!"; isOver = true; console.log("X方赢!!!!"); } else if (this.Iswin() == 2) { info = "O方赢!!!!"; isOver = true; console.log("O方赢!!!!"); } if (this.IsBlank() == false) //是否有空位置 { info = "平局!!!!"; isOver = true; console.log("平局!!!!"); } this.setData({ info: info, }) if (isOver == true) { wx.showModal({ title: '提示', content: info, success: function(res) { if (res.confirm) { console.log('用户单击确定') } else if (res.cancel) { console.log('用户单击取消') } } }); } }, IsBlank()函数判断是否还有空位置,以便判断和局。 IsBlank: function() { for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (pos[i][j] == 0) return true; } } return false; }, changeRole()改变下棋者的角色。 changeRole: function() { if (role == 1) { //如果是X方换成O方 role = 2; 153 第 5 章 井字棋游戏 } else { //如果是O方换成X方 role = 1; } }, drawQi ()按 pos 数组记录的下棋落子情况画棋子。 drawQi: function() { //画棋子 let ctx = this.ctx; for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (pos[i][j] == 1) ctx.drawImage('/images/png/X.png', i * 50, j * 50, 50, 50); if (pos[i][j] == 2) ctx.drawImage('/images/png/O.png', i * 50, j * 50, 50, 50); } } ctx.draw(); }, Iswin()按照 8 种赢的情况,依次遍历判断。 //输赢判断 Iswin: function() { //见前文 }, /** * 用户单击右上角分享 */ onShareAppMessage: function() { } }) 5.4.3 人机对战游戏页面 在 微 信 小 程 序 pages 下 新 建 文 件 夹 computerThree , 在 其 下 新 建 page , 命 名 为 computerThree,用来实现人人对战游戏页面。 1. computerThree.wxml 视图文件 <!--pages/computerThree/computerThree.wxml--> <view class='title'>人机对战</view> <canvas canvas-id='myCanvas' style='border:1rpx solid' bindtouchstart= "touchStart"></canvas> <text>{{info}}</text> computerThree.wxml 文件内部仅添加画布,并设置触屏事件函数。<text>组件显示游戏输 赢信息。