【跳棋】跳棋游戏-多人游戏-微信小程序开发流程详解

news2024/10/7 17:30:19

看到跳棋游戏,一个2到6人可一起玩的游戏,于是联想起,自己上小学时候陪同学们玩过的弹珠游戏,是不是跟跳棋游戏很像呢,看了跳棋玩法,有兴趣就研究,这里把跳棋游戏给弄出来了,想知道地图怎么画,对此感兴趣的同学可以研究研究,让家人孩子们一起参与游戏互动吧,珍惜陪伴的幸福时光。

打开微信开发工具,选择小程序,创建一个项目,

例如项目名称为miniprogram_chinese_draughts,然后选择以下,再确定创建

  • AppID 使用自己的测试号
  • 不使用云服务
  • JavaScript - 基础模板

开始页

新建小程序项目后,开发工具会自动生成一些基础模板文件,

在位置/pages/index/index这里,把index.wxml改成开始页面,添加一个选择人数的表单,和按钮,显示如下
在这里插入图片描述

跳棋游戏中的是不能有5个人参与的,因为第5个参与者没有对手,不公平吧,

index.js这里修改一下,添加一个方法enterGame(e),点击开始游戏会调用到,代码如下,

enterGame(e){
    const { count } = e.detail.value;
    wx.navigateTo({
      url: '/pages/game/game?args='+count,
    })
}

跳转到游戏页面,同时会传一个参数count表示参与人数

游戏页面

添加一个游戏页面,文件位置在pages/game/game,页面game.wxml里的添加布局,内容如下

<view class="page">
    <canvas class="canvas" id="canv" type="2d" bindtouchstart="onTouchStart"></canvas>
    <scroll-view class="scroll-view" scroll-y="true">
        <view class="game-info">
        <!-- 这里显示游戏状态信息的布局 --->
        </view>
    </scroll-view>
</view>

其中canvas画布,是最重要的组件,可用来绘制画面,
另外放置的组件是用于显示每个参与选手(玩家)的游戏状态,

初始化

开始页有传来的参数count,就从游戏页面onLoad(options)加载方法里获取,代码如下

/**
 * 页面的初始数据
 */
data: {
    players:[],//参与的玩家列表
    currentPlayer:0,//指定哪个玩家下棋
},
onLoad(options) {
    let index = 0;//默认的选择
    //如果从上一页有传来参数,这里改变默认的选择
    if(options?.args) index = ['2','3','4','6'].indexOf(options.args);
    //按照选择人数分配玩家
    let players = [[0,5],[1,2,5],[1,2,3,4],[0,1,2,3,4,5]][index].map(id=>{
        return {
            id: parseInt(id),//玩家的id
            color: ChessColors[id],//对应的颜色
            step: 0,//开始步数
            rank: 0,//用于记录的排名
        }
    });
    //更新玩家列表显示
    this.setData({
        players
    })
},

接下来,从游戏页面的准备完成事件onReady()方法里,

获取一下页面布局中的canvas,然后绑定触摸开始事件,代码如下

onReady() {
  wx.createSelectorQuery().select('#canv').fields({
        size:true,
        node:true
    },res=>{
        //...
        //获取画布数据
        this.canvasData = {
            canvas:res.node,//画布canvas元素的节点
            ctx:res.node.getContext('2d'),//画布canvas的绘制工具
        };
        //实例化地图对象
		const map = new Map(this.canvasData,this.data.players.map(p=>p.id));
		//绘制地图的异步方法
		map.drawMap(()=>{
			//绘制完成了,这里绘制所有参与玩家的棋子
		    map.drawChesses();
		});
		this.map = map;
        //...
    }).exec()
},
onTouchStart(e) {
	//...这里是canvas画布触摸开始的处理方法
}

从上面看,其中的Map是指地图模块,

在使用模块前,需要在代码文件头写一行导入模块的代码,也就是引用,代码如下

import { Map, ChessColors } from '../../utils/map.js';

还有ChessColors ,是默认分配玩家的棋子颜色集合,若想自定义,可在此修改,
模块文件代码共有240行(包括空格行),看着不多,实现了地图数据和地图绘制方法

游戏背景

在上面的初始化逻辑里,游戏背景已经绘制好了,

地图的绘制方法是drawMap(callback),参数callback是传入的回调方法,绘制完成后会调用,

这里简单说一下它的实现过程,代码如下

