workinggame / index.html
offerpk3's picture
Update index.html
a0d3437 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Doge Whale Game</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background: linear-gradient(to bottom, #4169E1, #000080);
color: white;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
header {
background-color: rgba(0, 0, 128, 0.7);
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
margin: 0;
font-size: 24px;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
}
.coin-display {
display: flex;
align-items: center;
gap: 5px;
background-color: rgba(0, 0, 0, 0.3);
padding: 5px 10px;
border-radius: 20px;
}
.coin-icon {
width: 20px;
height: 20px;
background-color: gold;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
color: #333;
}
.nav-buttons {
display: flex;
gap: 10px;
}
.nav-button {
background-color: rgba(65, 105, 225, 0.7);
border: 2px solid #87CEEB;
color: white;
padding: 5px 15px;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
}
.nav-button:hover {
background-color: rgba(135, 206, 235, 0.7);
transform: scale(1.05);
}
main {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
#game-container {
position: relative;
width: 800px;
height: 500px;
background-color: rgba(30, 144, 255, 0.3);
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
overflow: hidden;
}
#game-canvas {
width: 100%;
height: 100%;
display: block;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 128, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s;
}
.overlay.active {
opacity: 1;
pointer-events: all;
}
.overlay h2 {
font-size: 36px;
margin-bottom: 20px;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.overlay p {
font-size: 18px;
margin-bottom: 30px;
max-width: 80%;
text-align: center;
}
.button {
background-color: #4169E1;
color: white;
border: none;
padding: 10px 30px;
font-size: 18px;
border-radius: 30px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
.button:hover {
background-color: #87CEEB;
transform: scale(1.05);
}
#hud {
position: absolute;
top: 10px;
left: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
.bar-container {
width: 200px;
height: 20px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 10px;
overflow: hidden;
}
#health-bar {
height: 100%;
width: 100%;
background: linear-gradient(to right, #FF4500, #FF8C00);
transition: width 0.3s;
}
#power-bar {
height: 100%;
width: 0%;
background: linear-gradient(to right, #4169E1, #00BFFF);
transition: width 0.3s;
}
#score-display {
position: absolute;
top: 10px;
right: 10px;
font-size: 36px;
font-weight: bold;
color: white;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}
#combo-display {
position: absolute;
top: 50px;
right: 10px;
font-size: 24px;
color: gold;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s;
}
#power-up-notification {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 20px;
font-size: 18px;
opacity: 0;
transition: opacity 0.3s, transform 0.3s;
}
#mobile-controls {
position: absolute;
bottom: 20px;
left: 20px;
display: flex;
gap: 20px;
}
.mobile-button {
width: 50px;
height: 50px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
cursor: pointer;
user-select: none;
}
#combat-controls {
position: absolute;
bottom: 20px;
right: 20px;
display: flex;
gap: 20px;
}
.combat-button {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
cursor: pointer;
user-select: none;
}
#combat-fire {
background-color: rgba(255, 69, 0, 0.7);
}
#combat-special {
background-color: rgba(65, 105, 225, 0.7);
}
#auto-fire-toggle {
background-color: rgba(0, 255, 0, 0.7);
}
#auto-target-toggle {
background-color: rgba(0, 255, 0, 0.7);
}
#orientation-toggle {
position: absolute;
bottom: 20px;
left: 20px;
width: 40px;
height: 40px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.vertical #game-container {
width: 400px;
height: 600px;
}
.background-particle {
position: absolute;
background-color: rgba(255, 255, 255, 0.3);
border-radius: 50%;
pointer-events: none;
animation: float var(--duration) linear infinite;
}
@keyframes float {
0% {
transform: translateY(100vh);
}
100% {
transform: translateY(-100px);
}
}
.cloud {
position: absolute;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 50%;
pointer-events: none;
animation: drift 30s linear infinite;
}
@keyframes drift {
0% {
transform: translateX(-100px);
}
100% {
transform: translateX(100vw);
}
}
/* Multi-layered water animation */
.water-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 80px;
overflow: hidden;
z-index: 5;
}
.water-wave-1, .water-wave-2, .water-wave-3 {
position: absolute;
bottom: 0;
left: 0;
width: 200%;
height: 100%;
background-repeat: repeat-x;
background-position: 0 bottom;
transform-origin: center bottom;
}
.water-wave-1 {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="%23FF0000" fill-opacity="0.6" d="M0,192L48,197.3C96,203,192,213,288,229.3C384,245,480,267,576,250.7C672,235,768,181,864,181.3C960,181,1056,235,1152,234.7C1248,235,1344,181,1392,154.7L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path></svg>');
animation: wave1 13s linear infinite;
opacity: 0.7;
z-index: 3;
}
.water-wave-2 {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="%23808080" fill-opacity="0.5" d="M0,160L48,181.3C96,203,192,245,288,240C384,235,480,181,576,186.7C672,192,768,256,864,261.3C960,267,1056,213,1152,192C1248,171,1344,181,1392,186.7L1440,192L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path></svg>');
animation: wave2 7s linear infinite;
opacity: 0.5;
z-index: 2;
}
.water-wave-3 {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="%23A00000" fill-opacity="0.4" d="M0,224L48,213.3C96,203,192,181,288,154.7C384,128,480,96,576,106.7C672,117,768,171,864,197.3C960,224,1056,224,1152,208C1248,192,1344,160,1392,144L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path></svg>');
animation: wave3 10s linear infinite;
opacity: 0.3;
z-index: 1;
}
@keyframes wave1 {
0% { transform: translateX(0) translateZ(0) scaleY(1); }
50% { transform: translateX(-25%) translateZ(0) scaleY(1.1); }
100% { transform: translateX(-50%) translateZ(0) scaleY(1); }
}
@keyframes wave2 {
0% { transform: translateX(0) translateZ(0) scaleY(1); }
50% { transform: translateX(-30%) translateZ(0) scaleY(0.9); }
100% { transform: translateX(-50%) translateZ(0) scaleY(1); }
}
@keyframes wave3 {
0% { transform: translateX(-50%) translateZ(0) scaleY(1); }
50% { transform: translateX(-25%) translateZ(0) scaleY(1.05); }
100% { transform: translateX(0) translateZ(0) scaleY(1); }
}
/* Add water particles for extra effect */
.water-particle {
position: absolute;
background-color: rgba(255, 255, 255, 0.6);
border-radius: 50%;
pointer-events: none;
animation: float-up var(--duration) ease-out forwards;
z-index: 4;
}
@keyframes float-up {
0% {
transform: translateY(0) scale(1);
opacity: 0.7;
}
100% {
transform: translateY(-100px) scale(0);
opacity: 0;
}
}
#cloud-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100px;
overflow: hidden;
pointer-events: none;
}
/* Spinner Wheel Styles */
#spinner-overlay {
background-color: rgba(0, 0, 128, 0.9);
}
#spinner-container {
position: relative;
width: 300px;
height: 300px;
margin: 20px auto;
}
#spinner-wheel {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #4169E1;
position: relative;
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
transition: transform 3s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}
#spinner-arrow {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
border-top: 30px solid #FF4500;
z-index: 1;
}
.wheel-section {
position: absolute;
width: 50%;
height: 50%;
transform-origin: bottom right;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 18px;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}
#spin-result {
margin-top: 20px;
font-size: 24px;
font-weight: bold;
color: gold;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
height: 30px;
}
#spin-timer {
margin-top: 10px;
font-size: 16px;
color: white;
height: 20px;
}
/* Lottery Styles */
#lottery-overlay {
background-color: rgba(0, 0, 128, 0.9);
}
#lottery-container {
width: 80%;
max-width: 600px;
display: flex;
flex-direction: column;
align-items: center;
}
#lottery-info {
width: 100%;
text-align: center;
margin-bottom: 20px;
}
#lottery-pool {
font-size: 24px;
font-weight: bold;
color: gold;
margin-bottom: 10px;
}
#lottery-status {
font-size: 18px;
margin-bottom: 10px;
}
#lottery-timer {
font-size: 16px;
color: #87CEEB;
}
#lottery-cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
margin: 20px 0;
}
.lottery-card {
width: 100px;
height: 150px;
background-color: #4169E1;
border-radius: 10px;
position: relative;
cursor: pointer;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
transition: transform 0.3s;
}
.lottery-card:hover {
transform: scale(1.05);
}
.card-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #87CEEB;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 36px;
color: white;
transition: opacity 0.5s;
}
.card-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
font-weight: bold;
color: gold;
}
.lottery-card.scratched .card-cover {
opacity: 0;
pointer-events: none;
}
/* Admin Panel Styles */
#admin-overlay {
background-color: rgba(0, 0, 128, 0.9);
overflow-y: auto;
}
#admin-container {
width: 80%;
max-width: 800px;
padding: 20px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 10px;
margin: 20px 0;
}
.admin-section {
margin-bottom: 30px;
}
.admin-section h3 {
font-size: 24px;
margin-bottom: 15px;
color: #87CEEB;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
padding-bottom: 5px;
}
.admin-controls {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.admin-control {
display: flex;
flex-direction: column;
gap: 5px;
}
.admin-control label {
font-size: 16px;
}
.admin-control input[type="number"] {
padding: 8px;
border-radius: 5px;
border: 1px solid #4169E1;
background-color: rgba(0, 0, 0, 0.2);
color: white;
}
.admin-control input[type="checkbox"] {
width: 20px;
height: 20px;
}
.admin-buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
/* Store Styles */
#store-overlay {
background-color: rgba(0, 0, 128, 0.9);
overflow-y: auto;
}
#store-container {
width: 80%;
max-width: 800px;
padding: 20px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 10px;
margin: 20px 0;
}
.store-items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.store-item {
background-color: rgba(65, 105, 225, 0.3);
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
transition: transform 0.3s;
}
.store-item:hover {
transform: scale(1.03);
background-color: rgba(65, 105, 225, 0.5);
}
.store-item-image {
width: 60px;
height: 60px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: #87CEEB;
}
.store-item-name {
font-size: 18px;
font-weight: bold;
color: white;
}
.store-item-description {
font-size: 14px;
text-align: center;
color: #CCC;
}
.store-item-price {
font-size: 16px;
font-weight: bold;
color: gold;
}
.store-item-button {
background-color: #4169E1;
color: white;
border: none;
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
}
.store-item-button:hover:not(:disabled) {
background-color: #87CEEB;
transform: scale(1.05);
}
.store-item-button:disabled {
background-color: #666;
cursor: not-allowed;
}
#withdrawal-section {
text-align: center;
padding: 20px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 10px;
}
/* Leaderboard Styles */
#leaderboard-overlay {
background-color: rgba(0, 0, 128, 0.9);
}
#leaderboard-container {
width: 80%;
max-width: 600px;
padding: 20px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 10px;
margin: 20px 0;
}
.leaderboard-tabs {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
.leaderboard-tab {
padding: 8px 15px;
background-color: rgba(65, 105, 225, 0.3);
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
}
.leaderboard-tab:hover {
background-color: rgba(65, 105, 225, 0.5);
}
.leaderboard-tab.active {
background-color: #4169E1;
font-weight: bold;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.leaderboard-table th, .leaderboard-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.leaderboard-table th {
background-color: rgba(0, 0, 0, 0.3);
color: #87CEEB;
}
.leaderboard-table .rank, .leaderboard-table .score {
text-align: center;
}
/* Close and Back Buttons */
.close-button {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background-color: rgba(255, 0, 0, 0.7);
border: none;
border-radius: 50%;
color: white;
font-size: 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.3s;
z-index: 20;
}
.close-button:hover {
background-color: rgba(255, 0, 0, 0.9);
transform: scale(1.1);
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
width: 40px;
height: 40px;
background-color: rgba(65, 105, 225, 0.7);
border: none;
border-radius: 50%;
color: white;
font-size: 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.3s;
z-index: 20;
}
.back-button:hover {
background-color: rgba(65, 105, 225, 0.9);
transform: scale(1.1);
}
@media (max-width: 768px) {
#game-container {
width: 100%;
height: 100%;
border-radius: 0;
}
header {
padding: 5px 10px;
}
h1 {
font-size: 18px;
}
.nav-button {
padding: 3px 10px;
font-size: 14px;
}
#hud {
top: 5px;
left: 5px;
}
.bar-container {
width: 150px;
height: 15px;
}
#score-display {
font-size: 24px;
}
#combo-display {
font-size: 18px;
}
.mobile-button, .combat-button {
width: 40px;
height: 40px;
font-size: 18px;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>
<body>
<header>
<h1>Doge Whale Game</h1>
<div class="nav-buttons">
<button class="nav-button" id="play-button">Play</button>
<button class="nav-button" id="spinner-button">Spinner</button>
<button class="nav-button" id="lottery-button">Lottery</button>
<button class="nav-button" id="store-button">Store</button>
<button class="nav-button" id="leaderboard-button">Leaderboard</button>
<button class="nav-button" id="admin-button">Admin</button>
</div>
<div class="user-info">
<span>Guest</span>
<div class="coin-display">
<div class="coin-icon">$</div>
<span id="coin-count">0</span>
</div>
</div>
</header>
<main>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="hud">
<div class="bar-container">
<div id="health-bar"></div>
</div>
<div class="bar-container">
<div id="power-bar"></div>
</div>
</div>
<div id="score-display">0</div>
<div id="combo-display">Combo x1</div>
<div id="power-up-notification"></div>
<div id="mobile-controls">
<div class="mobile-button" id="mobile-up"><i class="fas fa-arrow-up"></i></div>
<div class="mobile-button" id="mobile-down"><i class="fas fa-arrow-down"></i></div>
</div>
<div id="combat-controls">
<div class="combat-button" id="combat-fire"><i class="fas fa-bolt"></i></div>
<div class="combat-button" id="combat-special"><i class="fas fa-star"></i></div>
<div class="combat-button" id="auto-fire-toggle"><i class="fas fa-robot"></i></div>
<div class="combat-button" id="auto-target-toggle"><i class="fas fa-crosshairs"></i></div>
</div>
<div class="water-container">
<div class="water-wave-1"></div>
<div class="water-wave-2"></div>
<div class="water-wave-3"></div>
</div>
<div class="overlay active" id="start-overlay">
<h2>Doge Whale Game</h2>
<p>Navigate through pipes and defeat enemy whales!</p>
<button class="button" id="start-button">Start Game</button>
</div>
<div class="overlay" id="game-over-overlay">
<h2>Game Over</h2>
<p>Your score: <span id="final-score">0</span></p>
<button class="button" id="restart-button">Play Again</button>
<button class="close-button" id="game-over-close-button"><i class="fas fa-times"></i></button>
</div>
<!-- Spinner Wheel Overlay -->
<div class="overlay" id="spinner-overlay">
<button class="close-button" id="spinner-x-button"><i class="fas fa-times"></i></button>
<button class="back-button" id="spinner-back-button"><i class="fas fa-arrow-left"></i></button>
<h2>Spin & Win</h2>
<p>Spin the wheel to win DWHL coins!</p>
<div id="spinner-container">
<div id="spinner-arrow"></div>
<div id="spinner-wheel">
<!-- Wheel sections will be generated by JavaScript -->
</div>
</div>
<div id="spin-result"></div>
<div id="spin-timer"></div>
<button class="button" id="spin-button">SPIN</button>
<button class="button" id="spinner-close-button">Close</button>
</div>
<!-- Lottery Overlay -->
<div class="overlay" id="lottery-overlay">
<button class="close-button" id="lottery-x-button"><i class="fas fa-times"></i></button>
<button class="back-button" id="lottery-back-button"><i class="fas fa-arrow-left"></i></button>
<h2>DWHL Lottery</h2>
<p>Pay 5 DWHL to enter the lottery pool and scratch cards to win!</p>
<div id="lottery-container">
<div id="lottery-info">
<div id="lottery-pool">Current Pool: 0 DWHL</div>
<div id="lottery-status">Enter the lottery for a chance to win!</div>
<div id="lottery-timer"></div>
</div>
<div id="lottery-cards">
<!-- Lottery cards will be generated by JavaScript -->
</div>
<button class="button" id="lottery-entry-button">Enter Lottery (5 DWHL)</button>
<button class="button" id="lottery-close-button">Close</button>
</div>
</div>
<!-- Admin Panel Overlay -->
<div class="overlay" id="admin-overlay">
<button class="close-button" id="admin-x-button"><i class="fas fa-times"></i></button>
<button class="back-button" id="admin-back-button"><i class="fas fa-arrow-left"></i></button>
<h2>Admin Panel</h2>
<p>Configure game settings and features</p>
<div id="admin-container">
<div class="admin-section">
<h3>Spinner Settings</h3>
<div class="admin-controls">
<div class="admin-control">
<label for="spinner-min-prize">Minimum Prize</label>
<input type="number" id="spinner-min-prize" min="1" max="100" value="1">
</div>
<div class="admin-control">
<label for="spinner-max-prize">Maximum Prize</label>
<input type="number" id="spinner-max-prize" min="1" max="1000" value="100">
</div>
<div class="admin-control">
<label for="spinner-cooldown">Cooldown (hours)</label>
<input type="number" id="spinner-cooldown" min="0" max="72" value="24">
</div>
<div class="admin-control">
<label for="spinner-enabled">Enable Spinner</label>
<input type="checkbox" id="spinner-enabled" checked>
</div>
</div>
</div>
<div class="admin-section">
<h3>Lottery Settings</h3>
<div class="admin-controls">
<div class="admin-control">
<label for="lottery-entry-fee">Entry Fee</label>
<input type="number" id="lottery-entry-fee" min="1" max="100" value="5">
</div>
<div class="admin-control">
<label for="lottery-max-prize">Maximum Prize</label>
<input type="number" id="lottery-max-prize" min="10" max="1000" value="200">
</div>
<div class="admin-control">
<label for="lottery-card-count">Card Count</label>
<input type="number" id="lottery-card-count" min="3" max="12" value="6">
</div>
<div class="admin-control">
<label for="lottery-win-chance">Win Chance (%)</label>
<input type="number" id="lottery-win-chance" min="1" max="100" value="30">
</div>
<div class="admin-control">
<label for="lottery-enabled">Enable Lottery</label>
<input type="checkbox" id="lottery-enabled" checked>
</div>
</div>
</div>
<div class="admin-section">
<h3>Game Settings</h3>
<div class="admin-controls">
<div class="admin-control">
<label for="game-difficulty">Difficulty</label>
<input type="number" id="game-difficulty" min="1" max="10" value="5">
</div>
<div class="admin-control">
<label for="game-coin-multiplier">Coin Multiplier</label>
<input type="number" id="game-coin-multiplier" min="0.1" max="10" step="0.1" value="1">
</div>
<div class="admin-control">
<label for="game-score-multiplier">Score Multiplier</label>
<input type="number" id="game-score-multiplier" min="0.1" max="10" step="0.1" value="1">
</div>
</div>
</div>
<div class="admin-buttons">
<button class="button" id="admin-save-button">Save Settings</button>
<button class="button" id="admin-reset-button">Reset to Default</button>
<button class="button" id="admin-close-button">Close</button>
</div>
</div>
</div>
<!-- Store Overlay -->
<div class="overlay" id="store-overlay">
<button class="close-button" id="store-x-button"><i class="fas fa-times"></i></button>
<button class="back-button" id="store-back-button"><i class="fas fa-arrow-left"></i></button>
<h2>DWHL Store</h2>
<p>Spend your DWHL coins on upgrades and items!</p>
<div id="store-container">
<div class="store-items">
<!-- Store items will be generated by JavaScript -->
</div>
<div id="withdrawal-section">
<h3>Withdraw DWHL</h3>
<p>Convert your DWHL coins to real value!</p>
<p>Current Balance: <span id="withdrawal-balance">0</span> DWHL</p>
<button class="button" id="withdrawal-button">Withdraw</button>
</div>
<button class="button" id="store-close-button">Close</button>
</div>
</div>
<!-- Leaderboard Overlay -->
<div class="overlay" id="leaderboard-overlay">
<button class="close-button" id="leaderboard-x-button"><i class="fas fa-times"></i></button>
<button class="back-button" id="leaderboard-back-button"><i class="fas fa-arrow-left"></i></button>
<h2>Leaderboard</h2>
<p>See the top players and their scores!</p>
<div id="leaderboard-container">
<div class="leaderboard-tabs">
<div class="leaderboard-tab active" data-tab="daily">Daily</div>
<div class="leaderboard-tab" data-tab="weekly">Weekly</div>
<div class="leaderboard-tab" data-tab="all-time">All Time</div>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th class="rank">Rank</th>
<th>Player</th>
<th class="score">Score</th>
</tr>
</thead>
<tbody id="leaderboard-body">
<!-- Leaderboard entries will be generated by JavaScript -->
</tbody>
</table>
<button class="button" id="leaderboard-close-button">Close</button>
</div>
</div>
</div>
</main>
<!-- Audio elements for sound effects -->
<audio id="water-ambient" loop preload="auto">
<source src="https://assets.mixkit.co/sfx/preview/mixkit-water-flowing-loop-1189.mp3" type="audio/mpeg">
</audio>
<audio id="water-splash" preload="auto">
<source src="https://assets.mixkit.co/sfx/preview/mixkit-water-splash-1295.mp3" type="audio/mpeg">
</audio>
<audio id="weapon-sound" preload="auto">
<source src="https://assets.mixkit.co/sfx/preview/mixkit-laser-weapon-shot-1681.mp3" type="audio/mpeg">
</audio>
<script>
// Game initialization
document.addEventListener('DOMContentLoaded', function() {
// Game variables
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const healthBar = document.getElementById('health-bar');
const powerBar = document.getElementById('power-bar');
const scoreDisplay = document.getElementById('score-display');
const comboDisplay = document.getElementById('combo-display');
const powerUpNotification = document.getElementById('power-up-notification');
const startOverlay = document.getElementById('start-overlay');
const gameOverOverlay = document.getElementById('game-over-overlay');
const finalScoreDisplay = document.getElementById('final-score');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button');
const gameOverCloseButton = document.getElementById('game-over-close-button');
const mobileUpButton = document.getElementById('mobile-up');
const mobileDownButton = document.getElementById('mobile-down');
const combatFireButton = document.getElementById('combat-fire');
const combatSpecialButton = document.getElementById('combat-special');
const autoFireToggle = document.getElementById('auto-fire-toggle');
const autoTargetToggle = document.getElementById('auto-target-toggle');
// Audio elements
const waterAmbientSound = document.getElementById('water-ambient');
const waterSplashSound = document.getElementById('water-splash');
const weaponSound = document.getElementById('weapon-sound');
// Set volume levels
waterAmbientSound.volume = 0.3;
waterSplashSound.volume = 0.4;
weaponSound.volume = 0.2;
// Game state
let gameActive = false;
let health = 100;
let power = 0;
let score = 0;
let combo = 1;
let coins = 0;
let lastFrameTime = 0;
let keys = {};
// Game objects
let player = {
x: 50,
y: 200,
width: 60,
height: 40,
velocity: 0,
color: '#FF4500'
};
let pipes = [];
let enemies = [];
let projectiles = [];
let enemyProjectiles = [];
let powerUps = [];
let particles = [];
let clouds = [];
// Game settings
const gameSettings = {
combat: {
autoFireEnabled: true,
autoFireRate: 500, // milliseconds
autoTargetingEnabled: true,
targetingRange: 300,
projectileSpeed: 10,
projectileSize: 5,
projectileDamage: 1
}
};
let lastAutoFireTime = 0;
// Whale animator
const whaleAnimator = new WhaleAnimator(ctx);
let whaleImagesLoaded = false;
// Initialize the game
function initGame() {
// Set canvas size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
// Reset game state
health = 100;
power = 0;
score = 0;
combo = 1;
// Update UI
healthBar.style.width = `${health}%`;
powerBar.style.width = `${power}%`;
scoreDisplay.textContent = score;
comboDisplay.textContent = `Combo x${combo}`;
comboDisplay.style.opacity = '0';
// Reset game objects
player = {
x: 50,
y: canvas.height / 2 - 20,
width: 60,
height: 40,
velocity: 0,
color: '#FF4500'
};
pipes = [];
enemies = [];
projectiles = [];
enemyProjectiles = [];
powerUps = [];
particles = [];
// Generate initial clouds
clouds = [];
generateClouds();
// Create water particles
createWaterParticles();
// Start ambient water sound
waterAmbientSound.play();
// Load whale images if not already loaded
if (!whaleImagesLoaded) {
whaleAnimator.loadImages().then(success => {
whaleImagesLoaded = success;
console.log("Whale images loaded:", success);
});
}
// Start game loop
gameActive = true;
requestAnimationFrame(gameLoop);
}
// Game loop
function gameLoop(timestamp) {
if (!gameActive) return;
// Calculate delta time
const deltaTime = (timestamp - lastFrameTime) / 1000;
lastFrameTime = timestamp;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update game state
updateGameState(deltaTime);
// Check collisions
checkCollisions();
// Draw game objects
drawGameObjects();
// Request next frame
requestAnimationFrame(gameLoop);
}
// Update game state
function updateGameState(deltaTime) {
// Update player position
if (keys['ArrowUp'] || keys['w']) {
player.velocity -= 0.5;
}
if (keys['ArrowDown'] || keys['s']) {
player.velocity += 0.5;
}
// Apply gravity
player.velocity += 0.2;
// Limit velocity
player.velocity = Math.max(-10, Math.min(10, player.velocity));
// Update player position
player.y += player.velocity;
// Keep player within bounds
if (player.y < 0) {
player.y = 0;
player.velocity = 0;
}
if (player.y + player.height > canvas.height) {
player.y = canvas.height - player.height;
player.velocity = 0;
}
// Generate pipes
if (Math.random() < 0.02) {
const gapHeight = 150;
const gapPosition = Math.random() * (canvas.height - gapHeight);
pipes.push({
x: canvas.width,
y: 0,
width: 50,
height: gapPosition,
passed: false
});
pipes.push({
x: canvas.width,
y: gapPosition + gapHeight,
width: 50,
height: canvas.height - gapPosition - gapHeight,
passed: false
});
}
// Update pipes
for (let i = 0; i < pipes.length; i++) {
pipes[i].x -= 2;
// Check if pipe is passed
if (!pipes[i].passed && pipes[i].x + pipes[i].width < player.x) {
pipes[i].passed = true;
score += 10;
scoreDisplay.textContent = score;
}
// Remove pipes that are off screen
if (pipes[i].x + pipes[i].width < 0) {
pipes.splice(i, 1);
i--;
}
}
// Generate enemies
if (Math.random() < 0.01) {
const enemy = {
x: canvas.width,
y: Math.random() * (canvas.height - 40),
width: 50,
height: 40,
velocity: -2 - Math.random() * 2,
color: '#4169E1',
health: 3,
lastShot: 0
};
enemies.push(enemy);
}
// Update enemies
for (let i = 0; i < enemies.length; i++) {
enemies[i].x += enemies[i].velocity;
// Enemy shooting
if (Date.now() - enemies[i].lastShot > 2000) {
enemies[i].lastShot = Date.now();
// Calculate angle to player
const dx = player.x + player.width/2 - (enemies[i].x + enemies[i].width/2);
const dy = player.y + player.height/2 - (enemies[i].y + enemies[i].height/2);
const angle = Math.atan2(dy, dx);
// Create enemy projectile
enemyProjectiles.push({
x: enemies[i].x,
y: enemies[i].y + enemies[i].height / 2,
size: 5,
color: '#FF0000',
vx: Math.cos(angle) * 5,
vy: Math.sin(angle) * 5
});
}
// Remove enemies that are off screen
if (enemies[i].x + enemies[i].width < 0) {
enemies.splice(i, 1);
i--;
}
}
// Auto-fire system
if (gameSettings.combat.autoFireEnabled && gameActive) {
const currentTime = Date.now();
if (currentTime - lastAutoFireTime >= gameSettings.combat.autoFireRate) {
lastAutoFireTime = currentTime;
autoFireProjectile();
}
}
// Update projectiles
updateProjectiles();
// Update enemy projectiles
for (let i = 0; i < enemyProjectiles.length; i++) {
enemyProjectiles[i].x += enemyProjectiles[i].vx;
enemyProjectiles[i].y += enemyProjectiles[i].vy;
// Remove projectiles that are off screen
if (
enemyProjectiles[i].x < 0 ||
enemyProjectiles[i].x > canvas.width ||
enemyProjectiles[i].y < 0 ||
enemyProjectiles[i].y > canvas.height
) {
enemyProjectiles.splice(i, 1);
i--;
}
}
// Update power-ups
for (let i = 0; i < powerUps.length; i++) {
powerUps[i].x -= 2;
// Remove power-ups that are off screen
if (powerUps[i].x + powerUps[i].size < 0) {
powerUps.splice(i, 1);
i--;
}
}
// Update particles
for (let i = 0; i < particles.length; i++) {
particles[i].x += particles[i].vx;
particles[i].y += particles[i].vy;
particles[i].alpha -= 0.01;
// Remove particles that are faded out
if (particles[i].alpha <= 0) {
particles.splice(i, 1);
i--;
}
}
// Update clouds
for (let i = 0; i < clouds.length; i++) {
clouds[i].x += clouds[i].speed;
// Remove clouds that are off screen
if (clouds[i].x - clouds[i].size > canvas.width) {
clouds.splice(i, 1);
i--;
}
}
// Generate new clouds
if (clouds.length < 5 && Math.random() < 0.01) {
generateCloud();
}
// Update whale animations
if (whaleImagesLoaded) {
whaleAnimator.update(deltaTime);
}
}
// Update projectiles with homing behavior
function updateProjectiles() {
for (let i = 0; i < projectiles.length; i++) {
const projectile = projectiles[i];
// Handle homing projectiles
if (projectile.homing && projectile.targetId >= 0 && projectile.targetId < enemies.length) {
const target = enemies[projectile.targetId];
// Calculate new angle to target
const dx = target.x + target.width/2 - projectile.x;
const dy = target.y + target.height/2 - projectile.y;
const targetAngle = Math.atan2(dy, dx);
// Gradually adjust angle towards target (homing effect)
const angleDiff = targetAngle - projectile.angle;
// Normalize angle difference to -PI to PI range
let normalizedAngleDiff = angleDiff;
while (normalizedAngleDiff > Math.PI) normalizedAngleDiff -= 2 * Math.PI;
while (normalizedAngleDiff < -Math.PI) normalizedAngleDiff += 2 * Math.PI;
// Adjust angle with a turning rate factor (0.1 for gentle homing)
projectile.angle += normalizedAngleDiff * 0.1;
// Update velocity based on new angle
projectile.vx = Math.cos(projectile.angle) * projectile.speed;
projectile.vy = Math.sin(projectile.angle) * projectile.speed;
} else if (!projectile.homing) {
// Non-homing projectiles move straight
projectile.vx = projectile.speed;
projectile.vy = 0;
}
// Move projectile
projectile.x += projectile.vx;
projectile.y += projectile.vy;
// Create trail effect for homing projectiles
if (projectile.homing) {
createParticles(projectile.x, projectile.y, 1, projectile.color);
}
// Remove projectiles that are off screen
if (
projectile.x < 0 ||
projectile.x > canvas.width ||
projectile.y < 0 ||
projectile.y > canvas.height
) {
projectiles.splice(i, 1);
i--;
}
}
}
// Draw game objects
function drawGameObjects() {
// Draw background
ctx.fillStyle = 'rgba(135, 206, 235, 0.3)'; // Light blue background
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw clouds
for (let i = 0; i < clouds.length; i++) {
const cloud = clouds[i];
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.beginPath();
ctx.arc(cloud.x, cloud.y, cloud.size, 0, Math.PI * 2);
ctx.fill();
// Draw additional cloud parts
ctx.beginPath();
ctx.arc(cloud.x + cloud.size * 0.5, cloud.y - cloud.size * 0.2, cloud.size * 0.7, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cloud.x - cloud.size * 0.5, cloud.y - cloud.size * 0.1, cloud.size * 0.6, 0, Math.PI * 2);
ctx.fill();
}
// Draw pipes with gradient
for (let i = 0; i < pipes.length; i++) {
const gradient = ctx.createLinearGradient(
pipes[i].x,
pipes[i].y,
pipes[i].x,
pipes[i].y + pipes[i].height
);
gradient.addColorStop(0, '#FF0000'); // Red at top
gradient.addColorStop(1, '#808080'); // Gray at bottom
ctx.fillStyle = gradient;
ctx.fillRect(pipes[i].x, pipes[i].y, pipes[i].width, pipes[i].height);
// Add highlight on the left edge for depth
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(pipes[i].x, pipes[i].y, 3, pipes[i].height);
// Add shadow on the right edge for depth
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(pipes[i].x + pipes[i].width - 3, pipes[i].y, 3, pipes[i].height);
// Add a border for better definition
ctx.strokeStyle = '#606060';
ctx.lineWidth = 2;
ctx.strokeRect(pipes[i].x, pipes[i].y, pipes[i].width, pipes[i].height);
}
// Draw power-ups
for (let i = 0; i < powerUps.length; i++) {
ctx.fillStyle = powerUps[i].color;
ctx.beginPath();
ctx.arc(powerUps[i].x, powerUps[i].y, powerUps[i].size, 0, Math.PI * 2);
ctx.fill();
// Draw icon
ctx.fillStyle = 'white';
ctx.font = `${powerUps[i].size}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(powerUps[i].icon, powerUps[i].x, powerUps[i].y);
}
// Draw player whale
if (whaleImagesLoaded) {
whaleAnimator.animateSwimming(player.x, player.y, player.width, player.height, 2);
} else {
// Fallback to original player drawing
ctx.fillStyle = player.color;
ctx.beginPath();
ctx.ellipse(
player.x + player.width / 2,
player.y + player.height / 2,
player.width / 2,
player.height / 2,
0,
0,
Math.PI * 2
);
ctx.fill();
// Draw player eye
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(
player.x + player.width * 0.7,
player.y + player.height * 0.4,
player.width * 0.1,
0,
Math.PI * 2
);
ctx.fill();
}
// Draw enemies with targeting indicators
for (let i = 0; i < enemies.length; i++) {
if (whaleImagesLoaded) {
// Use whale animation for enemies
whaleAnimator.animateCombat(enemies[i].x, enemies[i].y, enemies[i].width, enemies[i].height);
} else {
// Fallback to original enemy drawing
ctx.fillStyle = enemies[i].color;
ctx.beginPath();
ctx.ellipse(
enemies[i].x + enemies[i].width / 2,
enemies[i].y + enemies[i].height / 2,
enemies[i].width / 2,
enemies[i].height / 2,
0,
0,
Math.PI * 2
);
ctx.fill();
// Draw enemy eye
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(
enemies[i].x + enemies[i].width * 0.3,
enemies[i].y + enemies[i].height * 0.4,
enemies[i].width * 0.1,
0,
Math.PI * 2
);
ctx.fill();
}
// Draw targeting indicator for nearest enemy
if (gameSettings.combat.autoTargetingEnabled) {
const nearestEnemy = findNearestEnemy();
if (nearestEnemy === enemies[i]) {
drawTargetingIndicator(enemies[i]);
}
}
}
// Draw projectiles
for (let i = 0; i < projectiles.length; i++) {
ctx.fillStyle = projectiles[i].color;
ctx.beginPath();
ctx.arc(projectiles[i].x, projectiles[i].y, projectiles[i].size, 0, Math.PI * 2);
ctx.fill();
// Draw trail for homing projectiles
if (projectiles[i].homing) {
ctx.strokeStyle = projectiles[i].color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(projectiles[i].x, projectiles[i].y);
ctx.lineTo(projectiles[i].x - 20, projectiles[i].y);
ctx.stroke();
}
}
// Draw enemy projectiles
for (let i = 0; i < enemyProjectiles.length; i++) {
ctx.fillStyle = enemyProjectiles[i].color;
ctx.beginPath();
ctx.arc(enemyProjectiles[i].x, enemyProjectiles[i].y, enemyProjectiles[i].size, 0, Math.PI * 2);
ctx.fill();
}
// Draw particles
for (let i = 0; i < particles.length; i++) {
ctx.fillStyle = `rgba(${particles[i].r}, ${particles[i].g}, ${particles[i].b}, ${particles[i].alpha})`;
ctx.beginPath();
ctx.arc(particles[i].x, particles[i].y, particles[i].size, 0, Math.PI * 2);
ctx.fill();
}
}
// Find the nearest enemy within range
function findNearestEnemy() {
if (enemies.length === 0) return null;
let nearestEnemy = null;
let shortestDistance = Infinity;
for (let i = 0; i < enemies.length; i++) {
const enemy = enemies[i];
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is within targeting range
if (distance < gameSettings.combat.targetingRange && distance < shortestDistance) {
shortestDistance = distance;
nearestEnemy = enemy;
}
}
return nearestEnemy;
}
// Draw targeting indicator for enemy
function drawTargetingIndicator(enemy) {
ctx.strokeStyle = 'rgba(255, 0, 0, 0.7)';
ctx.lineWidth = 2;
// Draw targeting circle
ctx.beginPath();
ctx.arc(
enemy.x + enemy.width/2,
enemy.y + enemy.height/2,
enemy.width/2 + 5,
0,
Math.PI * 2
);
ctx.stroke();
// Draw crosshair lines
const centerX = enemy.x + enemy.width/2;
const centerY = enemy.y + enemy.height/2;
const size = enemy.width/2 + 10;
ctx.beginPath();
// Horizontal line
ctx.moveTo(centerX - size, centerY);
ctx.lineTo(centerX + size, centerY);
// Vertical line
ctx.moveTo(centerX, centerY - size);
ctx.lineTo(centerX, centerY + size);
ctx.stroke();
}
// Fire projectile
function fireProjectile() {
let projectileType = 'normal';
let projectileColor = '#4169E1'; // Royal blue
let projectileSize = 5;
let projectileSpeed = 10;
// Create projectile
const projectile = {
x: player.x + player.width,
y: player.y + player.height / 2,
size: projectileSize,
speed: projectileSpeed,
color: projectileColor,
type: projectileType,
vx: projectileSpeed,
vy: 0
};
projectiles.push(projectile);
// Play weapon sound
weaponSound.currentTime = 0;
weaponSound.play();
}
// Auto-fire projectile with targeting
function autoFireProjectile() {
// Find nearest enemy
const targetEnemy = findNearestEnemy();
// If no enemy in range, don't fire
if (!targetEnemy) return;
let projectileType = 'homing';
let projectileColor = '#4169E1'; // Royal blue
let projectileSize = gameSettings.combat.projectileSize;
let projectileSpeed = gameSettings.combat.projectileSpeed;
// Calculate angle to target
const dx = targetEnemy.x + targetEnemy.width/2 - (player.x + player.width);
const dy = targetEnemy.y + targetEnemy.height/2 - (player.y + player.height/2);
const angle = Math.atan2(dy, dx);
// Create projectile with targeting information
const projectile = {
x: player.x + player.width,
y: player.y + player.height / 2,
size: projectileSize,
speed: projectileSpeed,
color: projectileColor,
type: projectileType,
damage: gameSettings.combat.projectileDamage,
homing: true,
targetId: enemies.indexOf(targetEnemy),
vx: Math.cos(angle) * projectileSpeed,
vy: Math.sin(angle) * projectileSpeed,
angle: angle
};
// Add to projectiles array
projectiles.push(projectile);
// Create muzzle flash effect
createParticles(player.x + player.width, player.y + player.height / 2, 5, projectileColor);
// Play weapon sound
weaponSound.currentTime = 0;
weaponSound.play();
}
// Create particles
function createParticles(x, y, count, color) {
// Parse color to RGB
let r, g, b;
if (color.startsWith('#')) {
// Hex color
const hex = color.substring(1);
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
} else if (color.startsWith('rgb')) {
// RGB color
const rgb = color.match(/\d+/g);
r = parseInt(rgb[0]);
g = parseInt(rgb[1]);
b = parseInt(rgb[2]);
} else {
// Default color (white)
r = g = b = 255;
}
// Create particles
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 3;
particles.push({
x: x,
y: y,
size: 1 + Math.random() * 3,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
alpha: 1,
r: r,
g: g,
b: b
});
}
}
// Generate clouds
function generateClouds() {
for (let i = 0; i < 5; i++) {
generateCloud(true);
}
}
// Generate a single cloud
function generateCloud(initial = false) {
const size = 20 + Math.random() * 30;
const x = initial ? Math.random() * canvas.width : -size * 2;
const y = 20 + Math.random() * 100;
const speed = 0.2 + Math.random() * 0.3;
clouds.push({
x: x,
y: y,
size: size,
speed: speed
});
}
// Create water particles for animation
function createWaterParticles() {
const waterContainer = document.querySelector('.water-container');
// Create a new particle
function createParticle() {
const particle = document.createElement('div');
particle.classList.add('water-particle');
// Random size between 3px and 8px
const size = 3 + Math.random() * 5;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
// Random position along the water
const posX = Math.random() * waterContainer.offsetWidth;
particle.style.left = `${posX}px`;
particle.style.bottom = `${Math.random() * 20}px`;
// Random duration between 1s and 3s
const duration = 1 + Math.random() * 2;
particle.style.setProperty('--duration', `${duration}s`);
// Add to container
waterContainer.appendChild(particle);
// Remove after animation completes
setTimeout(() => {
particle.remove();
}, duration * 1000);
}
// Create particles at random intervals
setInterval(createParticle, 300);
}
// Check water collision for sound effects
function checkWaterCollision() {
const waterLevel = canvas.height - 50; // Adjust based on water height
// If player is near water level and moving
if (player.y + player.height > waterLevel && player.velocity > 1) {
waterSplashSound.currentTime = 0;
waterSplashSound.play();
}
}
// Check collisions
function checkCollisions() {
// Check player-pipe collisions
for (let i = 0; i < pipes.length; i++) {
if (
player.x < pipes[i].x + pipes[i].width &&
player.x + player.width > pipes[i].x &&
player.y < pipes[i].y + pipes[i].height &&
player.y + player.height > pipes[i].y
) {
// Player hit pipe
takeDamage(10);
createParticles(player.x, player.y, 20, player.color);
}
}
// Check player-enemy collisions
for (let i = 0; i < enemies.length; i++) {
if (
player.x < enemies[i].x + enemies[i].width &&
player.x + player.width > enemies[i].x &&
player.y < enemies[i].y + enemies[i].height &&
player.y + player.height > enemies[i].y
) {
// Player hit enemy
takeDamage(20);
createParticles(enemies[i].x, enemies[i].y, 30, enemies[i].color);
// Remove enemy
enemies.splice(i, 1);
i--;
}
}
// Check projectile-enemy collisions
for (let i = 0; i < projectiles.length; i++) {
for (let j = 0; j < enemies.length; j++) {
if (
projectiles[i].x > enemies[j].x &&
projectiles[i].x < enemies[j].x + enemies[j].width &&
projectiles[i].y > enemies[j].y &&
projectiles[i].y < enemies[j].y + enemies[j].height
) {
// Projectile hit enemy
createParticles(projectiles[i].x, projectiles[i].y, 10, projectiles[i].color);
// Damage enemy
enemies[j].health--;
// Check if enemy is defeated
if (enemies[j].health <= 0) {
// Create explosion
createParticles(
enemies[j].x + enemies[j].width / 2,
enemies[j].y + enemies[j].height / 2,
30,
enemies[j].color
);
// Increase score
score += 50 * combo;
scoreDisplay.textContent = score;
// Increase combo
combo++;
comboDisplay.textContent = `Combo x${combo}`;
comboDisplay.style.opacity = '1';
// Increase power
power += 10 * combo;
if (power > 100) power = 100;
powerBar.style.width = `${power}%`;
// Chance to spawn power-up
if (Math.random() < 0.3) {
const powerUpType = Math.floor(Math.random() * 3);
let powerUpColor, powerUpIcon, powerUpEffect;
switch (powerUpType) {
case 0: // Health
powerUpColor = '#FF4500';
powerUpIcon = '+';
powerUpEffect = () => {
health += 20;
if (health > 100) health = 100;
healthBar.style.width = `${health}%`;
showNotification('Health +20');
};
break;
case 1: // Power
powerUpColor = '#4169E1';
powerUpIcon = '*';
powerUpEffect = () => {
power += 30;
if (power > 100) power = 100;
powerBar.style.width = `${power}%`;
showNotification('Power +30');
};
break;
case 2: // Coins
powerUpColor = '#FFD700';
powerUpIcon = '$';
powerUpEffect = () => {
updateCoins(10);
showNotification('Coins +10');
};
break;
}
powerUps.push({
x: enemies[j].x + enemies[j].width / 2,
y: enemies[j].y + enemies[j].height / 2,
size: 15,
color: powerUpColor,
icon: powerUpIcon,
effect: powerUpEffect
});
}
// Remove enemy
enemies.splice(j, 1);
j--;
}
// Remove projectile
projectiles.splice(i, 1);
i--;
break;
}
}
}
// Check player-enemy projectile collisions
for (let i = 0; i < enemyProjectiles.length; i++) {
if (
enemyProjectiles[i].x > player.x &&
enemyProjectiles[i].x < player.x + player.width &&
enemyProjectiles[i].y > player.y &&
enemyProjectiles[i].y < player.y + player.height
) {
// Player hit by enemy projectile
takeDamage(5);
createParticles(enemyProjectiles[i].x, enemyProjectiles[i].y, 10, enemyProjectiles[i].color);
// Remove projectile
enemyProjectiles.splice(i, 1);
i--;
}
}
// Check player-power up collisions
for (let i = 0; i < powerUps.length; i++) {
const dx = powerUps[i].x - (player.x + player.width / 2);
const dy = powerUps[i].y - (player.y + player.height / 2);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < powerUps[i].size + player.width / 2) {
// Player collected power-up
powerUps[i].effect();
// Remove power-up
powerUps.splice(i, 1);
i--;
}
}
// Check water collision for sound effects
checkWaterCollision();
}
// Take damage
function takeDamage(amount) {
health -= amount;
healthBar.style.width = `${health}%`;
// Reset combo
combo = 1;
comboDisplay.style.opacity = '0';
// Check if player is defeated
if (health <= 0) {
gameOver();
}
}
// Game over
function gameOver() {
gameActive = false;
finalScoreDisplay.textContent = score;
gameOverOverlay.classList.add('active');
// Stop ambient water sound
waterAmbientSound.pause();
waterAmbientSound.currentTime = 0;
}
// Show notification
function showNotification(text) {
powerUpNotification.textContent = text;
powerUpNotification.style.opacity = '1';
powerUpNotification.style.transform = 'translateX(-50%) translateY(-20px)';
setTimeout(() => {
powerUpNotification.style.opacity = '0';
powerUpNotification.style.transform = 'translateX(-50%) translateY(0)';
}, 2000);
}
// Update coins
function updateCoins(amount) {
coins += amount;
document.getElementById('coin-count').textContent = coins;
}
// Event listeners
startButton.addEventListener('click', () => {
startOverlay.classList.remove('active');
initGame();
});
restartButton.addEventListener('click', () => {
gameOverOverlay.classList.remove('active');
initGame();
});
gameOverCloseButton.addEventListener('click', () => {
gameOverOverlay.classList.remove('active');
});
// Keyboard controls
document.addEventListener('keydown', (e) => {
keys[e.key] = true;
// Fire on space
if (e.key === ' ' && gameActive) {
fireProjectile();
}
});
document.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Mobile controls
mobileUpButton.addEventListener('touchstart', () => {
keys['ArrowUp'] = true;
});
mobileUpButton.addEventListener('touchend', () => {
keys['ArrowUp'] = false;
});
mobileDownButton.addEventListener('touchstart', () => {
keys['ArrowDown'] = true;
});
mobileDownButton.addEventListener('touchend', () => {
keys['ArrowDown'] = false;
});
// Combat controls
combatFireButton.addEventListener('click', () => {
if (gameActive) {
fireProjectile();
}
});
combatSpecialButton.addEventListener('click', () => {
if (gameActive && power >= 50) {
// Special attack: multiple projectiles
for (let i = -2; i <= 2; i++) {
const angle = i * Math.PI / 10;
const projectile = {
x: player.x + player.width,
y: player.y + player.height / 2,
size: 5,
speed: 10,
color: '#FF8C00',
type: 'special',
vx: Math.cos(angle) * 10,
vy: Math.sin(angle) * 10
};
projectiles.push(projectile);
}
// Use power
power -= 50;
powerBar.style.width = `${power}%`;
// Play sound
weaponSound.currentTime = 0;
weaponSound.play();
}
});
// Auto-fire toggle
autoFireToggle.addEventListener('click', () => {
gameSettings.combat.autoFireEnabled = !gameSettings.combat.autoFireEnabled;
autoFireToggle.style.backgroundColor = gameSettings.combat.autoFireEnabled ?
'rgba(0, 255, 0, 0.7)' : 'rgba(255, 0, 0, 0.7)';
});
// Auto-targeting toggle
autoTargetToggle.addEventListener('click', () => {
gameSettings.combat.autoTargetingEnabled = !gameSettings.combat.autoTargetingEnabled;
autoTargetToggle.style.backgroundColor = gameSettings.combat.autoTargetingEnabled ?
'rgba(0, 255, 0, 0.7)' : 'rgba(255, 0, 0, 0.7)';
});
// Resize canvas when window is resized
window.addEventListener('resize', () => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
});
});
// Whale Animator Class
class WhaleAnimator {
constructor(gameContext) {
this.ctx = gameContext;
this.whaleImages = [];
this.animationSequences = {
swim: {
frames: [1, 2, 3, 4, 5, 6, 7, 8],
frameRate: 8, // frames per second
loop: true
},
attack: {
frames: [10, 11, 12, 13, 14, 15],
frameRate: 10,
loop: false
},
combat: {
frames: [20, 21, 22, 23, 24, 25, 26, 27],
frameRate: 12,
loop: true
},
idle: {
frames: [30, 31, 32, 33],
frameRate: 4,
loop: true
},
damaged: {
frames: [40, 41, 42, 43],
frameRate: 8,
loop: false
}
};
this.currentAnimation = 'swim';
this.currentFrame = 0;
this.frameCounter = 0;
this.lastFrameTime = 0;
}
// Load all whale images
async loadImages() {
const imagePaths = [];
for (let i = 1; i <= 200; i++) {
imagePaths.push(`/home/ubuntu/processed_images_hd/1 (${i}).png`);
}
// Load all images
for (let path of imagePaths) {
const img = new Image();
img.src = path;
await new Promise(resolve => {
img.onload = resolve;
img.onerror = resolve; // Continue even if some images fail to load
});
this.whaleImages.push(img);
}
console.log(`Loaded ${this.whaleImages.length} whale images`);
return this.whaleImages.length > 0;
}
// Set current animation
setAnimation(animationName) {
if (this.animationSequences[animationName] && this.currentAnimation !== animationName) {
this.currentAnimation = animationName;
this.currentFrame = 0;
this.frameCounter = 0;
}
}
// Update animation frame
update(deltaTime) {
const sequence = this.animationSequences[this.currentAnimation];
if (!sequence) return;
this.frameCounter += deltaTime * sequence.frameRate;
if (this.frameCounter >= 1) {
this.currentFrame = (this.currentFrame + Math.floor(this.frameCounter)) % sequence.frames.length;
this.frameCounter = this.frameCounter % 1;
// If animation is not looping and we reached the end
if (!sequence.loop && this.currentFrame === sequence.frames.length - 1) {
// Switch back to swimming animation
this.setAnimation('swim');
}
}
}
// Draw the current animation frame
draw(x, y, width, height, flipX = false) {
const sequence = this.animationSequences[this.currentAnimation];
if (!sequence || this.whaleImages.length === 0) return;
const frameIndex = sequence.frames[this.currentFrame];
if (frameIndex >= this.whaleImages.length) return;
const image = this.whaleImages[frameIndex];
this.ctx.save();
if (flipX) {
this.ctx.translate(x + width, y);
this.ctx.scale(-1, 1);
this.ctx.drawImage(image, 0, 0, width, height);
} else {
this.ctx.drawImage(image, x, y, width, height);
}
this.ctx.restore();
}
// Create a swimming animation
animateSwimming(x, y, width, height, speed) {
this.setAnimation('swim');
// Add wave-like motion
const waveAmplitude = 5;
const waveFrequency = 0.1;
const offsetY = Math.sin(Date.now() * waveFrequency) * waveAmplitude;
this.draw(x, y + offsetY, width, height);
}
// Create an attack animation
animateAttack(x, y, width, height) {
this.setAnimation('attack');
// Add forward thrust motion during attack
const thrustDistance = 10;
const attackProgress = this.currentFrame / this.animationSequences.attack.frames.length;
const thrustOffset = Math.sin(attackProgress * Math.PI) * thrustDistance;
this.draw(x + thrustOffset, y, width, height);
}
// Create a combat animation
animateCombat(x, y, width, height) {
this.setAnimation('combat');
// Add combat effects (rotation, scaling)
const rotationAmount = 0.05;
const scaleAmount = 0.1;
const rotationOffset = Math.sin(Date.now() * 0.01) * rotationAmount;
const scaleOffset = 1 + Math.abs(Math.sin(Date.now() * 0.02)) * scaleAmount;
this.ctx.save();
this.ctx.translate(x + width/2, y + height/2);
this.ctx.rotate(rotationOffset);
this.ctx.scale(scaleOffset, scaleOffset);
this.ctx.translate(-(x + width/2), -(y + height/2));
this.draw(x, y, width, height);
this.ctx.restore();
}
// Create a damaged animation
animateDamaged(x, y, width, height) {
this.setAnimation('damaged');
// Add flash effect
const flashRate = 100; // ms
const shouldFlash = Math.floor(Date.now() / flashRate) % 2 === 0;
if (shouldFlash) {
this.ctx.globalAlpha = 0.7;
}
this.draw(x, y, width, height);
this.ctx.globalAlpha = 1.0;
}
}
</script>
</body>
</html>