效果展示
对UI不满意可以自行调整,这里只是说一下游戏的逻辑,具体的API调用不做过多展示。
玩法分析
2048 的玩法非常简单,通过键盘的按下,所有的数字都向着同一个方向移动,如果出现两个相同的数字,就将这两个数字合成,如果所有的各自都被占满,并且无法合成的时候,视为游戏失败。
功能实现
场景
需要一个背景和16个格子,使用绘制矩形的API和一个双重循环就可以实现。
function drawMap() {
ctx.beginPath();
ctx.fillStyle = '#bbada0'
ctx.fillRect(200,0,600,600);
for(let i = 0; i < 4; i++){
for(let k = 0; k < 4; k++){
ctx.beginPath();
ctx.fillStyle = 'rgba(238, 228, 218, 0.35)';
ctx.fillRect(i * 150 + 210, k*150+10,130,130);
}
}
}
数字生成
我们随机生成一个数字2 或者 4 ,并且需要随机一个没有数字的位置。
在这之前我们需要准备几个变量
- number: 用来存储新生成的数字
- map: 一个二维数组,用来储存格子中的数字,如果是0则视为空
使用Math.random()
生成随机数,并且利用循环反复生成位置,知道生成的位置为空,将数字填入map数组的对应位置。
function createNumber(closebtn) {
let number = Math.random() > 0.5 ? 2 : 4;
let x, y;
let gameover = true;
for(let i = 0; i < 4; i++){
for(let j = 0; j < 4; j++){
if(numberlist[i][j] == 0){
gameover = false;
break;
}
}
}
// 这里是对游戏结束的判断
if(gameover) {
alert('gameover!');
init();
return;
}
while(1) {
x = Math.floor(Math.random() * 4);
y = Math.floor(Math.random() * 4);
if(numberlist[x][y] == 0) break;
}
numberlist[x][y] = number;
}
更新视图
之前我们定义的一个数组map
,用来储存格子中的数据,之后我们只需要根据这个map中的数据去更新我们的画布,就可以实现数字的渲染,之后的合成,也只需要对map
这个数组进行更新,然后再根据数组中的数据去渲染视图。
代码中也包含了2048的配色方案,对颜色不敏感的直接用这套方案就好
function drawNumber() {
for(let i = 0; i < 4; i++){
for(let j = 0; j < 4; j++){
let color = '';
switch(numberlist[i][j]) {
case 0: continue; break;
case 2: color = '#EEE4DA'; break;
case 4: color = '#ECE0C8'; break;
case 8: color = '#f2b179'; break;
case 16: color = '#f59563'; break;
case 32: color = '#f67c5f'; break;
case 64: color = '#f65e3b'; break;
case 128: color = '#edcf72'; break;
case 256: color = '#edcc61'; break;
case 512: color = '#edc850'; break;
case 1024: color = '#edc53f'; break;
case 2048: color = '#edc22e'; break;
}
ctx.beginPath();
ctx.fillStyle = color;
ctx.fillRect(j * 150 + 210, i*150+10, 130, 130);
ctx.fillStyle = 'black';
ctx.fillText(numberlist[i][j], j * 150 + 75 + 200, i * 150+75, 130);
}
}
}
事件监听
为对应的按键绑定事件:
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'w': case 'ArrowUp':
// 这个函数用来更新数据,下一步会见到
uploadNumberList('top');
break;
case 'a': case 'ArrowLeft':
uploadNumberList('left');
break;
case 's': case 'ArrowDown':
uploadNumberList('bottom');
break;
case 'd': case 'ArrowRight':
uploadNumberList('right');
break;
default: break;
}
数据更新
数据的更新,我们用向左来做例子,一共分为3步:
- 去0
- 合成
- 去0
下面是实现的代码:
整体的思路下面的代码中写有注释
// 4行, 向左
for(let i = 0; i < 4; i++){
// 每一行执行去0,加和,去0三项
// 建一个新数组拷贝非0元素
const arr = numberlist[i];
const newArr = [0,0,0,0];
let cnt = 0;
for(let j = 0; j < 4; j++){
if(arr[j] != 0) {
newArr[cnt] = arr[j];
cnt++;
}
}
// 求和
for(let j = 0; j < 3; j++){
if(newArr[j] == newArr[j+1] && newArr[j] != 0){
newArr[j] = newArr[j] * 2;
newArr[j+1] = 0;
}
}
// 去0
const newArr2 = [0,0,0,0];
cnt = 0;
for(let j = 0; j < 4; j++){
if(newArr[j] != 0) {
newArr2[cnt] = newArr[j];
cnt++;
}
}
// 经过上面的三步这一行已经处理完成,将新的处理完成的数组替换原有的
numberlist[i] = newArr2;
}
数据更新之后,不要忘记重新渲染下视图。
结束
到这里游戏就制作好了,像这种小游戏我觉得采用面向过程的方式更加合适
自己先玩上一把
另外像是 去0 和 求和 的过程完全可以封装成一个函数减少代码冗余。