cocos2d_js Elimination: Click on Exchange Edition

1. Game Start Logic:

- Two two-dimensional arrays, one for storing display wizards and one for storing display data (the digital part of the picture path).
- The standard for a game to start properly: there are no three bullets, nor can it be a dead end.
That is to say, in an array of display data, there can not be the same situation as data in three adjacent locations --- A, nor can there be the situation that three elimination can not be formed after exchange --- B.
Determine the total number of image types N, and then assign each element in the array of display data to a random number between 0 and N.
First check whether the generated set of data exists in case A, and if so refresh the data of the unqualified location, and then re-detect.
If case A does not exist, check case B. If case B exists, refresh all data, and then re-detect.

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));  //Create Gradual Color Background Layer
        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));
    },
//Create an array without triple elimination
    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. Detecting whether there is a logic of "three elimination":

- Define a checkHas3Arr function to detect three cancellation. Starting from the upper left corner of the debris area, it detects two positions on the left and two positions on the bottom of each debris. If three consecutive identities occur, load their subscripts into an array and return. In this way, each detection of whether there are three cancellations only needs to detect whether the return value of the call function is an empty array.

//Find out the three blocks
    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. The logic of judging whether it is a death or not:

Traverse the array to detect the results of all debris moving in all directions. As long as any one of the debris can be removed three times after a movement, it can be judged as a living situation. If there is no movement of debris that can make the game disappear three times, it will be judged as a dead end.

//Dead end judgment
    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) {
                            //The system operates by itself until the unsolvable state switch
                            if(autoPlay){
                                this.checkAfterExchange([i,j], [i+arr[0],j+arr[1]], false);
                                this.checkAllAndClear3()
                            }
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    },

4. The display logic of the game:

- Initialized display data only pass through three cancellation detection and dead-end detection at the same time. It can be used to refresh the display of debris elves, that is, to match the corresponding number of images to each elf according to its value.

//Traverse two-dimensional arrays, refresh display
    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({
    //Fragment display type
    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);
    },


    //Set Click Status
    setSelected:function(select)
    {
        this.opacity = select ? 125 : 255;
    },


    //Setting Display Status
    setType:function(_type){
        this.type = _type;
        if(_type == 0){
            this.visible = false;
            return
        }else{
            this.visible = true;
        }
        this.initWithFile("res/icon_"+this.type+".png");
    },

    //Setting Display Subscript Text
    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. Selection logic for manipulating debris movement:

- First, determine whether the current state is operable, whether debris is being eliminated, falling, and whether new blocks have been refreshed in the blanks.
- Then determine whether the selected fragments have been selected, and the selected fragments are always saved with the variable oldShard. Once the selected fragments have changed, the value of oldShard should be updated in time.
If there is no oldShard that has already been selected, set the debris state that you clicked on to be selected.
- If there is a selected fragment oldShard, then judge whether the fragment shard and oldShard clicked this time are the same.
If so, cancel the selected state. That is to say, we click on the fragment to select and double-click cancel.
If so, judge whether they are adjacent.
If adjacent, then simulate the exchange to see if it can form three elimination.
If three elimination can be formed, the real elimination logic will be executed.
The animation that fails to execute the exchange if it fails to form a three-elimination animation.
- If it is not adjacent, the selected state is exchanged. At this point, remember to update the value of oldShard.

    //Response events after fragmentation clicks
    _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
                    {
                        //Display transposed back animation
                        cc.log('No drop back');
                    }
                    this.oldShard.setSelected(false);
                    this.oldShard = null;
                }
                else
                {
                    this.oldShard.setSelected(false);
                    shard.setSelected(true);
                    this.oldShard = shard;
                }
            }
        }
    },

 //Post-operation inspection
    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. Elimination logic of three blocks:

- The first is to find them out. By calling the function checkArrHas3 to get its return value, we can get the subscripts of all the three blocks.
In fact, the so-called elimination is not really elimination, but by changing its original display data from 1-N to 0, so that all fragments with 0 display type will be set invisible when type setting is done inside the fragments. The effect of visual elimination has been achieved.
After the elimination is completed, the debris drop-fill logic is executed immediately.

//Check all more than three adjacent blocks for clearance after completion of calculation
    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('Enter a Soluble State');
            }
            else
            {
                this.isInDrop = false;
            }
        }
    },

7. The computational logic of the upper debris falling to fill the vacancy after the elimination of the three elimination blocks:

- Elimination of debris is a one-off and whole-body process, because all debris above the vacancy will fall. As we mentioned earlier, elimination is not really elimination, but the fragments of vacancies become invisible. So the fall here is not the real drop, but the invisible debris constantly exchanges position with the debris shown above until it moves to the debris not shown above.
Because the three elimination blocks may appear anywhere, and the shapes may vary. So we need to calculate the number of blanks below each debris to determine how many squares the debris needs to fall down, that is, which squares need to be swapped with.
We traverse debris from bottom to top, because we need to calculate the number of vacancies below the display debris, so we first determine whether the current traversed debris is visible. If we can see that we go from the current location of the debris to the bottom, we will encounter a blank counter + 1. After the number is counted, the value of the counter is counted. 0 indicates that there is no blank below, and no need to move. Non-zero means that there is a blank, and then according to the value of the counter offset down to find the corresponding grid, and then exchange display data can be.
Finally, brush the debris out of the blank, and then test whether the new debris can continue to think of three elimination.

 //The upper block falls to fill the gap.
    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)   //This logic calculates only one block drop at a time.
                {
                    for (var k = j; k < SHARD_NUM_H; k++)
                    {
                        if (this.shard_date_arr[i][k] == 0)  //Only those above the vacancy will enter here.
                        {
                            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);
    },
//Refresh the vacancy area with a new random block
    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);
    },

Keywords: Fragment

Added by Amal on Mon, 03 Jun 2019 21:48:43 +0300