<div id="game-container">
<div id="score-board">Score: <span id="score">0</span></div>
<div id="start-screen">
<h1>Shark Attack Survival</h1>
<p>Use Left/Right Arrows to move the boat.</p>
<p>Avoid the sharks!</p>
<button id="start-button">Start Game</button>
</div>
<div id="game-area">
<!-- Player Boat -->
<div id="boat">
<div id="person">🧑 P</div> <!-- Simple representation -->
</div>
<!-- Game elements will be added here by JS -->
</div>
<div id="game-over-screen" style="display: none;">
<h1>Game Over!</h1>
<p>Your Score: <span id="final-score">0</span></p>
<button id="restart-button">Play Again</button>
</div>
</div>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
overflow: hidden; /* Hide scrollbars if game area is slightly too big */
}
#game-container {
position: relative;
width: 600px;
height: 80vh; /* Use viewport height */
max-height: 700px; /* Max height */
border: 3px solid #333;
background-color: #add8e6; /* Light blue sea */
overflow: hidden; /* Keep elements inside */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
#game-area {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
to bottom,
#87CEEB 0%, /* Sky Blue */
#1E90FF 60%, /* Dodger Blue */
#00008B 100% /* Dark Blue */
);
background-size: 100% 200%; /* Taller background for scrolling */
animation: scroll-water 15s linear infinite;
}
@keyframes scroll-water {
0% { background-position: 0 0; }
100% { background-position: 0 100%; }
}
#boat {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 40px;
background-color: #8B4513; /* Brown */
border-radius: 10px 10px 30px 30px / 10px 10px 20px 20px;
border: 2px solid #5a2d0c;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
#person {
font-size: 20px;
/* You could use a background image for a better person/boat look */
}
.shark {
position: absolute;
width: 60px;
height: 30px;
/* background-color: grey; */ /* Placeholder */
font-size: 30px; /* Use Emoji */
line-height: 30px; /* Center emoji */
text-align: center;
z-index: 5;
/* background-image: url('shark-image.png'); /* Optional: Use an image */
/* background-size: contain; */
/* background-repeat: no-repeat; */
transform-origin: center center;
}
.fish {
position: absolute;
font-size: 20px;
z-index: 1;
opacity: 0.8;
}
.buoy {
position: absolute;
width: 25px;
height: 40px;
background-color: red;
border-radius: 50% 50% 10px 10px;
border: 2px solid darkred;
z-index: 2;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.buoy::before { /* Top part of buoy */
content: '';
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 8px;
height: 10px;
background-color: darkred;
}
#score-board {
position: absolute;
top: 10px;
left: 10px;
font-size: 1.5em;
color: white;
z-index: 20;
text-shadow: 1px 1px 2px black;
}
/* Screens */
#start-screen, #game-over-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
z-index: 30;
}
#start-screen h1, #game-over-screen h1 {
margin-bottom: 20px;
}
#start-screen p, #game-over-screen p {
margin-bottom: 30px;
font-size: 1.2em;
}
#start-button, #restart-button {
padding: 15px 30px;
font-size: 1.2em;
cursor: pointer;
border: none;
border-radius: 5px;
background-color: #4CAF50;
color: white;
transition: background-color 0.3s ease;
}
#start-button:hover, #restart-button:hover {
background-color: #45a049;
}
const gameArea = document.getElementById('game-area');
const boat = document.getElementById('boat');
const scoreDisplay = document.getElementById('score');
const finalScoreDisplay = document.getElementById('final-score');
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over-screen');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button');
// Game Settings
const boatSpeed = 15;
const sharkBaseSpeed = 2;
const fishSpeed = 1;
const buoySpeed = 1.5;
const sharkSpawnRate = 0.015; // Probability per frame
const fishSpawnRate = 0.02;
const buoySpawnRate = 0.005;
const difficultyIncreaseInterval = 10000; // Increase difficulty every 10 seconds (ms)
const difficultyMultiplier = 1.1; // How much faster things get
// Game State
let score = 0;
let gameInterval;
let isGameOver = false;
let keysPressed = {};
let currentSharkSpeed = sharkBaseSpeed;
let currentSharkSpawnRate = sharkSpawnRate;
let gameTime = 0;
let lastDifficultyIncrease = 0;
// --- Event Listeners ---
document.addEventListener('keydown', (e) => {
keysPressed[e.key] = true;
});
document.addEventListener('keyup', (e) => {
keysPressed[e.key] = false;
});
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', startGame);
// --- Game Functions ---
function startGame() {
// Reset State
score = 0;
gameTime = 0;
lastDifficultyIncrease = 0;
currentSharkSpeed = sharkBaseSpeed;
currentSharkSpawnRate = sharkSpawnRate;
isGameOver = false;
keysPressed = {};
scoreDisplay.textContent = score;
gameOverScreen.style.display = 'none';
startScreen.style.display = 'none';
gameArea.style.animationPlayState = 'running'; // Resume background scroll
// Clear previous game elements
document.querySelectorAll('.shark, .fish, .buoy').forEach(el => el.remove());
// Reset boat position
boat.style.left = '50%';
// Start game loop
clearInterval(gameInterval); // Clear any existing interval
gameInterval = setInterval(gameLoop, 1000 / 60); // ~60 FPS
}
function gameOver() {
isGameOver = true;
clearInterval(gameInterval);
finalScoreDisplay.textContent = score;
gameOverScreen.style.display = 'flex';
gameArea.style.animationPlayState = 'paused'; // Pause background scroll
}
function moveBoat() {
const gameRect = gameArea.getBoundingClientRect();
const boatRect = boat.getBoundingClientRect();
let currentLeft = parseFloat(boat.style.left || '50%'); // Get percentage
// Convert percentage to pixels for boundary check
let currentLeftPx = (currentLeft / 100) * gameRect.width;
if (keysPressed['ArrowLeft'] || keysPressed['a']) {
let nextLeftPx = currentLeftPx - boatSpeed;
// Boundary check (left) - account for boat width relative to its center anchor
if (nextLeftPx - boatRect.width / 2 > 0) {
boat.style.left = `${((nextLeftPx) / gameRect.width) * 100}%`;
} else {
boat.style.left = `${(boatRect.width / 2 / gameRect.width) * 100}%`; // Snap to edge
}
}
if (keysPressed['ArrowRight'] || keysPressed['d']) {
let nextLeftPx = currentLeftPx + boatSpeed;
// Boundary check (right) - account for boat width relative to its center anchor
if (nextLeftPx + boatRect.width / 2 < gameRect.width) {
boat.style.left = `${((nextLeftPx) / gameRect.width) * 100}%`;
} else {
boat.style.left = `${((gameRect.width - boatRect.width / 2) / gameRect.width) * 100}%`; // Snap to edge
}
}
}
function createShark() {
const shark = document.createElement('div');
shark.classList.add('shark');
shark.textContent = '🦈'; // Shark Emoji
shark.style.left = `${Math.random() * (gameArea.offsetWidth - 60)}px`; // Random horizontal start (minus shark width)
shark.style.top = '-40px'; // Start above screen
// Add slight horizontal drift/targeting
shark.dataset.drift = (Math.random() - 0.5) * 1; // -0.5 to +0.5 horizontal speed adjustment
// Maybe make some sharks target the player slightly more directly (optional enhancement)
// if (Math.random() < 0.3) { // 30% chance to target
// const boatRect = boat.getBoundingClientRect();
// const sharkStartX = parseFloat(shark.style.left);
// const targetX = boatRect.left + boatRect.width / 2 - gameArea.getBoundingClientRect().left;
// shark.dataset.drift = (targetX - sharkStartX) / (gameArea.offsetHeight / currentSharkSpeed) * 0.05; // Simple prediction
// }
gameArea.appendChild(shark);
}
function moveSharks() {
const sharks = document.querySelectorAll('.shark');
const gameRect = gameArea.getBoundingClientRect();
sharks.forEach(shark => {
let top = parseFloat(shark.style.top);
let left = parseFloat(shark.style.left);
const drift = parseFloat(shark.dataset.drift || 0);
top += currentSharkSpeed;
left += drift;
// Basic boundary check for horizontal drift
if (left < 0 || left > gameRect.width - shark.offsetWidth) {
shark.dataset.drift = -drift; // Reverse drift at edges
left += -drift * 2; // Move back into bounds slightly
}
shark.style.top = `${top}px`;
shark.style.left = `${left}px`;
// Flip shark based on drift direction
if (drift < 0 && shark.style.transform !== 'scaleX(-1)') {
shark.style.transform = 'scaleX(-1)';
} else if (drift > 0 && shark.style.transform === 'scaleX(-1)') {
shark.style.transform = 'scaleX(1)';
}
// Remove shark if it goes off screen bottom
if (top > gameArea.offsetHeight) {
shark.remove();
}
});
}
function createFish() {
const fish = document.createElement('div');
fish.classList.add('fish');
const fishTypes = ['🐠', '🐟', '🐡'];
fish.textContent = fishTypes[Math.floor(Math.random() * fishTypes.length)];
fish.style.left = `${Math.random() * (gameArea.offsetWidth - 30)}px`;
fish.style.top = '-30px';
fish.dataset.speed = fishSpeed + (Math.random() - 0.5); // Slight speed variation
fish.dataset.drift = (Math.random() - 0.5) * 0.5; // Horizontal drift
// Randomly flip fish
if (Math.random() < 0.5) {
fish.style.transform = 'scaleX(-1)';
}
gameArea.appendChild(fish);
}
function moveFish() {
const fishes = document.querySelectorAll('.fish');
fishes.forEach(fish => {
let top = parseFloat(fish.style.top);
let left = parseFloat(fish.style.left);
const speed = parseFloat(fish.dataset.speed);
const drift = parseFloat(fish.dataset.drift);
top += speed;
left += drift;
// Remove fish if it goes off screen
if (top > gameArea.offsetHeight || left < -30 || left > gameArea.offsetWidth) {
fish.remove();
} else {
fish.style.top = `${top}px`;
fish.style.left = `${left}px`;
}
});
}
function createBuoy() {
const buoy = document.createElement('div');
buoy.classList.add('buoy');
buoy.style.left = `${Math.random() * (gameArea.offsetWidth - 25)}px`;
buoy.style.top = '-50px';
gameArea.appendChild(buoy);
}
function moveBuoys() {
const buoys = document.querySelectorAll('.buoy');
buoys.forEach(buoy => {
let top = parseFloat(buoy.style.top);
top += buoySpeed; // Buoys just drift down slowly
if (top > gameArea.offsetHeight) {
buoy.remove();
} else {
buoy.style.top = `${top}px`;
}
});
}
function checkCollision() {
const boatRect = boat.getBoundingClientRect();
const sharks = document.querySelectorAll('.shark');
// Optional: Check collision with buoys if they are obstacles
// const buoys = document.querySelectorAll('.buoy');
sharks.forEach(shark => {
const sharkRect = shark.getBoundingClientRect();
// Simple Axis-Aligned Bounding Box (AABB) collision detection
if (
boatRect.left < sharkRect.right &&
boatRect.right > sharkRect.left &&
boatRect.top < sharkRect.bottom &&
boatRect.bottom > sharkRect.top
) {
gameOver();
}
});
// Example: Collision with Buoys (uncomment if buoys should end the game)
/*
buoys.forEach(buoy => {
const buoyRect = buoy.getBoundingClientRect();
if (
boatRect.left < buoyRect.right &&
boatRect.right > buoyRect.left &&
boatRect.top < buoyRect.bottom &&
boatRect.bottom > buoyRect.top
) {
gameOver();
}
});
*/
}
function updateScore() {
score++;
scoreDisplay.textContent = score;
}
function increaseDifficulty() {
gameTime += 1000 / 60; // Add frame time
if(gameTime - lastDifficultyIncrease > difficultyIncreaseInterval) {
console.log("Increasing difficulty!");
currentSharkSpeed *= difficultyMultiplier;
currentSharkSpawnRate = Math.min(0.1, currentSharkSpawnRate * difficultyMultiplier); // Increase spawn rate but cap it
// Optionally increase buoy speed/spawn rate too
lastDifficultyIncrease = gameTime;
}
}
// --- Main Game Loop ---
function gameLoop() {
if (isGameOver) return;
// 1. Handle Input & Move Player
moveBoat();
// 2. Spawn Enemies & Assets
if (Math.random() < currentSharkSpawnRate) {
createShark();
}
if (Math.random() < fishSpawnRate) {
createFish();
}
if (Math.random() < buoySpawnRate) {
createBuoy();
}
// 3. Move Enemies & Assets
moveSharks();
moveFish();
moveBuoys();
// 4. Check Collisions
checkCollision();
// 5. Update Score
updateScore();
// 6. Increase Difficulty over time
increaseDifficulty();
}
// Initially hide game area stuff until start
// (Handled by CSS display:none on start/game over screens)
// Optional: Pause animation initially
gameArea.style.animationPlayState = 'paused';
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.