贪吃蛇网页小游戏
贪吃蛇网页小游戏
git 远程作业仓库
这是远程作业仓库,目前是已经到了课程尾声、集大成的版本,刚开始开发的时候用w1-starter这个branch。 我自己做的版本在这里。
刚打开starter分支,发现里面有经典3剑客,js html css。
CSS 文件的作用是为一个游戏界面定义样式,每个样式规则的功能如下:
- body:
- 设置整个页面为 100% 的视窗高度(
100vh
)和宽度(100vw
),保证页面内容填满整个视窗。 - 使用
display: flex
来让内容居中,justify-content: center
和align-items: center
实现水平和垂直居中对齐。 - 去除页面默认的外边距(
margin: 0
),并设置灰色背景(background-color: gray
)。
- 设置整个页面为 100% 的视窗高度(
- #game-board:
- 游戏主界面,黑色背景,宽度和高度都设置为
100vmin
,保证在宽高变化时保持正方形。 - 使用
display: grid
创建 21×21 的网格布局,以便在每一单元格中放置游戏元素(如蛇和食物)。
- 游戏主界面,黑色背景,宽度和高度都设置为
- .snake:
- 为蛇的样式定义绿色背景(
background-color: lime
),以及一个黑色边框,边框宽度设置为0.25vmin
,确保在不同屏幕大小下视觉效果保持一致。
- 为蛇的样式定义绿色背景(
- .food:
- 定义食物的样式,背景为红色,边框颜色为灰色,宽度同样为
0.25vmin
,保证和蛇的单元格大小一致,且具有视觉对比。
- 定义食物的样式,背景为红色,边框颜色为灰色,宽度同样为
这个样式文件简洁明了,且响应式很好,适用于基于 CSS 网格布局的简易游戏界面。
测试实例:更改js文件,使得console.log 可以用interval函数进行更新显示。
html文件中加入<script src=”game.js”>
,得以应用js的脚本。
step1
snake.js:用数组,(x: 10,y:10),来表示蛇的每一个关节,然后pop去掉最后一个关节,复制一个头,unshift这个新头到数组第一个。
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
// TODO 1.1: Create the snake
const snakeBody = [
{x:11 , y:11},
{x:11 , y:10},
{x:11 , y:9},
]
// TODO 1.2: Create a function that updates the snake
// Hint: Search for the documentation for the Array pop() and unshift() methods
// in the MDN docs.
const updateSnake = () => {
snakeBody.pop();
const newHead = {...snakeBody[0]};
// ...,展开运算符,是复制snakeBody[0]的内容。不加...,复制的就是引用。
newHead.x += 0;
newHead.y += 1;
snakeBody.unshift(newHead);
}
// Don't change this function!
const drawSnake = (gameBoard) => {
for (let i = 0; i < snakeBody.length; i++) {
const segment = snakeBody[i];
const snakeElement = document.createElement('div');
snakeElement.style.gridRowStart = segment.y;
snakeElement.style.gridColumnStart = segment.x;
snakeElement.classList.add('snake');
gameBoard.appendChild(snakeElement);
}
};
game.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const SNAKE_SPEED = 5;
const gameBoard = document.getElementById('game-board');
const main = () => {
update();
draw();
};
setInterval(main, 1000 / SNAKE_SPEED);
const update = () => {
console.log('Updating');
// TODO 1.3: Update the snake here
updateSnake();
};
const draw = () => {
gameBoard.innerHTML = '';
drawSnake(gameBoard);
};
做好了1.1~1.3,刷新页面, 贪吃蛇一共有3个关节,无限向下移动。
step2
input.js:处理用户输入,按下不同方向键,向不同方向移动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 === 'ArrowLeft'&& inputDirection.y !== 0){
inputDirection = {x: -1, y: 0 };
} else if (event.key === 'ArrowRight'&& inputDirection.y !== 0){
inputDirection = {x: 1, y: 0 };
}
})
//获取用户按下的方向键所代表的当前的方向
const getInputDirection = () =>{
return inputDirection;
}
进入上一步中改变移动的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const updateSnake = () => {
// Remove tail segment
snakeBody.pop();
// Add new head segment
const newHead = { ...snakeBody[0] };
// TODO 2.2: Set the new head's position using the user's inputs
const snakeDirection = getInputDirection();
newHead.x += snakeDirection.x;
newHead.y += snakeDirection.y;
snakeBody.unshift(newHead);
};
step3
food和蛇变大的逻辑
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
//此代码定义了处理网格上蛇游戏各个方面的函数,
//包括检查某个位置是否被蛇占据、生成食物位置以及让蛇生长。
//游戏的网格大小。此处的网格为 21x21,这意味着位置范围从 (1,1) 到 (21,21)。
const GRID_SIZE = 21;
//onSnake:检查给定的position(具有x和y坐标)当前是否被蛇的任何部分占据。
const onSnake = (position) => {
for (let i = 0; i < snakeBody.length; i++) {
if (equalPositions(position, snakeBody[i])) {
return true;
}
}
return false;
};
const equalPositions = (pos1, pos2) => {
return pos1.x === pos2.x && pos1.y === pos2.y;
};
//growSnake:复制蛇的最后一段,加到array尾部。
const growSnake = () => {
snakeBody.push({ ...snakeBody[snakeBody.length - 1] });
};
//getNewFoodPosition:找到蛇尚未占据的新食物位置。
const getNewFoodPosition = () => {
let randomFoodPosition = randomGridPosition();
while (onSnake(randomFoodPosition)) {
randomFoodPosition = randomGridPosition();
}
return randomFoodPosition;
};
//Math.random生成 1 到 21 之间的随机x和y值
const randomGridPosition = () => {
return {
x: Math.floor(Math.random() * GRID_SIZE) + 1,
y: Math.floor(Math.random() * GRID_SIZE) + 1,
};
};
food:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// TODO 3.1: Create the food
let food = {
x: 5,
y: 5,
};
// TODO 3.2: Create a function to update the food
const updateFood = () =>{
if (onSnake(food)){
growSnake();
food = getNewFoodPosition();
}
};
// Don't change me!
const drawFood = (gameBoard) => {
const foodElement = document.createElement('div');
foodElement.style.gridRowStart = food.y;
foodElement.style.gridColumnStart = food.x;
foodElement.classList.add('food');
gameBoard.appendChild(foodElement);
};
step4
增加游戏逻辑。
蛇的逻辑更新:
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
//此代码定义了用于处理蛇的边界检查和自碰撞检测的函数
//出界
const outOfBounds = (position) => {
return position.x < 1 || position.x > GRID_SIZE || position.y < 1 || position.y > GRID_SIZE;
}
const snakeOutOfBounds = () => {
return outOfBounds(snakeBody[0]);
}
//检查蛇是否与自身发生碰撞
//工作原理:
//从索引 1 开始循环遍历蛇的身体各个部分(以避免将头部与自身进行比较)。
//使用该equalPositions函数检查头部(snakeBody[0])是否与任何其他段位于同一位置。
//true如果检测到碰撞则返回,否则返回false。
//此功能对于实现检测自碰撞的游戏逻辑至关重要,自碰撞通常会导致游戏结束。
const snakeIntersectSelf = () => {
for (let i = 1; i < snakeBody.length; i++) {
if (equalPositions(snakeBody[0], snakeBody[i])) {
return true;
}
}
return false;
}
游戏结束逻辑:
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
const SNAKE_SPEED = 5;
let gameOver = false;
const gameBoard = document.getElementById('game-board');
const main = () => {
update();
draw();
// TODO 4.3, 4.4: Add Game Over Alert, and clear interval!
if(gameOver){
alert("Game Over!!");
clearInterval(gameloop);
}
};
// TODO 4.4: Define the interval ID
// HINT: ONLY EDIT THE LINE BELOW!
let gameloop = setInterval(main, 1000 / SNAKE_SPEED);
const update = () => {
console.log('Updating');
updateSnake();
updateFood();
// TODO 4.2: Update Game State
gameOver = checkGameOver();
};
const draw = () => {
gameBoard.innerHTML = '';
drawSnake(gameBoard);
drawFood(gameBoard);
};
// TODO 4.1: Create a function that checks if the game is over
const checkGameOver = () =>{
return snakeOutOfBounds() || snakeIntersectSelf();
}
目前已经完成一遍的游戏,直到游戏结束,弹出窗口:游戏结束。
往后需要的工作:实现重启游戏的逻辑。
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
// 在文件开头添加初始状态常量
const INITIAL_SNAKE_BODY = [
{ x: 11, y: 11 },
{ x: 11, y: 10 },
{ x: 11, y: 9 },
];
// 修改 main 函数中的游戏结束处理
const main = () => {
update();
draw();
if(gameOver){
clearInterval(gameloop);
if(confirm("游戏结束!是否重新开始?")) {
resetGame();
}
}
};
// 添加重置游戏的函数
const resetGame = () => {
gameOver = false;
inputDirection = { x: 0, y: 1 }; // 重置蛇的移动方向
// 重置蛇的身体
snakeBody.length = 0;
INITIAL_SNAKE_BODY.forEach(segment => snakeBody.push({...segment}));
// 重置食物位置
food = getNewFoodPosition();
// 重新开始游戏循环
gameloop = setInterval(main, 1000 / SNAKE_SPEED);
};
This post is licensed under CC BY 4.0 by the author.