Introdução
O 2048 é um jogo extremamente popular e fácil de aprender que conquistou o mundo. Se ainda não jogou, pode descarregá-lo no seu telemóvel para experimentar. Este projeto irá guiá-lo na utilização de HTML, CSS, JavaScript e jQuery para criar uma versão web do jogo 2048.
- Aprender o processo de desenvolvimento de uma aplicação web
- Explorar como tornar a aplicação responsiva em dispositivos móveis para acomodar ecrãs de vários tamanhos e lidar com o layout e a inicialização
- Utilizar JavaScript e jQuery para escrever a lógica do jogo, implementar o movimento dos blocos e determinar os resultados do jogo.
👀 Pré-visualização

🎯 Tarefas
Neste projeto, irá aprender:
- Como criar o layout da página de um jogo web 2048 usando HTML e CSS
- Como implementar a lógica do jogo em JavaScript e jQuery
- Como lidar com o movimento e a fusão de blocos
- Como testar e executar o jogo web num navegador
🏆 Conquistas
Após concluir este projeto, será capaz de:
- Desenvolver uma aplicação web responsiva para o jogo 2048
- Utilizar JavaScript e jQuery para escrever a lógica e a funcionalidade do jogo
- Implementar o movimento e a fusão de blocos no jogo
- Testar e executar um jogo web num navegador
Preparação para o Desenvolvimento
Deixando de lado o estilo da página, o jogo 2048 pode ser abstraído como um array bidimensional. No estado inicial, dois números precisam ser gerados aleatoriamente. Os números gerados aleatoriamente só podem ser 2 ou 4. Na implementação seguinte, faremos com que a probabilidade de ocorrência de 2 e 4 seja igual. Claro, também pode tornar a probabilidade de 4 aparecer menor.
0 0 0 0
2 0 0 0
0 0 2 0
Em seguida, quando pressionar uma tecla direcional, os números mover-se-ão nessa direção e as células adjacentes com o mesmo número serão fundidas. Depois, outro número será gerado aleatoriamente. Por exemplo, se pressionarmos Cima → Esquerda → Cima, a interface pode tornar-se:
2 0 2 0
0 0 0 0
0 0 0 0
4 0 0 0
4 0 0 0
0 0 0 0
0 0 2 0
4 0 0 0
8 0 2 0
0 0 0 0
0 4 0 0
Desta forma, movemos e fundimos números continuamente até que os números não possam mais ser fundidos ou até que 2048 apareça, terminando o jogo. Como pode ver, o cerne do jogo é manipular este array bidimensional.
Estrutura de Arquivos do Projeto
Primeiro, precisamos criar a seguinte estrutura de ficheiros no caminho ~/project:
~/project
|__ index.html
|__ main.js
|__ support.js
|__ showanimation.js
|__ style.css
jquery.min.js já está colocado no caminho ~/project, pelo que pode usá-lo diretamente.
Agora, vamos começar oficialmente as nossas tarefas de codificação! Começaremos com o layout da página e completaremos gradualmente módulos como a inicialização do tabuleiro e o movimento dos blocos numéricos.
Layout da Página
Como pode ter observado, o jogo 2048 é construído em 16 quadrados. Abaixo, usaremos div + css para desenhar estas células 4X4. Por favor, adicione o seguinte código ao ficheiro index.html:
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>2048 Game</title>
<link rel="stylesheet" href="style.css" />
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="showanimation.js"></script>
<script type="text/javascript" src="support.js"></script>
</head>
<body>
<header>
<h1>2048 Game</h1>
<a href="javascript:new_game();" id="new_game_button">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>
Em seguida, precisamos adicionar estilos à página e a cada célula. Por favor, adicione o seguinte código ao ficheiro style.css:
body {
padding: 50px 0px;
}
header {
display: block;
margin: 0 auto;
width: 100%;
text-align: center;
}
header h1 {
font-family: Arial;
font-size: 40px;
font-weight: bold;
margin: 0 auto;
color: #776e65;
padding: 20px 0px;
}
header #new_game_button {
display: block;
margin: 0px auto;
width: 100px;
padding: 10px 10px;
background-color: #8f7a66;
font-family: Arial;
color: white;
border-radius: 10px;
text-decoration: none;
}
header #new_game_button:hover {
background-color: #9f8b77;
}
header p {
font-family: Arial;
font-size: 25px;
margin: 5px auto;
}
#grid_container {
width: 460px;
height: 460px;
padding: 20px;
margin: 0px 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;
}
Após concluir esta etapa, abra o ficheiro index.html usando a Pré-visualização,

e devemos ver o seguinte resultado:

