项目结构

1
2
3
4
5
6
7
8
9
10
catbook-react
├── food.js
├── game.js
├── index.html
├── input.js
├── README.md
├── snake.js
├── snakeUtils.js
└── style.css

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Snake</title>
<script src="snake.js"></script>
<script src="input.js"></script>
<script src="food.js"></script>
<script src="snakeUtils.js"></script>
<script src="game.js" defer></script>
</head>
<body>
<div id="game-board"></div>
</body>
</html>

代码说明:

  1. 基本结构
    • <head> 中引入了 style.css 来定义样式。
    • <div id="game-board"></div> 是游戏的主要区域。
  2. 脚本加载顺序
    • snake.jsinput.jsfood.jssnakeUtils.js 被同步加载。
    • game.js 使用了 defer 属性,延迟到 HTML 解析完成后才执行。

为什么使用 defer 属性?

  1. defer 会在 HTML 完全解析后执行脚本,避免阻塞页面的加载。
  2. 所有带 defer 的脚本会按照它们在 HTML 中的顺序依次执行。
  3. 如果脚本与 DOM 交互强相关(如 document.getElementById),defer 能保证脚本运行时 DOM 已加载完成。

使用 defer 的注意点:

  • 不建议与 async 混用。
  • 适合所有脚本对 DOM 操作的场景,特别是多个脚本间存在依赖关系时。

game.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const SNAKE_SPEED = 5;

const gameBoard = document.getElementById("game-board");
let isGameOver = false;

const main = () => {
update();
draw();
if (isGameOver) {
alert("Game Over");
resetGame();
}
};

let gameLoop = setInterval(main, 1000 / SNAKE_SPEED);

const update = () => {
console.log("Updating");
updateSnake();
updateFood();
isGameOver = checkGameOver();
};

const draw = () => {
gameBoard.innerHTML = "";
drawSnake(gameBoard);
drawFood(gameBoard);
};

const checkGameOver = () => {
return snakeOutOfBounds() || snakeIntersectSelf();
};

const resetGame = () => {
// Make sure the game loop is not still running
clearInterval(gameLoop);

// Reset the snake's position and direction
resetSnake();
resetDirection();

// Restart the interval
gameLoop = setInterval(main, 1000 / SNAKE_SPEED);
};

input.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let inputDirection = { x: 0, y: 1 };

window.addEventListener("keydown", (event) => {
if (event.key === "ArrowUp" && inputDirection.x !== 0) {
inputDirection = { x: 0, y: -1 };
} else if (event.key === "ArrowDown" && inputDirection.x !== 0) {
inputDirection = { x: 0, y: 1 };
} else if (event.key === "ArrowRight" && inputDirection.y !== 0) {
inputDirection = { x: 1, y: 0 };
} else if (event.key === "ArrowLeft" && inputDirection.y !== 0) {
inputDirection = { x: -1, y: 0 };
} else if (event.key === "r") {
resetGame();
}
});

const getInputDirection = () => {
return inputDirection;
};

const resetDirection = () => {
inputDirection = { x: 0, y: 1 };
};

// Create a “keydown” event listener
// (https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
// key has been pressed
// (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)
// List of key values:
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values