还记得小时候玩过的斗兽棋游戏不,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){
//...
e.complete();//执行动画结束时调用
},
//更新游戏状态
updateGameState(){
//...
}
})
- 一不小心就写到这么多,就到此结束吧,剩下的方法都有列出来的,一个都不能少,只是部分实现细节都用省略号代替,能耐心看到这里,讲得还算可以吧,实现过程是不难,就代码写得比较多,不知读者能否看懂,对此文章讲得实现思路有清晰了没有呢🙂
💡小提示
如果想要看更详细的,有相关的项目源码,请点此前往查看,
在资源一栏下,里面有斗兽棋项目源码,选择下载即可,然后用微信开发工具打开,直接编译运行就能看到效果
- 最后项目完工,游戏编译正常,运行效果如下图所示,是个动图哦,如觉得有帮助,麻烦请点个👍,或
❤
,鼓励一下,在此谢过~