2048小游戏项目总结
最近有在玩2048,所以写了个2048的代码
话不多说,先上截图和代码
游戏截图
HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, width=device-width, maximum-scale=1, minimum-scale=1.0, user-scalable=no">
<title>2048小游戏</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
<script src="js/2048.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div class="score">
<div class="current-score">
<strong>当前得分:</strong>
<span id="current-score">0</span>
</div>
<div class="most-score">
<strong>最高得分:</strong>
<span id="most-score">0</span>
</div>
</div>
<div class="contain">
<div class="line-1">
<div id="cd-11" class="n0"></div>
<div id="cd-12" class="n0"></div>
<div id="cd-13" class="n0"></div>
<div id="cd-14" class="n0"></div>
</div>
<div class="line-2">
<div id="cd-21" class="n0"></div>
<div id="cd-22" class="n0"></div>
<div id="cd-23" class="n0"></div>
<div id="cd-24" class="n0"></div>
</div>
<div class="line-3">
<div id="cd-31"class="n0"></div>
<div id="cd-32"class="n0"></div>
<div id="cd-33"class="n0"></div>
<div id="cd-34"class="n0"></div>
</div>
<div class="line-4">
<div id="cd-41" class="n0"></div>
<div id="cd-42" class="n0"></div>
<div id="cd-43" class="n0"></div>
<div id="cd-44" class="n0"></div>
</div>
</div>
<div class="controlButton">
<input type="button" value="开始" id="start" onclick="start()">
<input type="button" value="重新开始" id="newstart" disabled="disabled" onclick="newstart()">
</div>
<div class="information">
<p>温馨提示:该游戏在PC端通过键盘上的方向键进行,在移动端通过滑动屏幕进行!
</p>
</div>
<div class="number">
<div class="line-1">
<div id="cd-00" class="n2 n0">2</div>
<div id="cd-01" class="n4 n0">4</div>
<div id="cd-02" class="n8 n0">8</div>
<div id="cd-03" class="n16 n0">16</div>
</div>
<div class="line-2">
<div id="cd-10" class="n32 n0">32</div>
<div id="cd-11" class="n64 n0">64</div>
<div id="cd-12" class="n128 n0">128</div>
<div id="cd-13" class="n256 n0">256</div>
</div>
<div class="line-3">
<div id="cd-20" class="n512 n0">512</div>
<div id="cd-21" class="n1024 n0">1024</div>
<div id="cd-22" class="n2048 n0">2048</div>
<div id="cd-23" class="n4096 n0">4096</div>
</div>
<div class="line-4">
<div id="cd-30" class="n8192 n0">8192</div>
<div id="cd-31" class="n0"></div>
<div id="cd-32" class="n0"></div>
<div id="cd-33" class="n0"></div>
</div>
</div>
</body>
</html>
HTML代码中存在下方的代码是为了禁止该网页在移动端进行缩放操作(当然,安卓系统下的UC浏览器貌似不起作用)。其中,initial-scale表示初始缩放比例,maximum-scale表示允许最大的缩放比例,minimum-scale表示允许最小的缩放比例,user-scalable表示是否可手动缩放。一行代码解决手机浏览器禁止缩放的问题。
<meta name="viewport" content="initial-scale=1, width=device-width, maximum-scale=1, minimum-scale=1.0, user-scalable=no">
CSS代码
.information {
position: fixed;
top: 30px;
left: 50px;
}
.information p {
color: red;
width: 200px;
line-height: 24px;
}
.score {
font-size: 20px;
margin: 30px auto 0;
width: 400px;
height: 8px;
}
.current-score,.most-score{
width: 200px;
float: left;
text-align: center;
}
.current-score span {
color: blue;
}
.most-score span {
color: red;
}
.contain {
clear: both;
height: 400px;
width: 400px;
background-color: #ccc;
margin: 40px auto 20px;
padding: 5px;
}
.n0,.n2,.n4,.n8,.n16,.n32,.n64,.n128,.n256,.n512,.n1024,.n2048,.n4096,.n8192 {
height: 90px;
width: 90px;
background-color: gray;
float: left;
margin: 5px;
font-size: 30px;
text-align: center;
line-height: 90px;
color: #fff;
}
.controlButton {
width: 400px;
height: 50px;
margin: 0 auto;
text-align: center;
}
#newstart {
color: graytext;
}
/*设置每个数字的样式*/
.n2{background-color: #fff;color: black;}
.n4{background-color: #ede0c8;color: black;}
.n8{background-color: #f2b179;}
.n16{background-color: #f59563;}
.n32{background-color: #f67c5f;}
.n64{background-color: #f65e3b;}
.n128{background-color: #edcf72;}
.n256{background-color: #fec716;}
.n512{background-color: #ddc190;}
.n1024{background-color: #9f8963;}
.n2048{background-color: #cc8100;}
.n4096{background-color: #5d4d32;}
.n8192{background-color: #93c;}
.number{display: none;}
/*当屏幕宽度小于1080px时的样式*/
@media screen and (max-width: 1080px) {
.information{
position: relative;
top: 0;
left: 0;
text-align: center;
}
.information p {
width: auto;
}
}
通过上方的css代码对游戏界面进行布局和配色,其中代码块
@media screen and (max-width: 1080px) {
………………
}
是响应式布局的css写法,表示当屏幕的宽度小于1080px时相应的样式
JS代码
var score,mostScore;
var c=[];
//初始化二维数组,0号,5号单位置为0
for(var i=0;i<=5;i++){
c[i]=[];
for(var j=0;j<=5;j++)
c[i][j] = 0;
}
function start() {
controlButton();//控制按钮的可用性
score = mostScore = 0;//初始化分数
//开始时随机生成两块
randomDemo();
randomDemo();
}
function newstart() {
if (confirm("确定要重新开始游戏吗?") == true) {
// 将二维数组及当前分数清零
for(var i=0;i<=5;i++){
c[i]=[];
for(var j=0;j<=5;j++){
c[i][j] = 0;
if(i>0&&j>0&&i<5&&j<5){
document.getElementById("cd-"+i+j).innerText = "";
document.getElementById("cd-"+i+j).className = "n0";
}
}
}
score = 0;
document.getElementById("current-score").innerText = score;
//重新随机生成两块
randomDemo();
randomDemo();
}
}
//按钮的控制方法
function controlButton() {
var s1 = document.getElementById("start");
var ns = document.getElementById("newstart");
//禁用开始按钮
s1.setAttribute("disabled" ,"disabled");
s1.style.color="graytext";
//解禁重新开始按钮
ns.removeAttribute("disabled");
ns.style.color="inherit";
}
//随机生成方块
function randomDemo() {
var randomnum,i,j;
//随机产生坐标
i = Math.floor(Math.random() * 4)+1;
j = Math.floor(Math.random() * 4)+1;
//生成新的方块
if(c[i][j]==0){
randomnum = Math.random()<0.75 ? 2:4;
c[i][j] = randomnum;
document.getElementById("cd-"+i+j).innerText = randomnum;
document.getElementById("cd-"+i+j).className = "n"+randomnum;
return true;
}
else return randomDemo();
}
//界面的更新方法
function update() {
for(var i=1;i<=4;i++){
for(var j=1;j<=4;j++){
var pre = document.getElementById("cd-"+i+j);//当前对象
if(c[i][j] != 0){
pre.className = "n"+c[i][j];
pre.innerText = c[i][j];
}else {
pre.className = "n0";
pre.innerText = "";
}
}
}
}
//判断是否可以左移
function canLeft() {
for(var i=1;i<=4;i++){
if(c[i][2]+c[i][3]+c[i][4]!=0){
if(c[i][1]==0)
return true;
else if(c[i][2]==0)
return true;
else if(c[i][2]==c[i][1]||
c[i][2]==c[i][3]||
(c[i][4]!=0&&c[i][3]==c[i][4])||
(c[i][3]==0&&c[i][4])!=0)
return true;
}
}
return false;
}
//判断是否可以右移
function canRight() {
for(var i=1;i<=4;i++){
if(c[i][2]+c[i][3]+c[i][1]!=0){
if(c[i][4]==0)
return true;
else if(c[i][3]==0)
return true;
else if(c[i][3]==c[i][4]||
c[i][3]==c[i][2]||
(c[i][1]!=0&&c[i][1]==c[i][2])||
(c[i][2]==0&&c[i][1])!=0)
return true;
}
}
return false;
}
//判断是否可以上移
function canUp() {
for(var i=1;i<=4;i++){
if(c[2][i]+c[3][i]+c[4][i]!=0){
if(c[1][i]==0)
return true;
else if(c[2][i]==0)
return true;
else if(c[3][i]==0 && c[4][i]!=0)
return true;
else if((c[3][i]!=0&&c[3][i]==c[4][i])||
c[3][i]==c[2][i]||
(c[1][i]==c[2][i]))
return true;
}
}
return false;
}
//判断是否可以下移
function canDown() {
for(var i=1;i<=4;i++){
if(c[2][i]+c[3][i]+c[1][i]!=0){
if(c[4][i]==0)
return true;
else if(c[3][i]==0)
return true;
else if(c[2][i]==0 && c[1][i]!=0)
return true;
else if((c[1][i]!=0&&c[1][i]==c[2][i])||
c[3][i]==c[2][i]||
(c[3][i]==c[4][i]))
return true;
}
}
return false;
}
//左移方法
function left() {
var l = canLeft();
for(var i=1;i<=4;i++){
//1.先将一行内的元素以0为界排列好,如2020排成2200
for(var j=4;j>1;j--){
if(c[i][j-1] == 0){
c[i][j-1]+=c[i][j];
k=j;
while(k<5){
c[i][k] = c[i][k+1];
k++;
}
}
}
//2.合并相同且相邻的方块并更新数组
for(var j=1;j<4;j++){
if(c[i][j]==c[i][j+1] && c[i][j]!=0){
c[i][j]*=2;
switch(c[i][j]){
case 512:alert("恭喜您合并成512方块!");break;
case 1024:alert("恭喜您合并成1024方块!");break;
case 2048:alert("恭喜您合并成2048方块!");break;
case 4096:alert("恭喜您合并成4096方块!");break;
case 8192:alert("恭喜您合并成8192方块!");
}
score+=c[i][j];//更新分数
document.getElementById("current-score").innerText = score;
//判断是否破纪录
if(score>mostScore){
mostScore = score;
document.getElementById("most-score").innerText = score;
}
k=j+1;
//左移数组
while(k<5){
c[i][k] = c[i][k+1];
k++;
}
}
}
}
update();//更新界面
//判断是否可以左移,能则生成方块
if(l) randomDemo();
}
//右移方法
function right() {
var r = canRight();
for(var i=1;i<=4;i++){
//1.先将一行内的元素以0为界排列好,如2020排成0022
for(var j=1;j<4;j++){
if(c[i][j+1] == 0){
c[i][j+1]+=c[i][j];
k=j;
while(k>0){
c[i][k] = c[i][k-1];
k--;
}
}
}
//2.合并相同且相邻的方块并更新数组
for(var j=4;j>0;j--){
if(c[i][j]==c[i][j-1] && c[i][j]!=0){
c[i][j]*=2;
switch(c[i][j]){
case 512:alert("恭喜您合并成512方块!");break;
case 1024:alert("恭喜您合并成1024方块!");break;
case 2048:alert("恭喜您合并成2048方块!");break;
case 4096:alert("恭喜您合并成4096方块!");break;
case 8192:alert("恭喜您合并成8192方块!");
}
score+=c[i][j];//更新分数
document.getElementById("current-score").innerText = score;
//判断是否破纪录
if(score>mostScore){
mostScore = score;
document.getElementById("most-score").innerText = score;
}
k=j-1;
//数组右移
while(k>0){
c[i][k] = c[i][k-1];
k--;
}
}
}
}
update();//更新界面
//判断是否可以右移,能则生成方块
if(r) randomDemo();
}
//上移方法
function up() {
var u = canUp();
for(var j=1;j<=4;j++){
//1.先将一列内的元素以0为界排列好,如2202排成2220
for(var i=4;i>1;i--){
if(c[i-1][j] == 0){
c[i-1][j]+=c[i][j];
k=i;
while(k<5){
c[k][j] = c[k+1][j];
k++;
}
}
}
//2.合并相同且相邻的方块并更新数组
for(var i=1;i<4;i++){
if(c[i][j]==c[i+1][j] && c[i][j]!=0){
c[i][j]*=2;
switch(c[i][j]){
case 512:alert("恭喜您合并成512方块!");break;
case 1024:alert("恭喜您合并成1024方块!");break;
case 2048:alert("恭喜您合并成2048方块!");break;
case 4096:alert("恭喜您合并成4096方块!");break;
case 8192:alert("恭喜您合并成8192方块!");
}
score+=c[i][j];//更新分数
document.getElementById("current-score").innerText = score;
//判断是否破纪录
if(score>mostScore){
mostScore = score;
document.getElementById("most-score").innerText = score;
}
k=i+1;
//数组上移
while(k<5){
c[k][j] = c[k+1][j];
k++;
}
}
}
}
update();//更新界面
//判断是否可以上移,能则生成方块
if(u) randomDemo();
}
//下移方法
function down() {
var d = canDown();
for(var j=1;j<=4;j++){
//1.先将一列内的元素以0为界排列好,如2202排成0222
for(var i=1;i<4;i++){
if(c[i+1][j] == 0){
c[i+1][j]=c[i][j];
k=i;
while(k>0){
c[k][j] = c[k-1][j];
k--;
}
}
}
//2.合并相同且相邻的方块并更新数组
for(var i=4;i>0;i--){
if(c[i][j]==c[i-1][j] && c[i][j]!=0){
c[i][j]*=2;
switch(c[i][j]){
case 512:alert("恭喜您合并成512方块!");break;
case 1024:alert("恭喜您合并成1024方块!");break;
case 2048:alert("恭喜您合并成2048方块!");break;
case 4096:alert("恭喜您合并成4096方块!");break;
case 8192:alert("恭喜您合并成8192方块!");
}
score+=c[i][j];//更新分数
document.getElementById("current-score").innerText = score;
//判断是否破纪录
if(score>mostScore){
mostScore = score;
document.getElementById("most-score").innerText = score;
}
k=i-1;
//数组下移
while(k>0){
c[k][j] = c[k-1][j];
k--;
}
}
}
}
update();//更新界面
//判断是否可以下移,能则生成方块
if(d) randomDemo();
}
//判断游戏是否结束,即上下左右都不能移动
function over() {
if((!(canLeft()||canRight()||canUp()||canDown()))&&
document.getElementById("start").disabled)
alert("游戏结束,本局分数为"+score+",游戏的最高分数为"+mostScore);
}
//键盘事件的第一种写法
window.addEventListener("keydown",function(e) {
if(e.keyCode == 37){
e.preventDefault();
left();
over();
}
if(e.keyCode == 38){
e.preventDefault();
up();
over();
}
if(e.keyCode == 39){
e.preventDefault();
right();
over();
}
if(e.keyCode == 40){
e.preventDefault();
down();
over();
}
});
/*
//键盘事件的第二种写法
window.onkeydown = function (e) {
if(e.keyCode == 37){
e.preventDefault();
left();
over();
}
if(e.keyCode == 38){
e.preventDefault();
up();
over();
}
if(e.keyCode == 39){
e.preventDefault();
right();
over();
}
if(e.keyCode == 40){
e.preventDefault();
down();
over();
}
}*/
//监听移动设备的触摸开始
document.addEventListener('touchstart',function (event) {
startx = event.touches[0].pageX;
starty = event.touches[0].pageY;
});
//监听移动设备的触摸移动
document.addEventListener('touchmove',function (evnet) {
event.preventDefault();
});
//监听移动设备的触摸结束
document.addEventListener('touchend',function (event) {
endx = event.changedTouches[0].pageX;
endy = event.changedTouches[0].pageY;
var x = endx - startx;
var y = endy - starty;
//x
if(Math.abs(x) > Math.abs(y)){
if(x > 0){
//向右移动
if (canRight()){
right();
}
over();
} else {
//向左移动
if (canLeft()){
left();
}
over();
}
} else if(Math.abs(x) < Math.abs(y)) { //y
if (y < 0){
//向上移动
if (canUp()){
up();
}
over();
} else { //向下移动
if (canDown()){
down();
}
over();
}
}
});
JS中,用一个二维数组来存放每个方块的数值,其中适当地留空了某些空间以备数组的移动和变化
- JS随机函数
代码 | 含义 |
---|---|
Math.random() | 表示返回【0,1)之间的随机数 |
Math.floor(Math.random()*10) | 表示返回0至9之间的整数 |
Math.floor(Math.random()*11) | 表示返回0至10之间的整数 |
-
合并方块的思路
(1)当想要往某个方向移动时,先判断是否可往该方向移动,可移动时先将一行(列)中的数组元素以0位界排列好,如当下移时,某列的元素分别是2202,则排列成0222;当右移时,某行的元素分别是2020,则排成0022。不可移动时不生成也不合并不移动方块。
(2)合并相同且相邻的方块并更新数组、分数和界面。
(3)判断往某个方向移动时是否可以再生成新的方块 -
移动端JS触摸touch
(1) 4个监听事件
事件 | 触发时刻 |
---|---|
touchstart | 触摸屏幕上时触发 |
touchmove | 触摸屏幕中滑动时触发 |
touchend | 离开屏幕时触发 |
touchcancel | 系统取消触摸事件的时候触发 |
(2) 监听触摸后触摸事件会实现一个event对象,这个对象里面包括3个触摸函数列表。
函数 | 触发函数列表 |
---|---|
touches | 屏幕上所有手指列表 |
targetTouches | 在当前DOM标签上手指的列表 |
changedTouches | 涉及当前事件的手指的列表 |
(3)触摸函数的属性,用于获取坐标
属性 | 获取的坐标 |
---|---|
clientX | 触摸目标在浏览器中的x坐标 |
clientY | 触摸目标在浏览器中的y坐标 |
identifier | 标识触摸的唯一ID |
pageX | 触摸目标在当前DOM中的x坐标 |
pageY | 触摸目标在当前DOM中的y坐标 |
screenX | 触摸目标在屏幕中的x坐标 |
screenY | 触摸目标在屏幕中的y坐标 |
target | 触摸的DOM节点目标 |
- 键盘事件
对于键盘事件,我在网上找了个链接,大家可以参考一下
键盘事件讲解链接
Ending
在JS中我的算法思路不是很灵活也不是很高效,但基本还是实现了2048游戏的规则。另外还有一些操作也运用的比较陌生。倘若在阅读的过程中大家有发现错误的或者有更好的建议的,欢迎向我提出,我将感激不尽。