还记得小时候玩过的斗兽棋游戏不,90后的经典怀旧游戏哦,笔者TA远方在读小学的时候,曾玩过的游戏名单就有它,也许有人忘记了,现在才想起,理清一下斗兽棋游戏的规则:
首先,打开微信小程序开发工具,新建一个小程序项目后,打开游戏页面的布局文件👉 游戏的基本规则
吃小:象 > 狮 > 虎 > 豹>狼 > 狗 > 猫 > 鼠吃大:只有鼠 能吃 象同棋:互吃潜水:只有鼠 能游在河里,河边的不能吃河里的,同样,河里的不能吃河边的走法:只能往前后左右方向走一格,狮和虎可横向跳过小河陷阱:对方的棋落入我方陷阱,我方的任意棋都可吃陷阱里的嬴法:我方的棋走入对方的兽穴就算赢
/pages/index.index.wxml
,添加代码如下所示,<view class="page"><view class="canvas-box"><canvas class="canvas" canvas-id="canvA" id="canvA" type="2d"></canvas><canvas class="canvas" canvas-id="canvB" id="canvB" type="2d"></canvas><canvas class="canvas" canvas-id="canvC" id="canvC" type="2d" bindtouchstart="onTouch"></canvas></view><!-- ... --></view>
接下来,在样式文件/pages/index/index.wxss
中,写好布局样式,使得布局显示效果图如下,图中的绿色区域是用组件canvas多层显示的效果
接着,开始实现游戏的逻辑,打开主要文件/pages/index/index.js
,添加的代码如下,当页面加载时,最后走得是自动重置游戏逻辑
import CanvasContext from '../../utils/canvas_context';Page({data: {//定义所有画布组件canvas: {},},onLoad(){//...获取页面布局的canvas组件信息,包括size大小,node节点wx.createSelectorQuery().select('#canvA').fields({size: true, node: true },res=>{this.data.canvas.width = res.width;this.data.canvas.height = res.height;//处在底层的画布this.data.canvas.canvA = new CanvasContext(res);wx.createSelectorQuery().select('#canvB').fields({size: true, node: true },res=>{//处在中间层的画布this.data.canvas.canvB = new CanvasContext(res);wx.createSelectorQuery().select('#canvC').fields({size: true, node: true },res=>{//处在顶层的画布this.data.canvas.canvC = new CanvasContext(res);this.reset()}).exec()}).exec()}).exec()},//处理用户触摸点击的onTouch(e){//...具体后面有讲},//重置按钮点击onReset(){wx.showModal({title:'系统提示',content:'确定要重置游戏吗?',success: res => {if (!res.confirm) return;this.reset();wx.showToast({title:'已重置',icon:'none'})}})},//重置游戏逻辑reset(){//...下面接着讲}})
重置游戏逻辑,先处理初始化,再处理画出来,实现代码如下💡小提示
有没有注意到
CanvasContext
类,这是替代wx.createCanvasContext()
用的有相关文章【微信小程序提示wx.createCanvasContext方法已废弃的解决方案】
//游戏中大部分颜色信息都在这里定义const Colors = {//...};//游戏的所有棋子名字都在这里定义const beastNames = {//...象>狮>虎>豹>狼>狗>猫>鼠};Page({data: {//定义所有画布canvas: {},stateMsg: '初始化...',//游戏状态消息isMyFlag: true,//当前是否是我方先手trapCoordinate: [],//陷阱坐标denCoordinate: [],//兽穴坐标rillCoordinate: [],//小河坐标beastCoordinates: [],//所有兽布局坐标gameEnd: false,//游戏是否结束},//初始化游戏数据init(){//棋盘中就像地图,存在的一切物体都可化作数据, 只用坐标表示数据就可以了//...陷阱坐标数据this.data.trapCoordinate = [//...];//两个兽穴坐标数据,其中[3,0]是坐标数组,如一个兽穴在地图的坐标点(3,0)处this.data.denCoordinate = [ [3,0],[3,8] ];//小河坐标数据this.data.rillCoordinate = [//...];//所有的棋子数据this.data.beastCoordinates = [{name: beastNames.rat, rank: 0, myFlag: false, coordinate: [0,2] },//...];},//重置游戏逻辑reset(){const {canvA, canvB, canvC, width, height } = this.data.canvas;this.init();//...设置画布的相关参数 canvA.setFillStyle(Colors.backColor);canvA.setStrokeStyle(Colors.borderColor);canvA.fillRect(0,0,width,height);//画背景色canvA.beginPath();const cols = 7;//棋盘的横向格子数const rows = cols+2;//棋盘的纵向格子数const gw = Math.trunc(width/cols);//格子宽度const gh = Math.trunc(height/rows);//格子高度//遍历处理for(let i=1; i<rows; i++){//...把棋盘的所有格子都画出来}//此处省略...设置画布的相关参数//格子一半的宽度和高度,由于是手机竖屏显示,宽高比有不同const gwR = gw/2;const ghR = gh/2;//把所有的陷阱画出来this.data.trapCoordinate.forEach(e => {//...canvA.fillText("陷阱", x, y);});//把两个兽穴画出来this.data.denCoordinate.forEach(e => {//...canvA.fillText("兽穴",x,y);});//把两条小河画出来this.data.rillCoordinate.forEach(e => {//...canvA.fillRect(x+1,y+1,waW,waH);});canvA.draw();//将格子的宽高信息设置到canvas画布中,后面有用Object.assign(this.data.canvas, {gw,gh,gwR,ghR});//把所有的兽棋画出来this.data.beastCoordinates.forEach(e => {this.drawChess(e,canvB);});canvB.draw();//...初始化数据this.data.isMyFlag = true;this.data.gameEnd = false;this.setData({stateMsg: '👉我方先手'})},//画兽棋的方法drawChess(e,canv, isMax){const {gw, gh, gwR, ghR } = this.data.canvas;this.drawChessTo({x: e.coordinate[0] * gw + gwR,//计算出x坐标y: e.coordinate[1] * gh + ghR,//计算出y坐标name: e.name,//兽棋的名称myFlag: e.myFlag,//是否是我方的canv,//画布isMax,//是否画到最大,当拾起棋子时就画到最大gwR,//格子宽度一半});},//具体的画棋方法,根据坐标定位来画drawChessTo(e){//...此处省略}}
以上就是初始化游戏的处理过程,接下来,讲一下处理用户触摸点击的逻辑,这里的逻辑就复杂了一些,需要细分成三个逻辑来处理,分别是识棋,吃棋,走棋,实现代码如下
Page({data: {//定义所有画布canvas: {},stateMsg: '初始化...',//游戏状态消息isMyFlag: true,//当前是否是我方先手trapCoordinate: [],//陷阱denCoordinate: [],//兽穴rillCoordinate: [],//小河beastCoordinates: [],//所有兽gameEnd: false,//游戏是否结束},//处理用户触摸点击的onTouch(e){if (this.data.gameEnd || this.data.isLoadAnimation) return;const {canvC, gwR } = this.data.canvas;const touch = e.touches[0];//第一个触摸点let chess = this.findChess(touch);//触摸位置处是棋子if (chess!=null) {//如果当前有拾起的棋子if (this.data.selectChess!=null){//触摸的棋子不是我方的if (chess.myFlag!=this.data.selectChess.myFlag){this.eatChess(chess);return;}//若是我方的,当前还不是我方先手,不能选择我方的棋子else if(this.data.isMyFlag!=this.data.selectChess.myFlag){return;}}//拾起棋子前,判断是我方先手,只能选择我方的棋子else if (chess.myFlag!=this.data.isMyFlag){return;}} else {this.moveChess(touch);return;}//选择棋子后,画出来this.data.selectChess = chess;this.drawChess(chess,canvC,true);canvC.draw();},//识别棋盘中棋子findChess(touch){//...},//处理吃棋的方法eatChess(e){//...},//移动棋子的方法moveChess(t){//...},//画棋子的方法drawChess(e,canv, isMax){//...上面有讲过了}})
不够详细吧,那写具体一点,识棋,吃棋,走棋的实现方法大致思路如下
Page({data: {//定义所有画布canvas: {},stateMsg: '初始化...',//游戏状态消息isMyFlag: true,//当前是否是我方先手trapCoordinate: [],//陷阱denCoordinate: [],//兽穴rillCoordinate: [],//小河beastCoordinates: [],//所有兽gameEnd: false,//游戏是否结束},//处理吃棋的方法eatChess(e){if (e==null || this.data.selectChess==null) return;let x = e.coordinate[0];//x坐标let y = e.coordinate[1];//y坐标let c = this.getMoveCoordainte(x, y);if (c==null) return;//不可移动该位置let s = this.data.selectChess;//拾起的棋子所在的位置let isEat = false;//先判断该位置是否有陷阱let trap = this.data.trapCoordinate.find(m => m[0]==x && m[1]==y);let rill = null;//如果没有陷阱,判断是否有小河if (trap==null) rill = this.data.rillCoordinate.find(m => m[0]==x && m[1]==y);//拾起的棋子(鼠),是否在小河中let rill2 = this.data.rillCoordinate.find(m => m[0]==s.coordinate[0] && m[1]==s.coordinate[1]);if (rill) {isEat = rill!=null && rill2!=null;//在小河中是否互吃} else if (rill2==null) {//在陷阱中,任意吃if (trap) isEat = (y>4 && this.data.isMyFlag) || (y<4 && this.data.isMyFlag==false);//还是不能吃的话,最后用大吃小的规则判断,还有鼠 能吃 象if (!isEat) isEat = (s.rank==0 && e.rank==7) || (!(s.rank==7 && e.rank==0) && s.rank>=e.rank);}if (isEat) {//能吃,查找棋子的索引let index = this.data.beastCoordinates.findIndex(m => m.name==e.name && m.myFlag==e.myFlag);//如果存在,就吃棋if (index>=0){//...处理细节省略this.startAnimaFromCanvC({chess: s,start: {x: c2[0], y: c2[1] },end: {x, y },complete:()=>{//...省略处理细节//移除被吃掉的棋子this.data.beastCoordinates.splice(index,1);//如果进入对方的兽穴,就判定游戏结束if(c.endGame) this.data.gameEnd = true;this.data.selectChess = null;this.updateGameState();}});}}},//移动(走)棋子的方法moveChess(t){if (t==null || this.data.selectChess==null) return;const {canvB, canvC, gw, gh } = this.data.canvas;let x = Math.trunc(t.x / gw);let y = Math.trunc(t.y / gh);let c = this.getMoveCoordainte(x,y);//不可移动该位置if (c==null) return;//...此处省略细节this.startAnimaFromCanvC({chess: this.data.selectChess,start: {x: c2[0], y: c2[1] },end: {x, y },complete:()=>{//...此处省略细节//如果进入对方的兽穴,就判定游戏结束if(c.endGame) this.data.gameEnd = true;this.data.selectChess = null;this.updateGameState();}});},//识别棋盘中棋子findChess(touch){const {gw, gh } = this.data.canvas;//...知道了触摸点在棋盘中的位置,就可以从所有兽棋数据中查找并返回return this.data.beastCoordinates.find(m => {//...});},//获取可移动的坐标getMoveCoordainte(x,y){//...},//走棋,吃棋动作处理成动画startAnimaFromCanvC(e){//...plete();//执行动画结束时调用},//更新游戏状态updateGameState(){//...}})
一不小心就写到这么多,就到此结束吧,剩下的方法都有列出来的,一个都不能少,只是部分实现细节都用省略号代替,能耐心看到这里,讲得还算可以吧,实现过程是不难,就代码写得比较多,不知读者能否看懂,对此文章讲得实现思路有清晰了没有呢🙂
最后项目完工,游戏编译正常,运行效果如下图所示,是个动图哦,如觉得有帮助,麻烦请点个👍,或💡小提示
如果想要看更详细的,有相关的项目源码,请点此前往查看,
在资源一栏下,里面有斗兽棋项目源码,选择下载即可,然后用微信开发工具打开,直接编译运行就能看到效果
❤
,鼓励一下,在此谢过~