drawMap(callback){
   const { canvas, ctx } = this.canvasData;
   //定义它们分别是网格,所有棋子,格子大小,格子半径,圆半径
   const { grids, chesses, gSize, gR, gRp } = this;
	//初始化值
   const angle = Math.PI*2;
   //修改设置
   ctx.strokeStyle=BorderColor;
   grids.forEach((g,i)=>{
   		//这个方法是扫描一个格子位置中相邻的一些格子(圆)
       let gs = this.scanGrids(g);
       if (gs.length<=0)  return;
       //在这里画上一些格子之间的连接线
       gs.forEach(g1=>{
           ctx.beginPath();
           ctx.moveTo(g.x,g.y);
           ctx.lineTo(g1.x,g1.y);
           ctx.stroke();
       });
   });
   ctx.fillStyle=BorderColor;
   //接下来,画出所有的格子(圆),就是覆盖着画
   grids.forEach((g,i)=>{
       //...
       ctx.beginPath();
       ctx.arc(g.x,g.y,gRp,0,angle);
       ctx.fill();
       ctx.stroke();
   });
	//这里就算画好了,导出图像
   let img = canvas.createImage();
   img.onload = () => callback(img);
   img.src = canvas.toDataURL();
   //网格地图,需要设置到bgImg缓存起来(背景图),将来有用
   this.bgImg = img;
}

由于这里是设置到bgImg缓存中,每次更新就要重新绘制出来,
也可以将绘制的图像设置到背景图中,每次更新就不用重新绘制地图,直接在底层显示,
然后绘制其它的,例如绘制改变位置后的棋子

选手和棋子

绘制不同选手阵营中的棋子,也可以看作是珠子,不同选手代表的棋子和格子颜色不同,

如果棋子的位置改变后,就需要调用地图的方法drawChesses(selectIndex)重新绘制,

显示最新的所有棋子布局,代码如下

drawChesses(selectIndex){
	const { canvas, ctx } = this.canvasData;
    const { grids, chesses, gSize, gR, gRp } = this;
    const angle = Math.PI*2;
    //每次绘制前,都要清空画布
    ctx.clearRect(0,0,canvas.width,canvas.height);
    //将缓存的背景图重新绘制出来
    ctx.drawImage(this.bgImg, 0, 0);
    //然后,再去绘制所有的棋子
    let isSelect=false;
    chesses.forEach(chess=>{
    	//判断是否有选择,记录一下
        if(chess.index==selectIndex) isSelect=true;
    	//如果有选择棋子,就在棋子周围画一个圆(代表选择)
        this.drawChesse(chess,selectIndex);
    });
    //这里还要判断,如果没有棋子被选择过,那剩下可能是选择了没有棋子占用的格子
    if(!isSelect) {
        this.drawChesse(undefined,selectIndex);
    }
}

从上面看出,传入的参数selectIndex就是指玩家选择的格子索引,
同时也传给了方法drawChesse(chess,selectIndex),另一个参数chess就是指选择的棋子,没有选择就传undefined,这个方法实现了怎样绘制网格中的棋子

讲到这里,游戏页面就算绘制好了,编译运行看效果,

显示出来是下图这样的,一共6个玩家参与的跳棋游戏
在这里插入图片描述

游戏逻辑

从画布的触摸开始事件中去处理,就是上面提到的onTouchStart(e)方法,

游戏规则

这里再想一下游戏逻辑,当用户点击棋子,选择自己的棋子后,接下来应该怎样实现呢,

这里了解以下游戏规则,

按照游戏规则,理清一下实现思路:

  • 判断每个玩家的步数,在30步内未走出自己的阵营的,就判出局,不能再继续;
  • 走棋,有两个走法:第一个就是只走一步;第二个,就是以旁边的棋子为方向,跳到棋子方向的另一边,可连续跳;
  • 步数,走一步算一步,跳一次也算一步,如果是连续跳也算一步,可以修改过来的;
  • 将对方阵营所有的棋子空位全部占领就判赢,如果是三人以上的,赢的按排名计算,剩下的判出局;

怎么实现游戏逻辑的呢,实现游戏逻辑是最复杂的地方吧,说来也是,只要理解它的实现过程基本就可以自己尝试了,

选择棋子

第一步,要选择棋子,然后移动棋子,就在它的触摸事件中处理,代码如下

