继续学习egret,最近写了数字华容道的小游戏,非常简单的小游戏。首先预览一下效果:

数字华容道就是通过移动方块,将方块按照数字的排序进行排列。功能很简单,主要有刷新,提升阶数,如何一定有解,以及简单的存储数据。
(由于找背景图太累,最高关只设置到10阶,这个其实是没有限制的,按照逆序数打乱规则,这个的效率可以支持到很高阶)

想看重点的直接看第二大点~

————————————————————————————————————————

(全局用3阶为例,即3x3的难度)

零、首先创建一个全局需要的数据类

主要存储当前难度(阶数),不同难度的方块大小,最低关卡,最高关卡,玩家数据,当前是否能操作等数据。

class Data {public static BlockWidth: number = 200;//方块大小(游戏区域为600x600)public static Order_hard: number = 3;//阶数public static lowestPass: number = 2;//最低阶数(关卡控制)public static highestPass: number = 10;//最高阶数(关卡控制)public static isCanKeyDown: boolean = false;//当前是否能操作public static playerData: any;//玩家数据public static alldata: allData;//所有数据public static playerDataKey: string = '451281425';//存储数据的key值}

其他就是正常的egret的存储和读取缓存数据的方法。

egret.localStorage.getItem(Data.playerDataKey);let ccc = JSON.stringify(Data.playerData);
egret.localStorage.setItem(Data.playerDataKey, ccc);

一、创建方块类

(1)首先需要创建方块类,方便后面的移动,交换等操作。
方块有背景图,数字,边框等元素。
class Block extends egret.DisplayObjectContainer {public constructor(id: number, arrpos: number) {super();this.initBlock(id, arrpos);}public id: number = -1;//方块的id,即最终的正确位置public arrPos: number = -1;//现在的位置,被打乱后的位置public arrUp: number = -1;//方块上方的方块idpublic arrDown: number = -1;//方块下方的方块idpublic arrLeft: number = -1;//方块左边的方块idpublic arrRight: number = -1;//方块右方的方块idpublic numStr: egret.TextField;//方块中间显示的数字字符串public initBlock(id: number, arrpos: number): void {this.id = id;this.arrPos = arrpos;this.saveArrPos(arrpos);this.initGraphics();this.numStr = new egret.TextField();this.numStr.text = (this.id + 1).toString();this.numStr.bold = true;this.numStr.size = Data.BlockWidth / 2;this.numStr.x = this.shape.x + (Data.BlockWidth - this.numStr.width) / 2;this.numStr.y = this.shape.y + (Data.BlockWidth - this.numStr.height) / 2;if (id == Math.pow(Data.Order_hard, 2) - 1) {this.numStr.alpha = 0;}this.addChild(this.numStr);}
}

在这里定义每个方块拥有的参数,比如上下左右的方块id和方块的当前id,为后面的交换和移动做准备。
****注:这里的id为方块的正确位置,而arrpos表示当前方块被打乱后的位置。id的数值从创建起就不会变化。

(2)最后一个方块
if (id == Math.pow(Data.Order_hard, 2) - 1) {this.numStr.alpha = 0;
}

在这里我其实是创建了阶数的平方个方块,并将最后一个方块的透明度设为0,代表华容道里面的那个空缺位置。即创建了9个方块,但玩家实际上只能看到8个。

(3)给方块设置边界参数

创建的方块中由于不可能每个方块的周围都有方块,所以需要通过参数设置方块的边界

public saveArrPos(id: number): void {if (Math.floor(id / Data.Order_hard) * Data.BlockWidth == 0) {//第一行this.arrUp = -1;} else {this.arrUp = id - Data.Order_hard;}if (Math.floor(id / Data.Order_hard) * Data.BlockWidth == (Data.Order_hard - 1) * Data.BlockWidth) {//最后一行this.arrDown = -1;} else {this.arrDown = id + Data.Order_hard;}if (id % Data.Order_hard * Data.BlockWidth == 0) {//第一列this.arrLeft = -1;} else {this.arrLeft = id - 1;}if (id % Data.Order_hard * Data.BlockWidth == (Data.Order_hard - 1) * Data.BlockWidth) {//最后一列this.arrRight = -1;} else {this.arrRight = id + 1;}this.arrPos = id;}

二、创建主场景

(1)打乱规则


图中使用的打乱规则就是第二种

打乱的规则主要分为两种,第一种方便易懂,但效率差。第二种效率高,但要理解原因。

1)移动打乱的方法(不推荐)

以3x3为例,创建了9个方块(第9个方块为空缺方块,命名为方块9),将9个方块按正确答案的顺序排列,然后移动一定次数的方块9,并通过以下2点规则进行移动:

  • 边界时,只能向非边界方向移动
  • 移动方向不得与上一次移动方向相反,即不得回退

通过方块9的不断移动,打乱其他方块的位置。这样可以保证一定有解。

————————————————————
但是在实际操作过程中,有以下几点问题:

  • 方块打乱的效率低
  • 移动上百次甚至上千次,仍然会有部分甚至超过一半的方块仍在正确答案的位置。打乱效果不好

————————————————————
为了解决上面的问题,在打乱时新增一条规则:

  • 寻找当前方块的id和arrpos变量,即方块的所在位置是否与正确答案的位置是否相等,如果超过当前阶数方块数量的一半以上相同,则继续打乱。以3x3为例,如果有5个及以上方块的仍然在它正确答案的位置上,就继续打乱,不停止。

但是这样由于调用方法及运行次数太多,会有程序崩溃的可能,当阶数越大时,崩溃的概率越大。在3阶运行次数大概在800次以内,4阶开始有可能上万,5阶以上崩溃概率大大增加。

2)随机打乱,通过逆序数判断是否有解(这是最终采用的方法)

将9个方块放入数组中,进行随机打乱,然后判断打乱后的数组的逆序数是否符合判断条件。

这里简单说一下逆序数,比如[1,3,2]这样的数列,进行两两比较,如果前面的数大于后面的数,即为一个逆序数对,逆序数则+1。[1,3,2]中有{1,3},{1,2},{3,2},其中{3,2}是逆序数对,则逆序数+1。而[3,2,1]中的逆序数则为3。
注:空缺的那个方块是不参与逆序数的判断的,所以,进行逆序数判断时,要跳过空缺方块,即实际上只对1——8号方块进行逆序数判断,空缺方块的位置是额外的判断条件。(在打乱的数组中,判断如果id为空缺方块,则跳过计数,不+1)

了解了这个就可以开始判断打乱顺序的数组是否符合有解的条件了:

