前言
我是深鸿会的一个学习小组组长,同时也是深大数院的一名学生。在上一周我们小组观看并学习了张荣超老师在鸿蒙学院发布的视频,而且在视频里学到了很多知识,同时我们也收到了启发。于是我们运用张老师视频里教过的知识独立开发了一个小游戏“数字华容道”。以下是参与开发的小组成员:
likaijie12138
JiaYi__xixi
wx13415801099
GTH1144754040
概述
数字华容道是用尽量少的步数,尽量短的时间,将棋盘上的数字方块,按照从左到右、从上到下的顺序重新排列整齐。而我们要做的就是把这个小游戏移植到鸿蒙系统上。以下是我们所完成的效果图
1.模式介绍
2.玩法介绍
代码分析
主页面
先上效果图
这个界面的组成为两个部分:华容道图片和开始游戏的按钮。
下面我将一步一步讲解如何完成这两个部分。
插入“华容道“图像
找到default文件夹,创建一个新的文件夹“common”,将准备好的logo图片粘贴到common文件夹中。
打开index.hml,在
代码如下:
<image src="/common/3.png" class="img"/>
打开index.css,删除原有的title类,创建一个img类,将图片的参数输入,(这里只输入了宽度和高度)。
代码如下:
.img{
width: 250px;
height:250px;
}
打开index.js,将data中的内容删除。
至此,就可以达到如下的效果。
“开始游戏“按钮的设置
打开index.hml,在
代码如下:
<input type="button" value="开始游戏" class ="btn" onclick="clickAction" />
打开index.css,创建一个类btn,调节字体大小(font-size)为38px,将按钮的背景颜色(background-color)设为橘色,按钮上的文字的颜色(color)和按钮边框上的颜色(border-color)都设为黑色,最后调节按钮的宽度(width)为200px,高度(height)为50px。
代码如下:
.
btn{
font-size:38px;
background-color: #F8C387;
color: #000000;
border-color: #000000;
width:200px;
height:50px;
}
至此,就可以达到如下效果图。
页面跳转
接下来我们将要实现的是,点击“开始游戏“按钮,跳转到下一个界面,用户选择游戏难度。模式分为三种:简单模式、普通模式、困难模式。
效果图如下
总共要实现三个功能:按下按钮跳转页面,设置一个滚动选择器,设置“确认”按钮
点击按钮后的页面跳转
在pages文件夹下创建一个新的js page,命名为xuanze。
创建完后可以在config.json中的pages看到自动添加了刚创建的文件夹的路径。
打开index.js,先插入一个含有router函数的头文件,创建一个函数clickAction,添加router.replace函数,里面的参数uri的值为跳转页面的路径。
代码如下:
import router from '@system.router';
clickAction(){
router.replace({
uri:'pages/xuanze/xuanze'
});
设置一个滚动选择器
打开xuanze.hml,创建一个名为container1的区域组件,组件包含一个名为container2的区域组件和一个按钮组件。
<div class="container1">
<div class="container2">
</div>
</div>
创建一个名为pv1的滚动选择器,里面的数据picker1range通过动态绑定({ { }})的方式获得,把selected设置为"简单模式"使得初始选择简单模式,创建一个名为changeAction1的onchange事件,当滚动选择器中的数据发生改变的时候,会引发onchange事件。
<picker-view class="pv1" range="{
{picker1range}}" selected="简单模式" onchange="changeAction1"/>
打开xuanze.css,设置样式
.container1 {
flex-direction: column;
justify-content: center;
align-items: center;
width: 454px;
height: 454px;
}
.container2{
flex-direction:row;
justify-content:center;
align-items:center;
margin-top: 50ox;
width:454px;
height:250px;
}
.pv1{
width:300px;
height:300px;
}
打开xuanze.js,把数据赋予动态绑定的滚轮选择器中
export default {
data: {
picker1range:["简单模式","普通模式","困难模式"],
}
}
设置“进入游戏“按钮
在第一个进入游戏界面时,我们已经学过了如何去设置一个“游戏开始“的按钮,所以设置”确认“按钮的步骤跟前面设置按钮的步骤基本一致,所以这里就不再重复了,如果忘了怎么设置按钮的小伙伴可以往前翻翻,有详细解析。然后我们打开xuanze.js,输入以下代码
clickAction(){
router.replace({
uri:'pages/youxi/youxi',
params: {
"data": pickervalue}
});
},
changeAction1(pv){
console.log("选中项:" + pv.newValue);
pickervalue = pv.newValue;
}
第一个方法能够在跳转页面的同时把滑动选择器选择到的值通过字典的方式传递到下一个页面。第二个方法是在滑动选择器改变后获得滑动选择器改变后的值。
游戏界面
在pages文件夹下创建一个新的js page,命名为youxi。
创建界面
在创建游戏的时候,我们首先要画出一个棋盘,如下图所示。
首先我们打开youxi.hml,添加下面这段代码
<canvas class="canvas" ref="canvas" onswipe="swipeGrids"></canvas>
这样我们就创建了一个画布组件,而ref相当于画布组件的画笔,而onswipe为滑动事件,当我们手指在手表上面滑动时会引发swipeGrids()事件。
然后我们打开youxi.css,来为我们的canvas组件定制形状
.canvas {
width: 305px;
height: 305px;
background-color:#CD853F;
}
为了让画布上能显示出我们想要的数字,我们创建三个数组来储存方格上面的数字
initGrids3() {
grids_3 = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]];
},
initGrids4(){
grids_4=[[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,0]];
},
initGrids5(){
grids_5=[[1,2,3,4,5],
[6,7,8,9,10],
[11,12,13,14,15],
[16,17,18,19,20],
[21,22,23,24,0]];
},
当选择界面的滑动选择器选择不同内容的时候,能够画出不同的表格,我们把传递过来的值赋予给num,并建立对应数字的数组。
onInit() {
pickervalue=this.data
if(pickervalue=="简单模式"){
num=3;
this.initGrids3();
}
if(pickervalue=="普通模式"){
num=4;
this.initGrids4();
}
if(pickervalue=="困难模式"){
num=5;
this.initGrids5();
}
this.addToGrids()
},
到了最关键的时候了,怎么把数组乱序?我尝试过把二维数组转化为一维数组,然后用洗牌算法来把一维数组乱序,再把一维数组转化为二维数组。但是出现了一个问题,就是我这样乱序的话会出现一种无解的情况。我以3*3为例,这样乱序的情况下,会出现前两行是恢复正常的,但是最后一行的7与8是交互了的。我查了很多资料,资料都显示这种情况是无解的,所以这种算法是有弊端的。于是我用了另一种算法:随机出[0,3]的一个整数,当出现0的时候,向左滑一下,当出现1的时候向右滑一下…这样以此类推,然后循坏很多次,这样可以保证乱序出来的数组是可以有解的,乱序代码如下:
addToGrids(){
let row_0;
let column_0;
let random;
if(num==3){
for(let i=0;i<num*num*num;i++){
random=Math.floor(Math.random() * 4);
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
if(grids_3[row][column]==0){
row_0=row;
column_0=column;
}
}
}
if(random==0||random==1){
if(random==0){
if(column_0!=num-1){
let temp=grids_3[row_0][column_0];
grids_3[row_0][column_0]=grids_3[row_0][column_0+1];
grids_3[row_0][column_0+1]=temp;
}
}else{
if(column_0!=0){
let temp=grids_3[row_0][column_0];
grids_3[row_0][column_0]=grids_3[row_0][column_0-1];
grids_3[row_0][column_0-1]=temp;
}
}
}
if(random==2||random==3){
if(random==2){
if(row_0!=num-1){
let temp=grids_3[row_0][column_0];
grids_3[row_0][column_0]=grids_3[row_0+1][column_0];
grids_3[row_0+1][column_0]=temp;
}
}else{
if(row_0!=0){
let temp=grids_3[row_0][column_0];
grids_3[row_0][column_0]=grids_3[row_0-1][column_0];
grids_3[row_0-1][column_0]=temp;
}
}
}
}
}
if(num==4){
for(let i=0;i<num*num*num;i++){
random=Math.floor(Math.random() * 4);
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
if(grids_4[row][column]==0){
row_0=row;
column_0=column;
}
}
}
if(random==0||random==1){
if(random==0){
if(column_0!=num-1){
let temp=grids_4[row_0][column_0];
grids_4[row_0][column_0]=grids_4[row_0][column_0+1];
grids_4[row_0][column_0+1]=temp;
}
}else{
if(column_0!=0){
let temp=grids_4[row_0][column_0];
grids_4[row_0][column_0]=grids_4[row_0][column_0-1];
grids_4[row_0][column_0-1]=temp;
}
}
}
if(random==2||random==3){
if(random==2){
if(row_0!=num-1){
let temp=grids_4[row_0][column_0];
grids_4[row_0][column_0]=grids_4[row_0+1][column_0];
grids_4[row_0+1][column_0]=temp;
}
}else{
if(row_0!=0){
let temp=grids_4[row_0][column_0];
grids_4[row_0][column_0]=grids_4[row_0-1][column_0];
grids_4[row_0-1][column_0]=temp;
}
}
}
}
}
if(num==5){
for(let i=0;i<num*num*num;i++){
random=Math.floor(Math.random() * 4);
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
if(grids_5[row][column]==0){
row_0=row;
column_0=column;
}
}
}
if(random==0||random==1){
if(random==0){
if(column_0!=num-1){
let temp=grids_5[row_0][column_0];
grids_5[row_0][column_0]=grids_5[row_0][column_0+1];
grids_5[row_0][column_0+1]=temp;
}
}else{
if(column_0!=0){
let temp=grids_5[row_0][column_0];
grids_5[row_0][column_0]=grids_5[row_0][column_0-1];
grids_5[row_0][column_0-1]=temp;
}
}
}
if(random==2||random==3){
if(random==2){
if(row_0!=num-1){
let temp=grids_5[row_0][column_0];
grids_5[row_0][column_0]=grids_5[row_0+1][column_0];
grids_5[row_0+1][column_0]=temp;
}
}else{
if(row_0!=0){
let temp=grids_5[row_0][column_0];
grids_5[row_0][column_0]=grids_5[row_0-1][column_0];
grids_5[row_0-1][column_0]=temp;
}
}
}
}
}
},
把数组乱序后,我们onReady()内设置好画笔,在onShow()中把表格画出来。其中由于我们不同模式需要画的表格都是不一样的。所以我根据张荣超老师的2048视频所讲的内容进行推断,推出SIDELEN 与所画表格的行数列数的关系为
SIDELEN = 300/num-5,
然后根据不同的num我可以画出不同的正方形方格出来。
onReady(){
context = this.$refs.canvas.getContext('2d');
},
onShow(){
this.drawGrids();
},
drawGrids(){
const SIDELEN = 300/num-5;
if(num==3){
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
let gridStr = grids_3[row][column].toString();
context.fillStyle=colors["3"];
let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);
context.font = "36px HYQiHei-65S";
if (gridStr != "0") {
context.fillStyle = "#FFFFFF";
let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
let offsetY = (SIDELEN - 24) / 2;
context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);
}
}
}
}
if(num==4){
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
let gridStr = grids_4[row][column].toString();
context.fillStyle=colors["4"];
let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);
context.font = "30px HYQiHei-65S";
if (gridStr != "0") {
context.fillStyle = "#FFFFFF";
let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
let offsetY = (SIDELEN - 24) / 2;
context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);
}
}
}
}
if(num==5){
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
let gridStr = grids_5[row][column].toString();
context.fillStyle=colors["5"];
let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);
context.font = "24px HYQiHei-65S";
if (gridStr != "0") {
context.fillStyle = "#FFFFFF";
let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
let offsetY = (SIDELEN - 24) / 2;
context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);
}
}
}
}
},
这样我们就把我们所需要的方格在画布上画了出来。
添加功能
为了实现数字华容道的功能,我们需要在画布上添加滑动事件
<canvas class="canvas" ref="canvas" onswipe="swipeGrids"></canvas>
onswipe即为我们所添加的滑动事件,在youxi.js中滑动事件的名字为swipeGrids。当手指滑动的时候,我们需要做的就是把滑动的方向传递进滑动事件swipeGrids里。然后寻找0方格所在的位置,同时判断0方格能不能被移动,当0方格能被移动的时候,把0与滑动方向临近那个数字交换,这样就完成了一次滑动。代码如下
swipeGrids(event) {
let newGrids = this.changeGrids(event.direction);
if(num==3){
grids_3 = newGrids;
}
if(num==4){
grids_4 = newGrids;
}
if(num==5){
grids_5 = newGrids;
}
this.drawGrids();
},
changeGrids(direction){
let row_0;
let column_0;
if(num==3){
let newGrids_3=grids_3;
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
if(newGrids_3[row][column]==0){
row_0=row;
column_0=column;
}
}
}
if(this.isShow==false){
if(direction=='left'||direction=='right'){
if(direction=='left'){
if(column_0!=num-1){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0][column_0+1];
newGrids_3[row_0][column_0+1]=temp;
this.step+=1;
}
}else{
if(column_0!=0){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0][column_0-1];
newGrids_3[row_0][column_0-1]=temp;
this.step+=1;
}
}
}
if(direction=='up'||direction=='down'){
if(direction=='up'){
if(row_0!=num-1){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0+1][column_0];
newGrids_3[row_0+1][column_0]=temp;
this.step+=1;
}
}else{
if(row_0!=0){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0-1][column_0];
newGrids_3[row_0-1][column_0]=temp;
this.step+=1;
}
}
}
}
return newGrids_3;
}
if(num==4){
let newGrids_4=grids_4;
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
if(newGrids_4[row][column]==0){
row_0=row;
column_0=column;
}
}
}
if(this.isShow==false){
if(direction=='left'||direction=='right'){
if(direction=='left'){
if(column_0!=num-1){
let temp=newGrids_4[row_0][column_0];
newGrids_4[row_0][column_0]=newGrids_4[row_0][column_0+1];
newGrids_4[row_0][column_0+1]=temp;
this.step+=1;
}
}else{
if(column_0!=0){
let temp=newGrids_4[row_0][column_0];
newGrids_4[row_0][column_0]=newGrids_4[row_0][column_0-1];
newGrids_4[row_0][column_0-1]=temp;
this.step+=1;
}
}
}
if(direction=='up'||direction=='down'){
if(direction=='up'){
if(row_0!=num-1){
let temp=newGrids_4[row_0][column_0];
newGrids_4[row_0][column_0]=newGrids_4[row_0+1][column_0];
newGrids_4[row_0+1][column_0]=temp;
this.step+=1;
}
}else{
if(row_0!=0){
let temp=newGrids_4[row_0][column_0];
newGrids_4[row_0][column_0]=newGrids_4[row_0-1][column_0];
newGrids_4[row_0-1][column_0]=temp;
this.step+=1;
}
}
}
}
return newGrids_4;
}
if(num==5){
let newGrids_5=grids_5;
for (let row = 0; row < num; row++) {
for (let column = 0; column < num; column++) {
if(newGrids_5[row][column]==0){
row_0=row;
column_0=column;
}
}
}
if(this.isShow==false){
if(direction=='left'||direction=='right'){
if(direction=='left'){
if(column_0!=num-1){
let temp=newGrids_5[row_0][column_0];
newGrids_5[row_0][column_0]=newGrids_5[row_0][column_0+1];
newGrids_5[row_0][column_0+1]=temp;
this.step+=1;
}
}else{
if(column_0!=0){
let temp=newGrids_5[row_0][column_0];
newGrids_5[row_0][column_0]=newGrids_5[row_0][column_0-1];
newGrids_5[row_0][column_0-1]=temp;
this.step+=1;
}
}
}
if(direction=='up'||direction=='down'){
if(direction=='up'){
if(row_0!=num-1){
let temp=newGrids_5[row_0][column_0];
newGrids_5[row_0][column_0]=newGrids_5[row_0+1][column_0];
newGrids_5[row_0+1][column_0]=temp;
this.step+=1;
}
}else{
if(row_0!=0){
let temp=newGrids_5[row_0][column_0];
newGrids_5[row_0][column_0]=newGrids_5[row_0-1][column_0];
newGrids_5[row_0-1][column_0]=temp;
this.step+=1;
}
}
}
}
return newGrids_5;
}
},
这样我们就完成了滑动事件的创建。为了让游戏有趣一点,我们决定给游戏添加一个计步数功能。打开youxi.hml,添加txt组件,组件内容为步数,步数的值通过动态绑定的方式由youxi.js获得。
<text class="step">
步数:{
{
step}}
</text>
在youxi.js中初始化步数的值为0
data: {
step:0,
isShow:false
},
在滑动的时候,每滑动一次,步数就添加1,如下代码所示
if(direction=='left'||direction=='right'){
if(direction=='left'){
if(column_0!=num-1){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0][column_0+1];
newGrids_3[row_0][column_0+1]=temp;
this.step+=1;
}
}else{
if(column_0!=0){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0][column_0-1];
newGrids_3[row_0][column_0-1]=temp;
this.step+=1;
}
}
}
这样我们完成了数字华容道的滑动功能和计数功能,接下来我们将为数字华容道加入游戏结束的功能。
打开youxi,hml,添加一个组件在画布上面,并在这个组件里包含有一个文本组件和一个控制文本组件是否显示的组件。文本是否显示通过动态绑定的方式由youxi.js获得
<stack class="stack">
<canvas class="canvas" ref="canvas" onswipe="swipeGrids"></canvas>
<div class="subcontainer" show="{
{isShow}}">
<text class="gameover">
游戏结束
</text>
</div>
</stack>
打开youxi.js,先把文本组件设为不可见。
data: {
step:0,
isShow:false
},
然后写一个方法GameOver,当游戏结束时,并把文本组件变为可见。
swipeGrids(event) {
let newGrids = this.changeGrids(event.direction);
if(num==3){
grids_3 = newGrids;
}
if(num==4){
grids_4 = newGrids;
}
if(num==5){
grids_5 = newGrids;
}
this.drawGrids();
if(this.GameOver()==true){
colors = THEME.faded;
this.drawGrids();
this.isShow = true;
}
},
GameOver(){
if(num==3){
for(let row=0;row<num;row++){
for(let column=0;column<num;column++){
if(row!=num-1||column!=num-1) {
if (grids_3[row][column] != row * num + (column + 1)) {
return false
}
}
}
}
return true
}
if(num==4){
for(let row=0;row<num;row++){
for(let column=0;column<num;column++){
if(row!=num-1||column!=num-1) {
if (grids_4[row][column] != row * num + (column + 1)) {
return false
}
}
}
}
return true
}
if(num==5){
for(let row=0;row<num;row++){
for(let column=0;column<num;column++){
if(row!=num-1||column!=num-1) {
if (grids_5[row][column] != row * num + (column + 1)) {
return false
}
}
}
}
return true
}
},
这样我们就完成了游戏结束的显示了,为了使游戏结束时,游戏不能再继续进行了,我们需要在滑动事件里加一个判定,当文本组件为不可见的时候,才能进行滑动操作,代码示例如下
if(this.isShow==false){
if(direction=='left'||direction=='right'){
if(direction=='left'){
if(column_0!=num-1){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0][column_0+1];
newGrids_3[row_0][column_0+1]=temp;
this.step+=1;
}
}else{
if(column_0!=0){
let temp=newGrids_3[row_0][column_0];
newGrids_3[row_0][column_0]=newGrids_3[row_0][column_0-1];
newGrids_3[row_0][column_0-1]=temp;
this.step+=1;
}
}
}
为了使游戏画面好看一点,我们的方格需要不同的颜色。同时当游戏结束的时候,把颜色变淡一点,以方便看出来游戏结束已经结束了。首先我们创建两个字典normal和faded,字典的键和元素都为数字以及数字对应的颜色,当游戏未结束,颜色用normal字典的颜色,当游戏结束的时候,颜色字典变为faded,同时重新绘制一次方格。
const THEME = {
normal: {
"3":"#FB8B05",
"4":"#2775B6",
"5":"#DD8AF8"
},
faded:{
"3":"#E3BD8D",
"4":"#66A9C9",
"5":"#F8D1EE"
}
};
var colors = THEME.normal;
if(this.GameOver()==true){
colors = THEME.faded;
this.drawGrids();
this.isShow = true;
}
总结
以上就是我们小组对于数字华容道的开发过程,其中也包括了我们的一些心得与学习体验,也有我们在开发过程中遇到的难题。总的来说,这次数字华容道的开发对于我们小组来也算说是一次宝贵的学习经验。
转载:https://blog.csdn.net/clarelove/article/details/110203054