onTouchStart(e) {
	//...
   const touch = e.touches[0];//第一个触摸点(位置)
   const { grids, gRp, chesses } = this.map;
   //所有玩家,指向当前下棋的玩家
   const { players, currentPlayer } = this.data;
	//通过触摸点位置判断,从网格中找出选择的棋子
   let chess;
   let grid = grids.find(grid=>{
   		//先从网格位置判断...如果是在这个格子里,再判断是否有棋子在这个格子上...
   });
   //没有点到格子的画,就返回不处理了
   if (grid==undefined) return;
   //给出当前在下棋的玩家
   const player = players[currentPlayer];
   let { selectGridData } = this;
   //判断之前是否选择过网格(棋子)了,还有就是选择的棋子是当前在下棋的玩家的,如果条件满足了,反之就是不满足
   if (!(selectGridData?.chess?.id==player.id)) {
   		//不满足以上条件,就重新设置选择数据
       this.selectGridData = { grid, chess, moveCount:0 };
       //重新绘制所有棋子
       this.map.drawChesses(grid.i);
       //不再继续处理
       return;
   }
	//如果选择到棋子
   if (chess) {
   		//不是自己的,提示一下
       if (chess.id!=player.id) {
           this.showToast(`${player.id+1}号玩家操作`);
           return;
       }
       //如果是相邻跳过的,又选择下一个棋子,就不是连续跳了
       if (selectGridData.moveCount>1) {
       		//换下一个玩家
           this.setNextPlayer();
           return;
       }
       //剩下的,只有选择棋子了吧,直接给它更新显示
       Object.assign(this.selectGridData, { chess, grid });
       this.map.drawChesses(grid.i);
       return;
   }
	//获得移动到下一个位置的距离,相对格子的,一个格子算一个单位距离
   let count = this.map.gotoGrid(selectGridData.grid,grid);
   if (count>0) {
   		//判断是否只移动1个(走),然后就是上次移动的是否大于1个(跳),满足条件
       if (!(count==1 && selectGridData.moveCount>count)) {
       		//反之不满足,这里更新选择的棋子位置
           chess = selectGridData.chess;
           chess.index =  grid.i;
           //记录移动距离
           selectGridData.moveCount = count;
           //棋子步数加1
           player.step++;
           //获取玩家在自己阵营内的所有棋子
           let chesses2 = this.map.getPlayerChessesAtGrids(player.id);
           //判断这个玩家是否在游戏,还有步数
           if (player.rank==0 && player.step==MinStep) {
           		//步数大于MinStep限制,还未走出自己阵营,就得通知这个玩家出局了...
           }
           //更新显示玩家的状态
           this.setData({
               players
           });
       }
   }
   //若只移动1个(走),就更新绘制棋子,换下一个玩家
   if (count==1){
       this.map.drawChesses(grid.i);
       this.setNextPlayer();
       return;
   }
	//剩下的,就是其它操作了,直接更新显示即可
   Object.assign(this.selectGridData, { chess, grid });
   this.map.drawChesses(grid.i);
},

看上面,就会注意到,还有一些细节都在地图模块的方法里了,

上面还有个重要的方法setNextPlayer(),是处理换下一个玩家来,切换玩家前还要加个判断,

就是判断玩家输赢,最后若是剩下一个玩家还没赢的话,那么游戏就结束,代码如下

setNextPlayer(){
   const { players, currentPlayer } = this.data;
	//定义当前参与的玩家
   const player = players[currentPlayer];
   //找出当前玩家的所有棋子
   let chesses2 = this.map.getPlayerChessesAtGrids(player.id);
   //计算当前玩家是否全部占领对方的阵营
   let isWin = chesses2.reduce((current,next,index)=>{
   		//棋子flag属性判断即可...
   });
   if (isWin) {
       let rank=0;
       //全部占领了,更新排名,不再参与...
       player.rank = rank+1;
       //可通过排名和参与人数判断是否到最后了
       if (player.rank+1 >= players.length) {
           //这里是最后的了,直接弹出游戏结束...
           this.showGameOver(`${player.id+1}号玩家赢了,成功占领对方的阵营`);
           return;
       }else{
       		//提示一下,然后继续
           this.showModal(`${player.id+1}号玩家赢了,成功占领对方的阵营`);
       }
   }
	//换下一个玩家
   let current = (currentPlayer+1)%players.length;
   //此处省略了...
   this.setData({
       currentPlayer: current,
       players
   });
   this.showToast(`${players[current].id+1}号玩家操作`);
},

