Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 281 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇小游戏</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1e3c72, #2a5298);
font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
color: #fff;
padding: 20px;
}
h1 { margin-bottom: 10px; font-size: 28px; letter-spacing: 2px; }
.info {
display: flex;
gap: 30px;
margin-bottom: 12px;
font-size: 18px;
}
.info span { font-weight: bold; color: #ffd54f; }
canvas {
background: #0f1d3a;
border: 3px solid #ffd54f;
border-radius: 8px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
touch-action: none;
}
.controls {
margin-top: 16px;
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
button {
padding: 10px 22px;
font-size: 15px;
border: none;
border-radius: 6px;
background: #ffd54f;
color: #1e3c72;
font-weight: bold;
cursor: pointer;
transition: transform 0.15s, background 0.15s;
}
button:hover { background: #ffca28; transform: translateY(-2px); }
button:active { transform: translateY(0); }
.hint {
margin-top: 14px;
font-size: 13px;
opacity: 0.7;
text-align: center;
}
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 10;
}
.overlay.show { display: flex; }
.overlay h2 { font-size: 36px; margin-bottom: 12px; color: #ffd54f; }
.overlay p { font-size: 18px; margin-bottom: 20px; }
</style>
</head>
<body>
<h1>贪吃蛇</h1>
<div class="info">
分数: <span id="score">0</span>
最高: <span id="best">0</span>
</div>
<canvas id="game" width="400" height="400"></canvas>
<div class="controls">
<button id="startBtn">开始</button>
<button id="pauseBtn">暂停</button>
<button id="resetBtn">重置</button>
</div>
<div class="hint">使用方向键 / WASD 控制方向,空格暂停</div>

<div class="overlay" id="overlay">
<h2 id="overlayTitle">游戏结束</h2>
<p id="overlayText">点击「开始」再来一局</p>
</div>

<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlayTitle');
const overlayText = document.getElementById('overlayText');

const CELL = 20;
const COLS = canvas.width / CELL;
const ROWS = canvas.height / CELL;

let snake, dir, nextDir, food, score, best, running, timer, speed;

best = parseInt(localStorage.getItem('snake_best') || '0', 10);
bestEl.textContent = best;

function init() {
snake = [{ x: 10, y: 10 }, { x: 9, y: 10 }, { x: 8, y: 10 }];
dir = { x: 1, y: 0 };
nextDir = { x: 1, y: 0 };
score = 0;
speed = 130;
placeFood();
scoreEl.textContent = score;
draw();
}

function placeFood() {
while (true) {
const f = {
x: Math.floor(Math.random() * COLS),
y: Math.floor(Math.random() * ROWS),
};
if (!snake.some(s => s.x === f.x && s.y === f.y)) {
food = f;
return;
}
}
}

function step() {
if ((nextDir.x !== -dir.x || nextDir.y !== -dir.y)) {
dir = nextDir;
}
const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };

if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS) {
return gameOver();
}
if (snake.some(s => s.x === head.x && s.y === head.y)) {
return gameOver();
}

snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
scoreEl.textContent = score;
if (speed > 60) speed -= 2;
placeFood();
restartTimer();
} else {
snake.pop();
}
draw();
}

function draw() {
ctx.fillStyle = '#0f1d3a';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.strokeStyle = 'rgba(255, 255, 255, 0.04)';
for (let i = 1; i < COLS; i++) {
ctx.beginPath();
ctx.moveTo(i * CELL, 0);
ctx.lineTo(i * CELL, canvas.height);
ctx.stroke();
}
for (let i = 1; i < ROWS; i++) {
ctx.beginPath();
ctx.moveTo(0, i * CELL);
ctx.lineTo(canvas.width, i * CELL);
ctx.stroke();
}

ctx.fillStyle = '#ff5252';
ctx.beginPath();
ctx.arc(
food.x * CELL + CELL / 2,
food.y * CELL + CELL / 2,
CELL / 2 - 2, 0, Math.PI * 2
);
ctx.fill();

snake.forEach((seg, i) => {
ctx.fillStyle = i === 0 ? '#ffd54f' : `hsl(${50 + i * 4}, 80%, ${60 - Math.min(i, 20)}%)`;
ctx.fillRect(seg.x * CELL + 1, seg.y * CELL + 1, CELL - 2, CELL - 2);
});
}

function gameOver() {
running = false;
clearInterval(timer);
if (score > best) {
best = score;
localStorage.setItem('snake_best', String(best));
bestEl.textContent = best;
overlayTitle.textContent = '新纪录!';
} else {
overlayTitle.textContent = '游戏结束';
}
overlayText.textContent = `本局得分 ${score} 分`;
overlay.classList.add('show');
}

function restartTimer() {
clearInterval(timer);
timer = setInterval(step, speed);
}

function start() {
if (running) return;
overlay.classList.remove('show');
if (!snake) init();
running = true;
restartTimer();
}

function pause() {
if (!running) return;
running = false;
clearInterval(timer);
}

function reset() {
pause();
overlay.classList.remove('show');
init();
}

document.getElementById('startBtn').addEventListener('click', start);
document.getElementById('pauseBtn').addEventListener('click', pause);
document.getElementById('resetBtn').addEventListener('click', () => { reset(); start(); });

const keyMap = {
ArrowUp: { x: 0, y: -1 }, w: { x: 0, y: -1 }, W: { x: 0, y: -1 },
ArrowDown: { x: 0, y: 1 }, s: { x: 0, y: 1 }, S: { x: 0, y: 1 },
ArrowLeft: { x: -1, y: 0 }, a: { x: -1, y: 0 }, A: { x: -1, y: 0 },
ArrowRight: { x: 1, y: 0 }, d: { x: 1, y: 0 }, D: { x: 1, y: 0 },
};

window.addEventListener('keydown', (e) => {
if (e.key === ' ') {
e.preventDefault();
running ? pause() : start();
return;
}
const d = keyMap[e.key];
if (d) {
e.preventDefault();
nextDir = d;
}
});

let touchStart = null;
canvas.addEventListener('touchstart', (e) => {
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
});
canvas.addEventListener('touchend', (e) => {
if (!touchStart) return;
const dx = e.changedTouches[0].clientX - touchStart.x;
const dy = e.changedTouches[0].clientY - touchStart.y;
if (Math.abs(dx) > Math.abs(dy)) {
nextDir = dx > 0 ? { x: 1, y: 0 } : { x: -1, y: 0 };
} else {
nextDir = dy > 0 ? { x: 0, y: 1 } : { x: 0, y: -1 };
}
touchStart = null;
});

init();
})();
</script>
</body>
</html>
Loading