/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Bird class var Bird = Container.expand(function () { var self = Container.call(this); self.speed = 0; self.dir = 1; // 1 = right, -1 = left self.isActive = true; var bird = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.isActive) return; self.x += self.speed * self.dir; }; self.crash = function () { if (!self.isActive) return; self.isActive = false; LK.getSound('bird_hit').play(); tween(self, { alpha: 0 }, { duration: 400, onFinish: function onFinish() { self.visible = false; } }); }; return self; }); // Kite class var Kite = Container.expand(function () { var self = Container.call(this); self.speed = 0; self.dir = 1; self.isActive = true; var kite = self.attachAsset('kite', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.isActive) return; self.x += self.speed * self.dir; }; self.crash = function () { if (!self.isActive) return; self.isActive = false; tween(self, { alpha: 0 }, { duration: 400, onFinish: function onFinish() { self.visible = false; } }); }; return self; }); // Paratrooper class var Paratrooper = Container.expand(function () { var self = Container.call(this); // State: 0 = falling, 1 = parachute open, 2 = landed, 3 = crashed, 4 = rescued self.state = 0; self.speed = 0; self.hasParachute = false; self.isActive = true; // Attach body var body = self.attachAsset('paratrooper_body', { anchorX: 0.5, anchorY: 0.0 }); body.y = 0; // Attach parachute (hidden at first) var chute = self.attachAsset('parachute', { anchorX: 0.5, anchorY: 1.0, y: 0 }); chute.visible = false; // For tap detection self.down = function (x, y, obj) { if (self.state === 0 && self.isActive) { self.openParachute(); } }; // Open parachute self.openParachute = function () { if (self.state !== 0) return; self.state = 1; self.hasParachute = true; chute.visible = true; // Slow down self.speed = self.speed * 0.28; // Animate parachute pop chute.scaleY = 0.1; tween(chute, { scaleY: 1 }, { duration: 200, easing: tween.bounceOut }); LK.getSound('parachute_open').play(); }; // Crash animation self.crash = function () { if (!self.isActive) return; self.isActive = false; self.state = 3; // Flash red LK.effects.flashObject(self, 0xff0000, 600); LK.getSound('crash').play(); // Fade out tween(self, { alpha: 0 }, { duration: 600, onFinish: function onFinish() { self.visible = false; } }); }; // Rescue animation self.rescue = function () { if (!self.isActive) return; self.isActive = false; self.state = 4; // Flash green LK.effects.flashObject(self, 0x00ff00, 400); // Fade out tween(self, { alpha: 0 }, { duration: 400, onFinish: function onFinish() { self.visible = false; } }); }; // Bird/kite collision self.hitHazard = function () { if (self.state === 0 && self.isActive) { self.crash(); } }; self.update = function () { if (!self.isActive) return; self.y += self.speed; }; return self; }); // Plane class (for visual, not interactive) var Plane = Container.expand(function () { var self = Container.call(this); var plane = self.attachAsset('plane', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ // Game constants // Paratrooper body (box), parachute (ellipse), bird (ellipse), kite (box), ground (box), plane (box) var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var GROUND_HEIGHT = 60; var LEVELS = 20; // Level config: Each level increases paratroopers, speed, and hazards var levelConfigs = []; for (var i = 0; i < LEVELS; i++) { // Level 1: 10 paratroopers, then +2 per level var paratroopersToRescue = 10 + i * 2; // Level duration: 1 minute (60 seconds * 60 FPS = 3600 ticks) var levelDurationTicks = 60 * 60; // Calculate spawn interval so all paratroopers spawn within the level duration var spawnInterval = Math.floor(levelDurationTicks / paratroopersToRescue); // Clamp spawnInterval to a minimum of 10 ticks for high levels spawnInterval = Math.max(spawnInterval, 10); levelConfigs.push({ paratroopers: paratroopersToRescue, speed: 9 + i * 1.2, hazards: i < 3 ? 0 : Math.min(1 + Math.floor((i - 2) / 2), 4), hazardSpeed: 7 + i * 0.7, hazardTypes: i < 6 ? ['bird'] : i < 12 ? ['bird', 'kite'] : ['bird', 'kite'], spawnInterval: spawnInterval, levelDurationTicks: levelDurationTicks }); } // State // Load saved progress if available, otherwise start at level 1 var currentLevel = storage.currentLevel || 1; var bestLevel = storage.bestLevel || 1; var paratroopers = []; var hazards = []; var rescuedCount = 0; var lostCount = 0; var totalToRescue = 0; var isLevelActive = false; var plane = null; var ground = null; var levelText = null; var rescuedText = null; var lostText = null; var bestLevelText = null; var levelTimeout = null; var spawnTick = 0; var hazardTick = 0; var dragNode = null; // Add ground ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: GAME_HEIGHT - GROUND_HEIGHT }); game.addChild(ground); // Add level text levelText = new Text2('Level 1', { size: 90, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); // Add objective text (upper left side, not in top left corner) var objectiveText = new Text2('', { size: 70, fill: 0xFFFFAA }); // Anchor to left, top objectiveText.anchor.set(0, 0); // Place at x=2 to avoid top left menu, y=0 (moved twice more left) objectiveText.x = 2; objectiveText.y = 50; LK.gui.top.addChild(objectiveText); // Add rescued/lost text rescuedText = new Text2('Rescued: 0', { size: 70, fill: 0xFFFFFF }); rescuedText.anchor.set(0, 0); LK.gui.top.addChild(rescuedText); lostText = new Text2('Lost: 0', { size: 70, fill: 0xFF4444 }); lostText.anchor.set(1, 0); LK.gui.topRight.addChild(lostText); // Add best level text bestLevelText = new Text2('Best: ' + bestLevel, { size: 60, fill: 0xFFFF00 }); bestLevelText.anchor.set(0.5, 0); LK.gui.bottom.addChild(bestLevelText); // Start music LK.playMusic('bgmusic'); // Show level number before starting, wait for tap to begin function showLevelIntro(level, callback) { // Hide timer and texts if visible if (levelTimerText) levelTimerText.visible = false; levelText.setText('Level ' + level); levelText.alpha = 1; levelText.visible = true; // Overlay to block input except tap to continue var introOverlay = new Container(); introOverlay.interactive = true; introOverlay.hitArea = { x: 0, y: 0, width: GAME_WIDTH, height: GAME_HEIGHT }; // Add a "Tap to Start" text var tapText = new Text2('Tap to Start', { size: 100, fill: 0xffffff }); tapText.anchor.set(0.5, 0.5); tapText.x = GAME_WIDTH / 2; tapText.y = GAME_HEIGHT / 2 + 180; introOverlay.addChild(tapText); // Add overlay to game game.addChild(introOverlay); // Only allow tap after a short delay to avoid accidental skip var canTap = false; LK.setTimeout(function () { canTap = true; }, 400); introOverlay.down = function () { if (!canTap) return; // Animate out tween(levelText, { alpha: 0 }, { duration: 400 }); tween(tapText, { alpha: 0 }, { duration: 400 }); LK.setTimeout(function () { introOverlay.destroy(); if (callback) callback(); }, 420); }; } // Start first level after intro showLevelIntro(currentLevel, function () { startLevel(currentLevel); }); var levelTimerText = null; var levelTimerTimeout = null; function startLevel(level) { // Update best level if needed if (level > bestLevel) { bestLevel = level; storage.bestLevel = bestLevel; if (bestLevelText) bestLevelText.setText('Best: ' + bestLevel); } // Save progress storage.currentLevel = level; // Reset state for (var i = 0; i < paratroopers.length; i++) { paratroopers[i].destroy(); } for (var i = 0; i < hazards.length; i++) { hazards[i].destroy(); } paratroopers = []; hazards = []; rescuedCount = 0; lostCount = 0; isLevelActive = true; spawnTick = 0; hazardTick = 0; totalToRescue = levelConfigs[level - 1].paratroopers; if (objectiveText) { objectiveText.setText('Rescue ' + totalToRescue + ' Paratroopers'); } levelText.setText('Level ' + level); rescuedText.setText('Rescued: 0'); lostText.setText('Lost: 0'); // Animate level text levelText.alpha = 1; tween(levelText, { alpha: 0 }, { duration: 1200 }); // Plane flyby if (plane) plane.destroy(); plane = new Plane(); plane.y = 180; plane.x = -200; game.addChild(plane); tween(plane, { x: GAME_WIDTH + 200 }, { duration: 1800, easing: tween.linear, onFinish: function onFinish() { plane.destroy(); plane = null; } }); // Add or reset timer text if (!levelTimerText) { levelTimerText = new Text2('01:00', { size: 80, fill: 0xFFFFFF }); levelTimerText.anchor.set(0.5, 0); LK.gui.top.addChild(levelTimerText); } // Place timer below level text levelTimerText.y = levelText.height + 10; levelTimerText.setText('01:00'); // Clear any previous timer if (levelTimerTimeout) { LK.clearTimeout(levelTimerTimeout); levelTimerTimeout = null; } // Start timer countdown var ticksLeft = levelConfigs[level - 1].levelDurationTicks; if (levelTimerText) levelTimerText.visible = true; function updateTimer() { if (!isLevelActive) return; ticksLeft--; var seconds = Math.floor(ticksLeft / 60); var ms = ticksLeft % 60; var timeStr = (seconds < 10 ? '0' : '') + seconds + ':' + (ms < 10 ? '0' : '') + ms; if (levelTimerText) levelTimerText.setText(timeStr); if (ticksLeft > 0 && isLevelActive) { levelTimerTimeout = LK.setTimeout(updateTimer, 1000 / 60); } else if (isLevelActive) { // Time's up, end level isLevelActive = false; // If not all rescued, game over, else win/next if (rescuedCount === totalToRescue) { LK.getSound('levelup').play(); if (currentLevel < LEVELS) { currentLevel++; LK.setTimeout(function () { startLevel(currentLevel); }, 1200); } else { LK.showYouWin(); } } else { LK.showGameOver(); } } } updateTimer(); } // Paratrooper spawn function spawnParatrooper() { var config = levelConfigs[currentLevel - 1]; var p = new Paratrooper(); // Random x, avoid edges var margin = 120; p.x = margin + Math.random() * (GAME_WIDTH - 2 * margin); p.y = 220; p.speed = config.speed + Math.random() * 2; p.state = 0; p.hasParachute = false; p.isActive = true; paratroopers.push(p); game.addChild(p); } // Hazard spawn function spawnHazard() { var config = levelConfigs[currentLevel - 1]; var type = config.hazardTypes[Math.floor(Math.random() * config.hazardTypes.length)]; var h = null; var y = 400 + Math.random() * (GAME_HEIGHT - GROUND_HEIGHT - 800); var dir = Math.random() < 0.5 ? 1 : -1; var speed = config.hazardSpeed + Math.random() * 2; var x = dir === 1 ? -100 : GAME_WIDTH + 100; if (type === 'bird') { h = new Bird(); } else { h = new Kite(); } h.x = x; h.y = y; h.dir = dir; h.speed = speed; h.isActive = true; hazards.push(h); game.addChild(h); } // Game move handler (for drag, but not used in this game) game.move = function (x, y, obj) { // No drag in this game }; // Game down handler (for tap on paratroopers) game.down = function (x, y, obj) { // Find topmost paratrooper under tap for (var i = paratroopers.length - 1; i >= 0; i--) { var p = paratroopers[i]; if (p.isActive && p.state === 0 && p.visible) { // Check if tap is inside body var local = p.toLocal({ x: x, y: y }); var body = p.children[0]; if (local.x >= body.x - body.width / 2 && local.x <= body.x + body.width / 2 && local.y >= body.y && local.y <= body.y + body.height) { p.openParachute(); break; } } } }; // Game update game.update = function () { if (!isLevelActive) return; var config = levelConfigs[currentLevel - 1]; // Spawn paratroopers if (paratroopers.length < totalToRescue && LK.ticks % config.spawnInterval === 0) { spawnParatrooper(); } // Spawn hazards if (config.hazards > 0 && hazards.length < config.hazards && LK.ticks % (80 + Math.floor(Math.random() * 40)) === 0) { spawnHazard(); } // Update paratroopers for (var i = paratroopers.length - 1; i >= 0; i--) { var p = paratroopers[i]; p.update(); // Check for ground collision if (p.isActive && p.y + 90 >= GAME_HEIGHT - GROUND_HEIGHT) { if (p.state === 1) { // Landed safely p.rescue(); rescuedCount++; rescuedText.setText('Rescued: ' + rescuedCount); } else if (p.state === 0) { // Crashed p.crash(); lostCount++; lostText.setText('Lost: ' + lostCount); // Flash screen LK.effects.flashScreen(0xff0000, 400); } // Remove after animation LK.setTimeout(function (paratrooper) { return function () { paratrooper.destroy(); }; }(p), 700); paratroopers.splice(i, 1); continue; } // Check for hazard collision for (var j = hazards.length - 1; j >= 0; j--) { var h = hazards[j]; if (h.isActive && p.isActive && p.state === 0 && p.intersects(h)) { p.hitHazard(); h.crash(); lostCount++; lostText.setText('Lost: ' + lostCount); LK.effects.flashScreen(0xff0000, 400); LK.setTimeout(function (paratrooper) { return function () { paratrooper.destroy(); }; }(p), 700); paratroopers.splice(i, 1); break; } } } // Update hazards for (var i = hazards.length - 1; i >= 0; i--) { var h = hazards[i]; h.update(); // Remove if off screen if (h.dir === 1 && h.x > GAME_WIDTH + 120 || h.dir === -1 && h.x < -120) { h.destroy(); hazards.splice(i, 1); } } // Level end condition // If all paratroopers rescued before timer ends, go to next level immediately if (isLevelActive && rescuedCount === totalToRescue) { isLevelActive = false; LK.getSound('levelup').play(); if (currentLevel < LEVELS) { currentLevel++; LK.setTimeout(function () { startLevel(currentLevel); }, 1200); } else { LK.showYouWin(); } return; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.dir = 1; // 1 = right, -1 = left
self.isActive = true;
var bird = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.isActive) return;
self.x += self.speed * self.dir;
};
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
LK.getSound('bird_hit').play();
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Kite class
var Kite = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.dir = 1;
self.isActive = true;
var kite = self.attachAsset('kite', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.isActive) return;
self.x += self.speed * self.dir;
};
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Paratrooper class
var Paratrooper = Container.expand(function () {
var self = Container.call(this);
// State: 0 = falling, 1 = parachute open, 2 = landed, 3 = crashed, 4 = rescued
self.state = 0;
self.speed = 0;
self.hasParachute = false;
self.isActive = true;
// Attach body
var body = self.attachAsset('paratrooper_body', {
anchorX: 0.5,
anchorY: 0.0
});
body.y = 0;
// Attach parachute (hidden at first)
var chute = self.attachAsset('parachute', {
anchorX: 0.5,
anchorY: 1.0,
y: 0
});
chute.visible = false;
// For tap detection
self.down = function (x, y, obj) {
if (self.state === 0 && self.isActive) {
self.openParachute();
}
};
// Open parachute
self.openParachute = function () {
if (self.state !== 0) return;
self.state = 1;
self.hasParachute = true;
chute.visible = true;
// Slow down
self.speed = self.speed * 0.28;
// Animate parachute pop
chute.scaleY = 0.1;
tween(chute, {
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
LK.getSound('parachute_open').play();
};
// Crash animation
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
self.state = 3;
// Flash red
LK.effects.flashObject(self, 0xff0000, 600);
LK.getSound('crash').play();
// Fade out
tween(self, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Rescue animation
self.rescue = function () {
if (!self.isActive) return;
self.isActive = false;
self.state = 4;
// Flash green
LK.effects.flashObject(self, 0x00ff00, 400);
// Fade out
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Bird/kite collision
self.hitHazard = function () {
if (self.state === 0 && self.isActive) {
self.crash();
}
};
self.update = function () {
if (!self.isActive) return;
self.y += self.speed;
};
return self;
});
// Plane class (for visual, not interactive)
var Plane = Container.expand(function () {
var self = Container.call(this);
var plane = self.attachAsset('plane', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Paratrooper body (box), parachute (ellipse), bird (ellipse), kite (box), ground (box), plane (box)
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GROUND_HEIGHT = 60;
var LEVELS = 20;
// Level config: Each level increases paratroopers, speed, and hazards
var levelConfigs = [];
for (var i = 0; i < LEVELS; i++) {
// Level 1: 10 paratroopers, then +2 per level
var paratroopersToRescue = 10 + i * 2;
// Level duration: 1 minute (60 seconds * 60 FPS = 3600 ticks)
var levelDurationTicks = 60 * 60;
// Calculate spawn interval so all paratroopers spawn within the level duration
var spawnInterval = Math.floor(levelDurationTicks / paratroopersToRescue);
// Clamp spawnInterval to a minimum of 10 ticks for high levels
spawnInterval = Math.max(spawnInterval, 10);
levelConfigs.push({
paratroopers: paratroopersToRescue,
speed: 9 + i * 1.2,
hazards: i < 3 ? 0 : Math.min(1 + Math.floor((i - 2) / 2), 4),
hazardSpeed: 7 + i * 0.7,
hazardTypes: i < 6 ? ['bird'] : i < 12 ? ['bird', 'kite'] : ['bird', 'kite'],
spawnInterval: spawnInterval,
levelDurationTicks: levelDurationTicks
});
}
// State
// Load saved progress if available, otherwise start at level 1
var currentLevel = storage.currentLevel || 1;
var bestLevel = storage.bestLevel || 1;
var paratroopers = [];
var hazards = [];
var rescuedCount = 0;
var lostCount = 0;
var totalToRescue = 0;
var isLevelActive = false;
var plane = null;
var ground = null;
var levelText = null;
var rescuedText = null;
var lostText = null;
var bestLevelText = null;
var levelTimeout = null;
var spawnTick = 0;
var hazardTick = 0;
var dragNode = null;
// Add ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GAME_HEIGHT - GROUND_HEIGHT
});
game.addChild(ground);
// Add level text
levelText = new Text2('Level 1', {
size: 90,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
// Add objective text (upper left side, not in top left corner)
var objectiveText = new Text2('', {
size: 70,
fill: 0xFFFFAA
});
// Anchor to left, top
objectiveText.anchor.set(0, 0);
// Place at x=2 to avoid top left menu, y=0 (moved twice more left)
objectiveText.x = 2;
objectiveText.y = 50;
LK.gui.top.addChild(objectiveText);
// Add rescued/lost text
rescuedText = new Text2('Rescued: 0', {
size: 70,
fill: 0xFFFFFF
});
rescuedText.anchor.set(0, 0);
LK.gui.top.addChild(rescuedText);
lostText = new Text2('Lost: 0', {
size: 70,
fill: 0xFF4444
});
lostText.anchor.set(1, 0);
LK.gui.topRight.addChild(lostText);
// Add best level text
bestLevelText = new Text2('Best: ' + bestLevel, {
size: 60,
fill: 0xFFFF00
});
bestLevelText.anchor.set(0.5, 0);
LK.gui.bottom.addChild(bestLevelText);
// Start music
LK.playMusic('bgmusic');
// Show level number before starting, wait for tap to begin
function showLevelIntro(level, callback) {
// Hide timer and texts if visible
if (levelTimerText) levelTimerText.visible = false;
levelText.setText('Level ' + level);
levelText.alpha = 1;
levelText.visible = true;
// Overlay to block input except tap to continue
var introOverlay = new Container();
introOverlay.interactive = true;
introOverlay.hitArea = {
x: 0,
y: 0,
width: GAME_WIDTH,
height: GAME_HEIGHT
};
// Add a "Tap to Start" text
var tapText = new Text2('Tap to Start', {
size: 100,
fill: 0xffffff
});
tapText.anchor.set(0.5, 0.5);
tapText.x = GAME_WIDTH / 2;
tapText.y = GAME_HEIGHT / 2 + 180;
introOverlay.addChild(tapText);
// Add overlay to game
game.addChild(introOverlay);
// Only allow tap after a short delay to avoid accidental skip
var canTap = false;
LK.setTimeout(function () {
canTap = true;
}, 400);
introOverlay.down = function () {
if (!canTap) return;
// Animate out
tween(levelText, {
alpha: 0
}, {
duration: 400
});
tween(tapText, {
alpha: 0
}, {
duration: 400
});
LK.setTimeout(function () {
introOverlay.destroy();
if (callback) callback();
}, 420);
};
}
// Start first level after intro
showLevelIntro(currentLevel, function () {
startLevel(currentLevel);
});
var levelTimerText = null;
var levelTimerTimeout = null;
function startLevel(level) {
// Update best level if needed
if (level > bestLevel) {
bestLevel = level;
storage.bestLevel = bestLevel;
if (bestLevelText) bestLevelText.setText('Best: ' + bestLevel);
}
// Save progress
storage.currentLevel = level;
// Reset state
for (var i = 0; i < paratroopers.length; i++) {
paratroopers[i].destroy();
}
for (var i = 0; i < hazards.length; i++) {
hazards[i].destroy();
}
paratroopers = [];
hazards = [];
rescuedCount = 0;
lostCount = 0;
isLevelActive = true;
spawnTick = 0;
hazardTick = 0;
totalToRescue = levelConfigs[level - 1].paratroopers;
if (objectiveText) {
objectiveText.setText('Rescue ' + totalToRescue + ' Paratroopers');
}
levelText.setText('Level ' + level);
rescuedText.setText('Rescued: 0');
lostText.setText('Lost: 0');
// Animate level text
levelText.alpha = 1;
tween(levelText, {
alpha: 0
}, {
duration: 1200
});
// Plane flyby
if (plane) plane.destroy();
plane = new Plane();
plane.y = 180;
plane.x = -200;
game.addChild(plane);
tween(plane, {
x: GAME_WIDTH + 200
}, {
duration: 1800,
easing: tween.linear,
onFinish: function onFinish() {
plane.destroy();
plane = null;
}
});
// Add or reset timer text
if (!levelTimerText) {
levelTimerText = new Text2('01:00', {
size: 80,
fill: 0xFFFFFF
});
levelTimerText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTimerText);
}
// Place timer below level text
levelTimerText.y = levelText.height + 10;
levelTimerText.setText('01:00');
// Clear any previous timer
if (levelTimerTimeout) {
LK.clearTimeout(levelTimerTimeout);
levelTimerTimeout = null;
}
// Start timer countdown
var ticksLeft = levelConfigs[level - 1].levelDurationTicks;
if (levelTimerText) levelTimerText.visible = true;
function updateTimer() {
if (!isLevelActive) return;
ticksLeft--;
var seconds = Math.floor(ticksLeft / 60);
var ms = ticksLeft % 60;
var timeStr = (seconds < 10 ? '0' : '') + seconds + ':' + (ms < 10 ? '0' : '') + ms;
if (levelTimerText) levelTimerText.setText(timeStr);
if (ticksLeft > 0 && isLevelActive) {
levelTimerTimeout = LK.setTimeout(updateTimer, 1000 / 60);
} else if (isLevelActive) {
// Time's up, end level
isLevelActive = false;
// If not all rescued, game over, else win/next
if (rescuedCount === totalToRescue) {
LK.getSound('levelup').play();
if (currentLevel < LEVELS) {
currentLevel++;
LK.setTimeout(function () {
startLevel(currentLevel);
}, 1200);
} else {
LK.showYouWin();
}
} else {
LK.showGameOver();
}
}
}
updateTimer();
}
// Paratrooper spawn
function spawnParatrooper() {
var config = levelConfigs[currentLevel - 1];
var p = new Paratrooper();
// Random x, avoid edges
var margin = 120;
p.x = margin + Math.random() * (GAME_WIDTH - 2 * margin);
p.y = 220;
p.speed = config.speed + Math.random() * 2;
p.state = 0;
p.hasParachute = false;
p.isActive = true;
paratroopers.push(p);
game.addChild(p);
}
// Hazard spawn
function spawnHazard() {
var config = levelConfigs[currentLevel - 1];
var type = config.hazardTypes[Math.floor(Math.random() * config.hazardTypes.length)];
var h = null;
var y = 400 + Math.random() * (GAME_HEIGHT - GROUND_HEIGHT - 800);
var dir = Math.random() < 0.5 ? 1 : -1;
var speed = config.hazardSpeed + Math.random() * 2;
var x = dir === 1 ? -100 : GAME_WIDTH + 100;
if (type === 'bird') {
h = new Bird();
} else {
h = new Kite();
}
h.x = x;
h.y = y;
h.dir = dir;
h.speed = speed;
h.isActive = true;
hazards.push(h);
game.addChild(h);
}
// Game move handler (for drag, but not used in this game)
game.move = function (x, y, obj) {
// No drag in this game
};
// Game down handler (for tap on paratroopers)
game.down = function (x, y, obj) {
// Find topmost paratrooper under tap
for (var i = paratroopers.length - 1; i >= 0; i--) {
var p = paratroopers[i];
if (p.isActive && p.state === 0 && p.visible) {
// Check if tap is inside body
var local = p.toLocal({
x: x,
y: y
});
var body = p.children[0];
if (local.x >= body.x - body.width / 2 && local.x <= body.x + body.width / 2 && local.y >= body.y && local.y <= body.y + body.height) {
p.openParachute();
break;
}
}
}
};
// Game update
game.update = function () {
if (!isLevelActive) return;
var config = levelConfigs[currentLevel - 1];
// Spawn paratroopers
if (paratroopers.length < totalToRescue && LK.ticks % config.spawnInterval === 0) {
spawnParatrooper();
}
// Spawn hazards
if (config.hazards > 0 && hazards.length < config.hazards && LK.ticks % (80 + Math.floor(Math.random() * 40)) === 0) {
spawnHazard();
}
// Update paratroopers
for (var i = paratroopers.length - 1; i >= 0; i--) {
var p = paratroopers[i];
p.update();
// Check for ground collision
if (p.isActive && p.y + 90 >= GAME_HEIGHT - GROUND_HEIGHT) {
if (p.state === 1) {
// Landed safely
p.rescue();
rescuedCount++;
rescuedText.setText('Rescued: ' + rescuedCount);
} else if (p.state === 0) {
// Crashed
p.crash();
lostCount++;
lostText.setText('Lost: ' + lostCount);
// Flash screen
LK.effects.flashScreen(0xff0000, 400);
}
// Remove after animation
LK.setTimeout(function (paratrooper) {
return function () {
paratrooper.destroy();
};
}(p), 700);
paratroopers.splice(i, 1);
continue;
}
// Check for hazard collision
for (var j = hazards.length - 1; j >= 0; j--) {
var h = hazards[j];
if (h.isActive && p.isActive && p.state === 0 && p.intersects(h)) {
p.hitHazard();
h.crash();
lostCount++;
lostText.setText('Lost: ' + lostCount);
LK.effects.flashScreen(0xff0000, 400);
LK.setTimeout(function (paratrooper) {
return function () {
paratrooper.destroy();
};
}(p), 700);
paratroopers.splice(i, 1);
break;
}
}
}
// Update hazards
for (var i = hazards.length - 1; i >= 0; i--) {
var h = hazards[i];
h.update();
// Remove if off screen
if (h.dir === 1 && h.x > GAME_WIDTH + 120 || h.dir === -1 && h.x < -120) {
h.destroy();
hazards.splice(i, 1);
}
}
// Level end condition
// If all paratroopers rescued before timer ends, go to next level immediately
if (isLevelActive && rescuedCount === totalToRescue) {
isLevelActive = false;
LK.getSound('levelup').play();
if (currentLevel < LEVELS) {
currentLevel++;
LK.setTimeout(function () {
startLevel(currentLevel);
}, 1200);
} else {
LK.showYouWin();
}
return;
}
};