游戏测试

就讲到这里,跳棋游戏的实现思路大概清晰了吧,

调用的其它方法不对,虽然没有讲,但是,见其名知其意,相信自己可以实现出来的,

来看一下,游戏运行的效果动图如下,这是两个人参与的,测试完感觉没问题,
请添加图片描述

想看项目源码的,请点此查看,找到资源类别一栏(如果是手机上看可能找不到,就换成电脑浏览器访问 ),

资源列表里面,找到其中的跳棋游戏项目源码,请放心下载,感谢支持。

游戏启示:对此你有什么启发呢

  • 选好目标,尽快占领,类似五行斗法里的相生相克,
  • 在这个游戏规则里,尽可能快速移动,落后就要挨打

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/705784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

com.alibaba.druid.pool.DruidDataSource报错(显示红色)

springbootAlibaba项目,application.yml配置如下&#xff1a; 圈中的地方报错&#xff0c;pom.xml也引入了druid依赖,如图&#xff1a; 最后改为&#xff1a; 解决。

Halcon学习之一维测量(2)

在Halcon学习之一维测量(1)文章中,我们介绍了直线测量中的基于边缘对的,还有一种是不基于边缘对的,而是基于边缘的。 接下来讲解就以,Halcon自带的例子,芯片pin引脚测量为例子,主要测量引脚的数量,引脚的平均宽度,还有引脚之间的距离: 源码: * Pin Measurement…

攻防世界-Crypto-Normal_RSA

题目描述&#xff1a;下载附件后&#xff0c;附件中只有两个文件&#xff0c;一个是加密后的密钥&#xff0c;一个是公钥文件 背景知识&#xff1a;RSA加密算法 现在相当于给出了密文和公钥&#xff0c;需要我们去求解明文 1. 思路分析 既然要解密&#xff0c;那么必须要获取…

【数据结构与算法】树和二叉树练习题(选择填空)

【数据结构与算法】树和二叉树练习题&#xff1a;选择填空类型 Ⅰ.知前序和中序推理后序Ⅱ.知后序和中序推理前序Ⅲ.深度/广度遍历Ⅳ.通过遍历推理树的形状Ⅴ.树节点问题 Ⅰ.知前序和中序推理后序 题目名称&#xff1a; 1.已知某二叉树的前序遍历序列为5 7 4 9 6 2 1&#xff…

ChatGPT实战:项目管理

人工智能有可能彻底改变许多行业&#xff0c;包括项目管理&#xff0c;及时了解最新技术以及它如何影响你的工作至关重要&#xff0c;因为学习好项目管理&#xff0c;不管你能不能做项目经理&#xff0c;在生活、工作的方面方面都会享受到懂得项目管理后带来的收益。 下面我们借…

np.averag的运算规则

今天写代码的时候&#xff0c;计算加权平均&#xff0c;一直没有搞懂np.average是怎么运算的&#xff0c;做个笔记记录一下&#xff1a; 创建一个&#xff08;3,4,5&#xff09;维度的np数组&#xff0c;然后在创建一个[1,0.5,1]的权重数组&#xff0c;计算他们的加权平均&…

Spring Boot Actuator实现应用监控

1、Spring Boot Actuator实现应用监控 前面的例子中&#xff0c;我们学习的都是 Prometheus 自身的内容&#xff0c;即监控的都是机器或者系统层面的指标。那么如果我 们需要对 Java 应用做监控&#xff0c;例如&#xff1a;监控 JVM 的信息&#xff0c;监控 Spring Bean 的信…

如何实现多 Tab 同步登陆和退出

一. 场景再现 前两天接到一个需求&#xff0c;要求实现类似于 B站 的那种&#xff0c;当我同时打开多个 Tab 标签的时候&#xff0c;如果我在某一个窗口退出了&#xff0c;那么其它窗口的登陆状态也需要同步退出。如下图&#xff0c;我同时打开了两个 tab 。 当我点击其中一个…

【Java】SpringBoot项目中resource目录下有多个配置文件,如何指定某个特定的配置文件生效?

在我们开发项目的时候&#xff0c;会有多个开发环境&#xff0c;比如测试环境&#xff0c;生产环境&#xff0c;开发环境&#xff0c;产品环境等环境&#xff0c;如下 怎么多的配置文件&#xff0c;那么哪一个配置文件会生效&#xff1f;我们如何指定我们需要的配置文件&…

