原生js实现2048

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2048game</title>
    <!-- 未使用虚拟地址时 -->
    <!-- <link rel="stylesheet" href="./css/reset.css">
    <link rel="stylesheet" href="./css/index.css">
    <link href="favicon.ico" rel="shortcut icon">
    <script src="./js/index.js"></script>
    <script src="./js/basis.js"></script>
    <script src="./js/animation.js"></script> -->
    <!-- 使用虚拟地址时 -->
    <link rel="stylesheet" href="static/css/reset.css">
    <link rel="stylesheet" href="/static/css/index.css">
    <link href="favicon.ico" rel="shortcut icon">
    <script src="/static/js/index.js"></script>
    <script src="/static/js/basis.js"></script>
    <script src="/static/js/animation.js"></script>
</head>
<body>
    <div class="wrapper">
        <div id="score">
            score
            <br>
            <span>0</span>
        </div>
        <a class="newGame" href="/static/index.html">New Game</a>
        <!--<a class="newGame" href="index.html">New Game</a>  -->
        <div class="bottomBox">
            <!-- 创建16个box的快捷方法
            .box$*16 -->
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
            <div class="box"><span></span></div>
        </div>
    </div>
    <div class="gameOver">游戏结束<a href="/static/index.html">重新开始游戏</a></div>
    <!-- <div class="gameOver">游戏结束<a href="index.html">重新开始游戏</a></div> -->
</body>
</html>

index.js

//4*4格子
var board=new Array();
for(let i=0;i<4;i++){
    board[i]=new Array(4).fill(0);
}
// updateBoardView();
// 游戏分数
var score=0;

//console.log("1");
window.onload = function(){
    // 1让游戏刚开始/每次刷新页面就随机在任何位置出现两个2/4,开始时棋盘内随机出现两个数字,出现的数字仅可能为2或4
    let boxs=document.getElementsByClassName("box");
    // 初始化棋盘任意位置上随机生成两个数字(2或4)
    randomNumber2or4(board,boxs);
    
    randomNumber2or4(board,boxs);
    
    /*2玩家可以选择上下左右四个方向,若棋盘内的数字出现位移或合并,视为有效移动
  玩家选择的方向上若有相同的数字则合并,每次有效移动可以同时合并,但不可以连续合并
  合并所得的所有新生成数字想加即为该步的有效得分*/
    //2玩家选择的方向行或列前方有空格则出现位移,每有效移动一步,棋盘的空位(无数字处)随机出现一个数字(依然可能为2或4)
    document.onkeydown=function(event){
        event = event ||window.event;
        // 键盘响应后根据board数组中的值进行移动和计算,并将计算后的新值传给有active类的文本中
        // alert(event.key)
        switch(event.key){
            case 'ArrowUp':
                moveUp(board,boxs)
                break;
            case 'ArrowDown':
                moveDown(board,boxs)
                break;
            case 'ArrowLeft':
                moveLeft(board,boxs)
                break;
            case 'ArrowRight':
                moveRight(board,boxs)
                break;
            default:
                alert('按错键,应使用键盘上下左右键')
                break;
        }
       
        //将score中的span的文本值设为实时分数
        document.getElementById("score").children[1].innerHTML=score;
        
        
    }  
}

basis.js

// 在空白处随机产生2或者4激活,并由小变大直至占满框。
function randomNumber2or4(board,boxs){
    // 判断是否还有空白
    while(isHasBlank(board)){
        let randomi=Math.floor(Math.random()*4);
        let randomj=Math.floor(Math.random()*4);
        if(board[randomi][randomj]==0){
            board[randomi][randomj]=Math.floor(1.05+Math.random())*2;//2:4=0.9:0.1
            let index=4*randomi+randomj;//将二维索引转化为一维
            boxs[index].children[0].innerHTML=board[randomi][randomj];
            numberColor(boxs[index].children[0]);
            // 添加动画,使用动画属性animation
            boxs[index].children[0].style.animation = "size0to1 1s";
            // boxs[index].children[0].style.animation-iteration-count = "1";
            break;
        }
    }
    return true;
}
function isHasBlank(board){
    for(let i=0;i<4;i++){
        for(let j=0;j<4;j++){
            if(board[i][j]==0){
                return true;
            }
        }
    }
    return false;
} 

