A fun game created with AI
Created by BrightBuilder5936 • November 22, 2025
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Emergency Rescue Helicopter</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #1a1a2e;
font-family: Arial, sans-serif;
overflow: hidden;
touch-action: manipulation;
}
#gameContainer {
position: relative;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
canvas {
border: 2px solid #fff;
background: #87ceeb;
touch-action: none;
max-width: 100%;
max-height: 100%;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: clamp(12px, 2.5vw, 16px);
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
z-index: 10;
}
#tokenDisplay {
position: absolute;
top: 10px;
right: 10px;
color: #ffd700;
font-size: clamp(14px, 3vw, 20px);
background: rgba(0,0,0,0.8);
padding: 10px 15px;
border-radius: 5px;
z-index: 10;
font-weight: bold;
border: 2px solid #ffd700;
}
#controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: clamp(10px, 2vw, 14px);
background: rgba(0,0,0,0.7);
padding: 8px 12px;
border-radius: 5px;
text-align: center;
}
.button {
position: absolute;
background: rgba(255,255,255,0.2);
border: 2px solid white;
color: white;
padding: 15px;
border-radius: 50%;
font-size: 20px;
cursor: pointer;
user-select: none;
touch-action: manipulation;
min-width: 60px;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.button:active {
background: rgba(255,255,255,0.4);
}
#dropBtn {
bottom: 20px;
right: 20px;
}
#unloadBtn {
bottom: 90px;
right: 20px;
font-size: 14px;
background: rgba(255,100,100,0.3);
}
#unloadBtn:active {
background: rgba(255,100,100,0.6);
}
#shopBtn {
bottom: 20px;
left: 20px;
font-size: 14px;
background: rgba(255,215,0,0.3);
border-color: #ffd700;
}
#shopBtn:active {
background: rgba(255,215,0,0.6);
}
#missionComplete {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.95);
color: white;
padding: 30px;
border-radius: 10px;
text-align: center;
display: none;
z-index: 20;
max-width: 90%;
}
#missionComplete h2 {
margin-bottom: 15px;
color: #4CAF50;
}
#missionComplete button {
margin-top: 20px;
padding: 10px 30px;
font-size: 18px;
background: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#missionComplete button:hover {
background: #45a049;
}
#notification {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.85);
color: white;
padding: 20px 30px;
border-radius: 8px;
font-size: clamp(14px, 3vw, 18px);
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
z-index: 15;
}
#notification.show {
opacity: 1;
}
#heliShop {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.95);
color: white;
padding: 20px;
transform: translateY(100%);
transition: transform 0.3s ease-in-out;
z-index: 25;
max-height: 60vh;
overflow-y: auto;
}
#heliShop.show {
transform: translateY(0);
}
#heliShop h2 {
text-align: center;
margin-bottom: 20px;
color: #ffd700;
}
.heli-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.heli-card {
background: rgba(255,255,255,0.1);
border: 2px solid #444;
border-radius: 8px;
padding: 15px;
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.heli-card:hover {
border-color: #ffd700;
transform: translateY(-5px);
}
.heli-card.selected {
border-color: #4CAF50;
background: rgba(76,175,80,0.2);
}
.heli-card.locked {
opacity: 0.6;
}
.heli-card.locked:hover {
transform: none;
}
.heli-card.mission-locked {
opacity: 0.4;
pointer-events: none;
}
.heli-preview {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
background: rgba(0,0,0,0.3);
border-radius: 5px;
}
.heli-name {
font-weight: bold;
font-size: 16px;
margin-bottom: 8px;
color: #ffd700;
}
.heli-stats {
font-size: 12px;
line-height: 1.6;
margin-bottom: 10px;
}
.heli-price {
font-weight: bold;
color: #ffd700;
font-size: 14px;
margin-bottom: 8px;
}
.heli-status {
font-size: 12px;
padding: 5px 10px;
border-radius: 4px;
text-align: center;
}
.heli-status.owned {
background: #4CAF50;
color: white;
}
.heli-status.selected {
background: #2196F3;
color: white;
}
.heli-status.buy {
background: #ffd700;
color: #000;
cursor: pointer;
}
.heli-status.locked {
background: #666;
color: #ccc;
}
.heli-status.mission-locked {
background: #ff4444;
color: white;
}
.unlock-badge {
position: absolute;
top: 10px;
right: 10px;
background: #ff4444;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
font-weight: bold;
}
#closeShop {
display: block;
margin: 20px auto 0;
padding: 10px 30px;
background: #666;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
#closeShop:hover {
background: #555;
}
#tutorial {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.95);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 30;
padding: 20px;
overflow-y: auto;
}
#tutorial.hidden {
display: none;
}
.tutorial-content {
max-width: 600px;
text-align: left;
}
.tutorial-content h1 {
color: #ffd700;
text-align: center;
margin-bottom: 20px;
font-size: clamp(24px, 5vw, 32px);
}
.tutorial-section {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
border-left: 4px solid #ffd700;
}
.tutorial-section h3 {
color: #4CAF50;
margin-bottom: 10px;
font-size: clamp(16px, 3.5vw, 20px);
}
.tutorial-section p, .tutorial-section ul {
font-size: clamp(12px, 2.5vw, 16px);
line-height: 1.6;
margin-bottom: 8px;
}
.tutorial-section ul {
margin-left: 20px;
}
.tutorial-section li {
margin-bottom: 5px;
}
.control-key {
display: inline-block;
background: #333;
padding: 2px 8px;
border-radius: 3px;
border: 1px solid #666;
font-family: monospace;
margin: 0 2px;
}
.color-legend {
display: flex;
align-items: center;
margin: 5px 0;
}
.color-box {
width: 20px;
height: 20px;
border: 1px solid #fff;
margin-right: 10px;
display: inline-block;
}
#startButton {
display: block;
margin: 30px auto 0;
padding: 15px 40px;
font-size: clamp(18px, 4vw, 24px);
background: #4CAF50;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
#startButton:hover {
background: #45a049;
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.4);
}
#startButton:active {
transform: translateY(0);
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="game"></canvas>
<div id="ui">
<div>Scenario: <span id="scenario">Flood</span></div>
<div>Rescued: <span id="rescued">0</span>/10</div>
<div>Fuel: <span id="fuel">100</span>%</div>
<div>Weight: <span id="weight">0</span>/<span id="maxWeight">4</span></div>
<div>Supplies: <span id="supplies">5</span></div>
</div>
<div id="tokenDisplay">🪙 <span id="tokens">0</span></div>
<div id="controls">Arrows/WASD: Move | SPACE: Drop Supply | E/Unload: Drop Off at Base</div>
<div class="button" id="dropBtn">DROP</div>
<div class="button" id="unloadBtn">UNLOAD</div>
<div class="button" id="shopBtn">SHOP</div>
<div id="notification"></div>
<div id="missionComplete">
<h2 id="missionText">Mission Complete!</h2>
<p id="missionStats"></p>
<p id="tokenReward" style="color: #ffd700; font-size: 20px; margin-top: 10px;"></p>
<p id="unlockMessage" style="color: #4CAF50; font-size: 16px; margin-top: 10px;"></p>
<button id="nextBtn">Next Mission</button>
</div>
<div id="heliShop">
<h2>🚁 Helicopter Shop 🚁</h2>
<div class="heli-grid" id="heliGrid"></div>
<button id="closeShop">Close Shop</button>
</div>
<div id="tutorial">
<div class="tutorial-content">
<h1>🚁 Emergency Rescue Helicopter 🚁</h1>
<div class="tutorial-section">
<h3>🎯 Mission Objective</h3>
<p>You are a rescue helicopter pilot! Your mission is to save people in emergency situations and bring them safely to the hospital.</p>
</div>
<div class="tutorial-section">
<h3>🎮 Controls</h3>
<p><strong>Desktop:</strong></p>
<ul>
<li><span class="control-key">↑↓←→</span> or <span class="control-key">WASD</span> - Fly helicopter</li>
<li><span class="control-key">SPACE</span> - Drop supply package</li>
<li><span class="control-key">E</span> - Unload at hospital</li>
</ul>
<p><strong>Mobile/Touch:</strong></p>
<ul>
<li>Touch and drag on screen to fly</li>
<li>Tap <strong>DROP</strong> button to drop supplies</li>
<li>Tap <strong>UNLOAD</strong> button at hospital</li>
</ul>
</div>
<div class="tutorial-section">
<h3>📍 Game Elements</h3>
<div class="color-legend">
<span class="color-box" style="background: #ff0000;"></span>
<span><strong>Hospital (H):</strong> Drop off rescued people and refill supplies</span>
</div>
<div class="color-legend">
<span class="color-box" style="background: #ffff00;"></span>
<span><strong>Fuel Station (F):</strong> Hover to refuel your helicopter</span>
</div>
<div class="color-legend">
<span class="color-box" style="background: #ffa500;"></span>
<span><strong>Orange Person:</strong> Ready to rescue</span>
</div>
<div class="color-legend">
<span class="color-box" style="background: #ff0000;"></span>
<span><strong>Red Person:</strong> Needs supply drop first</span>
</div>
<div class="color-legend">
<span class="color-box" style="background: #00ff00;"></span>
<span><strong>Green Person:</strong> Has supply, ready to rescue</span>
</div>
</div>
<div class="tutorial-section">
<h3>🎯 How to Play</h3>
<ol>
<li>Fly to people who need rescue</li>
<li>If they're red, drop a supply package first</li>
<li>Pick them up (helicopter can hold limited passengers)</li>
<li>Return to hospital (H) and press <span class="control-key">E</span> or <strong>UNLOAD</strong></li>
<li>Watch your fuel! Visit fuel stations (F) when needed</li>
<li>Rescue all 10 people to complete the mission</li>
</ol>
</div>
<div class="tutorial-section">
<h3>🪙 Token System & Progression</h3>
<ul>
<li>Earn tokens by completing missions</li>
<li>Complete missions to <strong>unlock</strong> new helicopters</li>
<li>Every mission completed unlocks 3 more helicopters in the shop</li>
<li>Use tokens to <strong>purchase</strong> unlocked helicopters</li>
<li>Each helicopter has unique stats (speed, fuel, capacity)</li>
<li>Better helicopters help you complete harder missions!</li>
</ul>
</div>
<div class="tutorial-section">
<h3>⚠️ Watch Out For</h3>
<ul>
<li><strong>Wind:</strong> Can push your helicopter around</li>
<li><strong>Fuel:</strong> Running out means mission failure</li>
<li><strong>Weight:</strong> More passengers = slower flying</li>
<li><strong>Scenarios:</strong> Face floods, forest fires, and earthquakes</li>
</ul>
</div>
<button id="startButton">START RESCUE MISSION!</button>
</div>
</div>
</div>
<script>
window.addEventListener('load', function() {
try {
const canvas = document.getElementById('game');
if (!canvas) { alert('Canvas not found'); return; }
const ctx = canvas.getContext('2d');
if (!ctx) { alert('Canvas context failed'); return; }
// Tutorial handling
const tutorial = document.getElementById('tutorial');
const startButton = document.getElementById('startButton');
const hasSeenTutorial = localStorage.getItem('heliTutorialSeen');
if (hasSeenTutorial !== 'true') {
if (tutorial) tutorial.classList.remove('hidden');
} else {
if (tutorial) tutorial.classList.add('hidden');
}
if (startButton && tutorial) {
startButton.addEventListener('click', function() {
tutorial.classList.add('hidden');
localStorage.setItem('heliTutorialSeen', 'true');
});
}
// Responsive canvas setup
function resizeCanvas() {
const maxWidth = window.innerWidth - 40;
const maxHeight = window.innerHeight - 40;
const size = Math.min(maxWidth, maxHeight, 800);
canvas.style.width = size + 'px';
canvas.style.height = size * 0.75 + 'px';
canvas.width = 800;
canvas.height = 600;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
window.addEventListener('orientationchange', () => setTimeout(resizeCanvas, 100));
// Token and progression system
let tokens = parseInt(localStorage.getItem('heliTokens') || '0');
let ownedHelicopters = JSON.parse(localStorage.getItem('ownedHelicopters') || '[0]');
let selectedHeliIndex = parseInt(localStorage.getItem('selectedHeli') || '0');
let missionsCompleted = parseInt(localStorage.getItem('missionsCompleted') || '0');
// Helicopter types with unlock requirements
const helicopterTypes = [
{
name: 'Standard Rescue',
speed: 3,
maxFuel: 100,
maxPassengers: 4,
fuelEfficiency: 1,
color: '#ff6600',
price: 0,
unlockMission: 0,
description: 'Balanced helicopter for general rescue missions'
},
{
name: 'Speed Hawk',
speed: 4.5,
maxFuel: 80,
maxPassengers: 3,
fuelEfficiency: 1.3,
color: '#ff0000',
price: 50,
unlockMission: 1,
description: 'Fast but burns fuel quickly'
},
{
name: 'Heavy Lifter',
speed: 2.5,
maxFuel: 120,
maxPassengers: 6,
fuelEfficiency: 0.8,
color: '#0066ff',
price: 75,
unlockMission: 1,
description: 'Carries more passengers, fuel efficient'
},
{
name: 'Eco Ranger',
speed: 3,
maxFuel: 150,
maxPassengers: 4,
fuelEfficiency: 0.5,
color: '#00ff00',
price: 100,
unlockMission: 1,
description: 'Excellent fuel economy, long missions'
},
{
name: 'Super Rescue',
speed: 4,
maxFuel: 130,
maxPassengers: 5,
fuelEfficiency: 0.7,
color: '#ffd700',
price: 150,
unlockMission: 2,
description: 'Best all-around performance'
},
{
name: 'Mega Transport',
speed: 2,
maxFuel: 200,
maxPassengers: 8,
fuelEfficiency: 0.6,
color: '#800080',
price: 200,
unlockMission: 2,
description: 'Maximum capacity, ultimate endurance'
}
];
// Game state
const keys = {};
let gameTime = 0;
let currentScenario = 0;
const scenarios = ['Flood', 'Forest Fire', 'Earthquake'];
// Helicopter
const heli = {
x: 100,
y: 100,
width: 60,
height: 30,
speed: 3,
fuel: 100,
maxFuel: 100,
passengers: 0,
maxPassengers: 4,
supplies: 5,
rotorAngle: 0,
color: '#ff6600',
fuelEfficiency: 1
};
// Apply selected helicopter stats
function applyHelicopterStats() {
const heliType = helicopterTypes[selectedHeliIndex];
if (!heliType) return;
heli.speed = heliType.speed;
heli.maxFuel = heliType.maxFuel;
heli.maxPassengers = heliType.maxPassengers;
heli.color = heliType.color;
heli.fuelEfficiency = heliType.fuelEfficiency;
}
applyHelicopterStats();
// Wind effect
let windForce = 0;
let windDirection = 1;
let windChangeTimer = 0;
// People to rescue
let people = [];
let rescued = 0;
const totalToRescue = 10;
// Supplies dropped
let droppedSupplies = [];
// Base/Hospital
const base = { x: 50, y: 500, width: 80, height: 60 };
// Fuel stations
let fuelStations = [];
// Update token display
function updateTokenDisplay() {
const tokenEl = document.getElementById('tokens');
if (tokenEl) tokenEl.textContent = tokens;
}
updateTokenDisplay();
// Notification system
function showNotification(message) {
const notif = document.getElementById('notification');
if (notif) {
notif.textContent = message;
notif.classList.add('show');
setTimeout(() => {
notif.classList.remove('show');
}, 2000);
}
}
// Check if helicopter is at base
function isAtBase() {
return heli.x < base.x + base.width &&
heli.x + heli.width > base.x &&
heli.y < base.y + base.height &&
heli.y + heli.height > base.y;
}
// Unload cargo at base
function unloadCargo() {
if (!isAtBase()) {
showNotification('Must be at Hospital (H) to unload!');
return;
}
let unloadedItems = [];
if (heli.passengers > 0) {
rescued += heli.passengers;
unloadedItems.push(`${heli.passengers} passenger${heli.passengers > 1 ? 's' : ''}`);
heli.passengers = 0;
}
if (heli.supplies < 5) {
const refilled = 5 - heli.supplies;
heli.supplies = 5;
unloadedItems.push(`+${refilled} supply${refilled > 1 ? 'ies' : 'y'}`);
}
if (unloadedItems.length > 0) {
showNotification('Unloaded: ' + unloadedItems.join(', '));
} else {
showNotification('Nothing to unload. Supplies refilled!');
heli.supplies = 5;
}
}
// Initialize level
function initLevel() {
people = [];
droppedSupplies = [];
rescued = 0;
heli.x = 100;
heli.y = 100;
heli.fuel = heli.maxFuel;
heli.passengers = 0;
heli.supplies = 5;
windForce = 0;
windDirection = 1;
applyHelicopterStats();
for (let i = 0; i < totalToRescue; i++) {
people.push({
x: 150 + Math.random() * 600,
y: 400 + Math.random() * 150,
width: 20,
height: 30,
needsSupply: Math.random() > 0.5,
hasSupply: false,
rescued: false
});
}
fuelStations = [
{ x: 700, y: 100, width: 40, height: 40 },
{ x: 400, y: 300, width: 40, height: 40 }
];
}
initLevel();
// Keyboard controls
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (e.key === ' ') {
e.preventDefault();
dropSupply();
}
if (e.key === 'e' || e.key === 'E') {
e.preventDefault();
unloadCargo();
}
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// Touch controls
let touchStartX = 0, touchStartY = 0;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const deltaX = e.touches[0].clientX - touchStartX;
const deltaY = e.touches[0].clientY - touchStartY;
if (Math.abs(deltaX) > 5) {
if (deltaX > 0) keys['ArrowRight'] = true;
else keys['ArrowLeft'] = true;
}
if (Math.abs(deltaY) > 5) {
if (deltaY > 0) keys['ArrowDown'] = true;
else keys['ArrowUp'] = true;
}
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
keys['ArrowUp'] = false;
keys['ArrowDown'] = false;
}, { passive: false });
// Drop button
const dropBtn = document.getElementById('dropBtn');
if (dropBtn) {
dropBtn.addEventListener('click', dropSupply);
dropBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
dropSupply();
}, { passive: false });
}
// Unload button
const unloadBtn = document.getElementById('unloadBtn');
if (unloadBtn) {
unloadBtn.addEventListener('click', unloadCargo);
unloadBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
unloadCargo();
}, { passive: false });
}
// Shop button
const shopBtn = document.getElementById('shopBtn');
const heliShop = document.getElementById('heliShop');
if (shopBtn && heliShop) {
shopBtn.addEventListener('click', () => {
heliShop.classList.toggle('show');
if (heliShop.classList.contains('show')) {
renderShop();
}
});
shopBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
heliShop.classList.toggle('show');
if (heliShop.classList.contains('show')) {
renderShop();
}
}, { passive: false });
}
// Close shop button
const closeShopBtn = document.getElementById('closeShop');
if (closeShopBtn && heliShop) {
closeShopBtn.addEventListener('click', () => {
heliShop.classList.remove('show');
});
}
// Render helicopter shop with unlock system
function renderShop() {
const heliGrid = document.getElementById('heliGrid');
if (!heliGrid) return;
heliGrid.innerHTML = '';
helicopterTypes.forEach((heliType, index) => {
const card = document.createElement('div');
card.className = 'heli-card';
const isOwned = ownedHelicopters.includes(index);
const isSelected = selectedHeliIndex === index;
const canAfford = tokens >= heliType.price;
const isUnlocked = missionsCompleted >= heliType.unlockMission;
if (isSelected) card.classList.add('selected');
if (!isOwned && !isUnlocked) card.classList.add('mission-locked');
else if (!isOwned) card.classList.add('locked');
let statusHTML = '';
let statusClass = '';
let statusText = '';
if (!isUnlocked) {
statusClass = 'mission-locked';
statusText = `Unlock: Complete ${heliType.unlockMission} mission${heliType.unlockMission > 1 ? 's' : ''}`;
} else if (isSelected) {
statusClass = 'selected';
statusText = 'EQUIPPED';
} else if (isOwned) {
statusClass = 'owned';
statusText = 'SELECT';
} else if (canAfford) {
statusClass = 'buy';
statusText = 'BUY';
} else {
statusClass = 'locked';
statusText = 'LOCKED';
}
const unlockBadge = !isUnlocked ? `<div class="unlock-badge">🔒 Mission ${heliType.unlockMission}</div>` : '';
card.innerHTML = `
${unlockBadge}
<div class="heli-preview" style="background: ${heliType.color};">
<svg width="60" height="30" viewBox="0 0 60 30">
<rect x="0" y="10" width="60" height="20" fill="${heliType.color}" stroke="#000" stroke-width="1"/>
<rect x="5" y="12" width="20" height="15" fill="rgba(135,206,235,0.5)" stroke="#000" stroke-width="1"/>
<line x1="5" y1="10" x2="55" y2="10" stroke="#333" stroke-width="3"/>
</svg>
</div>
<div class="heli-name">${heliType.name}</div>
<div class="heli-stats">
Speed: ${heliType.speed}<br>
Fuel: ${heliType.maxFuel}<br>
Capacity: ${heliType.maxPassengers}<br>
${heliType.description}
</div>
<div class="heli-price">${heliType.price === 0 ? 'FREE' : '🪙 ' + heliType.price + ' tokens'}</div>
<div class="heli-status ${statusClass}">
${statusText}
</div>
`;
if (isUnlocked) {
card.addEventListener('click', () => {
if (isOwned) {
selectedHeliIndex = index;
localStorage.setItem('selectedHeli', selectedHeliIndex);
applyHelicopterStats();
showNotification(`Equipped: ${heliType.name}`);
renderShop();
} else if (canAfford) {
tokens -= heliType.price;
ownedHelicopters.push(index);
selectedHeliIndex = index;
localStorage.setItem('heliTokens', tokens);
localStorage.setItem('ownedHelicopters', JSON.stringify(ownedHelicopters));
localStorage.setItem('selectedHeli', selectedHeliIndex);
updateTokenDisplay();
applyHelicopterStats();
showNotification(`Purchased: ${heliType.name}!`);
renderShop();
} else {
showNotification(`Need ${heliType.price - tokens} more tokens!`);
}
});
}
heliGrid.appendChild(card);
});
}
function dropSupply() {
if (heli.supplies > 0) {
droppedSupplies.push({
x: heli.x + heli.width / 2,
y: heli.y + heli.height,
width: 15,
height: 15,
vy: 2
});
heli.supplies--;
showNotification('Supply dropped!');
} else {
showNotification('No supplies! Return to Hospital (H)');
}
}
// Update game
function update() {
gameTime++;
// Update wind
windChangeTimer++;
if (windChangeTimer > 180) {
windForce = (Math.random() - 0.5) * 2;
windDirection = Math.random() > 0.5 ? 1 : -1;
windChangeTimer = 0;
}
// Helicopter movement with wind effect
const weightPenalty = 1 - (heli.passengers * 0.15);
const effectiveSpeed = heli.speed * weightPenalty;
if (keys['ArrowLeft'] || keys['a'] || keys['A']) {
heli.x -= effectiveSpeed;
}
if (keys['ArrowRight'] || keys['d'] || keys['D']) {
heli.x += effectiveSpeed;
}
if (keys['ArrowUp'] || keys['w'] || keys['W']) {
heli.y -= effectiveSpeed;
}
if (keys['ArrowDown'] || keys['s'] || keys['S']) {
heli.y += effectiveSpeed;
}
// Apply wind effect
heli.x += windForce * windDirection * 0.3;
// Boundaries
heli.x = Math.max(0, Math.min(canvas.width - heli.width, heli.x));
heli.y = Math.max(0, Math.min(canvas.height - heli.height, heli.y));
// Consume fuel (affected by efficiency)
if (gameTime % 2 === 0) {
heli.fuel -= 0.05 * (1 + heli.passengers * 0.1) * heli.fuelEfficiency;
heli.fuel = Math.max(0, heli.fuel);
}
// Rotor animation
heli.rotorAngle += 15;
// Check collision with people
people.forEach(person => {
if (!person.rescued &&
heli.x < person.x + person.width &&
heli.x + heli.width > person.x &&
heli.y < person.y + person.height &&
heli.y + heli.height > person.y) {
if (heli.passengers < heli.maxPassengers) {
if (!person.needsSupply || person.hasSupply) {
person.rescued = true;
heli.passengers++;
showNotification('Person rescued! Return to Hospital (H)');
} else {
if (gameTime % 60 === 0) {
showNotification('Person needs supply first!');
}
}
} else {
if (gameTime % 60 === 0) {
showNotification('Helicopter full! Unload at Hospital (H)');
}
}
}
});
// Check collision with fuel stations
fuelStations.forEach(station => {
if (heli.x < station.x + station.width &&
heli.x + heli.width > station.x &&
heli.y < station.y + station.height &&
heli.y + heli.height > station.y) {
if (gameTime % 3 === 0 && heli.fuel < heli.maxFuel) {
heli.fuel = Math.min(heli.maxFuel, heli.fuel + 1);
}
}
});
// Update dropped supplies
droppedSupplies = droppedSupplies.filter((supply, index) => {
supply.y += supply.vy;
let shouldKeep = true;
people.forEach(person => {
if (!person.hasSupply &&
supply.x < person.x + person.width &&
supply.x + supply.width > person.x &&
supply.y < person.y + person.height &&
supply.y + supply.height > person.y) {
person.hasSupply = true;
shouldKeep = false;
showNotification('Supply delivered!');
}
});
if (supply.y > canvas.height) {
shouldKeep = false;
}
return shouldKeep;
});
// Update UI
const scenarioEl = document.getElementById('scenario');
const rescuedEl = document.getElementById('rescued');
const fuelEl = document.getElementById('fuel');
const weightEl = document.getElementById('weight');
const maxWeightEl = document.getElementById('maxWeight');
const suppliesEl = document.getElementById('supplies');
if (scenarioEl) scenarioEl.textContent = scenarios[currentScenario];
if (rescuedEl) rescuedEl.textContent = rescued;
if (fuelEl) fuelEl.textContent = Math.floor(heli.fuel);
if (weightEl) weightEl.textContent = heli.passengers;
if (maxWeightEl) maxWeightEl.textContent = heli.maxPassengers;
if (suppliesEl) suppliesEl.textContent = heli.supplies;
// Check win condition
if (rescued >= totalToRescue) {
showMissionComplete();
}
// Check lose condition
if (heli.fuel <= 0) {
showMissionFailed();
}
}
// Draw game
function draw() {
// Background based on scenario
if (currentScenario === 0) {
ctx.fillStyle = '#87ceeb';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#4169e1';
ctx.fillRect(0, 400, canvas.width, 200);
} else if (currentScenario === 1) {
ctx.fillStyle = '#ff6347';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#8b4513';
ctx.fillRect(0, 400, canvas.width, 200);
for (let i = 0; i < 10; i++) {
ctx.fillStyle = Math.random() > 0.5 ? '#ff4500' : '#ffa500';
ctx.fillRect(
Math.random() * canvas.width,
400 + Math.random() * 200,
5, 10
);
}
} else {
ctx.fillStyle = '#708090';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#a9a9a9';
ctx.fillRect(0, 400, canvas.width, 200);
}
// Draw base/hospital
if (isAtBase()) {
ctx.fillStyle = '#ffff00';
ctx.fillRect(base.x - 5, base.y - 5, base.width + 10, base.height + 10);
}
ctx.fillStyle = '#ff0000';
ctx.fillRect(base.x, base.y, base.width, base.height);
ctx.fillStyle = '#ffffff';
ctx.font = '20px Arial';
ctx.fillText('H', base.x + 30, base.y + 35);
ctx.font = '12px Arial';
ctx.fillText('UNLOAD', base.x + 10, base.y + 52);
// Draw fuel stations
fuelStations.forEach(station => {
ctx.fillStyle = '#ffff00';
ctx.fillRect(station.x, station.y, station.width, station.height);
ctx.fillStyle = '#000000';
ctx.font = '16px Arial';
ctx.fillText('F', station.x + 12, station.y + 25);
});
// Draw people
people.forEach(person => {
if (!person.rescued) {
if (person.needsSupply && !person.hasSupply) {
ctx.fillStyle = '#ff0000';
} else if (person.hasSupply) {
ctx.fillStyle = '#00ff00';
} else {
ctx.fillStyle = '#ffa500';
}
ctx.fillRect(person.x, person.y, person.width, person.height);
ctx.fillStyle = '#000000';
ctx.fillRect(person.x - 5, person.y + 5, 5, 10);
ctx.fillRect(person.x + person.width, person.y + 5, 5, 10);
}
});
// Draw dropped supplies
droppedSupplies.forEach(supply => {
ctx.fillStyle = '#00ff00';
ctx.fillRect(supply.x, supply.y, supply.width, supply.height);
ctx.fillStyle = '#ffffff';
ctx.fillRect(supply.x + 3, supply.y + 3, 9, 2);
ctx.fillRect(supply.x + 6, supply.y, 3, 9);
});
// Draw helicopter
ctx.fillStyle = heli.color;
ctx.fillRect(heli.x, heli.y + 10, heli.width, heli.height - 10);
ctx.fillStyle = '#87ceeb';
ctx.fillRect(heli.x + 5, heli.y + 12, 20, 15);
ctx.fillStyle = heli.color;
ctx.fillRect(heli.x + heli.width, heli.y + 15, 15, 5);
ctx.save();
ctx.translate(heli.x + heli.width / 2, heli.y + 10);
ctx.rotate(heli.rotorAngle * Math.PI / 180);
ctx.fillStyle = '#333333';
ctx.fillRect(-30, -2, 60, 4);
ctx.restore();
ctx.fillStyle = '#333333';
ctx.fillRect(heli.x + 10, heli.y + heli.height, 15, 3);
ctx.fillRect(heli.x + 35, heli.y + heli.height, 15, 3);
// Draw wind indicator
if (Math.abs(windForce) > 0.5) {
ctx.fillStyle = '#ffffff';
ctx.font = '14px Arial';
const windText = windDirection > 0 ? '→ WIND' : '← WIND';
ctx.fillText(windText, canvas.width - 80, 30);
}
// Draw passengers in helicopter
for (let i = 0; i < heli.passengers; i++) {
ctx.fillStyle = '#ffff00';
ctx.fillRect(heli.x + 30 + i * 8, heli.y + 20, 6, 10);
}
}
function showMissionComplete() {
const modal = document.getElementById('missionComplete');
const text = document.getElementById('missionText');
const stats = document.getElementById('missionStats');
const tokenReward = document.getElementById('tokenReward');
const unlockMessage = document.getElementById('unlockMessage');
if (modal && text && stats && tokenReward) {
const tokensEarned = 10 + Math.floor(heli.fuel / 10);
tokens += tokensEarned;
missionsCompleted++;
localStorage.setItem('heliTokens', tokens);
localStorage.setItem('missionsCompleted', missionsCompleted);
updateTokenDisplay();
text.textContent = 'Mission Complete!';
stats.textContent = `All ${totalToRescue} people rescued!
Fuel remaining: ${Math.floor(heli.fuel)}%`;
tokenReward.textContent = `🪙 +${tokensEarned} tokens earned!`;
// Show unlock message
if (unlockMessage) {
const newlyUnlocked = helicopterTypes.filter(h =>
h.unlockMission === missionsCompleted && !ownedHelicopters.includes(helicopterTypes.indexOf(h))
).length;
if (newlyUnlocked > 0) {
unlockMessage.textContent = `🎉 ${newlyUnlocked} new helicopter${newlyUnlocked > 1 ? 's' : ''} unlocked in shop!`;
} else {
unlockMessage.textContent = '';
}
}
modal.style.display = 'block';
}
}
function showMissionFailed() {
const modal = document.getElementById('missionComplete');
const text = document.getElementById('missionText');
const stats = document.getElementById('missionStats');
const tokenReward = document.getElementById('tokenReward');
const unlockMessage = document.getElementById('unlockMessage');
const btn = document.getElementById('nextBtn');
if (modal && text && stats && btn && tokenReward) {
const tokensEarned = rescued * 1;
if (tokensEarned > 0) {
tokens += tokensEarned;
localStorage.setItem('heliTokens', tokens);
updateTokenDisplay();
}
text.textContent = 'Mission Failed!';
stats.textContent = `Out of fuel!
Rescued: ${rescued}/${totalToRescue}`;
tokenReward.textContent = tokensEarned > 0 ? `🪙 +${tokensEarned} tokens earned` : 'No tokens earned';
if (unlockMessage) unlockMessage.textContent = '';
btn.textContent = 'Retry';
modal.style.display = 'block';
}
}
// Next mission button
const nextBtn = document.getElementById('nextBtn');
if (nextBtn) {
nextBtn.addEventListener('click', () => {
const modal = document.getElementById('missionComplete');
if (modal) modal.style.display = 'none';
if (rescued >= totalToRescue) {
currentScenario = (currentScenario + 1) % scenarios.length;
}
initLevel();
const btn = document.getElementById('nextBtn');
if (btn) btn.textContent = 'Next Mission';
});
}
// Game loop
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
} catch(e) {
document.body.innerHTML = '<div style="color:white;padding:20px;">Game error: ' + e.message + '</div>';
console.error(e);
}
});
</script>
</body>
</html>
Install GB3A on your device for a better experience!