tanchishe / index.html
erdes's picture
设计一个贪吃蛇游戏 - Initial Deployment
fbe1f7f verified
Raw
History Blame Contribute Delete
20.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Snake Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
font-family: 'Press Start 2P', cursive;
background-color: #0f172a;
overflow: hidden;
}
.game-container {
position: relative;
}
.snake-segment {
position: absolute;
border-radius: 4px;
transition: all 0.1s ease;
}
.food {
position: absolute;
border-radius: 50%;
animation: pulse 1s infinite alternate;
}
@keyframes pulse {
0% { transform: scale(1); }
100% { transform: scale(1.2); }
}
.game-over {
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}
@keyframes shake {
10%, 90% { transform: translateX(-1px); }
20%, 80% { transform: translateX(2px); }
30%, 50%, 70% { transform: translateX(-4px); }
40%, 60% { transform: translateX(4px); }
}
.controls-btn {
transition: all 0.2s ease;
}
.controls-btn:active {
transform: scale(0.9);
filter: brightness(1.2);
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4">
<div class="text-center mb-6">
<h1 class="text-4xl md:text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-blue-500 mb-2">
NEON SNAKE
</h1>
<div class="flex justify-center items-center gap-4 mb-4">
<div class="text-green-400">
Score: <span id="score" class="text-white">0</span>
</div>
<div class="text-purple-400">
High Score: <span id="high-score" class="text-white">0</span>
</div>
</div>
</div>
<div class="game-container relative bg-gray-900 rounded-lg shadow-2xl shadow-blue-500/20 border border-gray-800 overflow-hidden"
style="width: min(90vw, 500px); height: min(90vw, 500px);">
<canvas id="game-canvas" class="w-full h-full"></canvas>
<div id="start-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-black bg-opacity-80 p-4">
<div class="text-center">
<h2 class="text-2xl md:text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-blue-500 mb-6">
NEON SNAKE
</h2>
<p class="text-gray-300 mb-8 text-sm md:text-base">
Use arrow keys or swipe to control the snake.<br>
Eat the glowing food to grow!
</p>
<button id="start-btn" class="px-8 py-3 bg-gradient-to-r from-green-500 to-blue-600 rounded-full font-bold text-white shadow-lg hover:shadow-green-500/30 transition-all">
START GAME
</button>
</div>
</div>
<div id="game-over-screen" class="absolute inset-0 hidden flex-col items-center justify-center bg-black bg-opacity-80 p-4">
<div class="text-center">
<h2 class="text-2xl md:text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-red-400 to-pink-500 mb-2">
GAME OVER!
</h2>
<p class="text-gray-300 mb-6">Your score: <span id="final-score" class="text-white">0</span></p>
<div class="flex gap-4 justify-center">
<button id="restart-btn" class="px-6 py-2 bg-gradient-to-r from-green-500 to-blue-600 rounded-full font-bold text-white text-sm">
PLAY AGAIN
</button>
<button id="menu-btn" class="px-6 py-2 bg-gradient-to-r from-purple-500 to-pink-600 rounded-full font-bold text-white text-sm">
MAIN MENU
</button>
</div>
</div>
</div>
</div>
<div class="mt-8 md:hidden">
<div class="grid grid-cols-3 gap-2 w-48 h-48">
<div></div>
<button id="up-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4">
</button>
<div></div>
<button id="left-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4">
</button>
<button id="down-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4">
</button>
<button id="right-btn" class="controls-btn bg-gray-800 text-white rounded-lg flex items-center justify-center p-4">
</button>
</div>
</div>
<div class="mt-8 text-gray-400 text-xs text-center">
<p>Use arrow keys or on-screen controls to play</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game elements
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over-screen');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const menuBtn = document.getElementById('menu-btn');
const scoreDisplay = document.getElementById('score');
const highScoreDisplay = document.getElementById('high-score');
const finalScoreDisplay = document.getElementById('final-score');
// Mobile controls
const upBtn = document.getElementById('up-btn');
const downBtn = document.getElementById('down-btn');
const leftBtn = document.getElementById('left-btn');
const rightBtn = document.getElementById('right-btn');
// Game variables
let snake = [];
let food = {};
let direction = 'right';
let nextDirection = 'right';
let gameSpeed = 150;
let score = 0;
let highScore = localStorage.getItem('snakeHighScore') || 0;
let gameLoop;
let gridSize = 20;
let canvasSize = Math.min(500, window.innerWidth * 0.9);
let tileSize = canvasSize / gridSize;
let touchStartX = 0;
let touchStartY = 0;
// Set canvas size
canvas.width = canvasSize;
canvas.height = canvasSize;
// Update high score display
highScoreDisplay.textContent = highScore;
// Initialize game
function initGame() {
// Reset snake
snake = [
{x: 5, y: 10},
{x: 4, y: 10},
{x: 3, y: 10}
];
// Reset direction
direction = 'right';
nextDirection = 'right';
// Reset score
score = 0;
scoreDisplay.textContent = score;
// Generate first food
generateFood();
// Start game loop
if (gameLoop) clearInterval(gameLoop);
gameLoop = setInterval(gameStep, gameSpeed);
}
// Generate food at random position
function generateFood() {
let foodPosition;
let overlapping;
do {
overlapping = false;
foodPosition = {
x: Math.floor(Math.random() * gridSize),
y: Math.floor(Math.random() * gridSize)
};
// Check if food overlaps with snake
for (let segment of snake) {
if (segment.x === foodPosition.x && segment.y === foodPosition.y) {
overlapping = true;
break;
}
}
} while (overlapping);
food = foodPosition;
}
// Main game loop
function gameStep() {
// Update direction
direction = nextDirection;
// Move snake
const head = {x: snake[0].x, y: snake[0].y};
switch (direction) {
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
case 'left':
head.x -= 1;
break;
case 'right':
head.x += 1;
break;
}
// Check for collisions
if (
head.x < 0 || head.x >= gridSize ||
head.y < 0 || head.y >= gridSize ||
snake.some(segment => segment.x === head.x && segment.y === head.y)
) {
gameOver();
return;
}
// Add new head
snake.unshift(head);
// Check if snake ate food
if (head.x === food.x && head.y === food.y) {
// Increase score
score += 10;
scoreDisplay.textContent = score;
// Generate new food
generateFood();
// Increase speed slightly every 50 points
if (score % 50 === 0 && gameSpeed > 50) {
gameSpeed -= 5;
clearInterval(gameLoop);
gameLoop = setInterval(gameStep, gameSpeed);
}
} else {
// Remove tail if no food was eaten
snake.pop();
}
// Draw game
drawGame();
}
// Draw game
function drawGame() {
// Clear canvas
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw grid
ctx.strokeStyle = '#1e293b';
ctx.lineWidth = 0.5;
for (let i = 0; i <= gridSize; i++) {
// Vertical lines
ctx.beginPath();
ctx.moveTo(i * tileSize, 0);
ctx.lineTo(i * tileSize, canvas.height);
ctx.stroke();
// Horizontal lines
ctx.beginPath();
ctx.moveTo(0, i * tileSize);
ctx.lineTo(canvas.width, i * tileSize);
ctx.stroke();
}
// Draw snake
snake.forEach((segment, index) => {
const isHead = index === 0;
const gradient = ctx.createLinearGradient(
segment.x * tileSize,
segment.y * tileSize,
segment.x * tileSize + tileSize,
segment.y * tileSize + tileSize
);
if (isHead) {
gradient.addColorStop(0, '#10b981');
gradient.addColorStop(1, '#3b82f6');
} else {
gradient.addColorStop(0, '#3b82f6');
gradient.addColorStop(1, '#8b5cf6');
}
ctx.fillStyle = gradient;
ctx.shadowBlur = isHead ? 10 : 5;
ctx.shadowColor = isHead ? '#10b981' : '#8b5cf6';
// Round corners for head and tail
const radius = isHead ? tileSize / 2 :
index === snake.length - 1 ? tileSize / 4 :
tileSize / 8;
roundRect(
ctx,
segment.x * tileSize + 1,
segment.y * tileSize + 1,
tileSize - 2,
tileSize - 2,
radius,
true,
false
);
ctx.shadowBlur = 0;
});
// Draw food
const foodGradient = ctx.createRadialGradient(
food.x * tileSize + tileSize / 2,
food.y * tileSize + tileSize / 2,
0,
food.x * tileSize + tileSize / 2,
food.y * tileSize + tileSize / 2,
tileSize / 2
);
foodGradient.addColorStop(0, '#f43f5e');
foodGradient.addColorStop(1, '#f97316');
ctx.fillStyle = foodGradient;
ctx.shadowBlur = 15;
ctx.shadowColor = '#f43f5e';
ctx.beginPath();
ctx.arc(
food.x * tileSize + tileSize / 2,
food.y * tileSize + tileSize / 2,
tileSize / 2 - 2,
0,
Math.PI * 2
);
ctx.fill();
ctx.shadowBlur = 0;
}
// Helper function to draw rounded rectangles
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
// Game over
function gameOver() {
clearInterval(gameLoop);
// Update high score
if (score > highScore) {
highScore = score;
localStorage.setItem('snakeHighScore', highScore);
highScoreDisplay.textContent = highScore;
}
// Show game over screen
finalScoreDisplay.textContent = score;
gameOverScreen.classList.remove('hidden');
gameOverScreen.classList.add('game-over', 'flex');
}
// Event listeners
startBtn.addEventListener('click', () => {
startScreen.classList.add('hidden');
initGame();
});
restartBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
gameOverScreen.classList.remove('flex');
initGame();
});
menuBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
gameOverScreen.classList.remove('flex');
startScreen.classList.remove('hidden');
});
// Keyboard controls
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowUp':
if (direction !== 'down') nextDirection = 'up';
break;
case 'ArrowDown':
if (direction !== 'up') nextDirection = 'down';
break;
case 'ArrowLeft':
if (direction !== 'right') nextDirection = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') nextDirection = 'right';
break;
}
});
// Mobile controls
upBtn.addEventListener('click', () => {
if (direction !== 'down') nextDirection = 'up';
});
downBtn.addEventListener('click', () => {
if (direction !== 'up') nextDirection = 'down';
});
leftBtn.addEventListener('click', () => {
if (direction !== 'right') nextDirection = 'left';
});
rightBtn.addEventListener('click', () => {
if (direction !== 'left') nextDirection = 'right';
});
// Touch controls for swipe gestures
canvas.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, false);
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!touchStartX || !touchStartY) return;
const touchEndX = e.touches[0].clientX;
const touchEndY = e.touches[0].clientY;
const diffX = touchStartX - touchEndX;
const diffY = touchStartY - touchEndY;
// Determine swipe direction
if (Math.abs(diffX) > Math.abs(diffY)) {
// Horizontal swipe
if (diffX > 0) {
// Left swipe
if (direction !== 'right') nextDirection = 'left';
} else {
// Right swipe
if (direction !== 'left') nextDirection = 'right';
}
} else {
// Vertical swipe
if (diffY > 0) {
// Up swipe
if (direction !== 'down') nextDirection = 'up';
} else {
// Down swipe
if (direction !== 'up') nextDirection = 'down';
}
}
// Reset touch coordinates
touchStartX = 0;
touchStartY = 0;
}, false);
// Initial draw
drawGame();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=erdes/tanchishe" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>