/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Box class
var Box = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0xFFFF00,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box1 class
var Box1 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0xFF00FF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box2 class
var Box2 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box2', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 7;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0x00FFFF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box3 class
var Box3 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box3', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 9;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible with heart color
tween(boxGraphics, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// The assets will be automatically created and loaded by the LK engine.
// Bubble class
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Start transparent
});
// Make the bubble opaque after 1 second
tween(bubbleGraphics, {
alpha: 1
}, {
duration: 1000
});
// Base speed, will be adjusted based on score
self.baseSpeed = -10;
// Calculate current speed based on score (faster as score increases)
self.speed = self.baseSpeed * (1 + score * DIFFICULTY_SCORE_FACTOR); // 5% speed increase per point
self.lastY = undefined; // Track last position for intersection checks
self.hasHitTop = false; // Flag to track if lantern has gone off screen at top
self.update = function () {
if (self.lastY === undefined) {
self.lastY = self.y;
}
// Recalculate speed based on current score to ensure it updates as score changes
self.speed = self.baseSpeed * (1 + score * DIFFICULTY_SCORE_FACTOR); // 5% speed increase per point
self.y += self.speed;
if (self.y < 1200) {
// Start tinting when approaching the top
tween(self, {
tint: 0xFF0000
}, {
duration: 500,
easing: tween.linear
});
}
// Check if the lantern is going off the top of the screen without being destroyed
if (self.lastY >= 0 && self.y < 0 && !self.hasHitTop) {
self.hasHitTop = true; // Mark as hit top to prevent multiple life deductions
// Player didn't destroy the lantern in time
lives -= 1;
// Create explosion effect at the top where the lantern left
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = 10; // Just at the edge of the screen
game.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
// Remove a heart icon when a life is lost
if (hearts.length > lives) {
var heartToRemove = hearts.pop();
if (heartToRemove) {
tween(heartToRemove.scale, {
x: 0,
y: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
heartToRemove.destroy();
}
});
}
}
if (lives < 0) {
// Handle player death with dramatic effect
handlePlayerDeath();
return;
}
self.destroy();
}
// Handle going completely off screen
// Note: Not removing the lantern when it intersects with player
// as per the requirement that player collision shouldn't remove lives
if (self.y < -200) {
self.destroy();
}
self.lastY = self.y;
};
});
// Explosion class with color customization
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
// Extend self with tint property accessor/mutator to allow tinting the explosion
Object.defineProperty(self, 'tint', {
get: function get() {
return explosionGraphics.tint;
},
set: function set(value) {
explosionGraphics.tint = value;
}
});
// Apply explosion animation
tween(explosionGraphics, {
scaleX: explosionGraphics.scaleX + 1,
scaleY: explosionGraphics.scaleY + 1
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
self.destroy();
}
});
self.update = function () {
// The explosion will disappear after a while
if (self.alpha > 0) {
self.alpha -= 0.005;
} else {
self.destroy();
}
};
});
// Harpoon class
var Harpoon = Container.expand(function () {
var self = Container.call(this);
var harpoonGraphics = self.attachAsset('harpoon', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -30; // Reduced speed for slower movement
self.originalSpeed = -30;
self.maxDistance = 2732 * 0.85; // Increased maximum distance - 85% of screen height
self.startY = 0; // Will store the starting Y position
self.trails = [];
// Apply a quick acceleration tween to give a nice launch effect
tween(self, {
speed: self.speed * 1.5 // Accelerate to 1.5x the slower speed
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Store initial position on first update
if (self.startY === 0) {
self.startY = self.y;
}
self.y += self.speed;
// Calculate total trail distance
var totalDistance = self.startY - self.y;
// Create trail segments for every 100 pixels
var segmentCount = Math.floor(totalDistance / 100);
var currentTrailCount = self.trails.length;
// Create new trail segments if needed
if (segmentCount > currentTrailCount) {
for (var i = currentTrailCount; i < segmentCount; i++) {
var trail = game.addChild(new Trail());
trail.x = self.x;
trail.y = self.y + 50 + i * 100;
trail.height = 100;
self.trails.push(trail);
}
}
// Update position of all trail segments
for (var i = 0; i < self.trails.length; i++) {
var trail = self.trails[i];
trail.y = self.y + 50 + i * 100;
}
// Destroy when reaching max distance or going off-screen
if (self.y < 0 || self.startY - self.y > self.maxDistance) {
self.destroy();
self.trails.forEach(function (trail) {
trail.destroy();
});
}
};
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.update = function () {
if (self.direction && self.direction === 'left') {
self.x -= self.speed;
} else if (self.direction === 'right') {
self.x += self.speed;
}
};
self.shoot = function () {
var harpoon = new Harpoon();
harpoon.x = player.x;
harpoon.y = player.y;
// Create a more dramatic scale effect for shooting with extended range
harpoon.scale.x = 0.7;
harpoon.scale.y = 1.3;
// Flag normal harpoons (from player) to allow them to create boxes
harpoon.isPlayerCreated = true;
// Apply a tween to normalize the scale while launching, with longer stretch for better visual feedback
tween(harpoon.scale, {
x: 1,
y: 1
}, {
duration: 250,
easing: tween.easeOut
});
// Trails will be created automatically in the harpoon update method
game.addChild(harpoon);
LK.getSound('crossbow').play();
};
});
// PlayerDeathEffect class - creates a dramatic death sequence with rainbow colors
var PlayerDeathEffect = Container.expand(function () {
var self = Container.call(this);
// Create the main container for all death effect elements
var effectContainer = new Container();
self.addChild(effectContainer);
// Rainbow colors array (red, orange, yellow, green, blue, indigo, violet)
var rainbowColors = [
// Indigo
0x9400D3,
// Violet
0xfaadad,
// Red
0xFF7F00,
// Orange
0xFFFF00,
// Yellow
0x00FF00,
// Green
0x0000FF,
// Blue
0x4B0082];
// Add a full-screen overlay for dramatic effect
var overlay = LK.getAsset('deadBg', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
// Full width (2048px)
scaleY: 27.32,
// Full height (2732px)
tint: rainbowColors[0],
// Start with red
alpha: 0
});
effectContainer.addChild(overlay);
// Add dramatic text
var gameOverText = new Text2('GAME OVER', {
size: 150,
fill: 0xFFFFFF,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 5
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 2732 / 2;
gameOverText.alpha = 0;
gameOverText.scale.set(0.1);
effectContainer.addChild(gameOverText);
// Function to create a rainbow tint sequence
function createRainbowTintSequence(target, startIndex, duration) {
var colorIndex = startIndex % rainbowColors.length;
var nextColorIndex = (colorIndex + 1) % rainbowColors.length;
tween(target, {
tint: rainbowColors[nextColorIndex]
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
createRainbowTintSequence(target, nextColorIndex, duration);
}
});
}
// Add rainbow flickering effect
self.startEffect = function () {
// Pulse the overlay with rainbow colors
tween(overlay, {
alpha: 0.5
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
// Start the rainbow color sequence
createRainbowTintSequence(overlay, 0, 300);
}
});
// Show and scale the text
tween(gameOverText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.elasticOut
});
// Create explosion effects at random positions with rainbow colors
for (var i = 0; i < 5; i++) {
LK.setTimeout(function (colorIndex) {
var explosion = new Explosion();
explosion.x = Math.random() * 2048;
explosion.y = Math.random() * 2732;
// Give each explosion a different color from the rainbow
explosion.tint = rainbowColors[colorIndex % rainbowColors.length];
effectContainer.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
}.bind(null, i), 300 * i);
}
// Show actual game over screen after the effect
LK.setTimeout(function () {
self.destroy();
LK.showGameOver();
}, 2500); // Show game over after 2.5 seconds
};
return self;
});
// PowerUpText class
var PowerUpText = Container.expand(function () {
var self = Container.call(this);
var textGraphics = self.attachAsset('PowerUpText', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y -= 2;
if (self.y < 0) {
self.destroy();
}
};
});
// Trail class
var Trail = Container.expand(function () {
var self = Container.call(this);
var trailGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
alpha: 0.9,
// Slightly transparent to enhance visual effect with faster movement
scaleY: 1.5 // Scale the trail to support longer reach
});
// The trail doesn't need its own update movement since it's positioned relative to the harpoon
self.update = function () {
// Trail no longer moves independently
// Its position is now fully controlled by the Harpoon
if (self.y > 2732) {
self.destroy();
}
};
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFFFFFFF // Init game with black background
});
/****
* Game Code
****/
DIFFICULTY_SCORE_FACTOR = 0.01;
var background = game.attachAsset('Landscape', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.setChildIndex(background, 0);
// Add a warning zone at the top to indicate the danger area
var warningZone = new Container();
var warningGraphics = LK.getAsset('scoreBg', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
// Make it span the entire width (2048px)
scaleY: 1.5,
tint: 0xFF3333,
// Red tint to indicate danger
alpha: 0.3 // Semi-transparent
});
warningZone.addChild(warningGraphics);
warningZone.x = 0;
warningZone.y = 0;
game.addChild(warningZone);
var player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 180;
game.addChild(player); // Add player after trail to ensure correct rendering order
// Add a description text for the warning zone
var warningText = new Text2('Lanterns escaping at the top will cost a life!', {
size: 30,
fill: 0xFFFFFF,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 5
});
warningText.anchor.set(0.5, 0);
warningText.x = 2048 / 2;
warningText.y = 130;
game.addChild(warningText);
// Make the warning text pulse to draw attention
var _pulseWarning = function pulseWarning() {
tween(warningText.scale, {
x: 1.2,
y: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(warningText.scale, {
x: 1.0,
y: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: _pulseWarning
});
}
});
};
_pulseWarning();
game.move = function (x, y, obj) {
// Store previous x for direction detection
if (player.lastX === undefined) {
player.lastX = player.x;
}
player.x = x;
// Check if moving left or right
if (player.x < player.lastX) {
// Moving left, mirror
if (player.scaleX > 0) {
player.scaleX *= -1;
}
} else if (player.x > player.lastX) {
// Moving right, un-mirror
if (player.scaleX < 0) {
player.scaleX *= -1;
}
}
player.lastX = player.x;
};
var score = 0;
var lives = 3;
var scoreBackground = new Container();
var scoreBgGraphics = scoreBackground.attachAsset('scoreBg', {
anchorX: 0.5,
anchorY: 0.1,
scaleX: 5,
scaleY: 5,
alpha: 1
});
scoreBackground.addChild(scoreBgGraphics);
scoreBackground.x = 0;
scoreBackground.y = 0;
LK.gui.top.addChild(scoreBackground);
var scoreTxt = new Text2('Lanterns destroyed: 0', {
size: 30,
fill: 0xFF3659,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 5
});
scoreTxt.anchor.set(0.5, -0.1);
scoreBackground.addChild(scoreTxt);
var hearts = [];
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * (i + 1) * 50,
// Position hearts with some spacing
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Function to apply box effects based on box type
function applyBoxEffect(box) {
// Check if the collected box is an instance of Box1
if (box instanceof Box1) {
// If we already have box1 harpoons, destroy them first
var existingHarpoons = game.children.filter(function (child) {
return child instanceof Harpoon && child.isFromBox1 === true;
});
existingHarpoons.forEach(function (harpoon) {
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
});
// Create six additional harpoons and trails
for (var i = 1; i <= 3; i++) {
var leftHarpoon = new Harpoon();
leftHarpoon.x = player.x - i * 150;
leftHarpoon.y = player.y;
// Mark harpoons created by box power-up
leftHarpoon.isPlayerCreated = false;
leftHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up
game.addChild(leftHarpoon);
var rightHarpoon = new Harpoon();
rightHarpoon.x = player.x + i * 150;
rightHarpoon.y = player.y;
// Mark harpoons created by box power-up
rightHarpoon.isPlayerCreated = false;
rightHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up
game.addChild(rightHarpoon);
// Set a timeout to remove the additional harpoons after 5 seconds
LK.setTimeout(function (lh, rh) {
if (lh.trails) {
lh.trails.forEach(function (trail) {
trail.destroy();
});
}
lh.destroy();
if (rh.trails) {
rh.trails.forEach(function (trail) {
trail.destroy();
});
}
rh.destroy();
}.bind(null, leftHarpoon, rightHarpoon), 5000);
}
}
// Check if the collected box is an instance of Box and reduce bubble speed
if (box instanceof Box) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
bubbles.forEach(function (bubble) {
bubble.speed /= 2;
});
LK.setTimeout(function () {
bubbles.forEach(function (bubble) {
bubble.speed *= 2;
});
}, 5000);
}
// Check if the collected box is an instance of Box3 and lives are less than 3
if (box instanceof Box3 && lives < 3) {
lives += 1;
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * lives * 50,
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Check if the collected box is an instance of Box2
if (box instanceof Box2) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var bubblesDestroyed = bubbles.length;
bubbles.forEach(function (bubble) {
bubble.destroy();
});
score += bubblesDestroyed;
scoreTxt.setText("Lanterns destroyed: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
// Create toast message based on box type
showToastForBox(box);
}
// Function to show toast message for box effects
function showToastForBox(box) {
var toastText = new Text2('', {
size: 250,
fill: 0x000000,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 15
});
var toastTextBg = new Text2('', {
size: 255,
fill: 0xffff00,
font: "'Comic Sans MS', cursive, sans-serif"
});
toastText.anchor.set(0.5, 0.5);
toastText.x = 2048 / 2;
toastText.y = 2732 / 2;
toastTextBg.anchor.set(0.5, 0.5);
toastTextBg.x = 2048 / 2;
toastTextBg.y = 2732 / 2;
game.addChild(toastText);
game.addChild(toastTextBg);
if (box instanceof Box1) {
toastText.setText("Smash!");
toastTextBg.setText("Smash!");
} else if (box instanceof Box2) {
toastText.setText("Destruction!");
toastTextBg.setText("Destruction!");
} else if (box instanceof Box) {
toastText.setText("Less madness!");
toastTextBg.setText("Less madness!");
} else if (box instanceof Box3) {
toastText.setText("Life up!");
toastTextBg.setText("Life up!");
}
// Tween the toast text to fade out and destroy after 2 seconds
tween(toastText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastText.destroy();
}
});
tween(toastTextBg, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastTextBg.destroy();
}
});
}
// Function to handle player death sequence with rainbow colors
function handlePlayerDeath() {
// Rainbow colors array
var rainbowColors = [0xFF0000,
// Red
0xFF7F00,
// Orange
0xFFFF00,
// Yellow
0x00FF00,
// Green
0x0000FF,
// Blue
0x4B0082,
// Indigo
0x9400D3 // Violet
];
// Create dramatic death effect
var deathEffect = new PlayerDeathEffect();
game.addChild(deathEffect);
deathEffect.startEffect();
// Mark game as over to stop spawning bubbles
game.isGameOver = true;
// Create a function for sequential rainbow color animation
function playerRainbowSequence(colorIndex) {
if (colorIndex >= rainbowColors.length) {
// One full cycle completed, start fading out
tween(player, {
alpha: 0,
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
return;
}
tween(player, {
alpha: 0.3,
tint: rainbowColors[colorIndex]
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
playerRainbowSequence(colorIndex + 1);
}
});
}
// Start the rainbow sequence
playerRainbowSequence(0);
}
var lastShot = -999;
game.down = function (x, y, obj) {
if (LK.ticks - lastShot > 10) {
player.shoot();
lastShot = LK.ticks;
}
};
// Start the music 'chinese' upon starting the game
LK.playMusic('chinese');
game.update = function () {
// Initialize game.isGameOver if it doesn't exist
if (game.isGameOver === undefined) {
game.isGameOver = false;
}
// Only spawn new bubbles if game isn't over
if (!game.isGameOver && LK.ticks % 50 == 0) {
var newBubble = new Bubble();
newBubble.x = Math.random() * 2048;
newBubble.y = 2732 * 0.75; // Start at 25% from bottom of screen
game.addChild(newBubble);
var randomTween = {
delta: Math.random() * (300 - 100) + 100,
// Random delta between 100 and 300
duration: Math.random() * (1500 - 800) + 800,
// Random duration between 800 and 1500
easing: [tween.easeIn, tween.easeOut, tween.elasticIn, tween.elasticOut, tween.bounceIn, tween.bounceOut, tween.easeInOut, tween.bounceInOut, tween.elasticInOut][Math.floor(Math.random() * 9)] // Random easing
};
tween(newBubble, {
x: Math.max(0, Math.min(2048, newBubble.x + randomTween.delta))
}, {
duration: randomTween.duration,
easing: randomTween.easing,
onFinish: function onFinish() {
tween(newBubble, {
x: Math.max(0, Math.min(2048, newBubble.x - randomTween.delta))
}, {
duration: randomTween.duration,
easing: randomTween.easing
});
}
});
}
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var harpoons = game.children.filter(function (child) {
return child instanceof Harpoon;
});
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
// Check for player intersection but don't damage the player
// We allow lanterns to pass through the player harmlessly
if (bubble.intersects(player)) {
// Don't destroy the bubble or harm the player
// This allows the bubble to continue moving up
// It will only damage the player if it exits off the top without being destroyed
}
for (var j = 0; j < harpoons.length; j++) {
var harpoon = harpoons[j];
// Enhanced collision detection for longer harpoon reach and multiple trail segments
var collides = bubble.intersects(harpoon);
if (!collides && harpoon.trails && harpoon.trails.length > 0) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
if (Math.abs(bubble.x - harpoon.x) < 50 && Math.abs(bubble.y - trail.y) < 50) {
collides = true;
break;
}
}
}
if (collides) {
bubble.destroy();
LK.getSound('explosion').play();
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
bubbles.splice(i, 1); // Remove bubble from the array
score += 1;
scoreTxt.setText("Lanterns destroyed: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
harpoons.splice(j, 1); // Remove harpoon from the array
// Create an explosion at the intersection point
var explosion = new Explosion();
explosion.x = bubble.x;
explosion.y = bubble.y;
game.addChild(explosion);
// Create a box at the intersection point with a 5% probability (half the previous 10%)
// Only spawn boxes when balloons are popped by player harpoons, not by explosion effects
if (!game.children.some(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
})) {
// Only create boxes when bubble is popped by a player-created harpoon, not by power-up harpoons
// Check explicitly that the harpoon was created by the player, not by any box power-up
if (Math.random() < 0.5 && harpoon instanceof Harpoon && harpoon.isPlayerCreated === true) {
var boxType = Math.floor(Math.random() * 4);
var box;
switch (boxType) {
case 0:
box = new Box();
break;
case 1:
box = new Box1();
break;
case 2:
box = new Box2();
break;
case 3:
box = new Box3();
break;
}
box.x = bubble.x;
box.y = bubble.y;
game.addChild(box);
}
}
}
}
var boxes = game.children.filter(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
});
for (var k = 0; k < boxes.length; k++) {
var box = boxes[k];
// Check if player collects/intersects the box
if (box.intersects(player)) {
// Create collection animation
tween(box.scale, {
x: 0,
y: 0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function (boxToDestroy) {
boxToDestroy.destroy();
}.bind(null, box)
});
// Play pickup sound
LK.getSound('powerup').play();
// Apply the power-up effect based on box type using centralized handler
applyBoxEffect(box);
// Remove box from tracking array
boxes.splice(k, 1);
// Break out of the loop since we've handled the box
break;
}
// Check for harpoon intersecting with boxes
for (var l = 0; l < harpoons.length; l++) {
var harpoon = harpoons[l];
var collides = box.intersects(harpoon);
// Check trail segments for collision as well
if (!collides && harpoon.trails && harpoon.trails.length > 0) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
if (trail && Math.abs(box.x - harpoon.x) < 50 && Math.abs(box.y - trail.y) < 50) {
collides = true;
break;
}
}
}
if (collides) {
// Store reference to box before destroying it
var boxToApplyEffect = box;
// Destroy elements
box.destroy();
LK.getSound('powerup').play();
// Safely destroy trails for harpoons created before we started using multiple trails
if (harpoon.trail) {
harpoon.trail.destroy();
}
// Safely destroy trail segments for current harpoons
if (harpoon.trails && harpoon.trails.length > 0) {
harpoon.trails.forEach(function (trail) {
if (trail) {
trail.destroy();
}
});
}
harpoon.destroy();
// Apply the power-up effect based on box type using centralized handler
applyBoxEffect(boxToApplyEffect);
// Remove from arrays
boxes.splice(k, 1);
harpoons.splice(l, 1);
break;
}
}
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Box class
var Box = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0xFFFF00,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box1 class
var Box1 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0xFF00FF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box2 class
var Box2 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box2', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 7;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0x00FFFF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box3 class
var Box3 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box3', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 9;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible with heart color
tween(boxGraphics, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// The assets will be automatically created and loaded by the LK engine.
// Bubble class
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Start transparent
});
// Make the bubble opaque after 1 second
tween(bubbleGraphics, {
alpha: 1
}, {
duration: 1000
});
// Base speed, will be adjusted based on score
self.baseSpeed = -10;
// Calculate current speed based on score (faster as score increases)
self.speed = self.baseSpeed * (1 + score * DIFFICULTY_SCORE_FACTOR); // 5% speed increase per point
self.lastY = undefined; // Track last position for intersection checks
self.hasHitTop = false; // Flag to track if lantern has gone off screen at top
self.update = function () {
if (self.lastY === undefined) {
self.lastY = self.y;
}
// Recalculate speed based on current score to ensure it updates as score changes
self.speed = self.baseSpeed * (1 + score * DIFFICULTY_SCORE_FACTOR); // 5% speed increase per point
self.y += self.speed;
if (self.y < 1200) {
// Start tinting when approaching the top
tween(self, {
tint: 0xFF0000
}, {
duration: 500,
easing: tween.linear
});
}
// Check if the lantern is going off the top of the screen without being destroyed
if (self.lastY >= 0 && self.y < 0 && !self.hasHitTop) {
self.hasHitTop = true; // Mark as hit top to prevent multiple life deductions
// Player didn't destroy the lantern in time
lives -= 1;
// Create explosion effect at the top where the lantern left
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = 10; // Just at the edge of the screen
game.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
// Remove a heart icon when a life is lost
if (hearts.length > lives) {
var heartToRemove = hearts.pop();
if (heartToRemove) {
tween(heartToRemove.scale, {
x: 0,
y: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
heartToRemove.destroy();
}
});
}
}
if (lives < 0) {
// Handle player death with dramatic effect
handlePlayerDeath();
return;
}
self.destroy();
}
// Handle going completely off screen
// Note: Not removing the lantern when it intersects with player
// as per the requirement that player collision shouldn't remove lives
if (self.y < -200) {
self.destroy();
}
self.lastY = self.y;
};
});
// Explosion class with color customization
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
// Extend self with tint property accessor/mutator to allow tinting the explosion
Object.defineProperty(self, 'tint', {
get: function get() {
return explosionGraphics.tint;
},
set: function set(value) {
explosionGraphics.tint = value;
}
});
// Apply explosion animation
tween(explosionGraphics, {
scaleX: explosionGraphics.scaleX + 1,
scaleY: explosionGraphics.scaleY + 1
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
self.destroy();
}
});
self.update = function () {
// The explosion will disappear after a while
if (self.alpha > 0) {
self.alpha -= 0.005;
} else {
self.destroy();
}
};
});
// Harpoon class
var Harpoon = Container.expand(function () {
var self = Container.call(this);
var harpoonGraphics = self.attachAsset('harpoon', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -30; // Reduced speed for slower movement
self.originalSpeed = -30;
self.maxDistance = 2732 * 0.85; // Increased maximum distance - 85% of screen height
self.startY = 0; // Will store the starting Y position
self.trails = [];
// Apply a quick acceleration tween to give a nice launch effect
tween(self, {
speed: self.speed * 1.5 // Accelerate to 1.5x the slower speed
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Store initial position on first update
if (self.startY === 0) {
self.startY = self.y;
}
self.y += self.speed;
// Calculate total trail distance
var totalDistance = self.startY - self.y;
// Create trail segments for every 100 pixels
var segmentCount = Math.floor(totalDistance / 100);
var currentTrailCount = self.trails.length;
// Create new trail segments if needed
if (segmentCount > currentTrailCount) {
for (var i = currentTrailCount; i < segmentCount; i++) {
var trail = game.addChild(new Trail());
trail.x = self.x;
trail.y = self.y + 50 + i * 100;
trail.height = 100;
self.trails.push(trail);
}
}
// Update position of all trail segments
for (var i = 0; i < self.trails.length; i++) {
var trail = self.trails[i];
trail.y = self.y + 50 + i * 100;
}
// Destroy when reaching max distance or going off-screen
if (self.y < 0 || self.startY - self.y > self.maxDistance) {
self.destroy();
self.trails.forEach(function (trail) {
trail.destroy();
});
}
};
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.update = function () {
if (self.direction && self.direction === 'left') {
self.x -= self.speed;
} else if (self.direction === 'right') {
self.x += self.speed;
}
};
self.shoot = function () {
var harpoon = new Harpoon();
harpoon.x = player.x;
harpoon.y = player.y;
// Create a more dramatic scale effect for shooting with extended range
harpoon.scale.x = 0.7;
harpoon.scale.y = 1.3;
// Flag normal harpoons (from player) to allow them to create boxes
harpoon.isPlayerCreated = true;
// Apply a tween to normalize the scale while launching, with longer stretch for better visual feedback
tween(harpoon.scale, {
x: 1,
y: 1
}, {
duration: 250,
easing: tween.easeOut
});
// Trails will be created automatically in the harpoon update method
game.addChild(harpoon);
LK.getSound('crossbow').play();
};
});
// PlayerDeathEffect class - creates a dramatic death sequence with rainbow colors
var PlayerDeathEffect = Container.expand(function () {
var self = Container.call(this);
// Create the main container for all death effect elements
var effectContainer = new Container();
self.addChild(effectContainer);
// Rainbow colors array (red, orange, yellow, green, blue, indigo, violet)
var rainbowColors = [
// Indigo
0x9400D3,
// Violet
0xfaadad,
// Red
0xFF7F00,
// Orange
0xFFFF00,
// Yellow
0x00FF00,
// Green
0x0000FF,
// Blue
0x4B0082];
// Add a full-screen overlay for dramatic effect
var overlay = LK.getAsset('deadBg', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
// Full width (2048px)
scaleY: 27.32,
// Full height (2732px)
tint: rainbowColors[0],
// Start with red
alpha: 0
});
effectContainer.addChild(overlay);
// Add dramatic text
var gameOverText = new Text2('GAME OVER', {
size: 150,
fill: 0xFFFFFF,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 5
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 2732 / 2;
gameOverText.alpha = 0;
gameOverText.scale.set(0.1);
effectContainer.addChild(gameOverText);
// Function to create a rainbow tint sequence
function createRainbowTintSequence(target, startIndex, duration) {
var colorIndex = startIndex % rainbowColors.length;
var nextColorIndex = (colorIndex + 1) % rainbowColors.length;
tween(target, {
tint: rainbowColors[nextColorIndex]
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
createRainbowTintSequence(target, nextColorIndex, duration);
}
});
}
// Add rainbow flickering effect
self.startEffect = function () {
// Pulse the overlay with rainbow colors
tween(overlay, {
alpha: 0.5
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
// Start the rainbow color sequence
createRainbowTintSequence(overlay, 0, 300);
}
});
// Show and scale the text
tween(gameOverText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.elasticOut
});
// Create explosion effects at random positions with rainbow colors
for (var i = 0; i < 5; i++) {
LK.setTimeout(function (colorIndex) {
var explosion = new Explosion();
explosion.x = Math.random() * 2048;
explosion.y = Math.random() * 2732;
// Give each explosion a different color from the rainbow
explosion.tint = rainbowColors[colorIndex % rainbowColors.length];
effectContainer.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
}.bind(null, i), 300 * i);
}
// Show actual game over screen after the effect
LK.setTimeout(function () {
self.destroy();
LK.showGameOver();
}, 2500); // Show game over after 2.5 seconds
};
return self;
});
// PowerUpText class
var PowerUpText = Container.expand(function () {
var self = Container.call(this);
var textGraphics = self.attachAsset('PowerUpText', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y -= 2;
if (self.y < 0) {
self.destroy();
}
};
});
// Trail class
var Trail = Container.expand(function () {
var self = Container.call(this);
var trailGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
alpha: 0.9,
// Slightly transparent to enhance visual effect with faster movement
scaleY: 1.5 // Scale the trail to support longer reach
});
// The trail doesn't need its own update movement since it's positioned relative to the harpoon
self.update = function () {
// Trail no longer moves independently
// Its position is now fully controlled by the Harpoon
if (self.y > 2732) {
self.destroy();
}
};
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFFFFFFF // Init game with black background
});
/****
* Game Code
****/
DIFFICULTY_SCORE_FACTOR = 0.01;
var background = game.attachAsset('Landscape', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.setChildIndex(background, 0);
// Add a warning zone at the top to indicate the danger area
var warningZone = new Container();
var warningGraphics = LK.getAsset('scoreBg', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
// Make it span the entire width (2048px)
scaleY: 1.5,
tint: 0xFF3333,
// Red tint to indicate danger
alpha: 0.3 // Semi-transparent
});
warningZone.addChild(warningGraphics);
warningZone.x = 0;
warningZone.y = 0;
game.addChild(warningZone);
var player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 180;
game.addChild(player); // Add player after trail to ensure correct rendering order
// Add a description text for the warning zone
var warningText = new Text2('Lanterns escaping at the top will cost a life!', {
size: 30,
fill: 0xFFFFFF,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 5
});
warningText.anchor.set(0.5, 0);
warningText.x = 2048 / 2;
warningText.y = 130;
game.addChild(warningText);
// Make the warning text pulse to draw attention
var _pulseWarning = function pulseWarning() {
tween(warningText.scale, {
x: 1.2,
y: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(warningText.scale, {
x: 1.0,
y: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: _pulseWarning
});
}
});
};
_pulseWarning();
game.move = function (x, y, obj) {
// Store previous x for direction detection
if (player.lastX === undefined) {
player.lastX = player.x;
}
player.x = x;
// Check if moving left or right
if (player.x < player.lastX) {
// Moving left, mirror
if (player.scaleX > 0) {
player.scaleX *= -1;
}
} else if (player.x > player.lastX) {
// Moving right, un-mirror
if (player.scaleX < 0) {
player.scaleX *= -1;
}
}
player.lastX = player.x;
};
var score = 0;
var lives = 3;
var scoreBackground = new Container();
var scoreBgGraphics = scoreBackground.attachAsset('scoreBg', {
anchorX: 0.5,
anchorY: 0.1,
scaleX: 5,
scaleY: 5,
alpha: 1
});
scoreBackground.addChild(scoreBgGraphics);
scoreBackground.x = 0;
scoreBackground.y = 0;
LK.gui.top.addChild(scoreBackground);
var scoreTxt = new Text2('Lanterns destroyed: 0', {
size: 30,
fill: 0xFF3659,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 5
});
scoreTxt.anchor.set(0.5, -0.1);
scoreBackground.addChild(scoreTxt);
var hearts = [];
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * (i + 1) * 50,
// Position hearts with some spacing
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Function to apply box effects based on box type
function applyBoxEffect(box) {
// Check if the collected box is an instance of Box1
if (box instanceof Box1) {
// If we already have box1 harpoons, destroy them first
var existingHarpoons = game.children.filter(function (child) {
return child instanceof Harpoon && child.isFromBox1 === true;
});
existingHarpoons.forEach(function (harpoon) {
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
});
// Create six additional harpoons and trails
for (var i = 1; i <= 3; i++) {
var leftHarpoon = new Harpoon();
leftHarpoon.x = player.x - i * 150;
leftHarpoon.y = player.y;
// Mark harpoons created by box power-up
leftHarpoon.isPlayerCreated = false;
leftHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up
game.addChild(leftHarpoon);
var rightHarpoon = new Harpoon();
rightHarpoon.x = player.x + i * 150;
rightHarpoon.y = player.y;
// Mark harpoons created by box power-up
rightHarpoon.isPlayerCreated = false;
rightHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up
game.addChild(rightHarpoon);
// Set a timeout to remove the additional harpoons after 5 seconds
LK.setTimeout(function (lh, rh) {
if (lh.trails) {
lh.trails.forEach(function (trail) {
trail.destroy();
});
}
lh.destroy();
if (rh.trails) {
rh.trails.forEach(function (trail) {
trail.destroy();
});
}
rh.destroy();
}.bind(null, leftHarpoon, rightHarpoon), 5000);
}
}
// Check if the collected box is an instance of Box and reduce bubble speed
if (box instanceof Box) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
bubbles.forEach(function (bubble) {
bubble.speed /= 2;
});
LK.setTimeout(function () {
bubbles.forEach(function (bubble) {
bubble.speed *= 2;
});
}, 5000);
}
// Check if the collected box is an instance of Box3 and lives are less than 3
if (box instanceof Box3 && lives < 3) {
lives += 1;
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * lives * 50,
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Check if the collected box is an instance of Box2
if (box instanceof Box2) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var bubblesDestroyed = bubbles.length;
bubbles.forEach(function (bubble) {
bubble.destroy();
});
score += bubblesDestroyed;
scoreTxt.setText("Lanterns destroyed: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
// Create toast message based on box type
showToastForBox(box);
}
// Function to show toast message for box effects
function showToastForBox(box) {
var toastText = new Text2('', {
size: 250,
fill: 0x000000,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 15
});
var toastTextBg = new Text2('', {
size: 255,
fill: 0xffff00,
font: "'Comic Sans MS', cursive, sans-serif"
});
toastText.anchor.set(0.5, 0.5);
toastText.x = 2048 / 2;
toastText.y = 2732 / 2;
toastTextBg.anchor.set(0.5, 0.5);
toastTextBg.x = 2048 / 2;
toastTextBg.y = 2732 / 2;
game.addChild(toastText);
game.addChild(toastTextBg);
if (box instanceof Box1) {
toastText.setText("Smash!");
toastTextBg.setText("Smash!");
} else if (box instanceof Box2) {
toastText.setText("Destruction!");
toastTextBg.setText("Destruction!");
} else if (box instanceof Box) {
toastText.setText("Less madness!");
toastTextBg.setText("Less madness!");
} else if (box instanceof Box3) {
toastText.setText("Life up!");
toastTextBg.setText("Life up!");
}
// Tween the toast text to fade out and destroy after 2 seconds
tween(toastText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastText.destroy();
}
});
tween(toastTextBg, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastTextBg.destroy();
}
});
}
// Function to handle player death sequence with rainbow colors
function handlePlayerDeath() {
// Rainbow colors array
var rainbowColors = [0xFF0000,
// Red
0xFF7F00,
// Orange
0xFFFF00,
// Yellow
0x00FF00,
// Green
0x0000FF,
// Blue
0x4B0082,
// Indigo
0x9400D3 // Violet
];
// Create dramatic death effect
var deathEffect = new PlayerDeathEffect();
game.addChild(deathEffect);
deathEffect.startEffect();
// Mark game as over to stop spawning bubbles
game.isGameOver = true;
// Create a function for sequential rainbow color animation
function playerRainbowSequence(colorIndex) {
if (colorIndex >= rainbowColors.length) {
// One full cycle completed, start fading out
tween(player, {
alpha: 0,
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
return;
}
tween(player, {
alpha: 0.3,
tint: rainbowColors[colorIndex]
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
playerRainbowSequence(colorIndex + 1);
}
});
}
// Start the rainbow sequence
playerRainbowSequence(0);
}
var lastShot = -999;
game.down = function (x, y, obj) {
if (LK.ticks - lastShot > 10) {
player.shoot();
lastShot = LK.ticks;
}
};
// Start the music 'chinese' upon starting the game
LK.playMusic('chinese');
game.update = function () {
// Initialize game.isGameOver if it doesn't exist
if (game.isGameOver === undefined) {
game.isGameOver = false;
}
// Only spawn new bubbles if game isn't over
if (!game.isGameOver && LK.ticks % 50 == 0) {
var newBubble = new Bubble();
newBubble.x = Math.random() * 2048;
newBubble.y = 2732 * 0.75; // Start at 25% from bottom of screen
game.addChild(newBubble);
var randomTween = {
delta: Math.random() * (300 - 100) + 100,
// Random delta between 100 and 300
duration: Math.random() * (1500 - 800) + 800,
// Random duration between 800 and 1500
easing: [tween.easeIn, tween.easeOut, tween.elasticIn, tween.elasticOut, tween.bounceIn, tween.bounceOut, tween.easeInOut, tween.bounceInOut, tween.elasticInOut][Math.floor(Math.random() * 9)] // Random easing
};
tween(newBubble, {
x: Math.max(0, Math.min(2048, newBubble.x + randomTween.delta))
}, {
duration: randomTween.duration,
easing: randomTween.easing,
onFinish: function onFinish() {
tween(newBubble, {
x: Math.max(0, Math.min(2048, newBubble.x - randomTween.delta))
}, {
duration: randomTween.duration,
easing: randomTween.easing
});
}
});
}
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var harpoons = game.children.filter(function (child) {
return child instanceof Harpoon;
});
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
// Check for player intersection but don't damage the player
// We allow lanterns to pass through the player harmlessly
if (bubble.intersects(player)) {
// Don't destroy the bubble or harm the player
// This allows the bubble to continue moving up
// It will only damage the player if it exits off the top without being destroyed
}
for (var j = 0; j < harpoons.length; j++) {
var harpoon = harpoons[j];
// Enhanced collision detection for longer harpoon reach and multiple trail segments
var collides = bubble.intersects(harpoon);
if (!collides && harpoon.trails && harpoon.trails.length > 0) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
if (Math.abs(bubble.x - harpoon.x) < 50 && Math.abs(bubble.y - trail.y) < 50) {
collides = true;
break;
}
}
}
if (collides) {
bubble.destroy();
LK.getSound('explosion').play();
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
bubbles.splice(i, 1); // Remove bubble from the array
score += 1;
scoreTxt.setText("Lanterns destroyed: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
harpoons.splice(j, 1); // Remove harpoon from the array
// Create an explosion at the intersection point
var explosion = new Explosion();
explosion.x = bubble.x;
explosion.y = bubble.y;
game.addChild(explosion);
// Create a box at the intersection point with a 5% probability (half the previous 10%)
// Only spawn boxes when balloons are popped by player harpoons, not by explosion effects
if (!game.children.some(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
})) {
// Only create boxes when bubble is popped by a player-created harpoon, not by power-up harpoons
// Check explicitly that the harpoon was created by the player, not by any box power-up
if (Math.random() < 0.5 && harpoon instanceof Harpoon && harpoon.isPlayerCreated === true) {
var boxType = Math.floor(Math.random() * 4);
var box;
switch (boxType) {
case 0:
box = new Box();
break;
case 1:
box = new Box1();
break;
case 2:
box = new Box2();
break;
case 3:
box = new Box3();
break;
}
box.x = bubble.x;
box.y = bubble.y;
game.addChild(box);
}
}
}
}
var boxes = game.children.filter(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
});
for (var k = 0; k < boxes.length; k++) {
var box = boxes[k];
// Check if player collects/intersects the box
if (box.intersects(player)) {
// Create collection animation
tween(box.scale, {
x: 0,
y: 0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function (boxToDestroy) {
boxToDestroy.destroy();
}.bind(null, box)
});
// Play pickup sound
LK.getSound('powerup').play();
// Apply the power-up effect based on box type using centralized handler
applyBoxEffect(box);
// Remove box from tracking array
boxes.splice(k, 1);
// Break out of the loop since we've handled the box
break;
}
// Check for harpoon intersecting with boxes
for (var l = 0; l < harpoons.length; l++) {
var harpoon = harpoons[l];
var collides = box.intersects(harpoon);
// Check trail segments for collision as well
if (!collides && harpoon.trails && harpoon.trails.length > 0) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
if (trail && Math.abs(box.x - harpoon.x) < 50 && Math.abs(box.y - trail.y) < 50) {
collides = true;
break;
}
}
}
if (collides) {
// Store reference to box before destroying it
var boxToApplyEffect = box;
// Destroy elements
box.destroy();
LK.getSound('powerup').play();
// Safely destroy trails for harpoons created before we started using multiple trails
if (harpoon.trail) {
harpoon.trail.destroy();
}
// Safely destroy trail segments for current harpoons
if (harpoon.trails && harpoon.trails.length > 0) {
harpoon.trails.forEach(function (trail) {
if (trail) {
trail.destroy();
}
});
}
harpoon.destroy();
// Apply the power-up effect based on box type using centralized handler
applyBoxEffect(boxToApplyEffect);
// Remove from arrays
boxes.splice(k, 1);
harpoons.splice(l, 1);
break;
}
}
}
}
};
a green cross, icon, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a sand clock pixel style.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a bomb, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a pixel harpoon, vertical and looking up, retro like in pang games.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
A banner to show a message, pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
two harpoons looking up, retro, pixel. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
A chinese flying paper lantern, retro pixel style. In-Game asset. 2d. High contrast. No shadows
fireworks, retro pixel style, colorful. In-Game asset. 2d. High contrast. No shadows
A chinese pixel background of an arcade game, sunset, retro style,. In-Game asset. 2d. High contrast. No shadows