2048这个游戏是通过对二维数组的操作来实现的,其算法核心如下:

  (以一行左移为例)

    c从0开始,遍历当前行中的元素,到<CN-1(CN是一个常量,表示的是游戏格子的列数)结束,每次+1

    找到当前位置下一个不为0的位置

    如果没找到

      直接退出循环

    否则

      如果当前值等于0

        将下一位置的值与当前位置的值交换

        将下一位置设为0

        将c-1(为了让下次循环依然检查当前位置)

      否则,如果当前值等于下一个值

        将当前值 * 2

        下一位置值设为0

具体代码如下

 <!-- 2048.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2048</title>
<link rel="stylesheet" href="2048.css">
<script src="2048.js"></script>
</head>
<body>
<!-- 分数 -->
<p>Score:<span id="score">0</span></p> <!-- 实现游戏的网格 -->
<div id="gridPanel">
</div> <div id="gameOver"><!--同时包含前景和背景的容器-->
<div><!--半透明灰色背景--></div>
<p><!--居中小窗口-->
Game Over!<br>
Score:<span id="finalScore"></span><br>
<a class="button" onclick="game.start()">Try again!</a>
</p>
</div>
</body>
</html>
 /*2048.css*/
@charset "utf-8"; #gridPanel{
width: 480px;
height: 480px;
margin: 0 auto;
background-color: #bbada0;
border-radius: 10px;
position: relative;
}
.grid, .cell{
width: 100px;
height: 100px;
border-radius: 6px;
}
.grid{
background-color: #ccc0b3;
float: left;
margin-top: 16px;
margin-left: 16px;
}
.cell{
text-align: center;
line-height: 100px;
color: #fff;
font-size: 60px;
position: absolute;
} /*由于想要更改游戏的网格数,设置了最大限制为8*8,所以此处前景格和背景格的样式如下*/
/*此功能的实现应该还有更简便的方法,能力有限,目前学的知识只能做到这样*/
#c00, #c01, #c02, #c03, #c04, #c05, #c06, #c07{top: 16px;}
#c10, #c11, #c12, #c13, #c14, #c15, #c16, #c17{top: 132px;}
#c20, #c21, #c22, #c23, #c24, #c25, #c26, #c27{top: 248px;}
#c30, #c31, #c32, #c33, #c34, #c35, #c36, #c37{top: 364px;}
#c40, #c41, #c42, #c43, #c44, #c45, #c46, #c47{top: 480px;}
#c50, #c51, #c52, #c53, #c54, #c55, #c56, #c57{top: 596px;}
#c60, #c61, #c62, #c63, #c64, #c65, #c66, #c67{top: 712px;}
#c70, #c71, #c72, #c73, #c74, #c75, #c76, #c77{top: 828px;} #c00, #c10, #c20, #c30, #c40, #c50, #c60, #c70{left: 16px;}
#c01, #c11, #c21, #c31, #c41, #c51, #c61, #c71{left: 132px;}
#c02, #c12, #c22, #c32, #c42, #c52, #c62, #c72{left: 248px;}
#c03, #c13, #c23, #c33, #c43, #c53, #c63, #c73{left: 364px;}
#c04, #c14, #c24, #c34, #c44, #c54, #c64, #c74{left: 480px;}
#c05, #c15, #c25, #c35, #c45, #c55, #c65, #c75{left: 596px;}
#c06, #c16, #c26, #c36, #c46, #c56, #c66, #c76{left: 712px;}
#c07, #c17, #c27, #c37, #c47, #c57, #c67, #c77{left: 828px;} .n2{background-color: #eee3da;}
.n4{background-color: #ede0c8;}
.n8{background-color: #f2b179;}
.n16{background-color: #f59563;}
.n32{background-color: #f67c5f;}
.n64{background-color: #f65e3d;}
.n128{background-color: #edcf72;}
.n256{background-color: #edcc61;}
.n512{background-color: #9c0;}
.n1024{background-color: #33b5e5;}
.n2048{background-color: #09c;}
.n4096{background-color: #a6c;}
.n8192{background-color: #93c;} .n2, .n4{color: #776e65;}
.n1024, .n2048, .n4096, .n8192{font-size: 40px;} /*显示分数*/
p{
width: 480px;
margin: 0 auto;
font-family: Arial;
font-weight: bold;
font-size: 40px;
padding-top: 50px;
} /* 游戏结束 */
/*Game Over*/
#gameOver{
width:100%;
height:100%;
position:absolute;
top:;
left:;
display:none;
}
#gameOver>div{
width:100%;
height:100%;
background-color:#555;
opacity:0.5;
}
#gameOver>p{
width:300px;
height:200px;
border:1px solid #edcf72;
line-height:1.6em;
text-align:center;
background-color:#fff;
border-radius:10px;
position:absolute;
top:50%;
left:50%;
margin-top:-100px;
margin-left:-150px;
}
.button{
padding:10px;
border-radius:6px;
background-color:#9f8b77;
color:#fff;
cursor:pointer;
}
 /*2048.js*/
var game = {
data : [], //存储所有单元格数据,二维数组
RN : 4, //总行数,可在此处改变,最大为8
CN : 4, //总列数,可在此处改变,最大为8
score : 0, //保存分数
state: 0, //游戏当前状态:Running|GameOver
RUNNING : 1, //运行中
GAMEOVER : 0, //游戏结束
PLAYING : 2; //动画播放中 //获得所有背景格的html代码
getGridHTML : function(){
for(var r = 0, arr = []; r < this.RN; r++){
for(var c = 0; c < this.CN; c++){
arr.push("" + r + c);
}
}
return '<div id="g' + arr.join('" class="grid"></div><div id="g') + '" class="grid"></div>';
},
//获得所有前景格的html代码
getCellHTML : function(){
for(var r = 0, arr = []; r < this.RN; r++){
for(var c = 0; c < this.CN; c++){
arr.push("" + r + c);
}
}
return '<div id="c' + arr.join('" class="cell"></div><div id="c') + '" class="cell"></div>';
},
//判断游戏状态为结束
isGameOver:function(){
//如果没有满,则返回false
if(!this.isFull()){
return false;
}else{//否则
//从左上角第一个元素开始,遍历二维数组
for(var r = 0; r < this.RN; r++){
for(var c = 0; c < this.CN; c++){
//如果当前元素不是最右侧元素
if(c < this.CN-1){
// 如果当前元素==右侧元素
if(this.data[r][c] == this.data[r][c + 1]){
return false;
}
}
//如果当前元素不是最下方元素
if(r < this.RN - 1){
// 如果当前元素==下方元素
if(this.data[r][c] == this.data[r + 1][c]){
return false;
}
}
}
}
return true;
}
},
//开始游戏
start : function(){
var panel = document.getElementById('gridPanel');
//游戏开始获得网格布局
panel.innerHTML = this.getGridHTML() + this.getCellHTML();
//将panel的高度设置为RN*116+16+"px"
panel.style.height = this.RN * 116 + 16 + 'px';
//将panel的宽度设置为CN*116+16+"px"
panel.style.width = this.CN * 116 + 16 + 'px'; this.data = []; //清空旧数组
for(var r = 0; r < this.RN; r++){ //r从0开始,到<RN结束,每次+1
this.data.push([]); //在data中压入一个空数组
for(var c = 0; c < this.CN; c++){ //c从0开始,到<CN结束,每次+1
this.data[r].push(0); //向data中r行,压入一个0
}
} this.state = this.RUNNING; //设置游戏状态
this.score = 0; //分数重置为0
//找到游戏结束界面,隐藏
var div = document.getElementById("gameOver");
div.style.display = "none"; this.randomNum();
this.randomNum();
this.updateView();
},
//初始界面生成两个随机数
randomNum : function(){ //在随机的不重复的位置生成一个2或4
if(!this.isFull()){ //只有不满时,才尝试生成随机数
for(;;){
var r = Math.floor(Math.random() * this.RN); //在0~RN-1之间生成一个行下标,存在r中
var c = Math.floor(Math.random() * this.CN); //在0~CN-1之间生成一个列下标,存在c中
/*
如果data中r行c列等于0
生成一个0~1之间的随机数
如果随机数>0.5,就在r行c列放入4
否则放入2
*/
if (this.data[r][c] == 0) {
this.data[r][c] = Math.random() > 0.5 ? 4 : 2;
break;// 退出循环
}
}
}
},
//将data数组中每个元素更新到页面div
updateView : function(){
//遍历data中每个元素的值
for(var r = 0; r < this.RN; r++){
for(var c = 0; c < this.CN; c++){
//找到页面上和当前位置对相应的div
var divObj = document.getElementById("c" + r + c);
if (this.data[r][c] == 0) { //如果当前值为0
divObj.innerHTML = ""; //清除innerHTML
divObj.className = "cell"; //还原className为"cell"
}else{
divObj.innerHTML = this.data[r][c]; //否则,将当前值放入innerHTML
divObj.className = "cell n" + this.data[r][c]; //修改className为"cell n" + 当前值
}
}
}
var span = document.getElementById("score");
span.innerHTML = this.score;
//判断并修改游戏状态为GAMEOVER
if(this.isGameOver()){
this.state = this.GAMEOVER;
var div = document.getElementById("gameOver");
var span = document.getElementById("finalScore");
span.innerHTML = this.score;
div.style.display = "block"; //修改div的style属性下的display子属性为"block"
}
},
//判断是否满格
isFull : function(){
for (var r = 0; r < this.RN; r++) {
for (var c = 0; c < this.CN; c++) {
if (this.data[r][c] == 0) { //如果当前元素等于0
return false; //返回false
}
}
}
return true; //遍历结束,返回true
},
//左移所有行
moveLeft : function(){
var before = this.data.toString();
for (var r = 0; r < this.RN; r++) { //遍历data中的每一行
this.moveLeftInRow(r); //左移当前行
}
var after = this.data.toString();
if(before != after){
animation.state();
// this.randomNum();
// this.updateView();
}
},
//左移一行,传入要移动的行号
moveLeftInRow : function(r){
//c从0开始,遍历当前行中的元素,到<CN-1结束,每次+1
for (var c = 0; c < this.CN-1; c++) {
//找到c之后下一个不为0的值的位置,存在next中
var nextc = this.getNextInRow(r,c);
if(nextc == -1){
break; //如果nextc等于-1,退出循环
}else{ //否则
if(this.data[r][c] == 0){ //如果当前位置等于0
this.data[r][c] = this.data[r][nextc]; //将当前位置设为下一个位置的值
this.data[r][nextc] = 0; //将下一位置设为0
var div = document.getElementById("c" + r + nextc);
animation.addTask(div,r,nextc,r,c);
c--; //保证下次依然检查当前元素
}else if(this.data[r][c] == this.data[r][nextc]){ //否则,如果当前位置等于下一位置
this.data[r][c] *= 2; //当前位置 = 当前位置值*2
this.score += this.data[r][c]; //增加分数
this.data[r][nextc] = 0; //将下一位置设为0
var div = document.getElementById("c" + r + nextc);
animation.addTask(div,r,nextc,r,c);
}
}
}
},
//找r行c列位置之后,不为0的下一个位置
getNextInRow : function(r,c){
for(var nextc = c + 1; nextc < this.CN; nextc++){ //nextc从c+1开始,遍历r行剩余元素
if(this.data[r][nextc] != 0){ //如果nextc不等于0
return nextc;
}
}
return -1; //循环结束,返回-1
},
//右移所有行
moveRight : function(){
var before = this.data.toString();
for (var r = 0; r < this.RN; r++) { //遍历data中的每一行
this.moveRightInRow(r); //右移当前行
}
var after = this.data.toString();
if(before != after){
animation.state();
}
},
//右移一行,传入要移动的行号
moveRightInRow : function(r){
//c从CN-1开始,到>0结束,每次-1
for (var c = this.CN-1; c > 0 ; c--) {
//找到c之后下一个不为0的值的位置,存在next中
var prevc = this.getPrevInRow(r,c);
if(prevc == -1){
break; //如果prevc等于-1,退出循环
}else{ //否则
if(this.data[r][c] == 0){ //如果当前位置等于0
this.data[r][c] = this.data[r][prevc]; //将当前位置设为下一个位置的值
this.data[r][prevc] = 0; //将下一位置设为0
var div = document.getElementById("c" + r + prevc);
animation.addTask(div, r, prevc, r, c);
c++; //保证下次依然检查当前元素
}else if(this.data[r][c] == this.data[r][prevc]){ //否则,如果当前位置等于下一位置
this.data[r][c] *= 2; //当前位置 = 当前位置值*2
this.score += this.data[r][c]; //增加分数
this.data[r][prevc] = 0; //将下一位置设为0
var div = document.getElementById("c"+r+prevc);
animation.addTask(div,r,prevc,r,c);
}
}
}
},
//找r行c列位置之后,不为0的下一个位置
getPrevInRow : function(r,c){
for(var prevc = c - 1; prevc >= 0; prevc--){ //prevc从c+1开始,遍历r行剩余元素
if(this.data[r][prevc] != 0){ //如果prevc不等于0
return prevc;
}
}
return -1; //循环结束,返回-1
},
//上移所有行
moveUp : function(){
var before = this.data.toString();
for (var c = 0; c < this.CN; c++) { //遍历data中的每一列
this.moveUpInCol(c); //右移当前行
}
var after = this.data.toString();
if(before != after){
animation.start();
}
},
//上移一列,传入要移动的列号
moveUpInCol : function(c){
//r从0开始,遍历当前列中的元素,到<RN-1结束,每次+1
for (var r = 0; r < this.RN-1 ; r++) {
//找到c之后下一个不为0的值的位置,存在next中
var nextr = this.getNextInCol(r,c);
if(nextr == -1){
break; //如果nextr等于-1,退出循环
}else{ //否则
if(this.data[r][c] == 0){ //如果当前位置等于0
this.data[r][c] = this.data[nextr][c]; //将当前位置设为下一个位置的值
this.data[nextr][c] = 0; //将下一位置设为0
var div = document.getElementById("c"+ nextr + c);
animation.addTask(div,nextr,c,r,c);
r--; //保证下次依然检查当前元素
}else if(this.data[r][c] == this.data[nextr][c]){ //否则,如果当前位置等于下一位置
this.data[r][c] *= 2; //当前位置 = 当前位置值*2
this.score += this.data[r][c]; //增加分数
this.data[nextr][c] = 0; //将下一位置设为0
var div = document.getElementById("c"+ nextr + c);
animation.addTask(div,nextr,c,r,c);
}
}
}
},
//找r行c列位置之后,不为0的下一个位置
getNextInCol : function(r,c){
for(var nextr = r + 1; nextr < this.RN; nextr++){ //nextr从c+1开始,遍历c列剩余元素
if(this.data[nextr][c] != 0){ //如果nextr不等于0
return nextr;
}
}
return -1; //循环结束,返回-1
},
//下移所有行
moveDown : function(){
var before = this.data.toString();
for (var c = 0; c < this.CN; c++) { //遍历data中的每一列
this.moveDownInCol(c); //右移当前行
}
var after = this.data.toString();
if(before != after){
animation.start();
}
},
//下移一列,传入要移动的列号
moveDownInCol : function(c){
//r从RN-1开始,遍历当前列中的元素,到>0结束,每次-1
for (var r = this.RN-1; r > 0 ; r--) {
//找到c之后下一个不为0的值的位置,存在next中
var prevr = this.getPrevInCol(r,c);
if(prevr == -1){
break; //如果prevr等于-1,退出循环
}else{ //否则
if(this.data[r][c] == 0){ //如果当前位置等于0
this.data[r][c] = this.data[prevr][c]; //将当前位置设为下一个位置的值
this.data[prevr][c] = 0; //将下一位置设为0
var div = document.getElementById("c" + prevr + c);
animation.addTask(div,prevr,c,r,c);
r++; //保证下次依然检查当前元素
}else if(this.data[r][c] == this.data[prevr][c]){ //否则,如果当前位置等于下一位置
this.data[r][c] *= 2; //当前位置 = 当前位置值*2
this.score += this.data[r][c]; //增加分数
this.data[prevr][c] = 0; //将下一位置设为0
var div = document.getElementById("c" + prevr + c);
animation.addTask(div,prevr,c,r,c);
}
}
}
},
//找r行c列位置之后,不为0的下一个位置
getPrevInCol : function(r,c){
for(var prevr = r - 1; prevr >= 0; prevr--){ //prevr从r-1开始,遍历c列剩余元素
if(this.data[prevr][c] != 0){ //如果prevr不等于0
return prevr;
}
}
return -1; //循环结束,返回-1
}
};
//当窗口加载后
window.onload = function(){
game.start();
/*键盘事件绑定*/
document.onkeydown = function(){
if(game.state == game.running){
var e = window.event || arguments[0];
var code = e.keyCode;
//如果按的是向左箭头,就调用左移方法
//左37 上38 右39 下40
if(code == 37){
game.moveLeft();
}else if(code == 39){
game.moveRight();
}else if(code == 38){
game.moveUp();
}else if(code == 40){
game.moveDown();
}
}
}
}
 
 /*animation.js*/
/*实现上下左右移动时的动画*/ var animation = fucntion(){
DURE : 500; //总时间
STEPS : 50; //总步数
moved : 0; //当前移动步数
timer : null; //保存当前计时器的序号
tasks : []; //放入每次任务需要移动的所有元素和距离 //像tasks数组中增加任务对象
addTask : function(divObj,currC,tarR,tarC){
var topDist = (tarR - currR) * 116;
var leftDist = (tarC - currC) * 116;
var topStep = topDist / this.STEPS;
var leftStep = leftDist / this.STEPS;
this.tasks.push(
{obj:divObj,top:topStep,left:leftStep}
);
},
move : function(){
for (var i = 0; i < this.tasks.length; i++) {
var task = this.tasks[i];
var style = getComputedStyle(task.obj);
task.obj.style.top = parseFloat(style.top) + task.top + "px";
task.obj.style.left = parseFloat(style.left) + task.left + "px";
}
if (--this.moved == 0) {
clearInterval(this.timer);
for (var i = 0; i < this.tasks.length; i++) {
var task = this.tasks[i];
task.obj.style.top = "";
task.obj.style.left = "";
}
this.tasks = [];
game.randomNum();
game.state = game.RUNNING;
game.updateView();
}
},
start : function(){
game.state = game.PLAYING;
var self = this;
self.moved = self.STEPS;
self.timer = setInterval(function(){
self.move();
},self.DURE / self.STEPS);
}
};

最新文章

  1. wex5平台放入tabs组件后运行时显示空白
  2. [转]Jenkins使用 管理节点
  3. 关于HTTP session随笔
  4. 关于【error C3646: 未知重写说明符】的若干种可能性
  5. PHP创建缩略图造成图片质量低下的完美解决方法
  6. c 语言 结构体
  7. 【Spring】Spring IOC原理及源码解析之scope=request、session
  8. xls 和 xml 数据 排序 绑定 -原创
  9. Xamarin.Forms本地化多语言
  10. 剑指offer-面试题16.反转链表
  11. java大数处理
  12. nginx安装文档
  13. 用ubuntu的grpb2引导Remix OS
  14. java--uploadify3.1多文件上传
  15. (转)Spring Boot 2(一):【重磅】Spring Boot 2.0权威发布
  16. 全文检索:haystack+elasticsearch
  17. C++ vector 使用笔记
  18. tomat修改启动路径
  19. JAVA与C++,C与C++的差别
  20. android--------自定义视频控件(视频全屏竖屏自动切换)

热门文章

  1. 写一个c程序辨别系统是大端or小端字节序
  2. 杭电acm 1032题
  3. ps -ef | grep java 输出的具体含义是什么?
  4. ES Docs-2:Exploring ES cluster
  5. java多线程系列:ThreadPoolExecutor源码分析
  6. Dedecms本地上传缩略图无法自动添加水印的解决方法
  7. 面向对象OO第三单元总结
  8. 洛谷P1057 传球游戏(记忆化搜索)
  9. vue 中的.sync语法糖
  10. sass(scss)10大常用重要特性