前几年2048小游戏风靡一时,很多人下载这个游戏玩,但是作为一个合格且没多少头发的程序员,不应该想一想,这个游戏否是可以自己实现呢?

1.html代码块

overflow: hidden;此样式解决微信内置浏览器下拉问题

<body style="overflow: hidden;">
    <div class="main">
        <div id="c00"></div>
        <div id="c01"></div>
        <div id="c02"></div>
        <div id="c03"></div>

        <div id="c10"></div>
        <div id="c11"></div>
        <div id="c12"></div>
        <div id="c13"></div>

        <div id="c20"></div>
        <div id="c21"></div>
        <div id="c22"> </div>
        <div id="c23"></div>

        <div id="c30"></div>
        <div id="c31"></div>
        <div id="c32"></div>
        <div id="c33"></div>
    </div>
    <div class="keymap">
        <div class="score">分数:<span id="score">0</span></div>
        <p><button onclick="game.reset()">重新开始</button></p>
    </div>
</body>

2.css样式

使用flex弹性布局

@media all and (max-device-width: 400px)解决响应式布局

.main{
    width: 400px;
    height: 400px;
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    padding: 10px;
    background:#bbada0;
    border-radius: 6px;
    position: absolute;
    top: calc(50% - 200px);
    left: calc(50% - 200px);
}
.main div{
    width: 90px;
    height: 90px;
    border-radius: 6px;
    background-color: #ccc0b3;
    text-align: center;
    line-height: 90px;
}
@media all and (max-device-width: 400px){
    .main{
        width: 340px;
        height: 340px;
        border-right: 1px solid #e0e0e0;
        display: flex;
        justify-content: space-between;
        flex-wrap: wrap;
        padding: 10px;
        background:#bbada0;
        border-radius: 6px;
        position: absolute;
        top: calc(50% - 170px);
        left: calc(50% - 180px);
    }

    .main div{
        width: 80px;
        height: 80px;
    }
}
.keymap{
    margin: 0 auto;
    width: 300px;
    text-align: center;
}
.score{
    height: 40px;
    line-height: 40px;
    font-size: 30px;
    margin: 20px;
}
.btn-1,.btn-2,.btn-3,.btn-4{
    display: flex;
    flex-flow: row;
    justify-content: center;
    margin-top: 10px;
}
.btn-4{
    margin-top: 40px;
}

button{
    height: 50px;
    width: 80px;
    margin: 0 10px;
    cursor: pointer;
    border-width: 0;
    outline: none;
    background-color: #f59563;
    font-family: KaiTi;
    border-radius: 3px;
    font-size: 20px;
}
button:hover{/*鼠标移动时的颜色变化*/
    background-color: antiquewhite;
}

