2048是一款休闲益智类的数字叠加小游戏。
游戏存在4种模式,分别是 3 X 3宫格
、4 X 4宫格(默认)
、5 X 5宫格
、6 X 6宫格
,每种模式的目标数字分别是 1024
、2048
、4096
、8192
,达到目标数字即可赢得胜利。
您可以通过键盘的上
、下
、左
、右
四个方向键进行操作,数字会按方向移动,相邻的两个数字相同就会合并,组成更大的数字,每次移动或合并后会自动增加一个数字。
游戏UI部分index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2048小游戏title>
<style>
* {
margin: 0;
padding: 0;
}
.left {
float: left;
}
.right {
float: right;
}
.clear-fixed:after {
content: "";
display: table;
clear: both;
}
.container {
width: 480px;
height: 530px;
position: absolute;
top: 20px;
left: 50%;
margin-left: -240px;
}
.container .header {
width: 100%;
height: 50px;
font-size: 25px;
}
.container .header .score-panel {
height: 50px;
text-align: center;
line-height: 50px;
}
#canvas {
width: 480px;
height: 480px;
background-color: #bbada0;
border-radius: 10px;
}
#game-over {
display: none;
position: absolute;
left: 50%;
top: 50px;
width: 480px;
height: 480px;
border-radius: 10px;
background-color: rgba(0, 0, 0, .6);
margin-left: -240px;
text-align: center;
z-index: 5;
}
#game-over .panel {
position: absolute;
left: 50%;
right: 50%;
top: 140px;
width: 220px;
height: 200px;
border-radius: 10px;
background-color: white;
margin-left: -110px;
text-align: center;
z-index: 6;
}
#again {
display: inline-block;
width: 170px;
height: 50px;
border-radius: 10px;
text-decoration: none;
background-color: #9F8D77;
color: white;
font-size: 36px;
}
style>
head>
<body>
<div class="container">
<div class="header clear-fixed">
<div class="score-panel left">
<span>SCORE: span>
<span id="score">0span>
div>
<div class="selection right">
<label for="mode">模式选择label>
<select id="mode">
<option value="3">3 X 3option>
<option value="4" selected>4 X 4option>
<option value="5">5 X 5option>
<option value="6">6 X 6option>
select>
div>
div>
<canvas id="canvas" width="480" height="480">canvas>
<div id="game-over">
<div class="panel">
<h1 id="state" style="margin-top: 5px;">h1>
<a href="javascript:;" id="again">Try againa>
div>
div>
<div>TIP: 通过键盘↑ ↓ ← → 键操作div>
div>
body>
<script src="game.js">script>
html>
游戏逻辑部分game.js
;(function (win, doc) {
win.$ = function (el) {
return /^#\S+/.test(el) ? doc.querySelector(el) : doc.querySelectorAll(el);
}
win.Game = function (id) {
this.canvas = $(id);
this.ctx = this.canvas.getContext('2d');
this.mapWidth = this.canvas.width;
this.mapHeight = this.canvas.height;
}
Game.prototype = {
score: 0, // 得分
isWin: false,
isOver: false,
cols: 4, // 列数
rows: 4, // 行数
spacing: 15, // 方格之间的间距
grids: [], // 方格对象数组
bgColors: {
// 方格背景色
0: '#ccc0b3', 2: '#eee3da', 4: '#ede0c8', 8: '#f2b179',
16: '#f59563', 32: '#f67c5f', 64: '#f65e3b', 128: '#edcf72',
256: '#edcc61', 512: '#9c0', 1024: '#33b5e5', 2048: '#09c',
4096: '#a6c', 8192: '#93c'
},
// 初始化
init: function () {
this.score = 0;
this.isWin = false;
this.isOver = false;
// 计算小方块宽度
this.width = (this.mapWidth - (this.cols + 1) * this.spacing) / this.cols;
// 计算小方块高度
this.height = (this.mapHeight - (this.rows + 1) * this.spacing) / this.rows;
// 初始化方块数组
for (var row = 0; row < this.rows; row++) {
this.grids[row] = [];
for (var col = 0; col < this.cols; col++) {
var x = col * this.width + this.spacing * (col + 1);
var y = row * this.height + this.spacing * (row + 1);
this.grids[row][col] = {
num: 0,
x: x,
y: y
};
}
}
this.random();
this.random();
this.draw();
this.updateScore();
},
// 开始游戏
start: function () {
var self = this;
self.init();
doc.onkeydown = function (e) {
// 绑定按键点击事件
if (this.isWin || this.isOver) {
return false;
}
switch (e.keyCode) {
// 判断按键
case 37: // left
self.dir = 3;
self.moveLeft();
break;
case 38: // up
self.dir = 1;
self.moveUp();
break;
case 39: // right
self.dir = 4;
self.moveRight();
break;
case 40: // down
self.dir = 2;
self.moveDown();
break;
}
self.updateScore();
};
},
// 随机生成数字
random: function () {
while (1) {
var row = Math.floor(Math.random() * this.rows);
var col = Math.floor(Math.random() * this.cols);
// 当前方块的值必须是0,才能生成新的值
if (this.grids[row][col].num === 0) {
// 生成2和4的概率比例是 3:2
this.grids[row][col].num = (Math.random() >= 0.6) ? 4 : 2;
break;
}
}
},
// 更新分数显示
updateScore() {
$('#score').innerText = this.score;
},
// 判断游戏结束
isGameOver: function () {
for (var row = 0; row < this.rows; row++) {
for (var col = 0; col < this.cols; col++) {
if (this.grids[row][col].num === 0) {
return false;
} else if (col != this.cols - 1 && this.grids[row][col].num === this.grids[row][col + 1].num) {
return false;
} else if (row != this.rows - 1 && this.grids[row][col].num === this.grids[row + 1][col].num) {
return false;
}
}
}
return true;
},
// 查找下一个不为0的数值的位置
find: function (row, col, start, condition) {
if (this.dir === 1) {
// up
for (var f = start; f < condition; f += 1) {
if (this.grids[f][col].num != 0) {
return f;
}
}
} else if (this.dir === 2) {
// down
for (var f = start; f >= condition; f += -1) {
if (this.grids[f][col].num != 0) {
return f;
}
}
} else if (this.dir === 3) {
// left
for (var f = start; f < condition; f += 1) {
if (this.grids[row][f].num != 0) {
return f;
}
}
} else if (this.dir === 4) {
// right
for (var f = start; f >= condition; f += -1) {
if (this.grids[row][f].num != 0) {
return f;
}
}
}
return null;
},
// 方块的移动
move: function (itertor) {
var before, // 没处理前
after; // 处理后
before = this.gridsToString(this.grids);
itertor(); //执行for函数
after = this.gridsToString(this.grids);
if (before != after) {
// 前后对比,如果不同就update
this.random();
this.draw();
}
},
// 处理左按键事件
moveLeft: function () {
var self = this;
this.move(function () {
for (var row = 0; row < self.rows; row++) {
var next;
for (var col = 0; col < self.cols; col++) {
next = self.find(row, col, col + 1, self.cols); // 找出第一个不为0的位置
if (next == null) {
break; // 没有找到就返回
}
// 如果当前位置为0
if (self.grids[row][col].num === 0) {
self.grids[row][col].num = self.grids[row][next].num; // 把找到的不为0的数值替换为当前位置的值
self.grids[row][next].num = 0; //找到的位置清0
col--; // 再次循环多一次,查看后面否有值与替换后的值相同,
} else if (self.grids[row][col].num === self.grids[row][next].num) {
// 如果当前位置与找到的位置数值相等,则相加
self.grids[row][col].num *= 2;
self.grids[row][next].num = 0;
self.score += self.grids[row][col].num;
}
}
}
});
},
// 处理右按键事件
moveRight: function () {
var self = this;
this.move(function () {
for (var row = 0; row < self.rows; row++) {
var next;
for (var col = self.cols - 1; col >= 0; col--) {
next = self.find(row, col, col - 1, 0); //找出第一个不为0的位置
if (next == null) {
break; //没有找到就返回
}
//如果当前位置为0
if (self.grids[row][col].num === 0) {
self.grids[row][col].num = self.grids[row][next].num; //把找到的不为0的数值替换为当前位置的值
self.grids[row][next].num = 0; //找到的位置清0
col++; //再次循环多一次,查看后面否有值与替换后的值相同,
} else if (self.grids[row][col].num === self.grids[row][next].num) {
//如果当前位置与找到的位置数值相等,则相加
self.grids[row][col].num *= 2;
self.grids[row][next].num = 0;
self.score += self.grids[row][col].num;
}
}
}
});
},
// 处理上按键事件
moveUp: function () {
var self = this;
this.move(function () {
for (var col = 0; col < self.cols; col++) {
var next;
for (var row = 0; row < self.rows; row++) {
next = self.find(row, col, row + 1, self.rows); // 找出第一个不为0的位置
if (next == null) {
break;
}
// 如果当前位置为0
if (self.grids[row][col].num === 0) {
self.grids[row][col].num = self.grids[next][col].num; // 把找到的不为0的数值替换为当前位置的值
self.grids[next][col].num = 0; // 找到的位置清0
row--; // 再次循环多一次,查看后面否有值与替换后的值相同
} else if (self.grids[row][col].num == self.grids[next][col].num) {
// 如果当前位置与找到的位置数值相等,则相加
self.grids[row][col].num *= 2;
self.grids[next][col].num = 0;
self.score += self.grids[row][col].num;
}
}
}
});
},
// 处理下按键事件
moveDown: function () {
var self = this;
this.move(function () {
for (var col = 0; col < self.cols; col++) {
var next;
for (var row = self.rows - 1; row >= 0; row--) {
next = self.find(row, col, row - 1, 0); // 找出第一个不为0的位置
if (next == null) {
break;
}
// 如果当前位置为0
if (self.grids[row][col].num === 0) {
self.grids[row][col].num = self.grids[next][col].num; // 把找到的不为0的数值替换为当前位置的值
self.grids[next][col].num = 0; // 找到的位置清0
row++; // 再次循环多一次,查看后面否有值与替换后的值相同
} else if (self.grids[row][col].num === self.grids[next][col].num) {
// 如果当前位置与找到的位置数值相等,则相加
self.grids[row][col].num *= 2;
self.grids[next][col].num = 0;
self.score += self.grids[row][col].num;
}
}
}
});
},
// 绘制游戏内容
draw: function () {
// 清空原有内容
this.ctx.clearRect(0, 0, this.mapWidth, this.mapHeight);
for (var row = 0; row < this.rows; row++) {
for (var col = 0; col < this.cols; col++) {
var x = this.grids[row][col].x; // 得到方块x坐标
var y = this.grids[row][col].y; // 得到方块y坐标
var num = this.grids[row][col].num; // 得到方块数字
var bgColor = this.bgColors[num]; // 得到方块背景色
// 绘制方块
this.fillRoundRect(this.ctx, x, y, this.width, this.height, 10, bgColor);
if (num > 0) {
// 只有方块数字大于0才绘制数字
// 绘制方块的数字
this.fillText(this.ctx, num, x + this.width / 2, y + this.height / 2, this.width - 20, num <= 4 ? '#776e65' : '#fff');
}
// 判断是否胜利
if (this.rows === 3 && num === 1024) {
this.isWin = true;
}
if (this.rows === 4 && num === 2048) {
this.isWin = true;
}
if (this.rows === 5 && num === 4096) {
this.isWin = true;
}
if (this.rows === 6 && num === 8192) {
this.isWin = true;
}
}
}
if (this.isWin) {
// 胜利
$('#state').innerHTML = 'YOU WIN
SCORE:
' + this.score;
$('#state').style.color = 'green';
$('#game-over').style.display = 'block';
}
if (this.isGameOver()) {
// 失败
this.isOver = true;
$('#state').innerHTML = 'GAME OVER
SCORE:
' + this.score;
$('#state').style.color = 'red';
$('#game-over').style.display = 'block';
}
},
// 绘制文字
fillText(ctx, text, x, y, maxWidth, fillColor) {
ctx.fillStyle = fillColor || "#000"; // 设置画笔颜色
ctx.font = "bold 40px '微软雅黑'"; // 设置字体
ctx.textAlign = 'center'; // 水平居中
ctx.textBaseline = "middle"; // 垂直居中
ctx.fillText(text, x, y, maxWidth);
},
// 绘制并填充圆角矩形
fillRoundRect: function (ctx, x, y, width, height, radius, fillColor) {
// 圆的直径必然要小于矩形的宽高
if (2 * radius > width || 2 * radius > height) {
return false;
}
ctx.save();
ctx.translate(x, y);
// 绘制圆角矩形的各个边
this.drawRoundRectPath(ctx, width, height, radius);
ctx.fillStyle = fillColor || "#000"; // 设置画笔颜色
ctx.fill();
ctx.restore();
},
// 绘制圆角矩形框
drawRoundRectPath: function (ctx, width, height, radius) {
ctx.beginPath(0);
// 从右下角顺时针绘制,弧度从0到1/2PI
ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
// 矩形下边线
ctx.lineTo(radius, height);
// 左下角圆弧,弧度从1/2PI到PI
ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
// 矩形左边线
ctx.lineTo(0, radius);
// 左上角圆弧,弧度从PI到3/2PI
ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2);
// 上边线
ctx.lineTo(width - radius, 0);
// 右上角圆弧
ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2);
// 右边线
ctx.lineTo(width, height - radius);
ctx.closePath();
},
// grids数组转成string
gridsToString: function (grids) {
var s = '[';
for (var i in grids) {
if (Object.prototype.toString.call(grids[i]) === '[object Array]') {
s += this.gridsToString(grids[i]);
} else if (Object.prototype.toString.call(grids[i]) === '[object Object]') {
s += JSON.stringify(grids[i]);
} else {
s += grids[i];
}
}
s += ']';
return s;
}
};
})(window, document);
var game = new Game('#canvas');
game.start();
$('#mode').onchange = function () {
game.rows = game.cols = $('#mode').value / 1;
game.init();
$('#mode').blur();
}
$('#again').onclick = function () {
$('#game-over').style.display = 'none';
game.init();
}
在线体验地址:https://www.feonix.cn/2048