yuanbao-game / index.html
6107 字节
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🐍 贪吃蛇</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #0f0f1a;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    font-family: 'Courier New', monospace;
    color: #e0e0e0;
    user-select: none;
  }
  h1 { color: #e11d48; margin-bottom: 10px; font-size: 1.8rem; }
  .score-board {
    display: flex;
    gap: 30px;
    margin-bottom: 12px;
    font-size: 1.1rem;
  }
  .score-board span { color: #f59e0b; }
  #gameCanvas {
    border: 2px solid #e11d48;
    border-radius: 8px;
    background: #1a1a2e;
    box-shadow: 0 0 20px rgba(225,29,72,0.3);
  }
  .controls {
    margin-top: 14px;
    display: flex;
    gap: 10px;
  }
  button {
    background: #e11d48;
    color: white;
    border: none;
    padding: 8px 20px;
    border-radius: 6px;
    font-size: 1rem;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.2s;
  }
  button:hover { background: #be123c; }
  .tip {
    margin-top: 10px;
    font-size: 0.85rem;
    color: #888;
  }
  .overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.7);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    z-index: 10;
  }
  .overlay h2 { color: #e11d48; font-size: 2rem; margin-bottom: 10px; }
  .overlay p { color: #f59e0b; font-size: 1.2rem; margin-bottom: 20px; }
  .hidden { display: none !important; }
</style>
</head>
<body>
<h1>🐍 贪吃蛇</h1>
<div class="score-board">
  <div>得分: <span id="score">0</span></div>
  <div>最高: <span id="best">0</span></div>
</div>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="controls">
  <button onclick="startGame()">开始游戏</button>
</div>
<p class="tip">方向键 / WASD 控制方向 &nbsp;|&nbsp; 手机请用下方按钮</p>
<div style="margin-top:10px; display:flex; gap:6px; flex-wrap:wrap; justify-content:center; max-width:200px;">
  <button onclick="setDir('up')">⬆️</button>
  <button onclick="setDir('left')">⬅️</button>
  <button onclick="setDir('down')">⬇️</button>
  <button onclick="setDir('right')">➡️</button>
</div>

<div class="overlay" id="overlay">
  <h2 id="overlayTitle">🐍 贪吃蛇</h2>
  <p id="overlayMsg">准备开始!</p>
  <button onclick="startGame()">开始游戏</button>
</div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const GRID = 20;
const COLS = canvas.width / GRID;
const ROWS = canvas.height / GRID;

let snake, dir, nextDir, food, score, best, gameLoop, running;

best = parseInt(localStorage.getItem('snakeBest') || '0');
document.getElementById('best').textContent = best;

function init() {
  snake = [{x:10,y:10}];
  dir = {x:1,y:0};
  nextDir = {x:1,y:0};
  score = 0;
  document.getElementById('score').textContent = 0;
  spawnFood();
}

function spawnFood() {
  do {
    food = {x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS)};
  } while (snake.some(s => s.x===food.x && s.y===food.y));
}

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

  // grid
  ctx.strokeStyle = '#22223a';
  for(let i=0;i<=COLS;i++) {
    ctx.beginPath(); ctx.moveTo(i*GRID,0); ctx.lineTo(i*GRID,canvas.height); ctx.stroke();
  }
  for(let i=0;i<=ROWS;i++) {
    ctx.beginPath(); ctx.moveTo(0,i*GRID); ctx.lineTo(canvas.width,i*GRID); ctx.stroke();
  }

  // food
  ctx.fillStyle = '#e11d48';
  ctx.shadowColor = '#e11d48';
  ctx.shadowBlur = 10;
  ctx.beginPath();
  ctx.arc(food.x*GRID+GRID/2, food.y*GRID+GRID/2, GRID/2-2, 0, Math.PI*2);
  ctx.fill();
  ctx.shadowBlur = 0;

  // snake
  snake.forEach((seg, i) => {
    const brightness = 1 - i * 0.03;
    ctx.fillStyle = `rgba(${Math.floor(34*brightness)},${Math.floor(197*brightness)},${Math.floor(94*brightness)},1)`;
    ctx.shadowColor = '#22c55e';
    ctx.shadowBlur = i===0 ? 12 : 4;
    ctx.beginPath();
    ctx.roundRect(seg.x*GRID+1, seg.y*GRID+1, GRID-2, GRID-2, 4);
    ctx.fill();
  });
  ctx.shadowBlur = 0;
}

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

  // 穿墙
  head.x = (head.x+COLS)%COLS;
  head.y = (head.y+ROWS)%ROWS;

  // 撞自己
  if (snake.some(s => s.x===head.x && s.y===head.y)) {
    gameOver(); return;
  }

  snake.unshift(head);

  if (head.x===food.x && head.y===food.y) {
    score += 10;
    document.getElementById('score').textContent = score;
    spawnFood();
    // 加速
    clearInterval(gameLoop);
    const speed = Math.max(80, 150 - Math.floor(score/30)*5);
    gameLoop = setInterval(step, speed);
  } else {
    snake.pop();
  }
  draw();
}

function gameOver() {
  running = false;
  clearInterval(gameLoop);
  if (score > best) {
    best = score;
    localStorage.setItem('snakeBest', best);
    document.getElementById('best').textContent = best;
  }
  document.getElementById('overlayTitle').textContent = '💀 游戏结束';
  document.getElementById('overlayMsg').textContent = `得分: ${score} 分`;
  document.getElementById('overlay').classList.remove('hidden');
}

function startGame() {
  document.getElementById('overlay').classList.add('hidden');
  clearInterval(gameLoop);
  init();
  running = true;
  draw();
  gameLoop = setInterval(step, 150);
}

function setDir(d) {
  if (!running) return;
  const map = {
    up:    {x:0,y:-1},
    down:  {x:0,y:1},
    left:  {x:-1,y:0},
    right: {x:1,y:0}
  };
  const nd = map[d];
  if (nd && !(nd.x===-dir.x && nd.y===-dir.y)) {
    nextDir = nd;
  }
}

document.addEventListener('keydown', e => {
  const map = {
    ArrowUp:'up', w:'up', W:'up',
    ArrowDown:'down', s:'down', S:'down',
    ArrowLeft:'left', a:'left', A:'left',
    ArrowRight:'right', d:'right', D:'right',
  };
  if (map[e.key]) { e.preventDefault(); setDir(map[e.key]); }
});

// init
init();
draw();
</script>
</body>
</html>
扫码访问