.n2{background-color:#eee3da !important}
.n4{background-color:#ede0c8 !important}
.n8{background-color:#f2b179 !important}
.n16{background-color:#f59563 !important}
.n32{background-color:#f67c5f !important}
.n64{background-color:#f65e3b !important}
.n128{background-color:#edcf72 !important}
.n256{background-color:#edcc61 !important}
.n512{background-color:#9c0 !important}
.n1024{background-color:#33b5e5 !important}
.n2048{background-color:#09c !important}
.n4096{background-color:#a6c !important}
.n8192{background-color:#93c !important }
.n2,.n4{color:#776e65 !important}

3.js逻辑处理

3.1声明一个game对象

let game = {
        score: 0,
        status: 0,
        data: [
            [],
            [],
            [],
            []
        ],
        start: function(){
            if(this.status == 1){
                alert('游戏已开始');
                return
            }
            this.status = 1;
            let data = localStorage.getItem('data');
            if(data){
                let arr = data.split(',');
                for(let i=0;i<arr.length;i++){
                    if(i<4){
                        this.data[0][i]=parseInt(arr[i]);
                    }else if(i<8){
                        this.data[1][i-4]=parseInt(arr[i]);
                    }else if(i<12){
                        this.data[2][i-8]=parseInt(arr[i]);
                    }else{
                        this.data[3][i-12]=parseInt(arr[i]);
                    }
                }
            }else{
                this.data = [
                    [0,0,0,0],
                    [0,0,0,0],
                    [0,0,0,0],
                    [0,0,0,0]
                ];
                this.randomData();
            }
            if(localStorage.getItem('score')){
                this.score = parseInt(localStorage.getItem('score'))
            }
            this.renderView();
        },
        randomData: function(){
            for(;;){
                let a = Math.floor(Math.random()*4);
                let b = Math.floor(Math.random()*4);
                if(this.data[a][b] == 0){
                    var num = Math.random()>0.3 ? 2:4;
                    this.data[a][b]=num;
                    break;
                }
            }
        },
        //更新视图
        renderView: function(){
            for(let i = 0; i < this.data.length; i++){
                let row = this.data[i];
                for(let j = 0; j<row.length; j++){
                    let cell = row[j];
                    let dom = document.getElementById('c'+i+j);
                    if(cell != 0){
                        dom.innerText=cell;
                        dom.className='n'+cell;
                    }else{
                        dom.innerText='';
                        dom.className='';
                    }
                }
            }
            localStorage.setItem('score',this.score);
            document.getElementById('score').innerText=this.score;
        },
        reset(){
            localStorage.clear();
            this.data = [
                [0,0,0,0],
                [0,0,0,0],
                [0,0,0,0],
                [0,0,0,0]
            ]
            this.score = 0;
            this.randomData();
            this.renderView();
        },
        checkGameOver: function(){
            for(let i = 0; i < this.data.length; i++){
                let row = this.data[i];
                for(let j = 0; j<row.length; j++){
                    let cell = row[j];
                    if(cell == 0){
                        return false;
                    }
                    if(i < this.data.length-1 && this.data[i][j]== this.data[i+1][j]){
                        return false;
                    }
                    if(j < this.data.length-1 && this.data[i][j]== this.data[i][j+1]){
                        return false;
                    }
                }
            }
            return true;
        },
        moveLeft: function(){
            let before = String(this.data);
            //遍历行
            for(let a = 0; a < this.data.length; a++){
                this.moveLeftInRow(a);
            }
            let after = String(this.data);
            if(before != after){
                this.randomData();
                this.renderView();
                if(this.checkGameOver()){
                    alert('游戏结束')
                }
            }
            localStorage.setItem('data',this.data.join(','))
        },
        moveLeftInRow: function(a){
            for(let b = 0; b < this.data.length - 1; b++){
                let nextb = this.getNextInRow(a,b);
                if(nextb != -1){
                    if(this.data[a][b] == 0){
                        this.data[a][b] = this.data[a][nextb];
                        this.data[a][nextb] = 0;
                        b--;   
                    }else if(this.data[a][b] == this.data[a][nextb]){
                        this.data[a][b] *= 2;
                        this.score += this.data[a][b]
                        this.data[a][nextb] = 0
                    }
                }else{
                    break;
                }
            }
        },
        getNextInRow: function(a,b){
            for(let i = b+1; i<this.data.length; i++){
                if(this.data[a][i] != 0){
                    return i;
                }
            }
            return -1;
        },
        moveRight: function(){
            let before = String(this.data);
            for(let a = 0; a < this.data.length; a++){
                this.moveRightInRow(a);
            }
            let after = String(this.data);
            if(before != after){
                this.randomData();
                this.renderView();
                if(this.checkGameOver()){
                    alert('游戏结束')
                }
            }
            localStorage.setItem('data',this.data.join(','))
        },
        moveRightInRow: function(a){
            for(let b=this.data.length-1; b > 0; b--){
                let nextb = this.moveNextRight(a,b);
                if(nextb != -1){
                    if(this.data[a][b] == 0){
                        this.data[a][b] = this.data[a][nextb];
                        this.data[a][nextb] = 0;
                        b++;    
                    }else if(this.data[a][b] == this.data[a][nextb]){
                        this.data[a][b] *= 2;
                        this.score += this.data[a][b]
                        this.data[a][nextb] = 0
                    }
                }else{
                    break;
                }
            }
        },
        moveNextRight(a,b){
            for(var i = b-1; i >= 0; i--){
                if(this.data[a][i] != 0){
                    return i;
                }
            }
            return -1;
        },
        moveUp: function(){
            let before = String(this.data);
            for(let b=0; b < this.data.length; b++){
                this.moveUpInRow(b);
            }
            let after = String(this.data);
            if(before != after){
                this.randomData();
                this.renderView();
                if(this.checkGameOver()){
                    alert('游戏结束')
                }
            }
            localStorage.setItem('data',this.data.join(','))
        },
        moveUpInRow(b){
            for(var a = 0; a < 3; a++){
                let nexta = this.moveNextUp(a,b);
                if(nexta != -1){
                    if(this.data[a][b] == 0){
                        this.data[a][b] = this.data[nexta][b];
                        this.data[nexta][b] = 0;
                        a--;    
                    }else if(this.data[a][b] == this.data[nexta][b]){
                        this.data[a][b] *= 2;
                        this.score += this.data[a][b]
                        this.data[nexta][b] = 0
                    }
                }else{
                    break;
                }
            }
        },
        moveNextUp(a,b){
            for(var i = a+1; i<4; i++){
                if(this.data[i][b] != 0){
                    return i;
                }
            }
            return -1;
        },
        moveDown: function(){
            let before = String(this.data);
            for(let b=0; b< this.data.length; b++){
                this.moveDownInRow(b);
            }
            let after = String(this.data);
            if(before != after){
                this.randomData();
                this.renderView();
                if(this.checkGameOver()){
                    alert('游戏结束')
                }
            }
            localStorage.setItem('data',this.data.join(','))
        },
        moveDownInRow: function(b){
            for(var a=3; a>0; a--){
                var nexta = this.moveNextDown(a,b)
                if(nexta != -1){
                    if(this.data[a][b]==0){
                        this.data[a][b] = this.data[nexta][b];
                        this.data[nexta][b] = 0;
                        a++;  
                    }else if(this.data[a][b] == this.data[nexta][b]){
                        this.data[a][b] *= 2;
                        this.score += this.data[a][b]
                        this.data[nexta][b] = 0;
                    }
                }else{
                    break;
                }
            }
        },
        moveNextDown(a,b){
            for(var i = a-1;i >= 0;i--){
                if(this.data[i][b] != 0){
                    return i;
                }
            }
            return -1;
        }
    }

3.2监听键盘上下左右按键

document.addEventListener('keyup',function(e){
    if(event.keyCode==37)	//左
        game.moveLeft();
    if(event.keyCode==39)	//右
        game.moveRight();
    if(event.keyCode==38)	//上
        game.moveUp();
    if(event.keyCode==40)	//下
        game.moveDown();
})

3.3封装移动端滑动事件

let eventUtil = {
        addHandler: function (element, type, handler) {
            if (element.addEventListener)
                element.addEventListener(type, handler, false);
            else if (element.attachEvent)
                element.attachEvent("on" + type, handler);
            else
                element["on" + type] = handler;
        },
        removeHandler: function (element, type, handler) {
            if(element.removeEventListener)
                element.removeEventListener(type, handler, false);
            else if(element.detachEvent)
                element.detachEvent("on" + type, handler);
            else
                element["on" + type] = handler;
        },
        /**
        * 监听触摸的方向
        * @param target            要绑定监听的目标元素
        * @param isPreventDefault  是否屏蔽掉触摸滑动的默认行为(例如页面的上下滚动,缩放等)
        * @param upCallback        向上滑动的监听回调(若不关心,可以不传,或传false)
        * @param rightCallback     向右滑动的监听回调(若不关心,可以不传,或传false)
        * @param downCallback      向下滑动的监听回调(若不关心,可以不传,或传false)
        * @param leftCallback      向左滑动的监听回调(若不关心,可以不传,或传false)
        */
        listenTouchDirection: function (target, isPreventDefault, upCallback, rightCallback, downCallback, leftCallback) {
            this.addHandler(target, "touchstart", handleTouchEvent);
            this.addHandler(target, "touchend", handleTouchEvent);
            this.addHandler(target, "touchmove", handleTouchEvent);
            var startX;
            var startY;
            function handleTouchEvent(event) {
                switch (event.type){
                    case "touchstart":
                        startX = event.touches[0].pageX;
                        startY = event.touches[0].pageY;
                        break;
                    case "touchend":
                        var spanX = event.changedTouches[0].pageX - startX;
                        var spanY = event.changedTouches[0].pageY - startY;

                        if(Math.abs(spanX) > Math.abs(spanY)){      //认定为水平方向滑动
                            if(spanX > 30){         //向右
                                if(rightCallback)
                                    rightCallback();
                            } else if(spanX < -30){ //向左
                                if(leftCallback)
                                    leftCallback();
                            }
                        } else {                                    //认定为垂直方向滑动
                            if(spanY > 30){         //向下
                                if(downCallback)
                                    downCallback();
                            } else if (spanY < -30) {//向上
                                if(upCallback)
                                    upCallback();
                            }
                        }

                        break;
                    case "touchmove":
                        //阻止默认行为
                        if(isPreventDefault)
                            event.preventDefault();
                        break;
                }
            }
        }
    }

3.4 监听滑动

eventUtil.listenTouchDirection(document.documentElement,true,
    ()=>{game.moveUp()},
    ()=>{game.moveRight()},
    ()=> {game.moveDown()},
    ()=>{game.moveLeft()})

预览地址