//更新board值
function updateView(board,boxs,mergeIndex){
    let unqualNum=0;
    for(let i=0;i<4;i++){
        for(let j=0;j<4;j++){
            let index=4*i+j;//将二维索引转化为一维
            // 让所有动画属性暂停
            boxs[index].children[0].style.animation = "";
            // boxs[index].children[0].style.animation = "sizeMergeTo1 0s";
            if(boxs[index].children[0].innerHTML!=board[i][j]){
                boxs[index].children[0].innerHTML=board[i][j];
                numberColor(boxs[index].children[0]);
                unqualNum++;
            }
        }
    }
    // 给合并数字添加动画
    for(let i=0;i<mergeIndex.length;i++){
        // alert(4*mergeIndex[i][0]+mergeIndex[i][1])
        boxs[4*mergeIndex[i][0]+mergeIndex[i][1]].children[0].style.animation = "sizeMergeTo1 1s";
    }
    // 若无移动则无反应,有移动(即有数据上的变化)则在空白处生成一个数字
    if(unqualNum!=0){
        randomNumber2or4(board,boxs);//在空白处随机生成一个数字
    }
    gameResult(board);
    return;
}

// 根据键盘的键来移动模块---这样写感觉没有利用到二维数组
function moveUp(board,boxs){
    let mergeIndex=[];
    for(let j=0;j<4;j++){
        let arr=[board[0][j],board[1][j],board[2][j],board[3][j]]
        let temp=arrayNew(arr);
        let mergeArr=temp[1];
        for(let i=0;i<4;i++){
            board[i][j]=temp[0][i];
            // 判断是否在合并的数组中
            for(let k=0;k<mergeArr.length;k++){
                if(mergeArr[k]==i){
                    mergeIndex.push([i,j])
                }
            }
        }
    }
    updateView(board,boxs,mergeIndex)//移动后更新视图
}
function moveDown(board,boxs){
    let mergeIndex=[];
    for(let j=0;j<4;j++){
        let arr=[board[3][j],board[2][j],board[1][j],board[0][j]]
        let temp=arrayNew(arr);
        let mergeArr=temp[1];
        for(let i=0;i<4;i++){
            board[i][j]=temp[0][3-i];
            for(let k=0;k<mergeArr.length;k++){
                if(mergeArr[k]==(3-i)){
                    mergeIndex.push([i,j])
                }
            }
        }
    }
    updateView(board,boxs,mergeIndex)//移动后更新视图
}
function moveLeft(board,boxs){
    let mergeIndex=[];
    for(let i=0;i<4;i++){
        let arr=board[i]
        let temp=arrayNew(arr);
        let mergeArr=temp[1];
        for(let j=0;j<4;j++){
            board[i][j]=temp[0][j];
            for(let k=0;k<mergeArr.length;k++){
                if(mergeArr[k]==j){
                    mergeIndex.push([i,j])
                }
            }
        }
    }
    updateView(board,boxs,mergeIndex)//移动后更新视图
}
function moveRight(board,boxs){
    let mergeIndex=[];
    for(let i=0;i<4;i++){
        let arr=[board[i][3],board[i][2],board[i][1],board[i][0]];
        let temp=arrayNew(arr);
        let mergeArr=temp[1];
        for(let j=0;j<4;j++){
            board[i][j]=temp[0][3-j];
            for(let k=0;k<mergeArr.length;k++){
                if(mergeArr[k]==(3-j)){
                    mergeIndex.push([i,j])
                }
            }
        }
    }
    updateView(board,boxs,mergeIndex)//移动后更新视图
}

