在上一篇展现了js编写2048小游戏的思路以及编程框架搭建,本篇将基于上篇的结构来讲解游戏关键操作方法的具体算法实现。适合已经阅读完上一篇文章https://blog.csdn.net/SuperZZQ_/article/details/82662732,这篇的文章在编辑过程中侧重于讲解如何将我们熟悉的、可见的操作通过对产生条件、触发效果、细分步骤方面进行解构,转化成编程思维,并利用前端语言写成代码,最终形成一个完整的小游戏。对于每一句代码进行了解读和剖析,适合初次开发小游戏的新手小白更好地理解程序,有助于日后开发小游戏过程中的编程思维培养与训练。
1. 操作描述:
每按下方向键后若格子能够发生变动(即有移动或合并操作产生),便在移动或合并操作后剩下的空白处某一随机位置新增一个数值为2的格子。
2. 编程思路:
我们需要结合其它方法来判断是否执行此方法,因此定义一个全局的布尔类型变量 moveAble,用作新增格子的“开关”。默认状态下让它“关闭”,即在声明时赋值为 false。在进行了移动或合并操作后将它“打开”,即在移动和合并的方法内部将true赋值给 moveAble。并在即将进行新一轮移动或合并操作前再将它“关闭”,即在上篇监听方法中执行向上/下/左/右方法之前将其值设为false。
了解这个“开关”的作用后开始编写新增格子方法。首先判断 moveAble 是否为 true ,为 true 则继续进行,否则控制台返回“不能增加新格子,请尝试其他方向移动!”。接着遍历整个二维数组,将数值为空的坐标保存进局部定义的数组变量 ableArr 中,对下标进行随机方法,将最终的随机下标值和格子值(2)或随机下标值传入 arrValueUpdate 与 drawCell 方法中来画出新格子。
3. 代码:
//新增格子方法
newCell:function(){
var i,j,len,index;
var ableArr = []; //用于保存遍历后提取出来的空格子位
if(this.moveAble != true){ //判断是否符合增加格子的条件
console.log('不能增加新格子,请尝试其他方向移动!');
return;
}
for (i = 0; i < 4; i++) { //通过遍历提取出空格子位数组
for (j = 0; j < 4; j++) {
if(this.arr[i][j].value == 0){
ableArr.push([i,j]);
}
}
}
len = ableArr.length;
if(len > 0){
index = getRandom(len); //在数组中随机出一个位置
i = ableArr[index][0];
j = ableArr[index][1];
this.arrValueUpdate(2,i,j); //画出格子
this.drawCell(i,j);
}else{
return;
}
},
1. 操作描述:
每按下方向键后,所有格子向该方向移动或合并,使格子最终都会彼此贴紧挨着该方向的边,当该方向上有两个相邻的数值相同的格子时它们合并成一个格子,数值为它们之和,并且继续合并到无相邻的数值相同的格子为止。
2. 编程思路:
这部分是最不容易构思及理解的部分,实现的方法并不唯一。以向下方向为例,在操作过程中会出现多种情形,我们需要将它们细分:
现目标方向为最底端,采取自下向上的方向遍历,使格子层层向下落可使得效率更高,以遍历到不为空的格子为视角向目标方向向下观察,根据下方不同情形做出反应。如:(只列举出了部分概括性情况)
向格子下方观察以便通过分布情况判断是否进行移动或合并操作,我们定义一个变量k,指向不为空的格子的下方,i 和 j 分别为遍历得到的不为空的格子的位置横纵下标,k指针下方为空则继续指向下,在这移动过程中同时判断:当其下方都为空(如第①条)或下方间隔一些空格子后有数值不同的格子时(如第③④条),进行移动操作。当其下方间隔一些空格子后有数值相同的格子时(如⑥⑦条),进行合并操作,与此同时,由于每执行方向键时只合并一轮(即如果出现4个2,会合并成2个4,而非直接变成8),可以再定义一个k的指针范围n,初始值为3(下标范围是0-3),合并到底端则“将底线上移”,提高效率同时防止多轮合并的产生。
3. 代码:
//向下方向
moveDown:function(){
var i,j,k,n;
for (i = 0; i < 4; i++) {
n = 3;
for (j = 3; j >= 0; j--) { //自底端向上遍历
if(this.arr[i][j].value==0){ //当值不为零时暂停
continue;
}
k = j+1; //指不为零的格子向目标方向的下一位
aa:
while(k<=n){
if(this.arr[i][k].value == 0){ //判断是否符合移动条件
if(k == n || (this.arr[i][k+1].value!=0 && this.arr[i][k+1].value!=this.arr[i][j].value)){
this.moveCell(i,j,i,k);
}
k++; //为空则k指针继续下移
}else{ //判断是否符合合并条件
if(this.arr[i][k].value == this.arr[i][j].value){
this.mergeCells(i,j,i,k);
n--; //向上缩小范围
}
break aa;
}
}
}
}
this.newCell();
},
//向上方向
moveUp:function(){
var i,j,k,n;
for (i = 0; i < 4; i++) {
n=0;
for (j = 0; j < 4; j++) {
if(this.arr[i][j].value==0){
continue;
}
k = j-1;
aa:
while(k>=n){
if(this.arr[i][k].value == 0){
if(k == n || (this.arr[i][k-1].value!=0 && this.arr[i][k-1].value!=this.arr[i][j].value)){
this.moveCell(i,j,i,k);
}
k--;
}else{
if(this.arr[i][k].value == this.arr[i][j].value){
this.mergeCells(i,j,i,k);
n++;
}
break aa;
}
}
}
}
this.newCell();
},
//向左方向
moveLeft:function(){
var i,j,k,n;
for (j = 0; j < 4; j++) {
n=0;
for (i = 0; i < 4; i++) {
if(this.arr[i][j].value==0){
continue;
}
k=i-1;
aa:
while(k>=n){
if(this.arr[k][j].value == 0){
if(k == n || (this.arr[k-1][j].value!=0 && this.arr[k-1][j].value!=this.arr[i][j].value)){
this.moveCell(i,j,k,j);
}
k--;
}else{
if(this.arr[k][j].value == this.arr[i][j].value){
this.mergeCells(i,j,k,j);
n++;
}
break aa;
}
}
}
}
this.newCell();
},
//向右方向
moveRight:function(){
var i,j,k,n;
for (j = 0; j < 4; j++) {
n = 3;
for (i = 3; i >= 0; i--) {
if(this.arr[i][j].value==0){
continue;
}
k = i+1;
aa:
while(k<=n){
if(this.arr[k][j].value == 0){
if(k == n || (this.arr[k+1][j].value!=0 && this.arr[k+1][j].value!=this.arr[i][j].value)){
this.moveCell(i,j,k,j);
}
k++;
}else{
if(this.arr[k][j].value == this.arr[i][j].value){
this.mergeCells(i,j,k,j);
n--;
}
break aa;
}
}
}
}
this.newCell();
},
1. 操作描述:
移动使格子从原有位置移动到某一位置使最终每个格子彼此贴紧并挨着特定方向的边,合并使该方向上的两个相邻的数值相同的格子中的一个格子消失,并将另一个格子的数值改为它们的和。
2. 编程思路:
移动:方法中设定参数,规定从下标 [i1] [j1] 移动到 [i2] [j2] 位置。将原位置的值注入到新位置中并清除原值。打开moveAble开关,最后通过 jquery 的方法将包含位置样式定义的类名更换,实现实际位置变化。
合并:方法中设定参数,规定将下标为 [i1] [j1] 和为 [i2] [j2] 的方格合并(使 [i2] [j2] 消失,并将 [i1] [j1] 移动至其位置)。位置。由于不同的数值在css中对应不同的样式,用temp保存原数值,用temp1表示合并后的数值。将 [i2] [j2] 位置数值更新成合并后的数值,[i1] [j1]位置的数值清空 ,注意这里说明了数值是与下标决定的位置相连的,而并非视觉上看到的“与格子相连”。而与视觉直接相关的是通过 jquery 的方法将类名更换,实现移动效果。打开moveAble开关,并将合并后的数值延迟注入。
3. 代码:
//移动格子
moveCell:function(i1,j1,i2,j2){
this.arr[i2][j2].value = this.arr[i1][j1].value;
this.arr[i1][j1].value = 0;
this.moveAble = true;
$(".pos"+i1+j1).removeClass("pos"+i1+j1).addClass("pos"+i2+j2);
},
//合并格子
mergeCells:function(i1,j1,i2,j2){
var temp =this.arr[i2][j2].value;
var temp1 = temp * 2;
this.arr[i2][j2].value = temp1;
this.arr[i1][j1].value = 0;
$(".pos"+i2+j2).addClass('toRemove');
var theDom = $(".pos"+i1+j1).removeClass("pos"+i1+j1).addClass("pos"+i2+j2).find('.number_cell_con');
this.moveAble = true;
setTimeout(function(){
$(".toRemove").remove();
theDom.addClass('n'+temp1).removeClass('n'+temp).find('span').html(temp1);
},200);
},
1. 操作描述:
当界面上的格子位满了且没有任何两个相邻格子有同样数值时,判定游戏结束,提示玩家并重置游戏。
2. 编程思路:
以反向思维来写此方法:若还有空格子或还有相邻的数值相同的格子则游戏不结束,否则将执行方法。每当按下一次方向键,执行向上/下/左/右方法后都调用一次判定游戏是否结束方法。
遍历整个二维数组,如果有值为零(空格子)则返回false,或位置相邻(分横向和纵向)的格子数值相同,返回false。否则游戏结束,弹出提示框并将游戏重置(初始化)。
3. 代码:
//判断游戏结束
checkLose:function(){
var i,j,temp;
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) { //判断游戏能够继续
temp = this.arr[i][j].value;
if(temp == 0){
return false;
}
if(this.arr[i+1] && (this.arr[i+1][j].value==temp)){
return false;
}
if((this.arr[i][j+1]!=undefined) && (this.arr[i][j+1].value==temp)){
return false;
}
}
}
alert('革命尚未成功,同志仍需努力^_^');
this.init();
return true;
}
2048小游戏到此就开发结束了,游戏源码可以上我的gitee https://gitee.com/SuperZZQ/tasks/tree/master/games/2048 或 github https://github.com/superZZQ/Weekly-task/tree/master/games/2048 参考学习。转载请注明出处,不足之处欢迎讨论^_^~。