<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇小游戏</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="gameCanvas" width="800" height="800"></canvas>
<button id="restartButton" style="display: none;" onclick="restartGame()">重新开始</button>
<script src="index.js"></script>
</body>
</html>
index.html
style.css
index.js
现在支持上传本地图片了!
body {
display: flex;/*设置元素的显示类型为弹性盒子(Flexbox)*/
flex-direction: column;/*设置主轴的方向(行或列),使子元素沿着垂直方向(主轴变为垂直方向)排列。*/
justify-content: center;/*设置弹性项目在主轴上的对齐方式*/
align-items: center;/*设置弹性项目在交叉轴上的对齐方式*/
height: 100vh;/*用于设置元素的高度。vh(长度单位),代表“视口高度”(Viewport Height)的1%。将元素的高度设置为视口(通常是浏览器窗口的可见部分)高度的100%,即让元素的高度充满整个视口的高度。*/
margin: 0;
background-color: #acd8ec;
}
canvas {
border: 1px solid #9db326;/*为一个元素设置宽度为1像素、样式为实线、颜色为黑色的边框。*/
background-color: #e7eccf;
}
button {
display: none;/*该元素会从文档布局中完全消失,就像它从未存在过一样。它不会占据任何空间,也不会在页面上生成任何盒子(box model),并且其内部的子元素也会被一并隐藏,不会被渲染或显示。*/
margin-top: 20px; /*上边距为20像素*/
padding: 10px 20px;/* 内边距:上下内边距为10像素,左右内边距为20像素 */
font-size: 20px;
font-weight: bold;/* 使按钮内的文本加粗 */
background-color: #ecee80;
border-radius: 5px; /* 边框圆角 */
cursor: pointer;/* 鼠标悬停时显示手指形状,表示可点击 */
position: absolute;/*用于设置一个元素的定位方式为绝对定位*/
/*
绝对定位有几个特点:
1、脱离文档流:元素不再占据其在文档流中的原始空间,其后的元素会填补其留下的空间。
2、相对于最近的已定位祖先元素定位:如果没有这样的祖先元素,则相对于初始包含块(通常是视口或HTML文档的根元素)。
3、不改变其他元素的布局:除了不占据原本的空间外,它也不会影响到其他元素的布局。
*/
}
const canvas = document.getElementById("gameCanvas");//获取 Canvas 元素及其上下文
const ctx = canvas.getContext("2d");
const restartButton = document.getElementById("restartButton");
const gridSize = 20; // 设置每个格子的大小
const rows = Math.floor(canvas.height / gridSize);
const cols = Math.floor(canvas.width / gridSize);
let snake = [];
let food = {};
let direction = { x: 1, y: 0 };
let lastDirection = { ...direction };
let gameOver = false;
function initGame() {
snake = [{ x: Math.floor(cols / 2), y: Math.floor(rows / 2) }, { x: Math.floor(cols / 2) + 1, y: Math.floor(rows / 2) }];
food = {
x: Math.floor(Math.random() * cols),
y: Math.floor(Math.random() * rows),
};
direction = { x: 1, y: 0 };
lastDirection = { x: 1, y: 0 };
gameOver = false;
restartButton.style.display = "none";
gameLoop();
}
function drawCell(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
ctx.strokeRect(x * gridSize, y * gridSize, gridSize, gridSize);
}
function drawSnake() {
snake.forEach((part) => drawCell(part.x, part.y, "#409EFF"));
}
function drawFood() {
drawCell(food.x, food.y, "#E6A23C");
}
function updateSnake() {
const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
if (head.x === food.x && head.y === food.y) {
food = {
x: Math.floor(Math.random() * cols),
y: Math.floor(Math.random() * rows),
};
} else {
snake.pop();
}
snake.unshift(head);
if (
head.x < 0 ||
head.x >= cols ||
head.y < 0 ||
head.y >= rows ||
snake.slice(1).some((part) => part.x === head.x && part.y === head.y)
) {
gameOver = true;
}
lastDirection = direction;
}
function gameLoop() {
if (!gameOver) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFood();
updateSnake();
drawSnake();
setTimeout(gameLoop, 100);
} else {
ctx.font = "100px Arial";
ctx.fillStyle = "#F56C6C";
ctx.fillText("Game Over", canvas.width / 6, canvas.height / 2.5);
restartButton.style.display = "block";
}
}
window.addEventListener("keydown", (event) => {
switch (event.key) {
case "ArrowUp":
if (lastDirection.y === 0) direction = { x: 0, y: -1 };
break;
case "ArrowDown":
if (lastDirection.y === 0) direction = { x: 0, y: 1 };
break;
case "ArrowLeft":
if (lastDirection.x === 0) direction = { x: -1, y: 0 };
break;
case "ArrowRight":
if (lastDirection.x === 0) direction = { x: 1, y: 0 };
break;
}
});
function restartGame() {
initGame();
}
restartButton.addEventListener("click", restartGame);
initGame();
预览