// 把一行或一列数据的数组输入进去,得到集合后 的新数组
function arrayNew(arr){
    let newArray=[];
    let mergeArr=[];
    for(let i=0;i<arr.length;i++){
        if(arr[i]!=0){
            newArray.push(arr[i]);
        }
    }
    // 相同数字合并
    for(let i=0;i<newArray.length-1;i++){
        if(newArray[i]!=0){
            if(newArray[i]==newArray[i+1]){
                score+=newArray[i]*2;
                mergeArr.push(i)
                newArray[i]=newArray[i]*2;
                newArray.splice(i+1, 1); //下标i+1开始,删除1个
            }
        }
    }
    // 新数组长度不够补0;
    let temp=arr.length-newArray.length;
    for(let i=0;i<temp;i++){
        newArray.push(0);
    }
    return [newArray,mergeArr];
}
// 设置每个元素的数字颜色(根据生成或合成的数字添加背景颜色、数字颜色),并且设置每个数字出现都是由小到大的渐变效果,移动时的滑动效果、合并时的略变大最后复原大小的效果
// 输入是包含数字文本的元素
function numberColor(num){
    let numText=num.innerHTML;
    if(numText!=0){
        num.style.display="block";
        if(numText==2||numText==4){
            num.style.color="#776e65";
        }else{
            num.style.color="#F9F6F2";
        }
        if(numText==2){
            num.style.backgroundColor='#EEE4DA';
        }else if(numText==4){
            num.style.backgroundColor="#ede0c8";
        }else if(numText==8){
            num.style.backgroundColor="#f2b179";
        }
        else if(numText==16){
            num.style.backgroundColor="#f59563";
        }
        else if(numText==32){
            num.style.backgroundColor="#f67e60";
        }
        else if(numText==64){
            num.style.backgroundColor="#f65e3b";
        }
        else if(numText==128){
            num.style.backgroundColor="#edcf72";
        }
        else if(numText==256){
            num.style.backgroundColor="#eccb60";
        }
        else if(numText==512){
            num.style.backgroundColor="#ecc84e";
        }
        else if(numText==1024){
            num.style.backgroundColor="#edc63a";
        }
        else if(numText==2048){
            num.style.backgroundColor="#ebc32d";
        }else{
            num.style.backgroundColor="";
        }
    }else{
        num.style.display="none";
    }

    
}
// 若铺满了整个页面也没有可再合并的盒子,则游戏失败
//若合成了2048,则弹框显示游戏成果
function gameResult(board){
    // alert(isHasBlank(board))
    if(!isHasBlank(board)){
        // alert("1")
        for(let i=0;i<4;i++){
            for(let j=0;j<4;j++){
                if(board[i][j]==0||(j+1<4&&board[i][j]==board[i][j+1])||(j-1>=0&&board[i][j]==board[i][j-1])||(i+1<4&&board[i][j]==board[i+1][j])||(i-1>=0&&board[i][j]==board[i-1][j])){
                    // alert(i);
                    // alert(j);
                    return;
                }
            }
        }
        // 弹出游戏结束框
        let gameOver=document.getElementsByClassName("gameOver")[0];
        gameOver.style.display='block';
    }
    
    // new game
}

// 合并和随机产生数字时,有个从小到大的渐变的变化

index.less

