游戏架构大致如下图所示,是典型的M(model游戏数据)V(view视图)C(control)结构。
我们的2048也是如上图结构所示。其中由于2048不需要什么游戏数据,所以无Model;游戏主逻辑是游戏的主函数,包括在main.js中;视图UI是html+css;支撑逻辑包含在support2048.js中;动画效果逻辑包含在showanimation2048.js。
UI部分(html+css)
- HTML设置大致的布局——包含
<header>
和grid-container
结构。
<header>
<h1>2048</h1>
<a href="javascript:newgame();" id="newgamebutton">New Game</a>
<p>score:<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>
- css主要就是对这两部分进行布局。用
margin:0 auto;
将块居中;用text-align:center;
将块中的文字居中。<header>
部分的宽度因为本身不大,所以可以设置死。grid-container
部分的宽度可以先写一个固定宽度,看着舒服就行,之后再用js在不同的屏幕上显示不同的宽度。
主逻辑部分
- 首先规定页面加载完完成的函数
-$(document).ready(function(){ prepareForMobile(); newgame(); });
- 其中的
prepareForMobile()
是根据屏幕的宽度动态调节grid-container与grid-cell
的间距与大小。
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);
}
其中的变量gridContainerWidth,cellSpace,cellSideLength
等规定在support2048.js中。
documentWidth=window.screen.availWidth; //屏幕可用宽度。相当于500px
gridContainerWidth=0.92*documentWidth; //grid-container的设定宽度值
cellSideLength=0.18*documentWidth; //grid-cell的长宽?计算着有点不对,应该是0.2
cellSpace=0.04*documentWidth;
newgame()
函数是新游戏入口程序。在每次页面加载或点击新游戏按钮之后都会运行。
function newgame(){
init();
generateOneNumber();
generateOneNumber();
}
- 其中的init()函数是初始化程序。完成了几件事:1.用jquery对每一个数字的背景归位;2.对每一个数字初始化为0,对每一个hasConflicted初始化为false;3.重新渲染页面:updateBoardView();4.对score初始化为0,并用jquery反馈到前台。
updateBoardView()用jquery对每一个格子生成了number-cell,如果board[i][j]不为0,则number-cell将覆盖grid-cell显示出来。 - generateOneNumber()函数是在随机一个空白格生成一个随机数。细节是如果只剩一个空白格,机器可能要试错很多次,使运行速度太慢,所以可以限制机器猜测的次数,如果超过50次还没有猜出来,就人为设定。
游戏循环部分
- 键盘事件监听:主要针对键盘上面的上下左右四个按键进行监听,当按键按下时,执行响应的函数,并且生成新的随机数,随后判断游戏界面中是否还存在空格,如果满了,是否可以进行移动,确定游戏是继续循环还是结束。下面以左按钮事件为例:
$(document).keydown(function(event){
event.preventDefault();
switch(event.keyCode){
case 37:
if(moveLeft()){
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);
}
break;
case 38: //up
if(moveUp()){
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);}
break;
case 39: //right
if(moveRight()){
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);}
break;
case 40: //down
if(moveDown()){
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);}
break;
default:
break;
}
});
function isgameover(){
if(nospace(board)&&nomove(board))
gameover();
}
function gameover(){
alert("game over!");
}
function moveLeft(){
if(!canMoveLeft(board)){
return false;
}
for(let i=0;i<4;i++){
for(let j=1;j<4;j++){
if(board[i][j]!==0){
for(let k=0;k<j;k++){
if(board[i][k]===0&&noBlockHorizontal(i,k,j,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,k,j,board)&&!hasConflicted[i][k]){
showMoveAnimation(i,j,i,k);
board[i][k]+=board[i][j];
board[i][j]=0;
score+=board[i][k];
updateScore(score);
hasConflicted[i][k]=true;
continue;
}
}
}
}
}
setTimeout("updateBoardView()",200);
return true;
}
按下左键时,首先判断是否能够左移:格子里是否存在空白格或者左边的格子和右边的格子相等;如果能够左移再进行左移操作。从第二列开始,(1). 当前格子是否为空且往左边移动的过程中没有别的数字挡着道路,成立的话,执行左移;(2). 当前格子与其左边格子的数字是否相同,且中间无别的内容,使得话,左移并且执行加法操作,合并数字。
左移结束后重新渲染页面。
触摸事件的监听
主要是针对移动端设计的。主要是通过touch事件完成,在touchstart中记录触摸的起点坐标event.touches[0].pageX和event.touches[0].pageY;在touchend中记录触摸结束点的坐标:event.changedTouches[0].pageX和event.changedTouches[0].pageY,然后进行运算,比较差值,得到deltaX和deltaY,可以分为以下四种情况:
//x
if((Math.abs(deltax))>=(Math.abs(deltay))){
if (deltax>0){
//move right
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);
}
else {
//move left;
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);
}
}
//y
else{
if (deltay>0){
//move down
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);
}
else {
//move up
setTimeout("generateOneNumber()",210);
setTimeout("isgameover()",300);
}
}
细节
1.游戏结束的判断,当四个方向都不能移动,且没有空格子时游戏结束
2.得分的处理,设置一个全局的变量score,在执行移动时,一旦发生相加操作,将相加的数值作为分数加入score中
3.动画的处理,通过jQuery的animate函数完成