2048
- 游戏介绍
2048是一款数字小游戏,游戏规则很简单,操作按键 上↑下↓左←右→来操作数字的移动。
- 游戏构成
运行环境:浏览器界面
文件组成: HTML CSS JS
游戏界面:
- 原理
游戏的实现不是很复杂,HTML界面就是简单的 4*4 网格,我采用的是 div 元素盒子包裹16个小div盒子,CSS样式方面简单设置就可以, 重点在 JS 逻辑。
我总结的js要点如下:
-
首先要定义两组变量来分别表示HTML页面的 行 和 列。
-
定义二维数组data来保存数字
-
data数组中的数组发生变化并不影响到HTML页面的显示内容,所以定义函数 updataView() 来把data数组中数字的变化更新到 HTML 页面上。
-
随机生成数字 2 或 4 是反复执行的,所以要用循环来执行这个操作。
-
random()方法可返回介于0.0~1.0之间的一个随机数。生成的随机数 *RN/CN 来确定数字应该放入的随机位置。
-
移动所有行/列不可能一步操作就能实现,所以先移动一行/列,然后用循环操作来达到移动所有行/列的效果。
-
判断是否移动是进行接下来操作的先决条件。打个比方:一个小时之前我在学校大门口碰到你了,现在我在食堂又一次看到你了,那我就认为你发生了移动。 用在这里也一样: 先将data转为字符串保存在before中执行完一些操作以后再将将data转为字符串保存在after中,然后判断before和after是否相等(before=after?),如果 before=after 就认为没有发生移动,如果 before!=after 就认为数字发生了变化也就是发生了移动。
-
移动一行原理图
-
游戏代码
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2048</title>
<link rel="stylesheet" href="2048.css">
</head>
<body>
<!--1.得分:score 这里是得分记录板-->
<p>SCORE:
<span id="score">0</span>
</p>
<!--2.接下来采用代码补全的方式来设计一个4*4的游戏界面-->
<!--代码补全: div#gridPanel>div#c00*16 +TAB -->
<div id="gridPanel">
<div id="c00"></div>
<div id="c01"></div>
<div id="c02"></div>
<div id="c03"></div>
<div id="c10"></div>
<div id="c11"></div>
<div id="c12"></div>
<div id="c13"></div>
<div id="c20"></div>
<div id="c21"></div>
<div id="c22"></div>
<div id="c23"></div>
<div id="c30"></div>
<div id="c31"></div>
<div id="c32"></div>
<div id="c33"></div>
</div>
<!--3.接下来是游戏结束面板-->
<div id="gameover">
<p>
GAME OVER!<br>
SCORE:<span id="final">0</span><br>
<a href="javascript:start()">TRY AGAIN!</a>
</p>
</div>
<script src="2048.js"></script>
</body>
</html>
CSS:
#gridPanel{
width: 480px;
height: 480px;
margin: 0 auto;
border-radius: 10px;
background:#bbada0;
}
div>div{
width: 100px;
height: 100px;
margin-top: 16px;
margin-left: 16px;
float: left;
background: #ccc0b3;
border-radius: 6px;
color: #fff;
font-size: 60px;
text-align: center;
line-height: 100px;
}
.n2{background-color:#eee3da}
.n4{background-color:#ede0c8}
.n8{background-color:#f2b179}
.n16{background-color:#f59563}
.n32{background-color:#f67c5f}
.n64{background-color:#f65e3b}
.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-size: 40px;font-weight: bold;
font-family: Arial;padding-top: 15px;
}
#gameover{
display: none;
/*要让div.gameover铺满全屏*/
position: absolute;top: 0;left: 0;
bottom: 0;right: 0;
background: rgba(55,55,55,.5);
}
#gameover>p{
width: 300px;
height:200px;background: #ffffff;
position: absolute; top:50%;left: 50%;
margin-top: -100px;margin-left: -150px;
text-align: center;line-height: 1.5em;
border-radius: 10px;
border: 1px solid #edcf72;
}
#gameover a{
padding: 10px;
background: #9f8d77;
color: #ffffff;
border-radius: 6px;
text-decoration: none;
}
JS:
var RN=4,CN=4;//定义总行数,总列数
var data;//定义变量date保存二维数组
var score=0;//保存得分
var status=0;//保存游戏状态
//0:表示游戏结束 1:表示运行中
const RUNNING=1, GAMEOVER=0;
//启动游戏
function start() {
//将游戏状态置为运行中
status=RUNNING;
//将得分归0
score=0;
//创建空数组保存在data中
data=[];
//r从0到<RN
for (var r=0;r<RN;r++){
//向data中压入一个空的子数组
data.push([]);
//c从0到<CN结束
for (var c=0;c<CN;c++){
//为data中r行c位置保存一个0
data[r][c]=0;
}
}
console.log(data.join("\n"));
//随机生成2个2或4
randomNum();randomNum();
//将data中的数据更新到页面内容中(调用函数 updataView)
updataView();
//为当前页面添加键盘按下处理函数
document.onkeydown=function (e) {
//判断按键号
switch (e.keyCode){
//左
case 37: moveLeft(); break;
//上
case 38: moveUp(); break;
//右
case 39: moveRight(); break;
//下
case 40: moveDown(); break;
}
}
}
//将data中的每个元素填写到页面的对应div中
function updataView() {
//双重循环 遍历 data
for (var r=0;r<RN;r++){
for (var c=0;c<CN;c++) {
//用r,c拼对应div的id
var id = "c" + r + c;
//用id找到对应div
var div=document.getElementById(id);
//如果data中r行c列不为0 将data中r行c列的值保存到div的内容中 否则清空页面中 div 的内容(因为r行c列为0 也有可能是值发生了移动,而页面的值会呆在原地并不会移动)
if (data[r][c] != 0){
div.innerHTML = data[r][c];
//设置div的class为n+data[r][c]
div.className = "n" + data[r][c];
}else {
div.innerHTML = "";
//清空div的class
div.className = "";
}
}
}
//设置id为score的span的内容为score(将内存中计算的得分显示到页面上)
var span=document.getElementById("score");
span.innerHTML=score;
//找到游戏结束的界面div
var div=document.getElementById("gameover");
//如果游戏结束
if (status==GAMEOVER){
//找到id为final的span,设置其内容为score
var span=document.getElementById("final");
span.innerHTML=score;
//设置gameoverdiv显示
div.style.display="block";
}else {
div.style.display="none";
}
}
//在data中一个随机位置生成2或4
function randomNum() {
//反复
while (true){
//random()方法可返回介于0~1之间的一个随机数
//在0~RN-1之间生成一个随机整数保存在r
var r=parseInt(Math.random()*RN);
//在0~CN-1之间生成一个随机整数保存在c
var c=parseInt(Math.random()*CN);
//以上两句代码意思是 在4*4界面中找随机位置
//如果data中r行c列的值为0
if (data[r][c]==0){
//为data中r行c列随机保存一个2或4
data[r][c]=Math.random()<0.5?2:4;
//退出循环
break;
}
}
}
//左移所有行
function moveLeft() {
//将data转为字符串保存在before中
var before=String(data);
//r从0到<RN
for (var r=0;r<RN;r++){
//左移第r行
moveLeftInRow(r);
}
//将data转为字符串保存在after中
var after=String(data);
//如果before不等于after(这里相当于不同时间在同一地点拍照,照片内容有变化就认为发生了移动)
if (before!=after){
//随机生成一个2或4
randomNum();
//如果游戏结束就修改游戏状态为GAMEOVER
if (isGAMEOVER()) status=GAMEOVER;
//更新页面
updataView();
}
}
//左移第r行
function moveLeftInRow(r) {
//c从0开始,到<CN-1
for(var c=0;c<CN-1;c++){
//找r行c右侧下一个不为0的位置nextc
var nextc=getNextcInRow(r,c);
//如果没找到就退出循环
if (nextc==-1)break;
else {
//如果c位置的值为0
if (data[r][c]==0){
//将nextc位置的值赋值给c位置
data[r][c]=data[r][nextc];
//将nextc位置的值置为0
data[r][nextc]=0;
//将c-1
c--;
}else if (data[r][c]==data[r][nextc]){
//否则如果c位置的值等于nextc位置的值
//将c位置的值*2
data[r][c]*=2;
//将*2后的元素值累加到score中
score+=data[r][c];
//将nextc位置的值置为0
data[r][nextc]=0;
}
}
}
}
//找r行c列右侧下一个不为0的位置
function getNextcInRow(r,c) {
//nextc从c+1开始,到<CN结束
for(var nextc=c+1;nextc<CN;nextc++){
//如果data中r行nextc位置不等于0就返回nextc
if (data[r][nextc]!=0) return nextc;
}
//返回-1
return -1;
}
//右移所有行
function moveRight() {
//将data专为字符串保存到before中
var before=String(data);
//r从0开始,到<RN结束
for (var r=0;r<RN;r++){
//右移第r行
moveRightInRow(r);
}
//将data转为字符串保存在after中
var after=String(data);
//如果before不等于after时
if (before!=after){
//随机生成2或4
randomNum();
//如果游戏结束就修改游戏状态为GAMEOVER
if (isGAMEOVER()) status=GAMEOVER;
//更新页面
updataView();
}
}
//右移第r行
function moveRightInRow(r) {
//c从CN-1开始,到>0结束,递减1
for (var c=CN-1;c>0;c--){
//查找r行c列左侧前一个不为0的位置prevc
var prevc=getPrevcInRow(r,c);
//如果没有找到就退出循环
if (prevc==-1) break;
else {
//如果c位置的值为0
if (data[r][c]==0){
//用prevc位置的值代替c位置的值
data[r][c]=data[r][prevc];
//将prevc位置的值置为0
data[r][prevc]=0;
//c+1
c++;
}else if (data[r][c]==data[r][prevc]){
//否则如果c位置的值等于prevc位置的值 那么将c位置的值*2
data[r][c]*=2;
//将*2后的元素值累加到score中
score+=data[r][c];
//将prevc位置的值置为0
data[r][prevc]=0;
}
}
}
}
//查找r行c列左侧前一个不为0的位置
function getPrevcInRow(r,c) {
//prevc从c-1开始,到>=0,递减1
for (var prevc=c-1;prevc>=0;prevc--){
//如果data中r行prevc位置的值不等于0就返回prevc
if (data[r][prevc]!=0) return prevc;
}
//返回-1
return -1;
}
//上移所有列
function moveUp() {
//将data转为字符串保存到before中
var before=String(data);
//c从0到<CN
for (var c=0;c<CN;c++){
//上移第c列
moveUpInCol(c);
}
//将data转为字符串保存在after中
var after=String(data);
//如果before不等于after
if (before!=after){
//随机生成一个2或4
randomNum();
//如果游戏结束就修改游戏状态为GAMEOVER
if (isGAMEOVER()) status=GAMEOVER;
//更新页面
updataView();
}
}
//上移第c列
function moveUpInCol(c) {
//r从0到RN-1
for (var r=0;r<RN-1;r++){
//找c列r行下方第一个不为0的位置nextr
var nextr=getNextrInCol(r,c);
//如果没找到就退出循环
if (nextr==-1) break;
else {
//如果r行c列的值为0
if (data[r][c]==0){
//就用nextr行c列的值代替r行c列的值
data[r][c]=data[nextr][c];
//将nextr行c列的值置为0
data[nextr][c]=0;
//r留在原地
r--;
}else if (data[r][c]==data[nextr][c]){
//否则如果r行c列的值等于nextr行c列的值,就将r行c列的值*2
data[r][c]*=2;
//将*2后的元素值累加到score中
score+=data[r][c];
//将nextr行c列的值置为0
data[nextr][c]=0;
}
}
}
}
//查找r行c列下方下一个不为0的位置
function getNextrInCol(r,c) {
//nextr从r+1开始到<RN结束
for (var nextr=r+1;nextr<RN;nextr++){
//如果nextr行c列的值不为0就返回nextr
if (data[nextr][c]!=0) return nextr;
}
//返回 -1
return -1;
}
//下移所有列
function moveDown() {
//将data转为字符串保存到before
var before=String(data);
//c从0开始——> <CN结束
for(var c=0;c<CN;c++){
//下移第c列
moveDownInCol(c);
}
//将data转为字符串保存到after中
var after=String(data);
//如果before!=after
if (before!=after){
//随机生成一个2或4
randomNum();
//如果游戏结束就修改游戏状态为GAMEOVER
if (isGAMEOVER()) status=GAMEOVER;
//更新页面
updataView();
}
}
//下移第c列
function moveDownInCol(c) {
//r从RN-1到0
for(var r=RN-1;r>0;r--){
//找c列r行上一个不为0的位置
var prevr=getPrevrInCol(r,c);
//如果没有找到就退出循环
if (prevr==-1) break;
else {
//如果c列r行的值为0
if (data[r][c]==0){
//将c列prevr行的值给r行c列
data[r][c]=data[prevr][c];
//将c列prevr行置0
data[prevr][c]=0;
//将r留在原地
r++;
}else if (data[r][c]==data[prevr][c]){
//如果两者相等 将原来的值*2
data[r][c]*=2;
//将该结果累加到 score中
score+=data[r][c];
//将上一位置的值清空
data[prevr][c]=0;
}
}
}
}
//查找上一个不为0的位置
function getPrevrInCol(r,c) {
//prevr从RN-1开始到prevr>=0结束
for(var prevr=r-1;prevr>=0;prevr--){
//如果data[prevr][c]!=0 返回prevr
if (data[prevr][c]!=0) return prevr;
}
//返回 -1
return -1;
}
//判断游戏是否结束
function isGAMEOVER() {
//遍历data
for (var r=0;r<RN;r++){
for (var c=0;c<CN;c++){
//如果当前元素是0,就返回false
if (data[r][c]==0) return false;
//如果c<CN-1且当前元素等于右侧元素就返回false
if (c<CN-1&&data[r][c]==data[r][c+1]) return false;
//如果r<RN-1切当前元素等于下方元素就返回false
if (r<RN-1&&data[r][c]==data[r+1][c]) return false;
}
}
//返回true
return true;
}
start();
- 注意事项
要让页面div元素铺满全屏有两种方法
- position: absolute;top: 0;left: 0; bottom: 0;right: 0;
-
width: 100%;height: 100%;
opacity(模糊)是可以继承的
解决方法:background:rgba()可以有效的避免继承 :background: rgba(55,55,55,.5);
- 总结
以上内容纯属个人见解还有很多不足之处,如有兴趣希望可以互相交流一下心得体会。代码部分并非原创,也有参考其他资料和各大博主的文章内容,还请见谅。