underwatorsnake / index.html
offerpk3's picture
Update index.html
bde1327 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Underwater Snake Game</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(180deg, #0066cc 0%, #004080 50%, #001a33 100%);
font-family: 'Arial', sans-serif;
color: white;
overflow: hidden;
}
.bubbles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.bubble {
position: absolute;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
animation: rise 8s infinite linear;
}
@keyframes rise {
0% { transform: translateY(100vh) scale(0); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(-100px) scale(1); opacity: 0; }
}
.game-container {
text-align: center;
background: rgba(0, 50, 100, 0.3);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
z-index: 2;
position: relative;
}
h1 {
margin: 0 0 20px 0;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
color: #00ccff;
}
.score {
font-size: 1.5em;
margin-bottom: 20px;
font-weight: bold;
color: #66ff99;
}
.level {
font-size: 1.2em;
margin-bottom: 10px;
color: #ffcc00;
}
#gameCanvas {
border: 3px solid rgba(0, 200, 255, 0.5);
border-radius: 15px;
background: radial-gradient(ellipse at center, rgba(0, 100, 200, 0.3) 0%, rgba(0, 50, 150, 0.6) 100%);
box-shadow: inset 0 0 30px rgba(0, 150, 255, 0.3);
}
.controls {
margin-top: 20px;
font-size: 1.1em;
opacity: 0.9;
color: #ccffff;
}
.control-buttons {
margin-top: 15px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
max-width: 200px;
margin-left: auto;
margin-right: auto;
}
.control-btn {
background: rgba(0, 150, 255, 0.3);
border: 2px solid rgba(0, 200, 255, 0.6);
color: white;
padding: 15px;
font-size: 1.2em;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.control-btn:hover {
background: rgba(0, 150, 255, 0.5);
transform: scale(1.1);
}
.control-btn:active {
transform: scale(0.95);
}
.empty { opacity: 0; pointer-events: none; }
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 30, 60, 0.9);
padding: 30px;
border-radius: 15px;
text-align: center;
display: none;
border: 2px solid rgba(0, 200, 255, 0.5);
}
.restart-btn {
background: linear-gradient(45deg, #00ccff, #0080ff);
border: none;
color: white;
padding: 12px 25px;
font-size: 1.2em;
border-radius: 25px;
cursor: pointer;
margin-top: 15px;
transition: transform 0.2s;
}
.restart-btn:hover {
transform: scale(1.05);
}
</style>
</head>
<body>
<div class="bubbles" id="bubbles"></div>
<div class="game-container">
<h1>🐍 Underwater Snake 🌊</h1>
<div class="level">Level: <span id="level">1</span></div>
<div class="score">Fish Caught: <span id="score">0</span></div>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="controls">
Control your sea snake to catch swimming fish!
</div>
<div class="control-buttons">
<div class="empty"></div>
<button class="control-btn" onmousedown="setDirection(0, -1)" ontouchstart="setDirection(0, -1)"></button>
<div class="empty"></div>
<button class="control-btn" onmousedown="setDirection(-1, 0)" ontouchstart="setDirection(-1, 0)"></button>
<div class="empty"></div>
<button class="control-btn" onmousedown="setDirection(1, 0)" ontouchstart="setDirection(1, 0)"></button>
<div class="empty"></div>
<button class="control-btn" onmousedown="setDirection(0, 1)" ontouchstart="setDirection(0, 1)"></button>
<div class="empty"></div>
</div>
<div class="game-over" id="gameOver">
<h2>Game Over!</h2>
<p>Level Reached: <span id="finalLevel">1</span></p>
<p>Fish Caught: <span id="finalScore">0</span></p>
<button class="restart-btn" onclick="restartGame()">Dive Again</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const levelElement = document.getElementById('level');
const gameOverElement = document.getElementById('gameOver');
const finalScoreElement = document.getElementById('finalScore');
const finalLevelElement = document.getElementById('finalLevel');
// Game variables
const gridSize = 20;
const tileCount = canvas.width / gridSize;
let snake = [
{x: 10, y: 10}
];
let fish = [];
let bloodDrops = [];
let dx = 0;
let dy = 0;
let score = 0;
let level = 1;
let gameSpeed = 200; // Starting speed (slower)
let gameRunning = true;
let audioContext;
let shouldExtendTail = false;
// Initialize audio context
function initAudio() {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
} catch (e) {
console.log('Web Audio API not supported');
}
}
// Play eating sound
function playEatingSound() {
if (!audioContext) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Create a "chomp" sound effect
oscillator.frequency.setValueAtTime(200, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(100, audioContext.currentTime + 0.1);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
oscillator.type = 'square';
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.2);
}
// Blood drop class
class BloodDrop {
constructor(x, y) {
this.x = x * gridSize + gridSize / 2;
this.y = y * gridSize + gridSize / 2;
this.vx = (Math.random() - 0.5) * 4;
this.vy = Math.random() * 2 + 1;
this.gravity = 0.3;
this.life = 60; // frames
this.maxLife = 60;
this.size = Math.random() * 3 + 2;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += this.gravity;
this.life--;
// Bounce off sides
if (this.x <= 0 || this.x >= canvas.width) {
this.vx *= -0.5;
this.x = Math.max(0, Math.min(canvas.width, this.x));
}
// Stop at bottom
if (this.y >= canvas.height - this.size) {
this.y = canvas.height - this.size;
this.vy = 0;
this.vx *= 0.8;
}
}
draw() {
const alpha = this.life / this.maxLife;
ctx.save();
ctx.globalAlpha = alpha;
// Blood drop gradient
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(0.5, '#cc0000');
gradient.addColorStop(1, '#800000');
ctx.fillStyle = gradient;
ctx.shadowColor = '#ff0000';
ctx.shadowBlur = 5;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
}
isDead() {
return this.life <= 0;
}
}
class Fish {
constructor() {
this.x = Math.floor(Math.random() * tileCount);
this.y = Math.floor(Math.random() * tileCount);
this.dx = (Math.random() - 0.5) * 0.3;
this.dy = (Math.random() - 0.5) * 0.3;
this.color = this.getRandomFishColor();
this.size = Math.random() * 0.3 + 0.7;
this.swimTimer = 0;
}
getRandomFishColor() {
const colors = ['#ff6b35', '#f7931e', '#ffdd00', '#ff1744', '#e91e63', '#9c27b0'];
return colors[Math.floor(Math.random() * colors.length)];
}
update() {
this.swimTimer += 0.1;
// Random direction changes
if (Math.random() < 0.02) {
this.dx = (Math.random() - 0.5) * 0.3;
this.dy = (Math.random() - 0.5) * 0.3;
}
// Move fish
this.x += this.dx;
this.y += this.dy;
// Bounce off walls
if (this.x <= 0 || this.x >= tileCount - 1) {
this.dx *= -1;
this.x = Math.max(0, Math.min(tileCount - 1, this.x));
}
if (this.y <= 0 || this.y >= tileCount - 1) {
this.dy *= -1;
this.y = Math.max(0, Math.min(tileCount - 1, this.y));
}
}
draw() {
const x = this.x * gridSize + gridSize / 2;
const y = this.y * gridSize + gridSize / 2;
const size = gridSize * this.size;
ctx.save();
ctx.translate(x, y);
// Fish body
ctx.fillStyle = this.color;
ctx.shadowColor = this.color;
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.ellipse(0, 0, size * 0.6, size * 0.4, 0, 0, 2 * Math.PI);
ctx.fill();
// Fish tail
ctx.beginPath();
ctx.moveTo(-size * 0.6, 0);
ctx.lineTo(-size * 0.9, -size * 0.3);
ctx.lineTo(-size * 0.9, size * 0.3);
ctx.closePath();
ctx.fill();
// Fish eye
ctx.fillStyle = 'white';
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(size * 0.2, -size * 0.1, size * 0.15, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(size * 0.25, -size * 0.1, size * 0.08, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
}
}
// Draw snake head with mouth and direction
function drawSnakeHead(x, y, direction) {
const centerX = x + gridSize / 2;
const centerY = y + gridSize / 2;
const size = gridSize * 0.4;
ctx.save();
ctx.translate(centerX, centerY);
// Rotate based on direction
let angle = 0;
if (direction.x === 1) angle = 0; // Right
else if (direction.x === -1) angle = Math.PI; // Left
else if (direction.y === 1) angle = Math.PI / 2; // Down
else if (direction.y === -1) angle = -Math.PI / 2; // Up
ctx.rotate(angle);
// Head shape
ctx.fillStyle = '#00ff80';
ctx.shadowColor = '#00ff80';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.ellipse(0, 0, size + 2, size, 0, 0, 2 * Math.PI);
ctx.fill();
// Snake pattern on head
ctx.shadowBlur = 0;
ctx.fillStyle = '#00cc60';
ctx.beginPath();
ctx.ellipse(-2, -size/3, size/3, size/6, 0, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.ellipse(-2, size/3, size/3, size/6, 0, 0, 2 * Math.PI);
ctx.fill();
// Eyes
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(size/2, -size/3, 3, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(size/2, size/3, 3, 0, 2 * Math.PI);
ctx.fill();
// Eye pupils
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(size/2 + 1, -size/3, 1.5, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(size/2 + 1, size/3, 1.5, 0, 2 * Math.PI);
ctx.fill();
// Mouth
ctx.strokeStyle = '#003300';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(size + 2, -3);
ctx.lineTo(size + 6, 0);
ctx.lineTo(size + 2, 3);
ctx.stroke();
// Forked tongue (occasionally)
if (Math.random() < 0.1) {
ctx.strokeStyle = '#ff3366';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(size + 6, 0);
ctx.lineTo(size + 10, -2);
ctx.moveTo(size + 6, 0);
ctx.lineTo(size + 10, 2);
ctx.stroke();
}
ctx.restore();
}
// Draw snake tail
function drawSnakeTail(x, y, prevX, prevY) {
const centerX = x + gridSize / 2;
const centerY = y + gridSize / 2;
// Direction from current to previous segment
const dirX = prevX - x;
const dirY = prevY - y;
ctx.save();
ctx.translate(centerX, centerY);
// Rotate based on direction
let angle = 0;
if (dirX > 0) angle = 0; // Right
else if (dirX < 0) angle = Math.PI; // Left
else if (dirY > 0) angle = Math.PI / 2; // Down
else if (dirY < 0) angle = -Math.PI / 2; // Up
ctx.rotate(angle);
// Tail gradient
const gradient = ctx.createLinearGradient(-gridSize/2, 0, gridSize/2, 0);
gradient.addColorStop(0, 'rgba(0, 255, 128, 0.8)');
gradient.addColorStop(1, 'rgba(0, 255, 128, 0.2)');
ctx.fillStyle = gradient;
ctx.shadowColor = '#00ff80';
ctx.shadowBlur = 8;
// Tapered tail shape
ctx.beginPath();
ctx.moveTo(-gridSize/3, -gridSize/4);
ctx.lineTo(gridSize/2, -2);
ctx.lineTo(gridSize/2, 2);
ctx.lineTo(-gridSize/3, gridSize/4);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Generate fish
function spawnFish() {
// Always maintain 3-4 fish on screen
while (fish.length < 3 + Math.floor(level / 3)) {
fish.push(new Fish());
}
}
// Draw underwater background effects
function drawWaterEffects() {
// Water gradient
const gradient = ctx.createRadialGradient(canvas.width/2, canvas.height/4, 0, canvas.width/2, canvas.height/2, canvas.width);
gradient.addColorStop(0, 'rgba(0, 150, 255, 0.1)');
gradient.addColorStop(0.5, 'rgba(0, 100, 200, 0.2)');
gradient.addColorStop(1, 'rgba(0, 50, 150, 0.3)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Animated water lines
ctx.strokeStyle = 'rgba(0, 200, 255, 0.1)';
ctx.lineWidth = 1;
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(0, i * 80 + Math.sin(Date.now() * 0.001 + i) * 10);
ctx.lineTo(canvas.width, i * 80 + Math.sin(Date.now() * 0.001 + i + 1) * 10);
ctx.stroke();
}
}
// Draw game elements
function drawGame() {
drawWaterEffects();
// Draw snake body segments
ctx.shadowBlur = 10;
for (let i = snake.length - 1; i >= 0; i--) {
const segment = snake[i];
const x = segment.x * gridSize;
const y = segment.y * gridSize;
if (i === 0) {
// Snake head - draw last so it's on top
const direction = {x: dx, y: dy};
drawSnakeHead(x, y, direction);
} else if (i === snake.length - 1) {
// Snake tail
const prevSegment = snake[i - 1];
drawSnakeTail(x, y, prevSegment.x * gridSize, prevSegment.y * gridSize);
} else {
// Snake body with gradient
const alpha = (snake.length - i) / snake.length;
ctx.fillStyle = `rgba(0, 255, 128, ${alpha * 0.9})`;
ctx.shadowColor = '#00ff80';
ctx.beginPath();
ctx.roundRect(x + 1, y + 1, gridSize - 2, gridSize - 2, 8);
ctx.fill();
// Add scales pattern
ctx.shadowBlur = 0;
ctx.fillStyle = `rgba(0, 200, 100, ${alpha * 0.6})`;
ctx.fillRect(x + 3, y + 3, gridSize - 6, 2);
ctx.fillRect(x + 3, y + gridSize - 5, gridSize - 6, 2);
ctx.shadowBlur = 10;
}
}
ctx.shadowBlur = 0;
// Update and draw fish
fish.forEach((f, index) => {
f.update();
f.draw();
});
// Update and draw blood drops
for (let i = bloodDrops.length - 1; i >= 0; i--) {
const drop = bloodDrops[i];
drop.update();
drop.draw();
if (drop.isDead()) {
bloodDrops.splice(i, 1);
}
}
}
// Move snake
function moveSnake() {
if (dx === 0 && dy === 0) return;
const head = {x: snake[0].x + dx, y: snake[0].y + dy};
// Check wall collision
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
gameOver();
return;
}
// Check self collision
for (let segment of snake) {
if (head.x === segment.x && head.y === segment.y) {
gameOver();
return;
}
}
snake.unshift(head);
// Check fish collision
let fishEaten = false;
for (let i = fish.length - 1; i >= 0; i--) {
const f = fish[i];
if (Math.floor(f.x) === head.x && Math.floor(f.y) === head.y) {
// Fish caught!
score += 10;
scoreElement.textContent = score;
// Play eating sound
playEatingSound();
// Create blood drops at fish position
for (let j = 0; j < 8; j++) {
bloodDrops.push(new BloodDrop(f.x, f.y));
}
// Remove eaten fish
fish.splice(i, 1);
// Mark that tail should extend
fishEaten = true;
// Level up every 50 points
const newLevel = Math.floor(score / 50) + 1;
if (newLevel > level) {
level = newLevel;
levelElement.textContent = level;
// Increase speed (decrease interval)
gameSpeed = Math.max(80, 200 - (level - 1) * 15);
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
break;
}
}
// Only remove tail if no fish was eaten
if (!fishEaten) {
snake.pop();
}
}
// Set direction function for buttons
function setDirection(newDx, newDy) {
if (!audioContext) {
initAudio();
}
if (!gameRunning) return;
// Prevent reverse direction
if ((newDx === 1 && dx !== -1) || (newDx === -1 && dx !== 1) || newDx === 0) {
if ((newDy === 1 && dy !== -1) || (newDy === -1 && dy !== 1) || newDy === 0) {
dx = newDx;
dy = newDy;
}
}
}
// Game over
function gameOver() {
gameRunning = false;
clearInterval(gameInterval);
finalScoreElement.textContent = score;
finalLevelElement.textContent = level;
gameOverElement.style.display = 'block';
}
// Restart game
function restartGame() {
snake = [{x: 10, y: 10}];
fish = [];
bloodDrops = [];
dx = 0;
dy = 0;
score = 0;
level = 1;
gameSpeed = 200;
gameRunning = true;
scoreElement.textContent = score;
levelElement.textContent = level;
gameOverElement.style.display = 'none';
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
}
// Handle keyboard input
document.addEventListener('keydown', (e) => {
// Initialize audio on first user interaction
if (!audioContext) {
initAudio();
}
const key = e.key.toLowerCase();
if (key === 'arrowleft' || key === 'a') setDirection(-1, 0);
if (key === 'arrowup' || key === 'w') setDirection(0, -1);
if (key === 'arrowright' || key === 'd') setDirection(1, 0);
if (key === 'arrowdown' || key === 's') setDirection(0, 1);
});
// Initialize audio on button click
function setDirection(newDx, newDy) {
if (!audioContext) {
initAudio();
}
if (!gameRunning) return;
// Prevent reverse direction
if ((newDx === 1 && dx !== -1) || (newDx === -1 && dx !== 1) || newDx === 0) {
if ((newDy === 1 && dy !== -1) || (newDy === -1 && dy !== 1) || newDy === 0) {
dx = newDx;
dy = newDy;
}
}
}
// Create floating bubbles
function createBubbles() {
const bubblesContainer = document.getElementById('bubbles');
setInterval(() => {
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble.style.left = Math.random() * 100 + '%';
bubble.style.width = bubble.style.height = Math.random() * 20 + 10 + 'px';
bubble.style.animationDuration = (Math.random() * 3 + 5) + 's';
bubblesContainer.appendChild(bubble);
setTimeout(() => {
bubble.remove();
}, 8000);
}, 300);
}
// Game loop
function gameLoop() {
if (gameRunning) {
moveSnake();
spawnFish();
drawGame();
}
}
// Initialize game
let gameInterval;
initAudio();
createBubbles();
spawnFish(); // Initial fish spawn
drawGame();
gameInterval = setInterval(gameLoop, gameSpeed);
</script>
</body>
</html>