调用office的Excel制作统计图,并保存成图片

public class CMSChart{private Chart chart;public CMSChart(Chart chart){this.chart chart;}/// <summary>/// 保存图片/// </summary>/// <param name"fullPicName"></param>public void SaveAs(string fullPicName){ChartImageFormat …

VMware 虚拟机安装 CentOS 8

一、准备 演示过程使用的软件版本如下&#xff1a; VMware 16.2.5 build-20904516 CentOS 8.5.2111-x86_64-dvd1.iso VMware 下载安装请参考&#xff1a;VMware16.x 下载与安装 CentOS 镜像下载请参考&#xff1a;CentOS 各版本镜像下载 VMware 15 及以下版本&#xff0c;不支…

nginx部署本地项目如何让异地公网访问?服务器端口映射配置!

接触过IIS或apache的小伙伴们&#xff0c;对nginx是比较容易理解的&#xff0c;nginx有点类似&#xff0c;又有所差异&#xff0c;在选择使用时根据自己本地应用场景来部署使用即可。通过一些对比可能会更加清楚了解&#xff1a; 1.nginx是轻量级&#xff0c;比apache占用更少…

C# 多数元素

169 多数元素 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&…

FPGA USB FX2 数据回环试验 驱动CY7C68013A实现 提供工程源码和技术支持

目录 1、前言2、我这儿已有的 FPGA USB 通信方案3、CY7C68013A芯片解读和硬件设计FX2 简介SlaveFIFO模式及其配置 4、工程详细设计方案5、vivado工程6、上板调试验证7、福利&#xff1a;工程代码的获取 1、前言 目前USB2.0的实现方案很多&#xff0c;但就简单好用的角度而言&a…

kkfileview部署使用

1.gitee下载源码 kkFileView: 使用spring boot打造文件文档在线预览项目解决方案&#xff0c;支持doc、docx、ppt、pptx、wps、xls、xlsx、zip、rar、ofd、xmind、bpmn 、eml 、epub、3ds、dwg、psd 、mp4、mp3以及众多类文本类型文件在线预览 2.去掉cad 3.替换水印字体为免费…

php宝塔搭建好用的二次元图系统php源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。本期给大家带来一套好用的二次元图系统php源码。抽时间看了一下&#xff0c;功能还是不错的&#xff0c;有参考价值。感兴趣的朋友可以自行下载学习。 技术架构 PHP7.0 nginx mysql5.7 JS CSS HTMLcnetos7以…

德国汽车行业杂志HANSERautomotive就浪潮信息算法团队在自动驾驶领域所取得的核心突破,进行专题署名报道

近日&#xff0c;德国专业汽车行业杂志HANSERautomotive就浪潮信息算法团队在自动驾驶领域所取得的核心突破&#xff0c;进行专题署名文章报道“Wahrnehmungssysteme und 3D-Objekterkennung Alles im Blick ”(《感知系统和三维物体识别&#xff0c;一切尽收眼底》)。针对AI感…

python ---- 根据文件名称中的数字排序

1. 需求分析 获取文件夹中的所有文件名称&#xff1b;筛选所有文件名称中的图片文件&#xff1b;获取图片文件名称中的数字&#xff1b;对数字排序&#xff1b;返回排序后的图片文件名称列表。 2. 获取所有图片文件名称 2.1 代码实现分析 使用 os.listdir 获取文件夹下的所…

keil_arm满减栈应用场景

.text .global _start _start:满减压栈使用场景ldr sp,0x40000800mov r0,#0x1mov r1,#0x2bl add1_funcadd r0,r0,r1 r0 r0 r1 0x3b stopadd1_func: 压栈保存现场 r0 0x1 r1 0x2stmfd sp!,{r0,r1&#xff0c;lr}mov r0,#0x3mov r1,#0x4bl add2_funcadd r0,r0,r1 r0 r…

如何在Windows中批量创建多个文件夹

你需要更好地组织你的文件和文档吗&#xff1f;如果你在笔记本电脑或台式机上将相关文件分组到不同的文件夹中&#xff0c;那么总是很容易找到你需要的东西。你还可以更改图标的大小&#xff0c;使其在视觉上更美观。 在 Windows 中创建一个文件夹的传统做法是&#xff1a;右键…