mysite
/ game.html
11056 字节
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>贪吃蛇小游戏</title>
<style>
:root{
--bg:#0b1220; --panel:#111c30; --line:rgba(148,163,184,.16);
--txt:#e2e8f0; --muted:#94a3b8; --accent:#34d399; --food:#fb7185;
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent;}
html,body{margin:0;padding:0;}
body{
min-height:100vh; background:radial-gradient(1200px 600px at 50% -10%,#16233d,#0b1220 60%);
color:var(--txt); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
padding:16px 14px 28px; gap:12px;
}
h1{font-size:20px; margin:6px 0 0; letter-spacing:.5px; font-weight:600;}
.hud{
width:100%; max-width:460px; display:flex; align-items:center; justify-content:space-between;
gap:10px; font-size:15px; color:var(--muted);
}
.hud b{color:var(--txt); font-size:18px; font-variant-numeric:tabular-nums;}
.board{
position:relative; width:min(92vw,440px); aspect-ratio:1/1;
border-radius:16px; overflow:hidden; background:#0f172a;
box-shadow:0 18px 40px rgba(0,0,0,.45), inset 0 0 0 1px var(--line);
touch-action:none;
}
canvas{display:block; width:100%; height:100%;}
.overlay{
position:absolute; inset:0; display:flex; flex-direction:column;
align-items:center; justify-content:center; gap:12px; text-align:center;
background:rgba(9,14,26,.82); backdrop-filter:blur(3px); padding:20px;
transition:opacity .15s ease;
}
.overlay.hidden{display:none;}
.overlay h2{margin:0; font-size:22px; font-weight:600;}
.overlay p{margin:0; font-size:14px; color:var(--muted); line-height:1.6; max-width:280px;}
button{
font:inherit; color:#04231a; background:var(--accent); border:0; border-radius:12px;
padding:11px 22px; font-weight:600; cursor:pointer; transition:transform .08s ease, filter .15s ease;
}
button:active{transform:scale(.97);}
button.ghost{background:#1e293b; color:var(--txt);}
.pad{
display:grid; grid-template-columns:repeat(3,60px); grid-template-rows:repeat(2,54px);
gap:8px; margin-top:2px;
}
.pad button{
background:#1b2740; color:var(--txt); border:1px solid var(--line);
border-radius:12px; font-size:20px; padding:0; display:flex; align-items:center; justify-content:center;
}
.pad button:active{background:#243455;}
.pad .up{grid-column:2; grid-row:1;}
.pad .left{grid-column:1; grid-row:2;}
.pad .down{grid-column:2; grid-row:2;}
.pad .right{grid-column:3; grid-row:2;}
.hint{margin:2px 0 0; font-size:12.5px; color:var(--muted); text-align:center; line-height:1.7; max-width:440px;}
.back{font-size:13px; color:var(--muted); text-decoration:none;}
.back:hover{color:var(--txt);}
</style>
</head>
<body>
<h1>贪吃蛇</h1>
<div class="hud">
<span>得分 <b id="score">0</b></span>
<span>最高 <b id="best">0</b></span>
</div>
<div class="board" id="board">
<canvas id="cv"></canvas>
<div class="overlay" id="overlay">
<h2 id="ovTitle">贪吃蛇</h2>
<p id="ovText">用方向键或滑动控制蛇,吃到食物得分,撞墙或撞到自己就结束。</p>
<button id="startBtn">开始游戏</button>
</div>
</div>
<div class="pad" id="pad">
<button class="up" data-dir="up" aria-label="上">▲</button>
<button class="left" data-dir="left" aria-label="左">◀</button>
<button class="down" data-dir="down" aria-label="下">▼</button>
<button class="right" data-dir="right" aria-label="右">▶</button>
</div>
<p class="hint">键盘:方向键 / WASD 移动,空格暂停。手机:在棋盘上滑动,或点下方方向键。</p>
<a class="back" href="index.html">← 返回首页</a>
<script>
(function () {
'use strict';
var COLS = 20, ROWS = 20;
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
var board = document.getElementById('board');
var scoreEl = document.getElementById('score');
var bestEl = document.getElementById('best');
var overlay = document.getElementById('overlay');
var ovTitle = document.getElementById('ovTitle');
var ovText = document.getElementById('ovText');
var startBtn = document.getElementById('startBtn');
var state = 'ready'; // ready | running | paused | over
var snake, dir, nextDir, food, score;
var best = 0;
var moveInterval = 130; // 每步毫秒数,随得分加快
var acc = 0, last = 0, rafId = 0;
var cell = 20, cssSize = 0;
try {
var stored = localStorage.getItem('snake_best');
if (stored) { best = parseInt(stored, 10) || 0; }
} catch (e) {}
bestEl.textContent = String(best);
function resize() {
var rect = board.getBoundingClientRect();
var size = Math.floor(Math.min(rect.width, rect.height));
if (!size || size < 80) { size = 300; }
var dpr = window.devicePixelRatio || 1;
cv.width = Math.floor(size * dpr);
cv.height = Math.floor(size * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
cssSize = size;
cell = size / COLS;
draw();
}
function reset() {
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;
moveInterval = 130;
acc = 0; last = 0;
scoreEl.textContent = '0';
placeFood();
draw();
}
function placeFood() {
var free = [];
for (var y = 0; y < ROWS; y++) {
for (var x = 0; x < COLS; x++) {
var hit = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].x === x && snake[i].y === y) { hit = true; break; }
}
if (!hit) { free.push({ x: x, y: y }); }
}
}
if (!free.length) { return; }
food = free[Math.floor(Math.random() * free.length)];
}
function setDir(d) {
if (d.x === -dir.x && d.y === -dir.y) { return; } // 不能直接反向
if (d.x === -nextDir.x && d.y === -nextDir.y) { return; } // 同一帧内不能连转两次
nextDir = { x: d.x, y: d.y };
}
function step() {
dir = nextDir;
var 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) { gameOver(); return; }
for (var i = 0; i < snake.length - 1; i++) {
if (snake[i].x === head.x && snake[i].y === head.y) { gameOver(); return; }
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score++;
scoreEl.textContent = String(score);
if (score > best) {
best = score;
bestEl.textContent = String(best);
try { localStorage.setItem('snake_best', String(best)); } catch (e) {}
}
moveInterval = Math.max(60, 130 - score * 4);
placeFood();
} else {
snake.pop();
}
}
function loop(ts) {
if (state !== 'running') { return; }
if (!last) { last = ts; }
var dt = ts - last;
last = ts;
acc += dt;
var guard = 0;
while (acc >= moveInterval && state === 'running') {
acc -= moveInterval;
step();
guard++;
if (guard > 8) { acc = 0; break; }
}
draw();
if (state === 'running') { rafId = requestAnimationFrame(loop); }
}
function showOverlay(title, text, btn) {
ovTitle.textContent = title;
ovText.textContent = text;
startBtn.textContent = btn;
overlay.classList.remove('hidden');
}
function hideOverlay() { overlay.classList.add('hidden'); }
function startGame() {
reset();
state = 'running';
hideOverlay();
last = 0; acc = 0;
rafId = requestAnimationFrame(loop);
}
function pauseGame() {
if (state !== 'running') { return; }
state = 'paused';
if (rafId) { cancelAnimationFrame(rafId); }
showOverlay('已暂停', '当前得分 ' + score + ',按空格或点下面的按钮继续。', '继续');
}
function resumeGame() {
if (state !== 'paused') { return; }
state = 'running';
hideOverlay();
last = 0; acc = 0;
rafId = requestAnimationFrame(loop);
}
function gameOver() {
state = 'over';
if (rafId) { cancelAnimationFrame(rafId); }
draw();
showOverlay('游戏结束', '本局得分 ' + score + ',最高 ' + best + '。', '再玩一次');
}
startBtn.addEventListener('click', function () {
if (state === 'paused') { resumeGame(); }
else { startGame(); }
});
document.addEventListener('keydown', function (e) {
var k = e.key;
var d = null;
if (k === 'ArrowUp' || k === 'w' || k === 'W') { d = { x: 0, y: -1 }; }
else if (k === 'ArrowDown' || k === 's' || k === 'S') { d = { x: 0, y: 1 }; }
else if (k === 'ArrowLeft' || k === 'a' || k === 'A') { d = { x: -1, y: 0 }; }
else if (k === 'ArrowRight' || k === 'd' || k === 'D') { d = { x: 1, y: 0 }; }
else if (k === ' ' || k === 'Spacebar') {
e.preventDefault();
if (state === 'running') { pauseGame(); }
else if (state === 'paused') { resumeGame(); }
else { startGame(); }
return;
}
if (d) {
if (k.indexOf('Arrow') === 0) { e.preventDefault(); }
if (state === 'ready' || state === 'over') { startGame(); }
if (state === 'running') { setDir(d); }
}
});
var DIRS = { up: { x: 0, y: -1 }, down: { x: 0, y: 1 }, left: { x: -1, y: 0 }, right: { x: 1, y: 0 } };
Array.prototype.forEach.call(document.querySelectorAll('.pad button'), function (btn) {
btn.addEventListener('click', function (e) {
e.preventDefault();
var d = DIRS[btn.getAttribute('data-dir')];
if (!d) { return; }
if (state === 'ready' || state === 'over') { startGame(); }
if (state === 'running') { setDir(d); }
});
});
var touchStart = null;
board.addEventListener('touchstart', function (e) {
if (e.touches.length === 1) {
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
}, { passive: true });
board.addEventListener('touchmove', function (e) { e.preventDefault(); }, { passive: false });
board.addEventListener('touchend', function (e) {
if (!touchStart) { return; }
var t = e.changedTouches[0];
var dx = t.clientX - touchStart.x;
var dy = t.clientY - touchStart.y;
touchStart = null;
var adx = Math.abs(dx), ady = Math.abs(dy);
if (Math.max(adx, ady) < 24) { return; }
if (state === 'ready' || state === 'over') { startGame(); }
if (state === 'running') {
if (adx > ady) { setDir({ x: dx > 0 ? 1 : -1, y: 0 }); }
else { setDir({ x: 0, y: dy > 0 ? 1 : -1 }); }
}
}, { passive: true });
window.addEventListener('resize', resize);
window.addEventListener('orientationchange', function () { setTimeout(resize, 200); });
reset();
resize();
showOverlay('贪吃蛇', '用方向键或滑动控制蛇,吃到食物得分,撞墙或撞到自己就结束。', '开始游戏');
})();
</script>
</body>
</html>