这周断断续续完成了2048小游戏的网页版,这里主要讲这款游戏的逻辑和代码实现。 参考:慕课网2048私人订制 https://www.imooc.com/learn/76 对着视频把源码敲了一遍,这里作一个思路整理。
1、游戏页面结构及样式 2、2048游戏逻辑 3、手机端优化 思路可以划分为上三步,在第一步中,首先规划好整体的排版。
页面内容包括2048header、游戏开始button、得分字段
、整个4*4方格grid-container。在container中每一个cell后期都要绑定一个number。
2048,开始、得分 设置text-align:center;margin左右auto居中,字体背景颜色等;
<header> <h1>2048</h1> <a href="javascrpit:newgame();">开始</a> <p>得分</p> </header>4*4方格,盒子模型
<div id="grid-container"> <div id="grid-cell-0-0> </div> </div> //grid-cell-0-0到3-3container设置width、height、background,bord-radius边框,padding,margin、position(relative相对定位,相当于浮动) grid-cell设置width,height、background-color、position(absolute绝对定位,可以被覆盖) number-cell覆盖每个grid-cell,,其中的数字为border-number,初使为0
1、newgame包括:init初始化游戏,生成gridcell4*4,number-cell清零,随机两个位置,generateOneNumber两次。 (0)生成页面结构后,加载DOM(文档对象模型)。通过$(document).ready()完成,ready事件在生成html标签、元素,显示CSS样式后实现。在ready()事件中执行newgame函数,该函数链接到《开始》按钮。 (1)init:通过类选择器number-cell $(.number-cell).remove()清零,对grid-container中添加number-cell,number-cell中的数字都存在board【】【】二维数组中。 (2)generateOneNumber中随机生成两个位置,和随机2或4, board[randx][randy]=randNumber;
2、moveLeft、moveRight、moveUp、moveDown键盘操作。moveLeft发生,所有数字左移,相同数字求和。 (0)按下键盘的↑↓←→由KaTeX parse error: Expected 'EOF', got '&' at position 171: …,board[i][k]!=0&̲&在左移的这段(k~j)距离,…(’#score’).text(score)使得在得分:0处显示分数。 4、game over,判断所有border-number不为零(没有空格),且canMoveLeft等四个函数return false(不能移动)。
(0)在手机上与pc端的主要区别:页面大小、控制方式(按键盘和触控) (1)为了适应手机和pc,加一个获取屏幕大小的函数documentWidth=window.screen.availWidth,if 屏幕宽度大于500像素,认为是PC,grid-container按500像素显示,小于500,按手机来自适应。width和height按%百分比显示。 (2)触控用document.addEventListener监听,包括touchstart、touchmove和touchend。 (3)touchstart获取手指初始位置;touchmove是优化,使得手指点击无效;touchend获取手指离开屏幕的位置,通过xy坐标和滑动产生的位移来判断,执行moveLeft等上下左右四个函数。
注:bug修改 在一排四个格子出现2248这样的情况下,左移结果应该是4480,但是在三、2、(2)中,结果是16000,应该添加hasConflicted二维数组,判断是否加过,保证只加一次。 最后,附上慕课中给的源代码,仅作少量修改,侵删 整体结构: index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content=" width=device_value, height=device_value, initial-scale=1.0, minimum-scale=0.9, maxmum-scale=1.0, <!-- //用户可以缩小或放大屏幕尺寸的最小最大值--> user-scalable = yes" /> <title>Title</title> <link rel="stylesheet" type="text/css" href="2048.css"> <!-- <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>--> <!-- <script type="text/javascript" src="http://libs.baidu.com/jquery/1.9.0/jquery.min.js"></script>--> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="main2048.js"></script> <script type="text/javascript" src="showanimation.js"></script> <script type="text/javascript" src="support2048.js"></script> </head> <body> <header> <h1>2048</h1> <a href="javascript:newgame();" id="newgamebutton">开始</a> <p>得分: <span id="score">0</span> </p> </header> <div id="grid-container"> <div class="grid-cell" id="grid-cell-0-0"></div> <div class="grid-cell" id="grid-cell-0-1"></div> <div class="grid-cell" id="grid-cell-0-2"></div> <div class="grid-cell" id="grid-cell-0-3"></div> <div class="grid-cell" id="grid-cell-1-0"></div> <div class="grid-cell" id="grid-cell-1-1"></div> <div class="grid-cell" id="grid-cell-1-2"></div> <div class="grid-cell" id="grid-cell-1-3"></div> <div class="grid-cell" id="grid-cell-2-0"></div> <div class="grid-cell" id="grid-cell-2-1"></div> <div class="grid-cell" id="grid-cell-2-2"></div> <div class="grid-cell" id="grid-cell-2-3"></div> <div class="grid-cell" id="grid-cell-3-0"></div> <div class="grid-cell" id="grid-cell-3-1"></div> <div class="grid-cell" id="grid-cell-3-2"></div> <div class="grid-cell" id="grid-cell-3-3"></div> </div> </body> </html> <!--MVC结构:Model模型、visual界面UI、Control逻辑控制--> <!--待优化:--> <!--1、game over--> <!--2、最高得分--> <!--3、加分动画--> <!--可以通过phoneGap将web APP转换为native app--> <!--网站发布:dropBox&dropPages--> <!--或者发布方式:nginx+花生壳,存在nginx中即可。-->2048.css
header{ display: block; margin: 0 auto; width:100%; text-align: center; } header h1{ font-family: Arial; font-size: 40px; font-weight:bold ; } header #newgamebutton{ display: block; margin: 20px auto; width: 100px; padding:10px 10px; /*background-color: #9efff4;*/ background-color: chocolate; font-family: Arial; color: white; border-radius: 10px; text-decoration: none; } header #newgamebutton:hover{ background-color: cyan; } header p{ font-family:Arial; font-size: 25px; margin: 20px auto; } #grid-container{ width:460px; height: 460px; padding:20px; /*上下左右各自向外20px,盒子为500*500*/ margin: 50px auto; /*background-color: #bbada0;*/ background-color: #9efff4; background-color: rgba(22,27,255,0.9); border-radius: 10px; position: relative; } .grid-cell{ width: 100px; height: 100px; border-radius: 6px; /*background-color: #ccc0b3;*/ background-color: yellowgreen; position: absolute; } .number-cell{ border-radius:6px; font-family: Arial; font-size: 60px; font-weight: bold; font-height:100px; text-align:center; position:absolute; }main2048.js
var board = new Array(); var score =0; var hasConflicted = new Array(); //用于判断每个小格子是否发生了变化 //游戏初始化--------------------------------- $(document).ready(function(){ prepareForMobile(); newgame(); }); function prepareForMobile() { if( documentWidth > 500 ){ gridContainerWidth = 500; cellSpace = 20; cellSideLength = 100; } $('#grid-container').css('width',gridContainerWidth-2*cellSpace); $('#grid-container').css('height',gridContainerWidth-2*cellSpace); $('#grid-container').css('padding',cellSpace); $('#grid-container').css('border-radius',0.02*gridContainerWidth) $('.grid-cell').css('width',cellSideLength); $('.grid-cell').css('height',cellSideLength); $('.grid-cell').css('border-radius',0.02*cellSideLength); } function newgame() { //初始化棋盘格 init(); //在随机两个格子生成数字 generateOneNumber(); generateOneNumber(); } function init() { for (var i=0; i<4;i++) for (var j=0;j<4;j++){ var gridCell=$('#grid-cell-'+i+"-"+j); gridCell.css('top', getPosTop(i, j)); gridCell.css('left', getPosLeft(i, j)); //每个方格的位置由这两个函数决定,在support2048中 } for(var i = 0;i<4;i++){ board[i]=new Array(); hasConflicted[i] = new Array(); for(var j=0;j<4;j++){ board[i][j]=0; hasConflicted[i][j]= false; } } updateBoardView(); score = 0; } function updateBoardView(){ $(".number-cell").remove(); // 删除当前所有方格中的值 for (var i=0;i<4;i++) for (var j=0;j<4;j++){ $("#grid-container").append('<div class="number-cell" id="number-cell-'+i+'-'+j+'"></div>'); var theNumberCell = $('#number-cell-'+i+'-'+j); if (board[i][j]==0){ theNumberCell.css('width','0px'); theNumberCell.css('height','0px'); theNumberCell.css('top',getPosTop(i, j)+cellSideLength/2); theNumberCell.css('left',getPosLeft(i, j)+cellSideLength/2); }else{ // 不为零时用number-cell代替gridcell theNumberCell.css('width',cellSideLength); theNumberCell.css('height',cellSideLength); theNumberCell.css('top',getPosTop(i, j)); theNumberCell.css('left',getPosLeft(i, j)); theNumberCell.css('background-color',getNumberBackgroudColor(board[i][j])); theNumberCell.css('color',getNumberColor(board[i][j])); // 背景色和前景色 theNumberCell.text(board[i][j]); } hasConflicted[i][j]= false; } $('.number-cell').css('line-height',cellSideLength+'px'); $('.number-cell').css('font-size',0.6*cellSideLength+'px'); } function generateOneNumber(){//先随机找一个位置,再在这个位置上赋值 if (nospace(board)) return false; //随机一个位置 var randx=parseInt(Math.floor(Math.random()*4)); var randy=parseInt(Math.floor(Math.random()*4)); //产生0-4直接的浮点数,floor向下取整0123,parseInt强制类型转换为int var time=0; while(time<30){ //===========================================================不懂 if (board[randx][randy]==0) break; randx=parseInt(Math.floor(Math.random()*4)); randy=parseInt(Math.floor(Math.random()*4)); time++; } if(time>30){ for(var i=0;i<4;i++) for(var j=0;j<4;j++) if(board[i][j]==0){ randx=i; randy=j; } } //随机一个数字2,4 var randNumber = Math.random() <0.5? 2:4;//随机数小于0.5为2,大于0.5为4 //显示 board[randx][randy]=randNumber; showNumberWithAnimation(randx, randy, randNumber); return true; } $(document).keydown(function (event) { event.preventDefault();//阻挡按键本来会产生的效果,这里防止屏幕滚动条上下移动 switch (event.keyCode) { case 37: //left,向左移动,生成新的数,判断游戏是否结束 if (moveLeft()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); //设置延时,让alert有出现 的效果 } break; case 38://up if (moveUp()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); } break; case 39://right if (moveRight()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); } break; case 40://down if (moveDown()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); } break; default: break; } }) document.addEventListener('touchstart',function (event) { startx=event.touches[0].pageX;//event.touches获取多个手指触碰屏幕的信息【0】为一个手指 starty=event.touches[0].pageY; }); document.addEventListener('touchmove',function (event) { event.preventDefault(); }); document.addEventListener('touchend',function (event) { endx=event.changedTouches[0].pageX; endy=event.changedTouches[0].pageY; var deltax=endx-startx; var deltay=endy-starty; if(Math.abs(deltax)<0.3*documentWidth && Math.abs(deltay)<0.3*documentWidth) return; //x if(Math.abs(deltax)>=Math.abs(deltay)){ if(deltax>0){ //向右 if (moveRight()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); } }else{ //向左 if (moveLeft()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); //设置延时,让alert有出现 的效果 } } } else{ //y if(deltay>0){ //向下 if (moveDown()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); } }else{ //向上 if (moveUp()){ setTimeout("generateOneNumber()",200); setTimeout("isgameover();",200); } } } }); function isgameover() { if (nospace(board)&&nomove(board)){ gameover(); } } function gameover() { alert('Game Over'); } //--------------------------------------------------------Left------------------------------------- function moveLeft() { if (!canMoveLeft(board)){ return false; } //moveLeft for (var i=0;i<4;i++) for (var j=1;j<4;j++){ if (board[i][j]!=0){//不为0可以向左移动,移动要判断该行左侧是否空(或相等)且没有障碍物 for (var k=0;k<j;k++){ if (board[i][k]==0&&noBlockHorizontal(i, k, j, board)){ //move showMoveAnimation(i, j,i, k); board[i][k]=board[i][j]; board[i][j]=0; continue; } else if (board[i][k]==board[i][j] &&noBlockHorizontal(i, k, j, board)&& !hasConflicted[i][k]){ // else if (board[i][k]==board[i][j] &&noBlockHorizontal(i, k, j, board)){ //hasConflicted[i][k]没变化过,即没有发生add //move showMoveAnimation(i, j,i, k); //add board[i][k] +=board[i][j]; board[i][j]=0; score+=board[i][k]; updateScore(score); hasConflicted[i][k]=true;//只add一次 continue; } } } } setTimeout("updateBoardView()",200);//设置更新在200ms后发生,move动画就可以显示 return true; } //--------------------------------------------------------Right------------------------------------- function moveRight(){ if( !canMoveRight( board ) ) return false; //moveRight for( var i = 0 ; i < 4 ; i ++ ) for( var j = 2 ; j >= 0 ; j -- ){ if( board[i][j] != 0 ){ for( var k = 3 ; k > j ; k -- ){ if( board[i][k] == 0 && noBlockHorizontal( i , j , k , board ) ){ showMoveAnimation( i , j , i , k ); board[i][k] = board[i][j]; board[i][j] = 0; continue; } else if( board[i][k] == board[i][j] && noBlockHorizontal( i , j , k , board )&& !hasConflicted[i][k] ){ // else if( board[i][k] == board[i][j] && noBlockHorizontal( i , j , k , board )){ showMoveAnimation( i , j , i , k); board[i][k] *= 2; board[i][j] = 0; score+=board[i][k]; updateScore(score); hasConflicted[i][k]=true; continue; } } } } setTimeout("updateBoardView()",200); return true; } function moveUp() { if (!canMoveUp(board)){ return false; } // for(var j=0;j<4;j++) for (var i=1;i<4;i++) if (board[i][j]!=0) for (var k=0;k<i;k++) if (board[k][j]==0 && noBlockupdown(j, k, i, board)) { //move showMoveAnimation(i, j, k, j); board[k][j] = board[i][j]; board[i][j] = 0; continue; } // else if (board[k][j]==board[i][j]&&noBlockupdown(j, k, i, board)&& !hasConflicted[k][j]) { else if (board[k][j]==board[i][j]&&noBlockupdown(j, k, i, board)) { //move showMoveAnimation(i,j, k, j); //add board[k][j]+=board[i][j]; board[i][j]=0; score+=board[k][j]; updateScore(score); // hasConflicted[k][j]=true; continue; } setTimeout("updateBoardView()",200); return true; } function moveDown() { if (!canMoveDown(board)){ return false; } // for (var j=0;j<4;j++) for (var i=2;i>=0;i--) if(board[i][j]!=0){ for (var k=3;k>i;k--) if (board[k][j]==0&&noBlockupdown(j, i, k, board)){ //move showMoveAnimation(i, j, k, j); board[k][j]=board[i][j]; board[i][j]=0; } // else if (board[i][j]==board[k][j]&&noBlockupdown(j, i, k, board) && !hasConflicted[k][j]){ else if (board[i][j]==board[k][j]&&noBlockupdown(j, i, k, board)){ //move,add showMoveAnimation(i, j, k, j) board[k][j]+=board[i][j]; board[i][j]=0; score+=board[k][j]; updateScore(score); // hasConflicted[k][j]=true; } } setTimeout("updateBoardView()",200); return true; }support2048.js
documentWidth=window.screen.availWidth; gridContainerWidth=0.92*documentWidth; cellSideLength=0.18*documentWidth; cellSpace=0.04*documentWidth; function getPosTop(i,j) { return cellSpace + (cellSpace+cellSideLength)*i; } function getPosLeft(i,j) { return cellSpace + (cellSpace+cellSideLength)*j; } function getNumberBackgroudColor(number) { switch( number ){ case 2:return "#eee4da";break; case 4:return "#ede0c8";break; case 8:return "#f2b179";break; case 16:return "#f59563";break; case 32:return "#f67c5f";break; case 64:return "#f65e3b";break; case 128:return "#edcf72";break; case 256:return "#edcc61";break; case 512:return "#9c0";break; case 1024:return "#33b5e5";break; case 2048:return "#09c";break; case 4096:return "#a6c";break; case 8192:return "#93c";break; } return "black"; } function getNumberColor(number) { if (number<= 4) return "#776e65"; return "white"; } function nospace(board) { for (var i=0;i<4;i++) for (var j=0;j<4;j++) if (board[i][j]==0)//默认初始值为0,为0说明还有空格 return false; return true; } function noBlockHorizontal(row, col1,col2,board) { for (var i=col1+1;i<col2;i++) if (board[row][i]!=0) return false; return true; } function noBlockupdown(col, row1,row2,board) { for (var i=row1+1;i<row2;i++) if (board[i][col]!=0) return false; return true; } function canMoveLeft(board) { for (var i=0;i<4;i++) for (var j=1;j<4;j++){ if (board[i][j]!=0)//这个点不为0判断要向左移动 if (board[i][j-1]==0 || board[i][j-1]==board[i][j])//左侧是空的或者左右相等 return true; } return false; } function canMoveRight(board){ for (var i=0;i<4;i++) for (var j=2;j>=0;j--) if (board[i][j]!=0) if (board[i][j+1]==board[i][j] || board[i][j+1]==0) return true; return false; } function canMoveUp(board){ for (var j=0;j<4;j++) for (var i=1;i<4;i++) if (board[i][j]!=0){ if (board[i-1][j]==0 || board[i][j]==board[i-1][j]){ return true; } } return false; } function canMoveDown(board) { for (var j=0;j<4;j++) for (var i=2;i>=0;i--) if(board[i][j]!=0){ if (board[i+1][j]==board[i][j] || board[i+1][j]==0){ return true; } } return false; } function nomove(board) { if (canMoveLeft(board) || canMoveRight(board) || canMoveUp(board) || canMoveDown(board)){ return false; } return true; }showanimation.js 移动的动画效果
function showNumberWithAnimation(i, j, randNumber) { var numberCell=$('#number-cell-'+i+"-"+j); numberCell.css('background-color', getNumberBackgroudColor(randNumber)); numberCell.css('color', getNumberColor(randNumber)); numberCell.text(randNumber); numberCell.animate({ // animate函数是jQUERY中的动画函数,动画渐变50ms width:cellSideLength, height:cellSideLength, top:getPosTop(i, j), left:getPosLeft(i, j) },50); } function showMoveAnimation(fromx, fromy,tox, toy){ var numberCell = $('#number-cell-'+fromx+'-'+fromy); numberCell.animate({ top:getPosTop(tox, toy), left:getPosLeft(tox, toy) },200); } function updateScore(score) { $('#score').text(score); }