body{
    background-color: #faf8ef;
    .wrapper{
        width: 450px;
        margin: 100px auto;
        position: relative;
        #score{
            width: 100px;
            height: 40px;
            line-height: 20px;
            text-align: center;
            background-color: #bbada0;
            border: solid 2px #bbada0;
            border-radius: 2px;
            color:#f9f8e9;
            font-size: 16px;
            font-weight:bold;
            position: absolute;
            left: 0px;
            top:-70px;
            span{
                color: white;
                font-size: 20px;
            }
        }
        .newGame{
            width: 110px; 
            height: 40px;
            line-height: 40px;
            text-align: center;
            text-decoration: none;
            background-color: #8F7A66;
            border: solid 2px #8F7A66;
            border-radius: 2px;
            color:#f9f8e9;
            font-size: 16px;
            font-weight:bold;
            position: absolute;
            left: 340px;
            top:-70px;
        }
        .bottomBox{
            width: 440px;
            height: 440px;
            
            background-color: #BBADA0;
            border: solid 5px #BBADA0;
            border-radius: 5px;
            .box{
                width: 100px;
                height: 100px;
                background-color: #cdc1b4;
                margin: 5px;
                border-radius: 2px;
                float:left;
                span{
                    // span是内联元素,不设置display:block即使设置宽高也不能显示设置的宽高,只会根据其中内容来宽高
                    // display:none;
                    height: 100px;
                    line-height: 100px;
                    text-align: center;
                    font-size: 50px;
                    font-weight:bold;
                    color: #776e65;
                }
                
            }
           
        }
    }
    .gameOver{
        display: none;
        position: absolute;
        width: 100%;
        height: 100%;
        line-height: 755px;
        background-color: rgb(202, 202, 230);
        font-size: 50px;
        text-align: center;
        text-decoration: none;
        opacity:0.7;
        color: black;
        a{
            display: inline-block;
            margin: 0 100px;
            opacity:1;
            color: green;
            font-size: 50px;
            // justify-content: center;
            // width: 70px;
            // height: 70px;
            // background-color: antiquewhite;
        }
    }
}
@keyframes size0to1{
    from{
        transform: scale(0);
    }
    to{
        transform: scale(1);
    }
}
@keyframes sizeMergeTo1{
    from{
        transform: scale(1.3);
    }
    to{
        transform: scale(1);
    }
}

server.js

//  首先在命令行cd到当前项目所在目录,接着在其中生成npm项目npm init -y,命令行窗口会显示项目信息
// 然后在命令行窗口中输入npm install express载入express依赖
const express = require('express');//在node应用中载入express
const app = express();//建立一个express实例
const expressPort=3000;//指定一个端口,端口号
// 使用listen进行监听
app.listen(expressPort,() => {
   console.log("running……on" + expressPort);
   })
// 接下来告诉服务器端口在接收到get请求后返回一个什么样的结果

// 静态文件的托管
app.use('/static',express.static('public'));

app.get('/',(req,res,next)=>{
   res.sendFile(__dirname +'/public/index.html')//需要绝对路径
})

初步记录:使用js编写2048代码,并通过express实现页面展示,可能有些小问题。
难点1:在初始随机产生空白和在有数字移动后随机产生一个空白
利用数组中的数据判断是否有空白,随机在空白索引处产生随机数字2或4,然后更新dom显示。

难点2:实现根据键盘实现上下左右移动并把相同数字合并
第一次是直接操作dom元素,根据不同方向依次移动元素。
第二次利用数组,对数组的数值操作后,再更新dom元素的文本值和颜色。
**注:**这里当时出现一个小bug,即当在某个方向没有可移动和可移动的元素时,在用户依然点击这个方向,页面应该无反应,所以在产生随机空白时应该加上限制:即在移动后更新所有位置元素,若发现无一个位置变化则不反应,若有变化则随机在空白处产生一个数字2/4。

难点3: 出新数字的动画和相同数字合并时的动画
在出现新产生的数字时,在其更新显示颜色时,添加并设置动画属性。
在对数组数据进行移动和合并操作时,给经过合并后的数字添加标志,然后在更新视图时,对有合并标志位置的div添加动画属性。

难点4:游戏结束后的弹窗-整个页面中其他的都点不动
使用和页面一样大小的div,在游戏结束前隐藏,游戏结束时显示。
至于游戏的结束标志:16个格子被铺满,且没有地方可以合并时为结束。

接下来计划:
1、严格来说并没有利用到二维数组的特性,需要研究如何利用二维数组将四个方向的移动函数合并为一个(如:旋转90度、180度之类的操作看可不可)
2、看看原版2048如何实现的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值