Inicializando o Tabuleiro de Xadrez
Com a crescente popularidade dos dispositivos móveis, precisamos lidar com a adaptabilidade a vários tamanhos de tela.
No início do jogo, precisamos gerar dois números aleatórios no tabuleiro. Isso é alcançado através de código JavaScript.
Em main.js, adicione o seguinte:
var board = new Array(); // Os números em cada célula
var score = 0; // A pontuação
var has_conflicted = new Array(); // Flag para resolver eliminações consecutivas
var startx = 0; // Coordenada x do ponto de partida ao tocar na tela do celular
var starty = 0; // Coordenada y do ponto de partida ao tocar na tela do celular
var endx = 0; // Coordenada x do ponto final ao tocar na tela do celular
var endy = 0; // Coordenada y do ponto final ao tocar na tela do celular
var success_string = "Success";
var gameover_string = "GameOver";
// Inicializa o tabuleiro após o documento HTML ter sido carregado
$(document).ready(function () {
// Lida com a adaptabilidade
prepare_for_mobile();
new_game();
});
// Inicia um novo jogo
function new_game() {
// Inicializa o tabuleiro
init();
// Gera números em duas células aleatórias
generate_one_number();
}
// Inicializa
function init() {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
var grid_cell = $("#grid_cell_" + i + "_" + j);
grid_cell.css("top", get_pos_top(i, j));
grid_cell.css("left", get_pos_left(i, j));
}
}
for (var i = 0; i < 4; i++) {
board[i] = new Array();
has_conflicted[i] = new Array();
for (var j = 0; j < 4; j++) {
board[i][j] = 0;
has_conflicted[i][j] = false;
}
}
update_board_view();
score = 0;
update_score(score);
}
// Atualiza a visualização do tabuleiro
function update_board_view() {
$(".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 number_cell = $("#number_cell_" + i + "_" + j);
if (board[i][j] == 0) {
number_cell.css("width", "0px");
number_cell.css("height", "0px");
number_cell.css("top", get_pos_top(i, j) + cell_side_length / 2);
number_cell.css("left", get_pos_left(i, j) + cell_side_length / 2);
} else {
number_cell.css("width", cell_side_length);
number_cell.css("height", cell_side_length);
number_cell.css("top", get_pos_top(i, j));
number_cell.css("left", get_pos_left(i, j));
number_cell.css(
"background-color",
get_number_background_color(board[i][j])
);
number_cell.css("color", get_number_color(board[i][j]));
number_cell.text(board[i][j]);
}
has_conflicted[i][j] = false;
}
}
$(".number_cell").css("line-height", cell_side_length + "px");
$(".number_cell").css("font-size", 0.6 * cell_side_length + "px");
}
// Gera um número em uma célula aleatória
function generate_one_number() {
if (nospace(board)) {
return false;
}
// Posição aleatória
var randx = parseInt(Math.floor(Math.random() * 4));
var randy = parseInt(Math.floor(Math.random() * 4));
var time = 0;
while (time < 50) {
if (board[randx][randy] == 0) {
break;
}
randx = parseInt(Math.floor(Math.random() * 4));
randy = parseInt(Math.floor(Math.random() * 4));
time++;
}
if (time == 50) {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (board[i][j] == 0) {
randx = i;
randy = j;
}
}
}
}
// Número aleatório
var rand_number = Math.random() < 0.5 ? 2 : 4;
// Exibe o número aleatório na posição aleatória
board[randx][randy] = rand_number;
show_number_with_animation(randx, randy, rand_number);
return true;
}
// Lida com a adaptabilidade
function prepare_for_mobile() {
if (document_width > 500) {
grid_container_width = 500;
cell_side_length = 100;
cell_space = 20;
}
$("#grid_container").css("width", grid_container_width - 2 * cell_space);
$("#grid_container").css("height", grid_container_width - 2 * cell_space);
$("#grid_container").css("padding", cell_space);
$("#grid_container").css("border-radius", 0.02 * grid_container_width);
$(".grid_cell").css("width", cell_side_length);
$(".grid_cell").css("height", cell_side_length);
$(".grid_cell").css("border-radius", 0.02 * grid_container_width);
}
Melhorar a Lógica do Jogo
Em seguida, precisamos melhorar a lógica do jogo, incluindo mover as peças com números e verificar se o jogo acabou. Complete o seguinte código em support.js:
document_width = window.screen.availWidth; // A largura da tela
grid_container_width = 0.92 * document_width; // A largura do tabuleiro do jogo
cell_side_length = 0.18 * document_width; // O tamanho de cada célula da grade
cell_space = 0.04 * document_width; // O espaço entre cada célula da grade
// Obtém a distância da célula da grade correspondente a partir do topo do tabuleiro do jogo
function get_pos_top(i, j) {
return cell_space + i * (cell_space + cell_side_length);
}
// Obtém a distância da célula da grade correspondente a partir do lado esquerdo do tabuleiro do jogo
function get_pos_left(i, j) {
return cell_space + j * (cell_space + cell_side_length);
}
// Obtém a cor de fundo do número correspondente
function get_number_background_color(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 8192:
return "#93c";
break;
}
return "black";
}
// Obtém a cor do número correspondente
function get_number_color(number) {
if (number <= 4) return "#776e65";
return "white";
}
// Verifica se há células de grade vazias no tabuleiro do jogo
function nospace(board) {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (board[i][j] == 0) {
return false;
}
}
}
return true;
}
Aprimorando os Efeitos de Animação
Em seguida, precisamos aprimorar os efeitos de animação, incluindo a exibição de peças com números e a atualização da pontuação. Este código está localizado em showanimation.js:
// Animação para exibir peças com números
function show_number_with_animation(i, j, rand_number) {
var number_cell = $("#number_cell_" + i + "_" + j);
number_cell.css("background-color", get_number_background_color(rand_number));
number_cell.css("color", get_number_color(rand_number));
number_cell.text(rand_number);
number_cell.animate(
{
width: cell_side_length,
height: cell_side_length,
top: get_pos_top(i, j),
left: get_pos_left(i, j)
},
50
);
}
// Atualizando a pontuação
function update_score(score) {
$("#score").text(score);
}
Mover as Peças com Números
Após concluir o layout e a inicialização, implementaremos agora a funcionalidade para mover e eliminar as peças com números até que o jogo seja bem-sucedido ou fracasse.
Adicione o seguinte código a main.js:
// Ouvir os movimentos das teclas de seta do teclado
$(document).keydown(function (event) {
if ($("#score").text() == success_string) {
new_game();
return;
}
switch (event.keyCode) {
case 37: // Esquerda
event.preventDefault();
if (move_left()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
break;
case 38: // Cima
event.preventDefault();
if (move_up()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
break;
case 39: // Direita
event.preventDefault();
if (move_right()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
break;
case 40: // Baixo
event.preventDefault();
if (move_down()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
break;
default:
break;
}
});
// Ouvir o evento touchstart em dispositivos móveis
document.addEventListener("touchstart", function (event) {
startx = event.touches[0].pageX;
starty = event.touches[0].pageY;
});
// Ouvir o evento touchmove em dispositivos móveis
document.addEventListener("touchmove", function (event) {
event.preventDefault();
});
// Ouvir o evento touchend em dispositivos móveis
document.addEventListener("touchend", function (event) {
endx = event.changedTouches[0].pageX;
endy = event.changedTouches[0].pageY;
var deltax = endx - startx;
var deltay = endy - starty;
if (
Math.abs(deltax) < 0.3 * document_width &&
Math.abs(deltay) < 0.3 * document_width
) {
return;
}
if ($("#score").text() == success_string) {
new_game();
return;
}
// Movimento no eixo x
if (Math.abs(deltax) >= Math.abs(deltay)) {
if (deltax > 0) {
// Mover para a direita
if (move_right()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
} else {
// Mover para a esquerda
if (move_left()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
}
} else {
// Movimento no eixo y
if (deltay > 0) {
// Mover para baixo
if (move_down()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
} else {
// Mover para cima
if (move_up()) {
setTimeout("generate_one_number()", 210);
setTimeout("is_gameover()", 300);
}
}
}
});
// Mover para a esquerda
function move_left() {
if (!can_move_left(board)) {
return false;
}
// Mover para a esquerda
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 && no_block_horizontal(i, k, j, board)) {
show_move_animation(i, j, i, k);
board[i][k] = board[i][j];
board[i][j] = 0;
break;
} else if (
board[i][k] == board[i][j] &&
no_block_horizontal(i, k, j, board) &&
!has_conflicted[i][k]
) {
show_move_animation(i, j, i, k);
board[i][k] += board[i][j];
board[i][j] = 0;
// Adicionar pontuação
score += board[i][k];
update_score(score);
has_conflicted[i][k] = true;
break;
}
}
}
}
}
setTimeout("update_board_view()", 200);
return true;
}
// Mover para a direita
function move_right() {
if (!can_move_right(board)) {
return false;
}
// Mover para a direita
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 && no_block_horizontal(i, j, k, board)) {
show_move_animation(i, j, i, k);
board[i][k] = board[i][j];
board[i][j] = 0;
break;
} else if (
board[i][k] == board[i][j] &&
no_block_horizontal(i, j, k, board) &&
!has_conflicted[i][k]
) {
show_move_animation(i, j, i, k);
board[i][k] += board[i][j];
board[i][j] = 0;
// Adicionar pontuação
score += board[i][k];
update_score(score);
has_conflicted[i][k] = true;
break;
}
}
}
}
}
setTimeout("update_board_view()", 200);
return true;
}
// Mover para cima
function move_up() {
if (!can_move_up(board)) {
return false;
}
// Mover para cima
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 && no_block_vertical(j, k, i, board)) {
show_move_animation(i, j, k, j);
board[k][j] = board[i][j];
board[i][j] = 0;
break;
} else if (
board[k][j] == board[i][j] &&
no_block_vertical(j, k, i, board) &&
!has_conflicted[k][j]
) {
show_move_animation(i, j, k, j);
board[k][j] += board[i][j];
board[i][j] = 0;
// Adicionar pontuação
score += board[k][j];
update_score(score);
has_conflicted[k][j] = true;
break;
}
}
}
}
}
setTimeout("update_board_view()", 200);
return true;
}
// Mover para baixo
function move_down() {
if (!can_move_down(board)) {
return false;
}
// Mover para baixo
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 && no_block_vertical(j, i, k, board)) {
show_move_animation(i, j, k, j);
board[k][j] = board[i][j];
board[i][j] = 0;
break;
} else if (
board[k][j] == board[i][j] &&
no_block_vertical(j, i, k, board) &&
!has_conflicted[k][j]
) {
show_move_animation(i, j, k, j);
board[k][j] += board[i][j];
board[i][j] = 0;
// Adicionar pontuação
score += board[k][j];
update_score(score);
has_conflicted[k][j] = true;
break;
}
}
}
}
}
setTimeout("update_board_view()", 200);
return true;
}
// Verificar se o jogo foi bem-sucedido ou fracassou
function is_gameover() {
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (board[i][j] == 2048) {
update_score(success_string);
return;
}
}
}
if (nospace(board) && nomove(board)) {
gameover();
}
}
// Atualizar o texto de fim de jogo quando o jogo termina
function gameover() {
update_score(gameover_string);
}
Melhorar support.js
Em seguida, precisamos melhorar o código em support.js, incluindo a verificação se é possível mover ou ainda é possível mover.
// Verificar se pode mover para a esquerda
function can_move_left(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] == board[i][j - 1]) {
return true;
}
}
}
}
return false;
}
// Verificar se pode mover para a direita
function can_move_right(board) {
for (var i = 0; i < 4; i++) {
for (var j = 2; j >= 0; j--) {
if (board[i][j] != 0) {
if (board[i][j + 1] == 0 || board[i][j] == board[i][j + 1]) {
return true;
}
}
}
}
return false;
}
// Verificar se pode mover para cima
function can_move_up(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;
}
// Verificar se pode mover para baixo
function can_move_down(board) {
for (var j = 0; j < 4; j++) {
for (var i = 2; i >= 0; i--) {
if (board[i][j] != 0) {
if (board[i + 1][j] == 0 || board[i + 1][j] == board[i][j]) {
return true;
}
}
}
}
return false;
}
// Verificar se não há blocos na direção horizontal
function no_block_horizontal(row, col1, col2, board) {
for (var i = col1 + 1; i < col2; i++) {
if (board[row][i] != 0) {
return false;
}
}
return true;
}
// Verificar se não há blocos na direção vertical
function no_block_vertical(col, row1, row2, board) {
for (var i = row1 + 1; i < row2; i++) {
if (board[i][col] != 0) {
return false;
}
}
return true;
}
// Verificar se ainda pode mover
function nomove(board) {
if (
can_move_down(board) ||
can_move_up(board) ||
can_move_right(board) ||
can_move_left(board)
) {
return false;
}
return true;
}
Completar o Jogo
Finalmente, precisamos completar o código em showanimation.js, incluindo a exibição da animação de movimento e da animação de mesclagem.
// Efeito de animação quando uma grade se move
function show_move_animation(fromx, fromy, tox, toy) {
var number_cell = $("#number_cell_" + fromx + "_" + fromy);
number_cell.animate(
{
top: get_pos_top(tox, toy),
left: get_pos_left(tox, toy)
},
200
);
}
Com isso, nossa versão web do 2048 está completa.
Execução e Testes
Abra index.html em um navegador web.

Para ver os seguintes efeitos, clique no botão Go Live no canto inferior direito do WebIDE e mude para a aba "Web 8080".

Resumo
Neste projeto, implementamos uma versão web do jogo 2048 usando HTML, CSS, JavaScript e jQuery. Também aprendemos como torná-lo compatível com dispositivos móveis. Acredita-se que, através deste projeto, você pode aprofundar sua compreensão da tecnologia front-end e aprimorar suas habilidades de aplicação abrangentes.



