codex秒生成的小游戏--贪吃蛇
·
<!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>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #111;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Segoe UI', system-ui, sans-serif;
color: #e0e0e0;
}
.game-wrapper {
background: #1a1a2e;
border-radius: 16px;
padding: 24px;
box-shadow: 0 8px 40px rgba(0,0,0,0.6);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 0 4px;
}
.score-box {
font-size: 18px;
font-weight: 600;
letter-spacing: 0.3px;
}
.score-box span {
color: #f0c040;
}
.controls {
display: flex;
gap: 8px;
}
.controls button {
background: #2d2d4e;
border: none;
color: #ccc;
font-size: 14px;
padding: 6px 18px;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s;
font-family: inherit;
}
.controls button:hover { background: #3d3d5e; }
.controls button:active { background: #4d4d6e; }
canvas {
display: block;
background: #0f0f23;
border-radius: 10px;
width: 480px;
height: 480px;
image-rendering: pixelated;
}
.footer {
margin-top: 12px;
text-align: center;
font-size: 13px;
color: #666;
}
.footer kbd {
background: #2a2a3e;
padding: 2px 8px;
border-radius: 4px;
font-family: inherit;
font-size: 12px;
color: #aaa;
}
@media (max-width: 540px) {
.game-wrapper { padding: 16px; border-radius: 12px; }
canvas { width: calc(100vw - 64px); height: calc(100vw - 64px); }
}
</style>
</head>
<body>
<div class="game-wrapper">
<div class="header">
<div class="score-box">🍎 <span id="score">0</span></div>
<div class="controls">
<button id="restartBtn">重新开始</button>
</div>
</div>
<canvas id="gameCanvas" width="480" height="480"></canvas>
<div class="footer"><kbd>↑</kbd> <kbd>↓</kbd> <kbd>←</kbd> <kbd>→</kbd> 或 <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> 控制方向</div>
</div>
<script>
(function(){
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const GRID_SIZE = 20; // 20×20 网格
const CELL_SIZE = 24; // 480/20
const TICK_MS = 140;
let snake, direction, nextDirection, food, score, gameOver, win;
let timer = null;
// ---- 初始化 / 重置 ----
function init() {
snake = [
{ x: 10, y: 10 },
{ x: 9, y: 10 },
{ x: 8, y: 10 },
];
direction = { x: 1, y: 0 };
nextDirection = { x: 1, y: 0 };
score = 0;
gameOver = false;
win = false;
scoreEl.textContent = '0';
spawnFood();
draw();
}
// ---- 生成食物(不与蛇重叠) ----
function spawnFood() {
const occupied = new Set(snake.map(p => `${p.x},${p.y}`));
const free = [];
for (let y = 0; y < GRID_SIZE; y++)
for (let x = 0; x < GRID_SIZE; x++)
if (!occupied.has(`${x},${y}`)) free.push({ x, y });
if (free.length === 0) {
win = true;
gameOver = true;
food = null;
return;
}
food = free[Math.floor(Math.random() * free.length)];
}
// ---- 游戏逻辑 ----
function tick() {
if (gameOver) return;
direction = { ...nextDirection };
const head = snake[0];
const nx = head.x + direction.x;
const ny = head.y + direction.y;
// 撞墙
if (nx < 0 || nx >= GRID_SIZE || ny < 0 || ny >= GRID_SIZE) {
gameOver = true;
draw();
return;
}
// 撞自身
for (const seg of snake) {
if (seg.x === nx && seg.y === ny) {
gameOver = true;
draw();
return;
}
}
// 移动
snake.unshift({ x: nx, y: ny });
const ate = (food && nx === food.x && ny === food.y);
if (ate) {
score++;
scoreEl.textContent = score;
spawnFood();
} else {
snake.pop();
}
draw();
}
// ---- 绘制 ----
function draw() {
ctx.clearRect(0, 0, 480, 480);
// 网格线(很淡)
ctx.strokeStyle = 'rgba(255,255,255,0.03)';
ctx.lineWidth = 0.5;
for (let i = 0; i <= GRID_SIZE; i++) {
const p = i * CELL_SIZE;
ctx.beginPath(); ctx.moveTo(p, 0); ctx.lineTo(p, 480); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, p); ctx.lineTo(480, p); ctx.stroke();
}
// 食物
if (food) {
const fx = food.x * CELL_SIZE, fy = food.y * CELL_SIZE;
// 苹果渐变
const grad = ctx.createRadialGradient(fx+6, fy+6, 2, fx+12, fy+12, 14);
grad.addColorStop(0, '#f55');
grad.addColorStop(1, '#c33');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(fx + CELL_SIZE/2, fy + CELL_SIZE/2, CELL_SIZE/2 - 2, 0, Math.PI*2);
ctx.fill();
// 高光
ctx.fillStyle = 'rgba(255,255,255,0.25)';
ctx.beginPath();
ctx.arc(fx + 8, fy + 8, 3, 0, Math.PI*2);
ctx.fill();
}
// 蛇身
for (let i = snake.length - 1; i >= 0; i--) {
const seg = snake[i];
const sx = seg.x * CELL_SIZE, sy = seg.y * CELL_SIZE;
const pad = 1;
const radius = 4;
const t = i / Math.max(snake.length - 1, 1);
// 头部亮绿 → 尾部暗绿
const r = Math.round(30 + t * 20);
const g = Math.round(200 - t * 80);
const b = Math.round(80 - t * 40);
ctx.fillStyle = `rgb(${r},${g},${b})`;
// 圆角矩形
const x = sx + pad, y = sy + pad;
const w = CELL_SIZE - pad*2, h = CELL_SIZE - pad*2;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + w - radius, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
ctx.lineTo(x + w, y + h - radius);
ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
ctx.lineTo(x + radius, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
}
// 蛇眼睛(头部)
if (snake.length > 0 && !gameOver) {
const head = snake[0];
const hx = head.x * CELL_SIZE, hy = head.y * CELL_SIZE;
ctx.fillStyle = '#fff';
let e1x, e1y, e2x, e2y;
if (direction.x === 1) { e1x=hx+16; e1y=hy+6; e2x=hx+16; e2y=hy+16; }
else if (direction.x === -1) { e1x=hx+6; e1y=hy+6; e2x=hx+6; e2y=hy+16; }
else if (direction.y === 1) { e1x=hx+6; e1y=hy+16; e2x=hx+16; e2y=hy+16; }
else { e1x=hx+6; e1y=hy+6; e2x=hx+16; e2y=hy+6; }
ctx.beginPath(); ctx.arc(e1x, e1y, 3, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(e2x, e2y, 3, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#111';
ctx.beginPath(); ctx.arc(e1x, e1y, 1.5, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(e2x, e2y, 1.5, 0, Math.PI*2); ctx.fill();
}
// 游戏结束遮罩
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.55)';
ctx.fillRect(0, 0, 480, 480);
ctx.fillStyle = '#fff';
ctx.font = 'bold 28px system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const msg = win ? '🎉 你赢了!' : '💀 游戏结束';
ctx.fillText(msg, 240, 210);
ctx.font = '16px system-ui, sans-serif';
ctx.fillStyle = '#aaa';
ctx.fillText('点击「重新开始」再来一局', 240, 260);
}
}
// ---- 键盘控制 ----
function handleKey(e) {
const key = e.key;
if (['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','w','W','s','S','a','A','d','D'].includes(key))
e.preventDefault();
if (gameOver) return;
const opp = (d1, d2) => (d1.x === -d2.x && d1.y === -d2.y);
let nd = null;
if (key === 'ArrowUp' || key === 'w' || key === 'W') nd = { x: 0, y: -1 };
else if (key === 'ArrowDown' || key === 's' || key === 'S') nd = { x: 0, y: 1 };
else if (key === 'ArrowLeft' || key === 'a' || key === 'A') nd = { x: -1, y: 0 };
else if (key === 'ArrowRight' || key === 'd' || key === 'D') nd = { x: 1, y: 0 };
if (nd && !opp(nd, direction)) nextDirection = nd;
}
// ---- 启动/停止循环 ----
function startLoop() {
stopLoop();
timer = setInterval(tick, TICK_MS);
}
function stopLoop() {
if (timer) { clearInterval(timer); timer = null; }
}
function restart() {
init();
startLoop();
}
// ---- 绑定事件 ----
document.addEventListener('keydown', handleKey);
document.getElementById('restartBtn').addEventListener('click', restart);
// ---- 开始 ----
init();
startLoop();
})();
</script>
</body>
</html>
更多推荐


所有评论(0)