# ===== CodeLab: game-js-snake =====
# 以下代码片段按文章出现顺序拼接, 共 6 段
# ----- 片段 1 (html) -----
# ----- 片段 2 (javascript) -----
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const SIZE = 20;
const COLS = 20, ROWS = 20;
let snake = [{ x: 10, y: 10 }];
let dir = { x: 1, y: 0 };
let nextDir = { x: 1, y: 0 };
let food = { x: 15, y: 10 };
let score = 0;
function tick() {
dir = nextDir;
const head = {
x: snake[0].x + dir.x,
y: snake[0].y + dir.y
};
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
document.getElementById('score').textContent = '得分:' + score;
spawnFood();
} else {
snake.pop();
}
draw();
}
function draw() {
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#e94560';
ctx.fillRect(food.x * SIZE, food.y * SIZE, SIZE, SIZE);
ctx.fillStyle = '#4ecca3';
snake.forEach(seg => {
ctx.fillRect(seg.x * SIZE, seg.y * SIZE, SIZE - 1, SIZE - 1);
});
}
# ----- 片段 3 (javascript) -----
let running = false;
let timer = null;
function tick() {
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(seg => seg.x === head.x && seg.y === head.y)) {
return gameOver();
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
document.getElementById('score').textContent = '得分:' + score;
spawnFood();
} else {
snake.pop();
}
draw();
}
function gameOver() {
clearInterval(timer);
running = false;
document.getElementById('score').textContent = '游戏结束!得分:' + score;
}
function spawnFood() {
while (true) {
const f = {
x: Math.floor(Math.random() * COLS),
y: Math.floor(Math.random() * ROWS)
};
if (!snake.some(seg => seg.x === f.x && seg.y === f.y)) {
food = f;
return;
}
}
}
# ----- 片段 4 (javascript) -----
document.addEventListener('keydown', e => {
if (e.key === 'ArrowUp' && dir.y !== 1) nextDir = { x: 0, y: -1 };
if (e.key === 'ArrowDown' && dir.y !== -1) nextDir = { x: 0, y: 1 };
if (e.key === 'ArrowLeft' && dir.x !== 1) nextDir = { x: -1, y: 0 };
if (e.key === 'ArrowRight' && dir.x !== -1) nextDir = { x: 1, y: 0 };
});
let touchStart = null;
canvas.addEventListener('touchstart', e => {
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
});
canvas.addEventListener('touchmove', e => {
e.preventDefault();
if (!touchStart) return;
const dx = e.touches[0].clientX - touchStart.x;
const dy = e.touches[0].clientY - touchStart.y;
if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return;
if (Math.abs(dx) > Math.abs(dy)) {
if (dx > 0 && dir.x !== -1) nextDir = { x: 1, y: 0 };
if (dx < 0 && dir.x !== 1) nextDir = { x: -1, y: 0 };
} else {
if (dy > 0 && dir.y !== -1) nextDir = { x: 0, y: 1 };
if (dy < 0 && dir.y !== 1) nextDir = { x: 0, y: -1 };
}
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
});
# ----- 片段 5 (javascript) -----
document.addEventListener('keydown', e => {
if (e.key === ' ' && running) {
e.preventDefault();
if (timer) {
clearInterval(timer);
timer = null;
} else {
timer = setInterval(tick, 150);
}
}
});
# ----- 片段 6 (javascript) -----
document.getElementById('start').addEventListener('click', () => {
if (running) return;
snake = [{ x: 10, y: 10 }];
dir = { x: 1, y: 0 };
nextDir = { x: 1, y: 0 };
score = 0;
running = true;
document.getElementById('score').textContent = '得分:0';
spawnFood();
timer = setInterval(tick, 150);
});