JavaScript 设计2048游戏
在这篇文章中,你将学习用JavaScript构建著名的2048游戏。这篇文章旨在培养问题解决和逻辑思维能力,因此将专注于JavaScript。我们将看到如何访问DOM元素并在用户与网页交互时多次更新它们,以创建一个交互式应用程序,即2048游戏。如果你还没玩过这个游戏,建议先玩一下,以熟悉一下。你可以很容易地在你的智能手机上安装它。
如何玩这个游戏
- 你将有一个4×4的矩阵,其中两个随机选择的单元格将其值设为2。
- 使用箭头键将所有单元格移动到相应的方向
- 当单元格朝一个特定方向移动时,相同的值将相加合并为一个单元格,不同值的单元格将只是朝这个特定方向移动,如果有空的单元格。
- 随着游戏的进行,分配的值的大小将增加。此外,空的单元格将随机分配随机值。所有的值都将是2的某个幂。
- 你必须聪明地进行组合,使你达到2048。这是胜利条件。
Demo:
编程方法
- 首先,使用CSS网格创建一个4×4的矩阵。每个单元格都是一个带有文本的HTML div 标签,文本使用 p 标签包裹。
- 在JavaScript中定义一个函数,随机选择一个单元格并给它赋值。
- 在moveBlocks函数中使用JavaScript事件监听器来响应键盘按键事件,如箭头键,并根据箭头指示的方向移动单元格。
- 为包含不同值的单元格以及空单元格分配不同的颜色。
- 创建shift(direction)和move(direction)函数,分别用于移动值和合并相同的值。
- 检查单元格中的最大值,并使用gameOver函数在最大值达到2048时显示获胜对话框,在没有空单元格且最大单元格值未达到2048时显示失败对话框。
项目结构
完整代码
Javascript
// Filename: script.js
// Get the game grid
const gridItems = [
...document.querySelectorAll(".grid-item"),
];
const score_val = document.querySelector(".score-value");
const result = document.querySelector(".result");
let score = 0;
let moves = 0;
let moveFactor = 4;
let options = [
2, 4, 8, 2, 4, 8, 2, 2, 4, 4, 2, 8, 2, 2, 4, 4, 2,
];
let matrix = [];
let prevMatrix;
let colors = [
"#caf0f8",
"#90e0ef",
"#00b4d8",
"#0077b6",
"#03045e",
"#023047",
"#fca311",
"#14213d",
"#e63946",
"#ffc300",
"#6a040f",
"#000000",
];
// Create the starting game grid.
let row = [];
for (let index = 1; index < gridItems.length + 1; index++) {
if (index % 4 === 0) {
let item = gridItems[index - 1];
item.firstElementChild.innerText = "";
row.push(item);
matrix.push(row);
row = [];
} else {
let item = gridItems[index - 1];
item.firstElementChild.innerText = "";
row.push(item);
}
}
// Assign any two grid blocks the value of 2
const rowIdx = Math.floor(Math.random() * 4);
const colIdx = Math.floor(Math.random() * 4);
let rowIdx2 = Math.floor(Math.random() * 4);
let colIdx2 = Math.floor(Math.random() * 4);
if (rowIdx === rowIdx2 && colIdx === colIdx2) {
rowIdx2 = Math.floor(Math.random() * 4);
colIdx2 = Math.floor(Math.random() * 4);
}
matrix[rowIdx][colIdx].firstElementChild.textContent = 2;
matrix[rowIdx2][colIdx2].firstElementChild.textContent = 2;
let availIndexes = updateAvailIndexes();
updateColors();
// Make web page able to listen to keydown event
document.addEventListener("keydown", moveBlocks);
// Method to extract columns from an 2D array.
const arrayColumn = (arr, n) => arr.map((x) => x[n]);
function moveBlocks(e) {
if (
e.key !== "ArrowLeft" &&
e.key !== "ArrowRight" &&
e.key !== "ArrowUp" &&
e.key !== "ArrowDown"
) {
return;
}
moves++;
matrixVals = getCurrentMatrixValues();
prevMatrix = matrixVals;
let col1 = arrayColumn(matrix, 0);
let col2 = arrayColumn(matrix, 1);
let col3 = arrayColumn(matrix, 2);
let col4 = arrayColumn(matrix, 3);
let row1 = matrix[0];
let row2 = matrix[1];
let row3 = matrix[2];
let row4 = matrix[3];
if (e.key === "ArrowLeft") {
moveLeft(row1);
moveLeft(row2);
moveLeft(row3);
moveLeft(row4);
}
if (e.key === "ArrowRight") {
moveRight(row1);
moveRight(row2);
moveRight(row3);
moveRight(row4);
}
if (e.key === "ArrowUp") {
moveLeft(col1);
moveLeft(col2);
moveLeft(col3);
moveLeft(col4);
}
if (e.key === "ArrowDown") {
moveRight(col1);
moveRight(col2);
moveRight(col3);
moveRight(col4);
}
matrixVals = getCurrentMatrixValues();
availIndexes = updateAvailIndexes();
updateColors();
let check = checkMatrixEquality(prevMatrix, matrixVals);
if (availIndexes.length === 0 && check === true) {
gameOver("loose");
}
if (moves % moveFactor === 0) {
generateNewBlock();
}
}
setInterval(() => {
availIndexes = updateAvailIndexes();
generateNewBlock();
}, 8000);
setTimeout(() => {
options.push(16);
setTimeout(() => {
options.push(16);
options.push(32);
setTimeout(() => {
options.push(16);
options.push(32);
options.push(64);
}, 40000);
}, 18000);
}, 120000);
function getCurrentMatrixValues() {
let gridItems = [
...document.querySelectorAll(".grid-item"),
];
let matrix_grid = [];
let row = [];
for (
let index = 1;
index < gridItems.length + 1;
index++
) {
if (index % 4 === 0) {
let item = gridItems[index - 1];
row.push(item.firstElementChild.innerText);
matrix_grid.push(row);
row = [];
} else {
let item = gridItems[index - 1];
row.push(item.firstElementChild.innerText);
}
}
return matrix_grid;
}
function shiftLeft(arr) {
for (let i = 0; i < 4; i++) {
for (let i = 1; i < 4; i++) {
let currElement = arr[i].firstElementChild;
let prevElement = arr[i - 1].firstElementChild;
if (prevElement.innerText == 0) {
prevElement.innerText =
currElement.innerText;
currElement.innerText = "";
}
}
}
}
function shiftRight(arr) {
for (let i = 0; i < 4; i++) {
for (let i = 2; i >= 0; i--) {
let currElement = arr[i].firstElementChild;
let nextElement = arr[i + 1].firstElementChild;
if (nextElement.innerText == 0) {
nextElement.innerText =
currElement.innerText;
currElement.innerText = "";
}
}
}
}
function moveRight(row) {
shiftRight(row);
for (let i = 2; i >= 0; i--) {
let currElement = row[i].firstElementChild;
let nextElement = row[i + 1].firstElementChild;
let val = parseInt(currElement.innerText);
let nextVal = parseInt(nextElement.innerText);
if (val === nextVal && val !== 0) {
let newVal = val + nextVal;
nextElement.innerText = newVal;
currElement.innerText = "";
score = score + 2;
score_val.innerText = score;
if (newVal === 2048) {
gameOver("Win");
}
}
}
shiftRight(row);
}
function moveLeft(row) {
shiftLeft(row);
for (let i = 1; i < 4; i++) {
let currElement = row[i].firstElementChild;
let prevElement = row[i - 1].firstElementChild;
let val = parseInt(currElement.innerText);
let prevVal = parseInt(prevElement.innerText);
if (val === prevVal && val !== 0) {
let newVal = val + prevVal;
prevElement.innerText = newVal;
currElement.innerText = "";
score = score + 2;
score_val.innerText = score;
if (newVal === 2048) {
gameOver("Win");
}
}
}
shiftLeft(row);
}
function updateAvailIndexes() {
matrixValues = getCurrentMatrixValues();
let grid = [];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (matrixValues[i][j] == "") {
grid.push([i, j]);
}
}
}
return grid;
}
function generateNewBlock() {
if (availIndexes.length !== 0) {
let randInt = Math.floor(
Math.random() * availIndexes.length
);
let coords = availIndexes[randInt];
let randInt3 = Math.floor(
Math.random() * options.length
);
let ele =
matrix[coords[0]][coords[1]].firstElementChild;
ele.innerText = options[randInt3];
updateColors();
}
}
function checkMatrixEquality(mat1, mat2) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (mat1[i][j] !== mat2[i][j]) {
return false;
}
}
}
return true;
}
function gameOver(status) {
if (status === "Win") {
result.innerText = "You Won!!!";
result.style.color = "rgb(78, 236, 144)";
} else {
result.innerText = "You Loose!!!";
result.style.color = "rgb(252, 51, 51)";
}
}
function updateColors() {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
let elem = matrix[i][j].firstElementChild;
if (elem.innerText == 0) {
elem.parentElement.style.backgroundColor =
colors[0];
elem.style.color = "black";
} else if (elem.innerText == 2) {
elem.style.color = "black";
elem.parentElement.style.backgroundColor =
colors[1];
} else if (elem.innerText == 4) {
elem.style.color = "black";
elem.parentElement.style.backgroundColor =
colors[2];
} else if (elem.innerText == 8) {
elem.style.color = "black";
elem.parentElement.style.backgroundColor =
colors[3];
} else if (elem.innerText == 16) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[4];
} else if (elem.innerText == 32) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[5];
} else if (elem.innerText == 64) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[6];
} else if (elem.innerText == 128) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[7];
} else if (elem.innerText == 256) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[8];
} else if (elem.innerText == 512) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[9];
} else if (elem.innerText == 1024) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[10];
} else if (elem.innerText == 2048) {
elem.style.color = "white";
elem.parentElement.style.backgroundColor =
colors[11];
}
}
}
}
HTML
<!-- filename: index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible"
content="IE=edge" />
<meta name="viewport" content=
"width=device-width, initial-scale=1.0" />
<!-- Importing Google Fonts API Link -->
<link rel="preconnect"
href="https://fonts.googleapis.com" />
<link rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin />
<link href=
"https://fonts.googleapis.com/css2?family=Montserrat:wght@600&family=Ubuntu:wght@700&display=swap"
rel="stylesheet" />
<!-- Importing our CSS File where we
write all our styles -->
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="container">
<div class="score">
<p class="score-title">Score:</p>
<p class="score-value">0</p>
</div>
<div class="result"></div>
<div class="grid">
<div class="grid-item">
<p id="1"></p>
</div>
<div class="grid-item">
<p id="2"></p>
</div>
<div class="grid-item">
<p id="3"></p>
</div>
<div class="grid-item">
<p id="4"></p>
</div>
<div class="grid-item">
<p id="5"></p>
</div>
<div class="grid-item">
<p id="6"></p>
</div>
<div class="grid-item">
<p id="7"></p>
</div>
<div class="grid-item">
<p id="8"></p>
</div>
<div class="grid-item">
<p id="9"></p>
</div>
<div class="grid-item">
<p id="10"></p>
</div>
<div class="grid-item">
<p id="11"></p>
</div>
<div class="grid-item">
<p id="12"></p>
</div>
<div class="grid-item">
<p id="13"></p>
</div>
<div class="grid-item">
<p id="14"></p>
</div>
<div class="grid-item">
<p id="15"></p>
</div>
<div class="grid-item">
<p id="16"></p>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS
/* filename style.css */
:root {
--primary-text-color: #27374d;
}
* {
box-sizing: border-box;
padding: 0%;
margin: 0%;
font-family: "Montserrat", sans-serif;
font-family: "Ubuntu", sans-serif;
}
body {
background-color: #dda15e;
color: var(--primary-text-color);
min-height: 100vh;
}
.container {
width: 400px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.score {
width: 100%;
display: flex;
align-items: center;
justify-content: space-evenly;
flex-wrap: wrap;
font-size: 2rem;
text-align: center;
padding: 10px;
}
.score-title {
width: 50%;
}
.score-value {
width: 50%;
}
.result {
font-size: 2rem;
text-align: center;
padding: 10px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr;
width: 400px;
height: 400px;
background-color: rgb(209, 207, 207);
box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.753);
}
.grid-item {
border: 1px solid var(--primary-text-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.rules {
border: 2px solid white;
padding: 20px;
}
.rules>h1 {
width: 100%;
font-size: 2rem;
text-align: center;
padding: 10px;
}
.rules-para {
padding: 20px 10px;
}
输出: