这是一个用js写的网页版2048游戏,实现2048游戏的基本功能。
比如:绘制网格、随机生成数字、动画移动数字、累计分数、重置游戏等。
同时,笔者在自己的理解下,对代码进行了非常详细的注释,相信有一点点基础的开发人员都能迅速看懂,快来试试吧!
当然,大神还可以在这个文档的基础上对游戏进行改进,比如添加一些更炫酷的动画效果,自定义数字、文字、图像等。
该项目的所有代码已上传,欢迎下载:点击打开链接
当然你也可以查看本文接下来给你展示的部分代码。
该项目主要由以下几个部分组成:
2048.css :是关于字体、表格、排版样式的定义文档。包括字体、表格、数字的大小、颜色、边距等。
index.html :是该网页的主界面设计文档。包括设置标题文字、分数、棋盘格,以及导入会用到的css文件、js文件。
main2048.js :是该游戏的主要功能的实现文档。包括开始游戏、初始化数据、随机生成数字、键盘上下左右的响应、数字移动的变化、游戏结束。
showAnimation.js :是该游戏的动画显示的实现文档。包括数字显示的动画、数字移动的动画、分数刷新显示。
support2048.js :是该游戏的一些小细节处理的实现文档。可以理解为对 main2048.js 中常用函数的封装,方便多次调用此类函数。包括获取单元格的坐标、设置数字的颜色、判断格子是否为空、判断数字能否移动等。
---------------------------------------------------------------------------------- 具体代码实现如下 -------------------------------------------------------------------------------------------------------------------
2048.css
header {
display: block;
margin: 0 auto;
width: 500px;
text-align: center;
}
header h1 {
font-family: Arial;
font-size: 60px;
font-weight: bold;
}
header #newgamebutton {
display: block;
margin: 20px auto;
width: 100px;
padding: 10px 10px;
background-color: #8f7a66;
font-family: Arial;
color: white;
border-radius: 10px;
text-decoration: none;
}
header #newgamebutton:hover {
background-color: #9f8b77;
}
header p {
font-family: Arial;
font-size: 25px;
margin: 20px auto;
}
#grid-container {
width: 460px;
height: 460px;
padding: 20px;
margin: 50px auto;
background-color: #bbada0;
border-radius: 10px;
position: relative;
}
.grid-cell {
width: 100px;
height: 100px;
border-radius: 6px;
background-color: #ccc0b3;
position: absolute;
}
.number-cell {
border-radius: 6px;
font-family: Arial;
font-weight: bold;
font-size: 60px;
line-height: 100px;
text-align: center;
position: absolute;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2048</title>
<link rel="stylesheet" type="text/css" href="2048.css"/>
<script type="text/javascript" src="http://libs.baidu.com/jquery/1.9.0/jquery.min.js"></script>
<script type="text/javascript" src="support2048.js"></script>
<script type="text/javascript" src="showAnimation.js"></script>
<script type="text/javascript" src="main2048.js"></script>
</head>
<body>
<!-- 绘制标题 -->
<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>
</body>
</html>
main2048.js
/**
* Created by Kay on 2016/3/7.
*/
// --------------------------------------------------------------------------------------------------------------------
var board = new Array();
var score = 0;
var hasConflicted = new Array();// 用来判断每个格子是否已经发生过碰撞,从而避免一下子加好几个格子
$(document).ready(function () {
newgame();
});
function newgame() {
// 初始化棋盘格
init();
// 在随机两个格子生成数字
generateOneNumber();
generateOneNumber();
}
// --------------------------------------------------------------------------------------------------------------------
/*
* 1、初始化棋盘格 gridCell
* 2、初始化二维数组 用于存储数据 board
* 3、初始化数据 清零 updateBoardView();
*/
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', getPosition(i));
gridCell.css('left', getPosition(j));
}
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;
updateScore(score);
}
// --------------------------------------------------------------------------------------------------------------------
// 初始化数据,就是将数据可视化!根据board[i][j]存的数值来画图!
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", getPosition(i) + 50);
theNumberCell.css("left", getPosition(j) + 50);
} else {
theNumberCell.css('width', '100px');
theNumberCell.css('height', '100px');
theNumberCell.css('top', getPosition(i));
theNumberCell.css('left', getPosition(j));
theNumberCell.css('background-color', getNumberBackgroundColor(board[i][j]));
theNumberCell.css('color', getNumberColor(board[i][j]));
theNumberCell.text(board[i][j]);
}
hasConflicted[i][j] = false;
}
}
// --------------------------------------------------------------------------------------------------------------------
// 随机选一个格子生成一个数字
function generateOneNumber() {
if (nospace(board))
return false;
// 随机一个位置
var randx = parseInt(Math.floor(Math.random() * 4));
var randy = parseInt(Math.floor(Math.random() * 4));
// 设置一个时间参数,50次以内系统还未生成一个空位置,那么就进行人工找一个空位置
var times = 0;
while (times < 50) {
if (board[randx][randy] == 0)
break;
randx = parseInt(Math.floor(Math.random() * 4));
randy = parseInt(Math.floor(Math.random() * 4));
times++;
}
if (times == 50) {
for (var i = 0; i < 4; i++)
for (var j = 0; j < 4; j++) {
if (board[i][j] == 0) {
randx = i;
randy = j;
}
}
}
// 随机一个数字
var randNumber = Math.random() < 0.5 ? 2 : 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()", 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: // default
break;
}
});
// --------------------------------------------------------------------------------------------------------------------
// 向左移动
function moveLeft() {
// 1、首先,判断能否向左移动
if (!canMoveLeft(board))
return false;
/*2、如果可以向左移动:
* ①当前的数字是否为0,不为0则进行左移 board[i][j] != 0
* ②如果左侧为空格子,则数字进行一个移位操作 board[i][k] == 0
* ③如果左侧有数字且不相等,则数字还是进行移位操作 noBlockHorizontal
* ④如果左侧有数字且相等,则数字进行相加操作 board[i][k] == board[i][j]
*/
for (var i = 0; i < 4; i++)
for (var j = 1; j < 4; j++) {
if (board[i][j] != 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]) {
//move
showMoveAnimation(i, j, i, k);
//add
board[i][k] += board[i][j];
board[i][j] = 0;
//add score
score += board[i][k];
updateScore(score);
hasConflicted[i][k] = true;
continue;
}
}
}
}
// 3、初始化数据 updateBoardView()
// 为显示动画效果,设置该函数的等待时间200毫秒
setTimeout("updateBoardView()", 200);
return true;
}
// --------------------------------------------------------------------------------------------------------------------
// 向上移动
function moveUp() {
if (!canMoveUp(board))
return false;
//moveUp
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 && noBlockVertical(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] && noBlockVertical(j, k, i, board) && !hasConflicted[k][j]) {
//move
showMoveAnimation(i, j, k, j);
//add
board[k][j] += board[i][j];
board[i][j] = 0;
//add score
score += board[k][j];
updateScore(score);
hasConflicted[k][j] = true;
continue;
}
}
}
}
setTimeout("updateBoardView()", 200);
return true;
}
// --------------------------------------------------------------------------------------------------------------------
// 向右移动
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)) {
//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, j, k, board) && !hasConflicted[i][k]) {
//move
showMoveAnimation(i, j, i, k);
//add
board[i][k] += board[i][j];
board[i][j] = 0;
//add score
score += board[i][k];
updateScore(score);
hasConflicted[i][k] = true;
continue;
}
}
}
}
setTimeout("updateBoardView()", 200);
return true;
}
// --------------------------------------------------------------------------------------------------------------------
// 向下移动
function moveDown() {
if (!canMoveDown(board))
return false;
//moveDown
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 && noBlockVertical(j, i, k, 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] && noBlockVertical(j, i, k, board) && !hasConflicted[k][j]) {
//move
showMoveAnimation(i, j, k, j);
//add
board[k][j] += board[i][j];
board[i][j] = 0;
//add score
score += board[k][j];
updateScore(score);
hasConflicted[k][j] = true;
continue;
}
}
}
}
setTimeout("updateBoardView()", 200);
return true;
}
// --------------------------------------------------------------------------------------------------------------------
// 游戏结束
function isgameover() {
if (nospace(board) && nomove(board)) {
gameover();
}
}
function gameover() {
alert("游戏结束!您的得分为:" + score);
}<strong>
</strong>
showAnimation.js
/**
* Created by Kay on 2016/3/7.
*/
// --------------------------------------------------------------------------------------------------------------------
/* 显示数字的动画:
* 在 x=i,y=j 的位置上 显示数字 该数字的值 = randNumber
* 在这个显示过程中 设置了一个50毫秒的动画效果
*/
function showNumberWithAnimation(i, j, randNumber) {
var numberCell = $('#number-cell-' + i + "-" + j);
numberCell.css('background-color', getNumberBackgroundColor(randNumber));
numberCell.css('color', getNumberColor(randNumber));
numberCell.text(randNumber);
numberCell.animate({
width: "100px",
height: "100px",
top: getPosition(i),
left: getPosition(j)
}, 50);
}
// --------------------------------------------------------------------------------------------------------------------
/* 移动数字的动画:
* 从 x=fromx,y=fromy 的位置 移动到 x=tox,y=toy 的位置
* 在这个显示过程中 设置了一个200毫秒的动画效果
*/
function showMoveAnimation(fromx, fromy, tox, toy){
var numberCell = $('#number-cell-' + fromx + '-' +fromy );
numberCell.animate({
top:getPosition( tox ),
left:getPosition( toy )
},200);
}
// --------------------------------------------------------------------------------------------------------------------
// 分数刷新显示,此处未设置动画
function updateScore(score){
$('#score').text(score);
}
support2048.js
/**
* Created by Kay on 2016/3/7.
*/
// --------------------------------------------------------------------------------------------------------------------
// 获取每个单元格的坐标
function getPosition( pos ) {
return 20 + pos * 120;
}
// --------------------------------------------------------------------------------------------------------------------
// 设置不同数字的不同背景颜色
function getNumberBackgroundColor(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 3192:
return "#93c";
break;
}
return "black";
}
// --------------------------------------------------------------------------------------------------------------------
// 设置数字的颜色:2和4的颜色都为#776e65,其它数字的颜色为白色
function getNumberColor(number) {
if ( number <= 4 )
return "#776e65";
return "white";
}
// --------------------------------------------------------------------------------------------------------------------
// 判断当前格子是否有数字 即判断是不是一个“非空(nospace)”的格子
function nospace(board) {
for ( var i = 0; i < 4; i++ )
for ( var j = 0; j < 4; j++ )
if ( board[i][j] == 0 ) // 如果没有数字,返回false
return false;
// 如果有数字,返回true
return true;
}
// --------------------------------------------------------------------------------------------------------------------
/* 判断能否向左移动
* 1、只需要判断每一行的后3列格子即可。
* 2、可以移动的条件是:
* ①当前格子有数字,即 board[i][j] != 0
* ②左侧格子没有数字,即 (board[i][j - 1] == 0
* ③左侧格子和当前格子数字相同,即 board[i][j - 1] == board[i][j]
*/
function canMoveLeft(board) {
for (var i = 0; i < 4; i++)
for (var j = 1; j < 4; j++)
if (board[i][j] != 0)
if (board[i][j - 1] == 0 || board[i][j - 1] == board[i][j])
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 - 1][j] == board[i][j])
return true;
return false;
}
function canMoveRight(board) {
for (var i = 0; i < 4; i++)
for (var j = 0; j < 3; j++)
if (board[i][j] != 0)
if (board[i][j+1] == 0 || board[i][j+1] == board[i][j])
return true;
return false;
}
function canMoveDown(board) {
for (var j = 0; j < 4; j++)
for (var i = 0; i < 3; i++)
if (board[i][j] != 0)
if (board[i + 1][j] == 0 || board[i + 1][j] == board[i][j])
return true;
return false;
}
// --------------------------------------------------------------------------------------------------------------------
// 判断水平方向是否可移动,即水平方向的两个目标格子之间没有其他数字 noBlockHorizontal
function noBlockHorizontal(row, col1, col2, board) {
for (var i = col1 + 1; i < col2; i++)
if (board[row][i] != 0) // 如果在这两者之间的其它格子有数字,返回false
return false;
// 如果两者之间没数字,返回true
return true;
}
// --------------------------------------------------------------------------------------------------------------------
// 判断垂直方向是否可移动,即垂直方向的两个目标格子之间没有其他数字 noBlockHorizontal
function noBlockVertical(col, row1, row2, board) {
for (var i = row1 + 1; i < row2; i++)
if (board[i][col] != 0) // 如果在这两者之间的其它格子有数字,返回false
return false;
// 如果两者之间没数字,返回true
return true;
}
function nomove( board ){
if( canMoveLeft( board ) ||
canMoveRight( board ) ||
canMoveUp( board ) ||
canMoveDown( board ) )
return false;
return true;
}
接下来,可能会再出个 “2048小游戏——网页版( 提高篇)”。主要是想对该游戏进行一个改进,2048的数字换成图片、支持手机端的滑动操作等。
看心情吧。