2048JS
实现思路
- 先创建一个map矩阵,记录下标(同时要在每个块设置自定义属性)
- 封装一个随机产生块的函数(在这个函数里要进行游戏结束的判断)
- 根据键盘上下左右的按键控制移动方向(移动端可以通过触屏移动)
- 开始移动(下面是移动的步骤)
- 先全部往同一个方向移动
- 第一次移动完合并(每次根据不同的方向按不同规则来移动,合并的同时记录当前合并的块的数值及总分,并根据数值改变块的颜色)
- 合并完再执行一次上面说的移动的函数
- 封装改变颜色的函数
实现代码
(为了更好的理解代码,代码没有进行封装,有些地方有冗余的代码,有兴趣的小伙伴可以自行封装)
1.先创建需要使用的变量及初始化map
let wrap = document.querySelector('.wrap');//获取包裹的wrap
let map = [];//存储记录2048的数值及位置
let direction = null; //移动的方向
let scroe = 0;
//初始化map
function mapInit(map) {
for (var i = 0; i < 4; i++) {
map[i] = [];
for (var j = 0; j < 4; j++) {
map[i][j] = 0;
}
}
return map;
}
map = mapInit(map); //存储生成块的位置(初始化)
2.封装生成块的函数
function randomBlock(wrap) {
let span = wrap.querySelectorAll('span');
if (span.length == 16) {
alert('游戏结束');
return false;
}
//随机生成x,y,如果生成的位置已有重新生成
do {
var x = parseInt(Math.random() * 4);
var y = parseInt(Math.random() * 4);
} while (map[x][y]);
//在当前生成的块位置设置一个初始值为2
map[x][y] = 2;
//新增一个span
var box = document.createElement('span');
//给span添加自定义属性x,y,及对应的count值
box.setAttribute('x', x);
box.setAttribute('y', y);
box.setAttribute('num', 2);
//生成块的位置
x = (x * 100) + 14 + (12 * x);
y = (y * 100) + 14 + (12 * y);
box.style.left = y + 'px';
box.style.top = x + 'px';
box.innerText = 2;
//添加进wrap里
wrap.appendChild(box);
}
3.全部往同一个方向移动
//根据方向全部往一个方向移动
function moveBox(direction) {
let span = wrap.querySelectorAll('span');
//遍历每个span块
for (let i = 0; i < span.length; i++) {
//获取每个span的x,y
let x = Number(span[i].getAttribute('x'));
let y = Number(span[i].getAttribute('y'));
switch (direction) {
case 'left':
//如果当前块位于最左边则
if (y == 0) {
continue;
} else {
//获取当前块左边有多少个块
let xBox = 0;
for (var j = 0; j < y; j++) {
if (map[x][j] != 0) {
xBox++;
}
}
//设置自定义属性y
span[i].setAttribute('y', xBox);
//移动
span[i].style.left = 14 + xBox * (100 + 12) + 'px';
}
break;
case 'right':
//如果当前块位于最右边则
if (y == 3) {
continue;
} else {
//获取当前块右边有多少个块
let xBox = 0;
for (var j = 3; j > y; j--) {
if (map[x][j] != 0) {
xBox++;
}
}
//改变自定义属性y
span[i].setAttribute('y', 3 - xBox);
//移动
span[i].style.left = 14 + (3 - xBox) * (100 + 12) + 'px';
}
break;
case 'top':
//如果当前块位于最上边则
if (x == 0) {
continue;
} else {
//获取当前块上边有多少个块
let yBox = 0;
for (var j = 0; j < x; j++) {
if (map[j][y] != 0) {
yBox++;
}
}
//改变自定义属性x
span[i].setAttribute('x', yBox);
//移动
span[i].style.top = 14 + yBox * (100 + 12) + 'px';
}
break;
case 'bottom':
//如果当前块位于最下边则
if (x == 3) {
continue;
} else {
//获取当前块上边有多少个块
let yBox = 0;
for (var j = 3; j > x; j--) {
if (map[j][y] != 0) {
yBox++;
}
}
//改变自定义属性x
span[i].setAttribute('x', 3 - yBox);
//移动
span[i].style.top = 14 + (3 - yBox) * (100 + 12) + 'px';
}
break;
}
}
//根据改变位置后的块修改map矩阵
map = resetMap();
}
4.合并及改变颜色的函数
(合并要根据方向进行不同的方式的合并)
//合并
function merge(direction) {
let span = wrap.querySelectorAll('span');
for (let i = 0; i < map.length; i++) {
//如果是左边
if (direction == 'left') {
for (let j = 0; j < map[i].length - 1; j++) {
if (map[i][j] != 0 && map[i][j] == map[i][j + 1]) {
for (let k = 0; k < span.length; k++) {
//移除这个块
if (Number(span[k].getAttribute('x')) == i && Number(span[k].getAttribute('y')) == (j + 1)) {
wrap.removeChild(span[k]);
}
//改变这个块的数字及标签上对应的坐标
if (Number(span[k].getAttribute('x')) == i && Number(span[k].getAttribute('y')) == j) {
//计算块相加得到的数字
let numAdd = Number(map[i][j + 1]) + Number(map[i][j]);
//改变自定义属性num的值
span[k].setAttribute('num', numAdd)
span[k].innerText = numAdd;
//改变颜色
changeColor(span[k]);
//计算总分
scroe += numAdd;
}
}
j++;
}
}
}
//如果是右边
if (direction == 'right') {
for (let j = 3; j >= 1; j--) {
if (map[i][j] != 0 && map[i][j] == map[i][j - 1]) {
for (let k = 0; k < span.length; k++) {
//移除这个块
if (Number(span[k].getAttribute('x')) == i && Number(span[k].getAttribute('y')) == (j - 1)) {
wrap.removeChild(span[k]);
}
//改变这个块的数字及标签上对应的坐标
if (Number(span[k].getAttribute('x')) == i && Number(span[k].getAttribute('y')) == j) {
let numAdd = Number(map[i][j - 1]) + Number(map[i][j]);
span[k].setAttribute('num', numAdd)
span[k].innerText = numAdd;
changeColor(span[k]);
scroe += numAdd;
}
}
j--;
}
}
}
//如果是上边
if (direction == 'top') {
for (let j = 0; j < map.length - 1; j++) {
if (map[j][i] != 0 && map[j][i] == map[j + 1][i]) {
for (let k = 0; k < span.length; k++) {
//移除这个块
if (Number(span[k].getAttribute('x')) == j + 1 && Number(span[k].getAttribute('y')) == i) {
wrap.removeChild(span[k]);
}
//改变这个块的数字及标签上对应的坐标
if (Number(span[k].getAttribute('x')) == j && Number(span[k].getAttribute('y')) == i) {
let numAdd = Number(map[j + 1][i]) + Number(map[j][i]);
span[k].setAttribute('num', numAdd)
span[k].innerText = numAdd;
changeColor(span[k]);
scroe += numAdd;
}
}
j++;
}
}
}
//如果是下边
if (direction == 'bottom') {
for (let j = 3; j >= 1; j--) {
if (map[j][i] != 0 && map[j - 1][i] == map[j][i]) {
for (let k = 0; k < span.length; k++) {
//移除这个块
if (Number(span[k].getAttribute('x')) == j - 1 && Number(span[k].getAttribute('y')) == i) {
wrap.removeChild(span[k]);
}
//改变这个块的数字及标签上对应的坐标
if (Number(span[k].getAttribute('x')) == j && Number(span[k].getAttribute('y')) == i) {
let numAdd = Number(map[j - 1][i]) + Number(map[j][i]);
span[k].setAttribute('num', numAdd)
span[k].innerText = numAdd;
changeColor(span[k]);
scroe += numAdd;
}
}
j--;
}
}
}
}
map = resetMap();
moveBox(direction);
document.querySelector('.score em').innerText = scroe;
randomBlock(wrap);
}
//改变颜色
function changeColor(dom) {
let num = dom.getAttribute('num');
switch (num) {
case '2': dom.style.backgroundColor = '#eee4da'; break;
case '4': dom.style.backgroundColor = '#ede0c8'; break;
case '8': dom.style.backgroundColor = '#f2b179'; break;
case '16': dom.style.backgroundColor = '#f59563'; break;
case '32': dom.style.backgroundColor = '#f67c5f'; break;
case '64': dom.style.backgroundColor = '#f65e3b'; break;
case '128': dom.style.backgroundColor = '#edcf72'; break;
case '256': dom.style.backgroundColor = '#edcc61'; break;
case '512': dom.style.backgroundColor = '#edc850'; break;
case '1024': dom.style.backgroundColor = '#edc53f'; break;
case '2048': dom.style.backgroundColor = '#edc22e'; break;
}
}
5.重构map矩阵的函数
//根据标签对应的位置重构map
function resetMap() {
let span = wrap.querySelectorAll('span');
map = mapInit(map);
for (var i = 0; i < span.length; i++) {
let x = Number(span[i].getAttribute('x'));
let y = Number(span[i].getAttribute('y'));
map[x][y] = Number(span[i].getAttribute('num'));
}
return map;
}
6.根据按键进行移动
//触屏控制移动方向
document.ontouchstart = function (e) {
var e = e || window.event;
//获取开始触碰的坐标
let xStart = e.touches[0].clientX;
let yStart = e.touches[0].clientY;
document.ontouchend = function (e) {
var e = e || window.event;
//获取触碰结束的坐标
let xEnd = e.changedTouches[0].clientX;
let yEnd = e.changedTouches[0].clientY;
//坐标差
let xDif = xEnd - xStart;
let yDif = yEnd - yStart;
//根据坐标差移动
if (xDif >= 50) { direction = 'right'; toMove('right'); }
else if (xDif <= -50) { direction = 'left'; toMove('left') }
else if (yDif >= 50) { direction = 'bottom'; toMove('bottom') }
else if (yDif <= -50) { direction = 'top'; toMove('top') }
}
}
//键盘控制移动
document.onkeydown = function (e) {
var e = e || window.event;
//获取span块的dom元素
switch (e.keyCode) {
case 37:
direction = 'left';
toMove('left');
break;
case 38:
direction = 'top';
toMove('top');
break;
case 39:
direction = 'right';
toMove('right');
break;
case 40:
direction = 'bottom';
toMove('bottom');
break;
}
}
//执行移动
function toMove(direction) {
//获取span块的dom元素
switch (direction) {
case 'left':
moveBox('left');
merge(direction);
break;
case 'top':
moveBox('top');
merge(direction);
break;
case 'right':
moveBox('right');
merge(direction);
break;
case 'bottom':
moveBox('bottom');
merge(direction);
break;
}
}
html代码
<p>
<span class="title">2048</span>
<span class="score">
<i>SCORE</i>
<em>0</em>
</span>
</p>
<div class="wrap">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
css样式
h1,h2,h3,h4,p,ul,li,body{
margin: 0;
padding: 0;
}
li{
float: left;
list-style: none;
}
i,em{
font-style: normal;
}
p{
display: flex;
width: 456px;
margin: 25px auto;
justify-content: space-between;
align-items: center;
}
p .title{
font-size: 60px;
color: #736d63;
font-weight: 550;
}
p .score{
width: 100px;
height: 55px;
display: flex;
justify-content: center;
flex-direction: column;
text-align: center;
background: #b5aa9c;
color: white;
}
p .score i{
font-size: 12px;
}
p .score em{
font-size: 20px;
font-weight: 550;
}
.wrap{
width: 448px;
height: 448px;
padding: 8px;
background: #bdae9c;
margin: auto;
border-radius: 10px;
position: relative;
}
.wrap ul{
width: 100%;
height: 100%;
}
.wrap ul li{
width: 100px;
height: 100px;
border-radius: 8px;
margin: 6px;
background: #c5baad;
}
.wrap span{
width: 100px;
height: 100px;
position: absolute;
background: #eee4da;
border-radius: 8px;
line-height: 100px;
text-align: center;
font-size: 50px;
font-weight: 550;
transition: all 0.2s;
}
游戏截图
体验地址
http://39.106.60.168/www/2048/