HTML结构
<div class="all">
//头部提示部分
<p class="first_tr">SCORE:<span id="score01"> 0</span></p>
//中间游戏部分
<ul class="main">
<li class="cell" id="n00"></li>
<li class="cell" id="n01"></li>
<li class="cell" id="n02"></li>
<li class="cell" id="n03"></li>
<li class="cell" id="n10"></li>
<li class="cell" id="n11"></li>
<li class="cell" id="n12"></li>
<li class="cell" id="n13"></li>
<li class="cell" id="n20"></li>
<li class="cell" id="n21"></li>
<li class="cell" id="n22"></li>
<li class="cell" id="n23"></li>
<li class="cell" id="n30"></li>
<li class="cell" id="n31"></li>
<li class="cell" id="n32"></li>
<li class="cell" id="n33"></li>
</ul>
</div>
//游戏结束遮罩层部分
<div id="mark">
<p id="gameover">
GAME OVER!<br />
SCORE:<span id="score02"> 0</span><br />
<a href="javascript:game.gamestart()">Try Again!</a>
</p>
</div>
注意
2048游戏:
- 整体的布局,注意分块
- 对于命名,设置相同的class名,设置初始样式
- 设置不同的ID,由于在JS中采用二维数组的方式进行插入数据,所以,ID的命名具有二维数组的下标的标示性
- 命名不能以数字开头
CSS样式
body,ul,li,div,span,p{
margin:0px;
padding: 0px;
list-style: none;
font-family: Arial;
/*box-sizing: border-box;*/
}
.first_tr,.first_tr span{
font-weight: bold;
font-size: 40px;
}
.first_tr span{
color: red;
}
.all{
width: 500px;
height: 500px;
margin:40% auto;
}
.main{
width: 480px;
height: 480px;
background-color: #BBADA0;
border-radius: 5px;
text-align: center;
}
.main li{
float: left;
width:100px;
height:100px;
margin-left: 16px;
margin-top: 16px;
text-align: center;
line-height:100px;
border-radius: 5px;
}
//初始化样式
.cell{
color: white;
font-size: 40px;
background-color: #CDC1B4;
}
//数据发生改变的样式
.n2{
background-color:#eee3da;
color:#776e65;
}
.n4{
background-color:#ede0c8;
color:#776e65;
}
.n8{
background-color:#f2b179;
}
.n16{
background-color:#f59563;
}
.n32{
background-color:#f59563;
}
.n64{
background-color:#f65e3b;
}
.n128{
background-color:#edcf72;
}
.n256{
background-color:#edcc61;
}
.n512{
background-color:#9c0;
}
.n1024{
background-color:#33b5e5;
font-size:40px;
}
.n2048{
background-color:#09c;
font-size:40px;
}
.n4096{
background-color:#a6c;
font-size:40px;
}
.n8192{
background-color:#93c;
font-size:40px;
}
//遮罩层样式
#mark{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
display: none;
}
#gameover{
width: 300px;
height: 200px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -150px;
margin-top: -100px;
box-shadow: 0 0 10px rgba(0,0,0,0.025);
border-radius: 10px;
background-color: white;
font-size: 25px;
font-weight: bold;
text-align: center;
line-height: 60px;
}
#gameover>span{
color: red;
}
#gameover>a{
color: white;
text-decoration: none;
display: inline-block;
width: 150px;
height: 35px;
background-color:#9F8D77;
line-height: 35px;
border-radius: 5px;
}
注意
- 将初始化样式和其他的样式分开
- 将每个不同数据的不同样式分开写,命名的方式同样要有规律
- 采用class样式的命名,最后附样式只需根据数据来更换
JS行为
过程
-
创建一个game对象,创建game的属性:游戏状态,游戏开始方法,游戏结束方法,赋值样式方法,随机产生2或者4的数据的方法,消除相加数据方法,数据移动方法,记录分数方法
-
开始game方法:初始化数据,随机产生两个数据,初始化分数
(1)创建二维数组,将数据初始化为0,
(2)开始游戏将分数清0 ,游戏状态初始化为游戏开始
(3)执行两次产生随机数2或者4
(4)执行样式方法的函数
gamestart: function() {
//创建二维数组,初始化数据为0
this.score = 0; //游戏开始,分数清零
this.data = [];
this.status = 1;
for(var r = 0; r < 4; r++) {
this.data[r] = [];
for(var c = 0; c < 4; c++) {
this.data[r][c] = 0;
}
}
this.randomNum();
this.randomNum();
this.boxStyle();
console.log(this.data);
},
- 界面样式的改变
(1)遍历数据数组,根据产生的数据,同步到页面
(2)同时将相应数据的样式同步到页面
【之前在css和html上的命名可以在此时直接做相应的拼接,利于赋className】
(3)根据规则将页面上的分数做相应的赋值
(4)页面视图中,游戏结束时遮罩层的显示,及最总分数的显示
boxStyle: function() {
//页面视图
for(var r = 0; r < 4; r++) {
for(var c = 0; c < 4; c++) {
//添加不同数据的样式
var li = document.getElementById("n" + r + c);
if(this.data[r][c] != 0) {
li.innerHTML = this.data[r][c];
li.className = "cell n" + this.data[r][c];
} else {
li.innerHTML = "";
li.className = "cell";
}
}
}
document.getElementById("score01").innerHTML = this.score; //游戏过程中分数的变化
//判断游戏是否结束
if(this.status != 1) {
document.getElementById("mark").style.display = "block";
document.getElementById("score02").innerHTML = this.score; //游戏结束的分数
} else {
document.getElementById("mark").style.display = "none";
}
},
- 游戏结束function
(1)首先遍历整个数组,根据游戏规则,判断游戏是否结束
(2)若不满足游戏结束规则,则,返回false,反之,true
(3)返回值在之后的函数中做判断,因此,此时让游戏是否结束的返回值为boolean,也可以在此时改变游戏的状态 status ,将游戏状态改变为结束游戏的状态,在之后的判断中,判断游戏的状态,也可
【在游戏开始前,重置了游戏的状态,为开始状态】
(4) 游戏的结束规则:当游戏的所有格子填满,且上下左右都没有相同的数据。
isgameOver: function() {
//游戏结束
for(var r = 0; r < 4; r++) {
for(var c = 0; c < 4; c++) {
//游戏结束标准,所有格子填满,且上下左右的没有相同的数据
if(this.data[r][c] == 0) {
return false;
}
if(c < 3) {
//两两相邻比较 c c+1
if(this.data[r][c] == this.data[r][c + 1]) {
return false;
}
}
if(r < 3) {
if(this.data[r][c] == this.data[r + 1][c]) {
return false;
}
}
}
}
return true;
},
- 界面数据的变化
【向左移动代码】
(1)首先对数据做一个拍照的记录,在之后判断是否移动或者变化
【String()操作,将数组进行拍照动作,记录当前数组内的元素的位置,数据的大小,转换为字符串类型。数组为引用类型,不能进行==的比较操作,而原始类型可以】
(2)左右的移动,是在列内的操作,因此每一行可有相同的列的操作
(3)在对列的操作后,记录此时的数据,在进行比较。
(4)若发生了改变,则按照规则产生一个随机的数据,并且赋予相应的样式
(5)同时也要进行游戏是否结束的判断,每一次移动都要进行是否结束的判断。
moveLeft: function(){
//先对数据做拍照,判断是否移动,引用类型的数据无法用 ==进行判断
var before = String(this.data);
//向左移动 是列的移动 每一行中的列的行为 且 第一列不动,
for(var r = 0; r < 4; r++) {
this.moveleft_col(r)
}
var after = String(this.data);
if(before != after) {
//移动了 生成一个随机数
this.randomNum();
console.log(1);
//首先判断是否结束游戏
if(this.isgameOver()) {
//isgameOver返回值为true 和 false
this.status = this.gameover;
}
this.boxStyle();
}
},
- 数据的移动—交换位置
(1)首先对列的数据的遍历,进行左右的比较判断,判断除了第一个数据之后的数据若不为空(不为零),则进行下一步的判断,若都为零,则跳出此行的循环遍历
(2)若有不为零的数据,则判断列中的第一个数据是否为零,若为零,则交换位置,反之比较是否相同,相同则相加(或者乘以2),并且,将后面的数据清0
(3)若不相同,则不交换位置
(4)c 的开始为0,结束为2,比较的数据是前一个c,同c+1相比较。所以,列的遍历,从0,开始,到倒数第二位结束。
(5)左右的移动,区别的对列的遍历,一个正序,一个倒序,比较的第一个数据,一个是第一位,一个是最后一位
moveleft_col:function(r){
for(var c = 0; c < 3; c++) {
var i = this.getNEXTinRow(r,c);
if(i != -1){
//如果一行中除了第一个之外有不为0的数
if(this.data[r][c] == 0) {
//如果左侧有为0 的则,交换位置
this.data[r][c] = this.data[r][i];
this.data[r][i] = 0;
c--;
} //如果相邻有相同的数据 则相加
else if(this.data[r][c] == this.data[r][i]) {
this.data[r][c] *= 2;
this.data[r][i] = 0;
this.score += this.data[r][c];
//这里相邻有不相同的也不做移动,不跳出 ,执行下一次的列循环
}
} else {
//如果一行中都是0 跳出列循环,执行下一行循环 不移动
break;
}
}
},
getNEXTinRow:function(r,c){
for(var i = c+1;i<4;i++){
if(this.data[r][i] != 0){
return i;
}
}
return -1;
},
- 上下的数据移动和变化同左右的移动是相同的,只是作用的是在每一行上,每一列重复动作
【 向上移动代码】
moveTop: function(){
//先对数据做拍照,判断是否移动,引用类型的数据无法用 ==进行判断
var before = String(this.data);
for(var c = 0; c< 4; c++) {
this.movetop_col(c)
}
var after = String(this.data);
if(before != after) {
//移动了 生成一个随机数
this.randomNum();
//首先判断是否结束游戏
if(this.isgameOver()) {
//isgameOver返回值为true 和 false
this.status = this.gameover;
}
this.boxStyle();
}
},
movetop_col:function(c){
for(var r = 0; r < 3; r++) {
var i = this.getNEXTinRow_top(r,c);
if(i != -1){
if(this.data[r][c] == 0) {
this.data[r][c] = this.data[i][c];
this.data[i][c] = 0;
r--;
}
else if(this.data[r][c] == this.data[i][c]) {
this.data[r][c] *= 2;
this.data[i][c] = 0;
this.score += this.data[r][c];
}
} else {
break;
}
}
},
getNEXTinRow_top:function(r,c){
for(var i = r+1;i < 4;i ++){
if(this.data[i][c] != 0){
return i;
}
}
return -1;
},
- 键盘事件
(1)键盘事件 —onkeydown属性
(2)event event代表事件的状态,例如触发event对象的元素、鼠标的位置及状态、按下的键等等,event对象只在事件发生的过程中才有效。
(3)keycode是在控制台查询上下左右键对应的Unicode码,相应的键触发相应的事件
【左:37;上:38;右:38;下:39】
document.onkeydown = function(event) {
//做兼容性处理
var event = event || e || arguments[0];
if(event.keyCode == 37) {
game.moveLeft();
}else if(event.keyCode == 39){
game.moveRight();
}else if(event.keyCode == 38){
game.moveTop();
}else if(event.keyCode == 40){
game.moveBottom();
}
}
- 移动端的触屏事件
(1)触屏事件—touchstart 手指按下,touchend手指抬起 ,touchmove 手指移动属性
(2)使用监听事件来调用触屏事件—addEventListener(“绑定的事件”,执行的动作function)
(3)触屏事件中,查询触屏开始的坐标,和结束的坐标,判断是左滑,还是右滑,上滑还是下滑
(4)判断滑动,要注意,滑的过程不是直线,倾斜的滑动,通过比较水平滑动的距离,和垂直滑动的距离,来控制上下滑动,还是左右滑动。
//移动版的触屏事件
var startX ,startY,endX , endY;
document.addEventListener('touchstart',function(event){
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
})
document.addEventListener('touchend',function(event){
endX = event.changedTouches[0].pageX;
endY = event.changedTouches[0].pageY;
var x = startX - endX;
var y = endX - endY;
var ab_x = Math.abs(x) > Math.abs(y)//斜划,判断x轴 y轴 滑动的距离,
if(x > 0 && ab_x){
game.moveLeft();
}else if(x < 0 && ab_x > 0){
game.moveRight();
}else if(y > 0 && !ab_x){
game.moveTop();
}else if(y < 0 && !ab_x){
game.moveBottom();
}
})
js所有代码
var game = {
data: [],
gameover: 0,
gamerunning: 1,
status: 1, //游戏状态
score: 0, //游戏分数
gamestart: function() {
//创建二维数组,初始化数据为0
this.score = 0; //游戏开始,分数清零
this.data = [];
this.status = 1;
for(var r = 0; r < 4; r++) {
this.data[r] = [];
for(var c = 0; c < 4; c++) {
this.data[r][c] = 0;
}
}
this.randomNum();
this.randomNum();
this.boxStyle();
console.log(this.data);
},
randomNum: function() {
//随机数
for(;;) {
//赋值
var r = Math.floor(Math.random() * 4);
var c = Math.floor(Math.random() * 4);
if(this.data[r][c] == 0) {
var num = Math.random() > 0.5 ? 2 : 4;
this.data[r][c] = num;
break;
}
}
},
boxStyle: function() {
//页面视图
for(var r = 0; r < 4; r++) {
for(var c = 0; c < 4; c++) {
//添加不同数据的样式
var li = document.getElementById("n" + r + c);
if(this.data[r][c] != 0) {
li.innerHTML = this.data[r][c];
li.className = "cell n" + this.data[r][c];
} else {
li.innerHTML = "";
li.className = "cell";
}
}
}
document.getElementById("score01").innerHTML = this.score; //游戏过程中分数的变化
//判断游戏是否结束
if(this.status != 1) {
document.getElementById("mark").style.display = "block";
document.getElementById("score02").innerHTML = this.score; //游戏结束的分数
} else {
document.getElementById("mark").style.display = "none";
}
},
isgameOver: function() {
//游戏结束
for(var r = 0; r < 4; r++) {
for(var c = 0; c < 4; c++) {
//游戏结束标准,所有格子填满,且上下左右的没有相同的数据
if(this.data[r][c] == 0) {
return false;
}
if(c < 3) {
//两两相邻比较 c c+1
if(this.data[r][c] == this.data[r][c + 1]) {
return false;
}
}
if(r < 3) {
if(this.data[r][c] == this.data[r + 1][c]) {
return false;
}
}
}
}
return true;
},
moveLeft: function(){
//先对数据做拍照,判断是否移动,引用类型的数据无法用 ==进行判断
var before = String(this.data);
//向左移动 是列的移动 每一行中的列的行为 且 第一列不动,
for(var r = 0; r < 4; r++) {
this.moveleft_col(r)
}
var after = String(this.data);
if(before != after) {
//移动了 生成一个随机数
this.randomNum();
console.log(1);
//首先判断是否结束游戏
if(this.isgameOver()) {
//isgameOver返回值为true 和 false
this.status = this.gameover;
}
this.boxStyle();
}
},
moveleft_col:function(r){
for(var c = 0; c < 3; c++) {
var i = this.getNEXTinRow(r,c);
if(i != -1){
//如果一行中除了第一个之外有不为0的数
if(this.data[r][c] == 0) {
//如果左侧有为0 的则,交换位置
this.data[r][c] = this.data[r][i];
this.data[r][i] = 0;
c--;
} //如果相邻有相同的数据 则相加
else if(this.data[r][c] == this.data[r][i]) {
this.data[r][c] *= 2;
this.data[r][i] = 0;
this.score += this.data[r][c];
//这里相邻有不相同的也不做移动,不跳出 ,执行下一次的列循环
}
} else {
//如果一行中都是0 跳出列循环,执行下一行循环 不移动
break;
}
}
},
getNEXTinRow:function(r,c){
for(var i = c+1;i<4;i++){
if(this.data[r][i] != 0){
return i;
}
}
return -1;
},
moveRight: function(){
//先对数据做拍照,判断是否移动,引用类型的数据无法用 ==进行判断
var before = String(this.data);
for(var r = 0; r < 4; r++) {
this.moveright_col(r)
}
var after = String(this.data);
if(before != after) {
//移动了 生成一个随机数
this.randomNum();
//首先判断是否结束游戏
if(this.isgameOver()) {
//isgameOver返回值为true 和 false
this.status = this.gameover;
}
this.boxStyle();
}
},
moveright_col:function(r){
for(var c = 3; c > 0; c--) {
var i = this.getNEXTinRow_right(r,c);
if(i != -1){
if(this.data[r][c] == 0) {
this.data[r][c] = this.data[r][i];
this.data[r][i] = 0;
c++;
} //如果相邻有相同的数据 则相加
else if(this.data[r][c] == this.data[r][i]) {
this.data[r][c] *= 2;
this.data[r][i] = 0;
this.score += this.data[r][c];
//这里相邻有不相同的也不做移动,不跳出 ,执行下一次的列循环
}
} else {
break;
}
}
},
getNEXTinRow_right:function(r,c){
for(var i = c-1;i>=0;i--){
if(this.data[r][i] != 0){
return i;
}
}
return -1;
},
moveTop: function(){
//先对数据做拍照,判断是否移动,引用类型的数据无法用 ==进行判断
var before = String(this.data);
for(var c = 0; c< 4; c++) {
this.movetop_col(c)
}
var after = String(this.data);
if(before != after) {
//移动了 生成一个随机数
this.randomNum();
//首先判断是否结束游戏
if(this.isgameOver()) {
//isgameOver返回值为true 和 false
this.status = this.gameover;
}
this.boxStyle();
}
},
movetop_col:function(c){
for(var r = 0; r < 3; r++) {
var i = this.getNEXTinRow_top(r,c);
if(i != -1){
if(this.data[r][c] == 0) {
this.data[r][c] = this.data[i][c];
this.data[i][c] = 0;
r--;
}
else if(this.data[r][c] == this.data[i][c]) {
this.data[r][c] *= 2;
this.data[i][c] = 0;
this.score += this.data[r][c];
}
} else {
break;
}
}
},
getNEXTinRow_top:function(r,c){
for(var i = r+1;i < 4;i ++){
if(this.data[i][c] != 0){
return i;
}
}
return -1;
},
moveBottom: function(){
//先对数据做拍照,判断是否移动,引用类型的数据无法用 ==进行判断
var before = String(this.data);
for(var c = 0; c < 4; c++) {
this.movebottom_col(c)
}
var after = String(this.data);
if(before != after) {
//移动了 生成一个随机数
this.randomNum();
//首先判断是否结束游戏
if(this.isgameOver()) {
//isgameOver返回值为true 和 false
this.status = this.gameover;
}
this.boxStyle();
}
},
movebottom_col:function(c){
for(var r = 3; r > 0; r--) {
var i = this.getNEXTinRow_bottom(r,c);
if(i != -1){
if(this.data[r][c] == 0) {
this.data[r][c] = this.data[i][c];
this.data[i][c] = 0;
r++;
}
else if(this.data[r][c] == this.data[i][c]) {
this.data[r][c] *= 2;
this.data[i][c] = 0;
this.score += this.data[r][c];
}
} else {
break;
}
}
},
getNEXTinRow_bottom:function(r,c){
for(var i = r-1;i >= 0;i--){
if(this.data[i][c] != 0){
return i;
}
}
return -1;
},
}
//键盘事件 onkeydown
document.onkeydown = function(event) {
//做兼容性处理
var event = event || e || arguments[0];
if(event.keyCode == 37) {
game.moveLeft();
}else if(event.keyCode == 39){
game.moveRight();
}else if(event.keyCode == 38){
game.moveTop();
}else if(event.keyCode == 40){
game.moveBottom();
}
}
//监听事件
//移动版的触屏事件
var startX ,startY,endX , endY;
document.addEventListener('touchstart',function(event){
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
})
document.addEventListener('touchend',function(event){
endX = event.changedTouches[0].pageX;
endY = event.changedTouches[0].pageY;
var x = startX - endX;
var y = endX - endY;
var ab_x = Math.abs(x) > Math.abs(y)//斜划,判断x轴 y轴 滑动的距离,
if(x > 0 && ab_x){
game.moveLeft();
}else if(x < 0 && ab_x > 0){
game.moveRight();
}else if(y > 0 && !ab_x){
game.moveTop();
}else if(y < 0 && !ab_x){
game.moveBottom();
}
})
game.gamestart()