1,游戏开始逻辑:
—-两个二维数组,一个用于存储显示精灵,一个用于存储显示数据(图片路径中的数字部分)。
—-游戏能够正常开始的标准:不存在三消块,也不能为死局。
即保存显示数据的数组中,不能够存在相邻三个位置的数据一样的情况–A,也不能出现交换后也无法形成三消的情况–B。
确定图片类型总数N,然后将显示数据的数组中的每一个元素赋值为一个0-N之间的随机数。
先检测生成的这一组数据是否存在情况A,如果有就刷新不合格位置的数据,然后重新检测。
如果情况A不存在,就检查情况B,如果存在情况B就刷新所有数据,然后重新检测。
ctor: function () {
this._super();
//var gradient = new cc.LayerGradient(cc.color(0,0,0,255),cc.color(0,255,0,255),cc.p(1,1)); //创建渐进色背景层
var background = new cc.Sprite("res/timg.jpg");
background.attr({anchorX:0, anchorY:0});
this.addChild(background);
var _size = cc.winSize;
this.shard_width = (new cc.Sprite(res.icon_1)).width;
var basePoint = cc.p((_size.width - this.shard_width *SHARD_NUM_W + this.shard_width )/2,
(_size.height + this.shard_width *SHARD_NUM_H - this.shard_width )/2);
this.shard_date_arr = Creat2X2Arr(SHARD_NUM_W, SHARD_NUM_H, 1);
this.shard_sprite_arr = Creat2X2Arr(SHARD_NUM_W, SHARD_NUM_H, 0);
for(var i=0;i<SHARD_NUM_W;i++)
{
for(var j=0;j<SHARD_NUM_H;j++)
{
var shard = new Shard();
shard.setPosition(basePoint.x + i * this.shard_width, basePoint.y - j * this.shard_width);
//shard.setArrIndex(i, j);
this.addChild(shard);
this.shard_sprite_arr[i][j] = shard;
this.shard_date_arr[i][j] = parseInt(Math.random()*SHARD_TYPE + 1);
}
}
this.buildWithout3Arr();
cc.eventManager.addCustomListener(USER_CLICK_SHARD_EVENT, this._getTouchOneShard.bind(this));
},
//创建一个不存在三消的数组
buildWithout3Arr:function()
{
var returnArr = this.checkArrHas3(this.shard_date_arr);
if(returnArr.length > 0)
{
for(var index in returnArr)
{
var randomPo = returnArr[index];
this.shard_date_arr[randomPo.x][randomPo.y] = parseInt(Math.random()*SHARD_TYPE + 1)
}
this.buildWithout3Arr();
return;
}
if(this.checkIsDeath())
{
for(var i=0;i<SHARD_NUM_W;i++)
{
for(var j=0;j<SHARD_NUM_H;j++)
{
this.shard_date_arr[i][j] = parseInt(Math.random()*SHARD_TYPE + 1);
}
}
this.buildWithout3Arr();
return;
}
this.flushShardShowWithArr();
},
2,检测是否存在三消的逻辑:
—-定义一个检测三消的函数checkHas3Arr,从碎片区域的左上角开始,检测每一个碎片的左边两个位置和下边两个位置。如果出现出现三个连续一样就将它们的下标装入一个数组并返回出去。这样每次检测是否存在三消只需要检测调用该函数的返回值是否为空数组即可。
//找出三消块
checkArrHas3:function(in_arr)
{
var with3Arr = [];
for(var i=0;i<SHARD_NUM_W;i++)
{
for (var j = 0; j < SHARD_NUM_H; j++)
{
if( i < SHARD_NUM_W - 2 && in_arr[i][j] == in_arr[i+1][j] && in_arr[i+1][j] == in_arr[i+2][j])
{
with3Arr.push(cc.p(i,j),cc.p(i+1,j),cc.p(i+2,j))
//with3Arr.push([i,j],[i+1,j],[i+2,j]);
}
if( j < SHARD_NUM_H - 2 && in_arr[i][j] == in_arr[i][j+1] && in_arr[i][j+1] == in_arr[i][j+2])
{
with3Arr.push(cc.p(i,j),cc.p(i,j+1),cc.p(i,j+2))
}
}
}
return with3Arr
},
3,判断是否为死局的逻辑:
—-遍历数组,检测所有碎片向各个方向移动后的结果。只要有任何一个碎片在经过一次移动后能产生三消,即可判断为活局。如果没有一个碎片的移动能让游戏形成三消,则判定为死局。
//死局判断
checkIsDeath:function(autoPlay)
{
var checkArr = [[-1,0],[1,0],[0,-1],[0,1]];
for (var i = SHARD_NUM_W - 1; i >= 0 ; i--)
{
for (var j = 0; j < SHARD_NUM_H; j++)
{
for(var index in checkArr)
{
var arr = checkArr[index];
if(i+arr[0] >= 0 && i+arr[0]<SHARD_NUM_W && j+arr[1] >= 0 && j+arr[1]<SHARD_NUM_H)
{
var resultArr = this.checkAfterExchange([i,j],[i+arr[0],j+arr[1]], true);
if(resultArr.length > 0) {
//系统自己操作直至无解状态开关
if(autoPlay){
this.checkAfterExchange([i,j], [i+arr[0],j+arr[1]], false);
this.checkAllAndClear3()
}
return false;
}
}
}
}
}
return true;
},
4,游戏的显示逻辑:
—-初始化的显示数据只有同时经过了三消检测和死局检测。才能用于刷新碎片精灵的显示,即按照其值给每一个精灵匹配对应的编号的图片。
//遍历二维数组,刷新显示
flushShardShowWithArr: function ()
{
for (var i = 0; i < SHARD_NUM_W; i++)
{
for (var j = 0; j < SHARD_NUM_H; j++)
{
var shard = this.shard_sprite_arr[i][j];
shard.setType(this.shard_date_arr[i][j]);
}
}
}
碎片类Shard.js:
var Shard = cc.Sprite.extend({
//碎片显示类型
type:0,
listener:null,
ctor:function() {
this._super();
var that = this;
var listener = cc.EventListener.create({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
swallowTouches: true,
onTouchBegan: function (touch, event) {
if(!that.visible) return false;
var target = event.getCurrentTarget();
var location = target.convertToNodeSpace(touch.getLocation());
var targetSize = target.getContentSize();
touch.getDelta()
var targetRectangle = cc.rect(0, 0, targetSize.width, targetSize.height);
if (cc.rectContainsPoint(targetRectangle, location))
{
cc.eventManager.dispatchCustomEvent(USER_CLICK_SHARD_EVENT, that);
return true;
}
return false;
},
});
this.listener = cc.eventManager.addListener(listener.clone(), this);
},
//设置点击状态
setSelected:function(select)
{
this.opacity = select ? 125 : 255;
},
//设置显示状态
setType:function(_type){
this.type = _type;
if(_type == 0){
this.visible = false;
return
}else{
this.visible = true;
}
this.initWithFile("res/icon_"+this.type+".png");
},
//设置显示下标文本
setArrIndex:function(i,j){
var lable = new cc.LabelTTF(i+","+j,'',10);
lable.enableStroke(cc.color(0,0,0,255),1)
lable.setPosition(10,10)
this.addChild(lable)
},
onExit:function(){
this._super()
cc.eventManager.removeListener(this.listener);
},
});
5,操作碎片移动时的选中逻辑:
-首先判断当前是否为可操作状态,是否有碎片正在消除,下落,空白处是否已经刷新出新块。
-然后再判断是否已经有碎片被选中,被选中的碎片始终用变量oldShard保存,所已一旦被选中的碎片发生变化,要及时更新oldShard的值。
–如果不存在已经被选中的碎片oldShard,就将本次点击到的碎片状态设为被选中。
–如果存在已经被选中的碎片oldShard,则判断本次点击到的碎片shard与oldShard是否为同一块。
—如果是则取消其被选中状态。也就是说,我们点击碎片是单击选中,双击取消。
—如果是,则判断一下它们是否相邻。
—-如果相邻,则模拟交换,看能否形成三消。
—–如果能形成三消就执行真正的消除逻辑。
—–如果不能形成三消就执行交换失败的动画。
—-如果不相邻,则交换选中状态。此时记得更新oldShard的值。
//碎片点击后的响应事件
_getTouchOneShard: function (event)
{
if(this.isInDrop) return;
var shard = event.getUserData();
if(this.oldShard == null)
{
shard.setSelected(true);
this.oldShard = shard;
}
else
{
if(this.oldShard == shard)
{
shard.setSelected(false);
this.oldShard = null;
}
else
{
var arr_index = this.getIndexArrByShard(shard);
var old_index = this.getIndexArrByShard(this.oldShard);
if((arr_index[0] == old_index[0] || arr_index[1] == old_index[1])
&& Math.abs(arr_index[0]-old_index[0]+arr_index[1]-old_index[1]) == 1)
{
var result = this.checkAfterExchange(old_index, arr_index, true);
if(result.length > 0){
this.isInDrop = true;
this.checkAfterExchange(old_index, arr_index, false);
this.checkAllAndClear3();
}
else
{
//显示换位回去的动画
cc.log('没有掉落 回位');
}
this.oldShard.setSelected(false);
this.oldShard = null;
}
else
{
this.oldShard.setSelected(false);
shard.setSelected(true);
this.oldShard = shard;
}
}
}
},
//操作后检查
checkAfterExchange:function(arr1, arr2, useCloneArr)
{
var copyArr = useCloneArr ? Clone2xArr( this.shard_date_arr) : this.shard_date_arr;
var temp = copyArr[arr1[0]][arr1[1]];
copyArr[arr1[0]][arr1[1]] = copyArr[arr2[0]][arr2[1]];
copyArr[arr2[0]][arr2[1]] = temp;
var result = this.checkArrHas3(copyArr);
return result
},
6,三消块的消除逻辑:
-想消除三消块,第一个是把他们找出来。通过调用函数checkArrHas3获取其返回值,即可得到所有三消块的下标。
–实际上所谓的消除并不是真正的消除,而是通过将其原有的显示数据,从1-N之间的随机数更改为0,这样在碎片内部进行类型设置时会将所有显示类型为0的碎片设置为不可见。已达到视觉上被消除的效果。
—消除完成后,紧接着执行碎片的下落补空逻辑。
//检查所有超过3个相邻的块儿计算完成后清除
checkAllAndClear3:function()
{
var clearArr = this.checkArrHas3(this.shard_date_arr);
if(clearArr.length > 0)
{
for(var index in clearArr)
{
var ccpOb = clearArr[index];
this.shard_date_arr[ccpOb.x][ccpOb.y] = 0;
}
this.flushShardShowWithArr();
setTimeout(this.shardDrop.bind(this), DELAY_TIME);
}
else
{
if(this.checkIsDeath())
{
cc.log('进入无解状态');
}
else
{
this.isInDrop = false;
}
}
},
7,三消块消除后,上方碎片下落填补空缺的计算逻辑:
-碎片的消除是一个牵一发而动全身的过程,因为空缺上方的所有碎片都会往下落。前面我们讲到,消除并非真正意义上的消除,只是空缺处的碎片变为了不可见。所以此处的下落也并非真正意义上的下落,而是隐形的碎片不断与上方显示的碎片交换位置,直到其移动到上方没有显示的碎片为止。
–由于三消块可能在任何地方出现,而且形状也可能各种各样。所以我们需要计算出每一个碎片下方的空白数量,由此来决定该碎片需要向下下落多少格,即需要跟下方哪一格进行交换。
—我们从下往上遍历碎片,因为我们要计算的是可显示碎片下方的空缺数,所以我们首先判断当前遍历到的碎片是否为可见,如果可见我们再从该碎片当前位置往下数到底,遇到空白计数器就+1。数到底以后再统计计数器的值,0表示下方无空白,无需移动。非0表示有空白,然后根据计数器的值跟往下方偏移找到对应的格子,然后交换显示数据即可。
—-最后将空白处刷出行的碎片,然后检测新的碎片能否继续想成三消。
//上方的块儿掉落 填补缺失
shardDrop: function ()
{
for (var i = 0; i < SHARD_NUM_W; i++)
{
for (var j = SHARD_NUM_H - 1; j >= 0; j--)
{
var downNum = 0;
if (this.shard_date_arr[i][j] != 0) //此段逻辑每次只计算一个块的下坠值
{
for (var k = j; k < SHARD_NUM_H; k++)
{
if (this.shard_date_arr[i][k] == 0) //只有空缺上方的才会进这里
{
downNum++;
}
}
if (downNum != 0)
{
this.shard_date_arr[i][j + downNum] = this.shard_date_arr[i][j];
this.shard_date_arr[i][j] = 0;
}
}
}
}
this.flushShardShowWithArr();
setTimeout(this.flushEmptyWithRandom.bind(this),DELAY_TIME);
},
//将空缺区域刷出新的随机块儿
flushEmptyWithRandom:function()
{
for (var i = 0; i < SHARD_NUM_W; i++)
{
for (var j = 0;j<SHARD_NUM_H ; j++)
{
if(this.shard_date_arr[i][j] == 0)
{
this.shard_date_arr[i][j] = parseInt(Math.random()*SHARD_TYPE + 1);
}
}
}
this.flushShardShowWithArr();
setTimeout(this.checkAllAndClear3.bind(this),DELAY_TIME);
},