  • 阶数为奇数时,逆序为偶数,不用判断空缺方块的位置;
  • 阶数为偶数,逆序数为偶数,空缺方块被打乱位置后所在的行数空缺方块应该在的正确位置的行数的差值为偶数;
  • 阶数为偶数,逆序数为奇数,空缺方块被打乱位置后所在的行数空缺方块应该在的正确位置的行数的差值为奇数;

满足上面任一条件的即为打乱的数组是有解数组。得到了数组就可以开始在主场景中添加方块。

public upset(): void {//打乱顺序this.luanarr = [];let max: number = Math.pow(Data.Order_hard, 2);for (let i: number = 0; i < max; i++) {this.luanarr.push(Math.pow(Data.Order_hard, 2) - 1 - i);//倒叙数组,以使打乱的数组更乱}for (let i: number = 0; i < max; i++) {//打乱倒叙数组let tempOne: number = Math.floor(Math.random() * max);let tempTwo: number = Math.floor(Math.random() * max);let temp = this.luanarr[tempOne];this.luanarr[tempOne] = this.luanarr[tempTwo];this.luanarr[tempTwo] = temp;}this.loopcheckRight();}public loopcheckRight(): void {//检查打乱的数组是否正确let isRight: boolean = this.checkIsHasAnswer();//检查打乱的数组是否有解if (isRight) {//如果该数组有解,不进行操作,或者添加一些ui} else {//如果数组无解,则重新打乱let max: number = Math.pow(Data.Order_hard, 2);for (let i: number = 0; i < max; i++) {//打乱倒叙数组let tempOne: number = Math.floor(Math.random() * max);let tempTwo: number = Math.floor(Math.random() * max);let temp = this.luanarr[tempOne];this.luanarr[tempOne] = this.luanarr[tempTwo];this.luanarr[tempTwo] = temp;}this.loopcheckRight();}}public checkIsHasAnswer(): boolean {//检查打乱的数组是否有解let inversionNumber: number = 0;//逆序数对的数量let max: number = Math.pow(Data.Order_hard, 2);let arrNoemety: Array<number> = [];for (let i: number = 0; i < max; i++) {if (this.luanarr[i] != this.luanarr.length - 1) {arrNoemety.push(this.luanarr[i]);}}for (let i: number = 0; i < arrNoemety.length; i++) {for (let j: number = i + 1; j < arrNoemety.length; j++) {if (arrNoemety[i] > arrNoemety[j]) {inversionNumber++;}}}if (this.checkSort() == false) {return false;}console.log("逆序数数量:", inversionNumber)//若格子列数为奇数,则逆序数必须为偶数;if (Data.Order_hard % 2 == 1 && inversionNumber % 2 == 0) {return true;}//若格子列数为偶数,且逆序数为偶数,则当前空格所在行数与初始空格所在行数的差为偶数;if (Data.Order_hard % 2 == 0 && inversionNumber % 2 == 0) {for (let i: number = 0; i < this.luanarr.length; i++) {if (this.luanarr[i] == this.luanarr.length - 1 && (Math.floor(i / Data.Order_hard) + 1 - Data.Order_hard) % 2 == 0) {return true;}}}//若格子列数为偶数,且逆序数为奇数,则当前空格所在行数与初始空格所在行数的差为奇数。if (Data.Order_hard % 2 == 0 && inversionNumber % 2 == 1) {for (let i: number = 0; i < this.luanarr.length; i++) {if (this.luanarr[i] == this.luanarr.length - 1 && (Math.floor(i / Data.Order_hard) + 1 - Data.Order_hard) % 2 == 1) {return true;}}}return false;}public checkSort(): boolean {//检查打乱后数组中方块位置没有被打乱的数量是否过多let sameNum: number = 0;for (let i: number = 0; i < this.luanarr.length; i++) {if (i == this.luanarr[i]) {sameNum++;}}if (sameNum >= Math.floor(Math.pow(Data.Order_hard, 2) / 2)) {//相同的数量return false;}return true;}
(2)添加方块
public oneMoreAgain(): void {Data.isCanKeyDown = false;//当前不可操作this.isStartGame = false;//当前没有开始游戏this.removeChildren();//移除场景中的元素this.upset();//利用上面的打乱规则获得被打乱的数组this.luanarrthis.blockArr = [];//一个新的方块数组for (let i: number = 0; i < this.luanarr.length; i++) {//循环被打乱的数组,i即为方块被打乱后的位置let block: Block = new Block(this.luanarr[i], i);//参数传递方块对象和方块位置this.resetPos(block, i);//设置方块位置this.addChild(block);//添加方块到场景this.blockArr.push(block);//方块数组中添加这个方块if (this.luanarr[i] == Math.pow(Data.Order_hard, 2) - 1) {//最后一个方块的透明度为0,并且存到一个空方块中单独操作block.alpha = 0;this.emptyBlock = block;}}if (this.gameui) {//添加展示玩家数据的ui界面this.addChild(this.gameui);}if (this.timeListen != -1) {//重置时间计数egret.clearInterval(this.timeListen);this.timeListen = -1;}this.gameui.curStepStr.text = '当前步数:' + 0;//步数计数this.gameui.curTime.text = '当前时间:' + 0 + 's';//时间计数this.changeInfoShow();//显示记录this.stepNum = 0;//当前步数this.cumulativeTime = 0;//当前时间Data.isCanKeyDown = true;//可以操作this.isStartGame = true;//开始游戏this.timeListen = egret.setInterval(this.timeadd, this, 1000);//开始计时}
public resetPos(block: Block, id: number): void {//根据在数组中的位置,设置方块位置block.x = id % Data.Order_hard * Data.BlockWidth + 22;block.y = Math.floor(id / Data.Order_hard) * Data.BlockWidth + 350;
}

三、操作

(1)移动方块

作为一个移动的游戏,必不可少的当然是上下左右的移动。(这个游戏中虽然看着像移动8个可见方块,但其实是对那个不可见的空缺方块进行的操作。每次交换的都是这个方块和周围的方块。)

/*** 向上*/public up(): void {if (this.emptyBlock.arrUp != -1) {//判断是否上边界this.tweenPos(this.emptyBlock.arrUp);}}/*** 向下*/public down(): void {if (this.emptyBlock.arrDown != -1) {//判断是否下边界this.tweenPos(this.emptyBlock.arrDown);}}/*** 向左*/public left(): void {if (this.emptyBlock.arrLeft != -1) {//判断是否左边界this.tweenPos(this.emptyBlock.arrLeft);}}/*** 向右*/public right(): void {if (this.emptyBlock.arrRight != -1) {//判断是否右边界this.tweenPos(this.emptyBlock.arrRight);}}public tweenPos(id: number): void {//按100毫秒移动方块if (this.isStartGame) {//如果在游戏中Data.isCanKeyDown = false;//移动中关闭移动操作let posX: number = this.emptyBlock.arrPos % Data.Order_hard * Data.BlockWidth + 22;//重置方块位置let posY: number = Math.floor(this.emptyBlock.arrPos / Data.Order_hard) * Data.BlockWidth + 350;egret.Tween.get(this.blockArr[id]).to({ x: posX, y: posY }, 100).call(function (): void {//移动方块this.exchange(id);//交换2个方块的数据this.stepNum++;//步数+1this.gameui.curStepStr.text = '当前步数:' + this.stepNum;//更新步数显示}, this);}}public exchange(id: number): void {//交换数据let temp: Block = this.blockArr[id];let temparrpos: number = temp.arrPos;let emptyarrpos: number = this.emptyBlock.arrPos;temp.saveArrPos(this.emptyBlock.arrPos);this.resetPos(temp, this.emptyBlock.arrPos);this.emptyBlock.saveArrPos(temparrpos);this.resetPos(this.emptyBlock, temparrpos);this.blockArr[temparrpos] = this.emptyBlock;this.blockArr[emptyarrpos] = temp;//在这里交换了2个方块的数据,但是注意,这里的id是不会更改的,//id为那个方块的正确位置,具有唯一且不可更改性if (this.isStartGame) {//每次交换完成后,检查是否过关for (let i: number = 0; i < this.blockArr.length; i++) {//循环方块数组if (this.blockArr[i].id != this.blockArr[i].arrPos) {//如果有某一个方块的位置不对,就跳出循环,表示没过关Data.isCanKeyDown = true;break;}if (i == this.blockArr.length - 1) {//游戏完成,当前关通过,更新显示的ui数据this.gameui.congratulationsStr.visible = true;this.gameui.nextLevelStr.visible = true;let curdata: allData = Data.playerData.alldata;if (curdata.everPassHeighestLevel < Data.Order_hard - 1) {curdata.everPassHeighestLevel = Data.Order_hard - 1;}let curdataArr: allData = Data.playerData.alldata.everyLevelData;if (curdataArr[Data.Order_hard - 2].minimumStepNum > this.stepNum) {curdataArr[Data.Order_hard - 2].minimumStepNum = this.stepNum + 1;}if (curdataArr[Data.Order_hard - 2].minimumTime > this.cumulativeTime) {curdataArr[Data.Order_hard - 2].minimumTime = this.cumulativeTime;}if (Data.Order_hard <= 10) {this.isMouseCanClick = false;for (let i: number = 0; i < this.blockArr.length; i++) {egret.Tween.get(this.blockArr[i].numStr).to({ alpha: 0 }, 800);}egret.Tween.get(this.emptyBlock).to({ alpha: 1 }, 800).call(function (): void {this.isMouseCanClick = true;}, this);}this.changeInfoShow();Data.savePlayerData();if (this.timeListen != -1) {egret.clearInterval(this.timeListen);this.timeListen = -1;}}}}}
(2)难度递增

难度的递增可以通过以下几点进行控制:

  • 阶数增加
  • 每次刷新及初始化游戏时,通过改变对打乱后数组的排序判断中的没有被打乱的方块数量进行控制。我这里默认是打乱的方块至少要占方块数量的50%以上,否则继续打乱。
  • 时间限制
  • 步数限制(这里如果要做唯一解,即给玩家指定步数过关,像象棋解决残局一样。推荐使用打乱规则的第一种,这种情况下只有一种情况,无需考虑效率)

四、优化

逻辑做完,最后做一点小小的优化:

  • 过关后将方块中的数字隐藏,并将空缺的方块显示出来,让玩家欣赏一下拼好后的图片
  • 切图片可以用ps中的切片工具,并且存储为web格式,就可批量导出切好的图片(我一开始真的是手动裁的图片。。。。)
  • 优化操作,可以通过键盘控制方块移动,也可以通过鼠标点击控制(滑动也可以做,把点击的判断修改一下即可)

按键:

private keydown(event): void {if (Data.isCanKeyDown == false) {return;}if (event.keyCode == 38) {//上game.instance.down();return;} else if (event.keyCode == 40) {//下game.instance.up();return;} else if (event.keyCode == 37) {//左game.instance.right();return;} else if (event.keyCode == 39) {//右game.instance.left();return;}}

点击:

public stageMousePos(e: egret.TouchEvent): void {if (Data.isCanKeyDown == false) {return;}// console.log("全局X:", e.$stageX);//鼠标的位置if (this.emptyBlock.arrUp != -1 && e.$stageX > this.emptyBlock.x && e.$stageX < this.emptyBlock.x + Data.BlockWidth&& e.$stageY > this.emptyBlock.y - Data.BlockWidth && e.$stageY < this.emptyBlock.y) {//在空方块上方this.tweenPos(this.emptyBlock.arrUp);}if (this.emptyBlock.arrDown != -1 && e.$stageX > this.emptyBlock.x && e.$stageX < this.emptyBlock.x + Data.BlockWidth&& e.$stageY > this.emptyBlock.y + Data.BlockWidth && e.$stageY < this.emptyBlock.y + 2 * Data.BlockWidth) {//在空方块下方this.tweenPos(this.emptyBlock.arrDown);}if (this.emptyBlock.arrLeft != -1 && e.$stageX < this.emptyBlock.x && e.$stageX > this.emptyBlock.x - Data.BlockWidth&& e.$stageY > this.emptyBlock.y && e.$stageY < this.emptyBlock.y + Data.BlockWidth) {//在空方块左方this.tweenPos(this.emptyBlock.arrLeft);}if (this.emptyBlock.arrRight != -1 && e.$stageX > this.emptyBlock.x + Data.BlockWidth && e.$stageX < this.emptyBlock.x + 2 * Data.BlockWidth&& e.$stageY > this.emptyBlock.y && e.$stageY < this.emptyBlock.y + Data.BlockWidth) {//在空方块右方this.tweenPos(this.emptyBlock.arrRight);}}
新手初学,有问题或者不完善欢迎大家纠正评论以及补充~谢谢



最后这是我玩的10阶的数据,100个方块,1394秒。23分钟,用了3071步,一个令人悲伤的故事

新手初学,有问题或者不完善欢迎大家纠正评论以及补充~谢谢

egret制作小游戏:数字华容道及有解判断(代码注释)(评论区有源码下载~)相关推荐

  1. 数字华容道java_从零开发HarmonyOS(鸿蒙)手机小游戏——数字华容道

    前言 2月16号HarmonyOS2.0手机开发者Beta版已经发布了,作为"1+8+N"战略的重要入口和生态核心,怎么能少得了手机应用开发呢,今天将由深鸿会深大学习小组(Zzt_ ...

  2. 从零开发HarmonyOS(鸿蒙)手机小游戏——数字华容道

    HarmonyOS(鸿蒙)手机第一个小游戏app--数字华容道 前言 概述 正文 创建项目 实现初始界面布局 实现数字的随机打乱 实现滑动或点击调换数字 实现游戏成功界面 源代码 结语 前言 2月16 ...

  3. 鸿蒙小游戏-数字华容道 自定义组件的踩坑记录

    前两天看到HarmonyOS开发者官网上发布的一个挑战HarmonyOS分布式趣味应用的帖子,然后有个想法想搞一个小游戏出来,结果三天的时间都卡在了自定义组件上,使用了各种方式方法去实现功能,但是还是 ...

  4. 哥们哥们,人机大战晓得吧玩家对战晓得吧,简易三子棋,呕心沥血500行代码手把手带你制作第一个小游戏,可以保存收藏以后接着看哟,最后有源码哦

    目录 前言 一.游戏想要有意思,函数不可少,整活的函数 二.三子棋的游戏界面 三.三子棋的功能步骤分析      1.菜单     2.三子棋实现的总体框架     3.棋盘创建     4.棋盘初始 ...

  5. 用JAVA制作小游戏——推箱子(二)

    本篇博客主要是推箱子游戏界面功能的代码讲解. 首先先给出这段代码的部分运行截图: 重难点: 游戏界面主要有五个重难点: 固定好地图的位置 地图的显示 构建菜单栏 读取地图数据 玩家操作功能实现 地图的 ...

  6. 如何制作小游戏(c++教程)(新手版)(2)

    如何制作小游戏2 你们期待已久的2终于来啦! #include<iostream> #include<ctime> //必要头文件 #include<cstdlib> ...

  7. python能制作游戏吗_如何用Python制作小游戏

    要想用Python制作小游戏,必须要安装一个插件Pygame 什么是Pygame Pygame是跨平台Python模块,专为电子游戏设计,包含图像.声音.建立在SDL基础上,允许实时电子游戏研发而无需 ...

  8. 用JAVA制作小游戏——飞机大战(三)

    本篇博客是对飞机大战游戏项目完整代码的展示 详细代码讲解: 用JAVA制作小游戏--飞机大战(一) 用JAVA制作小游戏--飞机大战(二) 最下方附整个程序的文件下载链接 代码展示 主界面 impor ...

  9. 用JAVA制作小游戏——飞机大战(二)

    本篇博客是对飞机大战游戏使用代码的展示 重难点: 首先需要鼠标能够控制战机,使鼠标在窗口内时始终能够使战机的位置与鼠标相同,实现鼠标控制战斗机移动. 其次需要能够以一定的速度产生子弹和敌机,并且以一定 ...

最新文章

  1. 修改windows cmd f2快捷_第三课——win10常用快捷键的使用
  2. 通过 SSH 连接到 VMware NAT 模式下的 Ubuntu 虚拟机环境
  3. android 上线apk,码云 Android apk 在线构建功能上线啦!
  4. 监控系统安装配置文档(Nagios+Cacti+Nconf)
  5. ffmpeg的编译(for x86,for arm)安装及使用(网络资料整理)
  6. 怎么用Linux命令查看BIOS信息,LINUX下怎样获取主板的信息用到什么命令
  7. 【干货】神经网络SRU
  8. C语言 标准库stdio.h
  9. Linux-v10.0
  10. .net之实现文件上传与下载
  11. 联想笔记本打字不显示选字框
  12. webrtc 入门第一章 基本设备操作
  13. MCMC原理解析(马尔科夫链蒙特卡洛方法)
  14. 数字逻辑设计基础(何建新)第三章
  15. Atitit 二维码和条码的历史发展 1.发展历程 编辑提及二维码的诞生,我们还得倒回至上个世纪 60 年代之后的日本,虽然 1945 年的第二次世界大战之中日本沦为战败国,可是在经济方面日本却进入
  16. java.io和java.nio性能简单对比
  17. 微信小程序配置每个页面的标题名称
  18. 2020_WWW_The Structure of Social Influence in Recommender Networks
  19. 差动驱动机器人轨迹-CoCube
  20. Redis 地理坐标(GEO)方法使用详解

热门文章

  1. MFC手动添加窗口 最大化 最小化 还原 关闭
  2. 对txt文件中的文件内容进行断句
  3. sybase函数用法之DateAdd
  4. 用Python画一个中国地图
  5. mac抓包工具Charles使用详细教程(图文)
  6. IP地址格式转换(htonl、ntohl;inet_addr、inet_ntoa)
  7. MindMaster---成功案例
  8. 同济大学2020计算机复试线,2020同济大学研究生考研复试分数线
  9. Microsoft office 家庭学生版(Home Student)下载地址
  10. zynq7000中断系统