/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
volume: 0.5
});
/****
* Classes
****/
var ColorCircle = Container.expand(function () {
var self = Container.call(this);
self.colorValue = 0xFFFFFF;
self.radius = 75;
self.speed = 2;
self.angle = Math.random() * Math.PI * 2;
self.lastWasIntersecting = false;
var circleGraphics = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.setColor = function (color) {
self.colorValue = color;
circleGraphics.tint = color;
};
self.update = function () {
// Move in a circular pattern (affected by time slow)
var effectiveSpeed = timeSlowActive ? self.speed * 0.3 : self.speed;
self.x += Math.cos(self.angle) * effectiveSpeed;
self.y += Math.sin(self.angle) * effectiveSpeed;
// Bounce off walls
if (self.x < self.radius || self.x > 2048 - self.radius) {
self.angle = Math.PI - self.angle;
self.x = Math.max(self.radius, Math.min(2048 - self.radius, self.x));
}
if (self.y < self.radius || self.y > 2732 - self.radius) {
self.angle = -self.angle;
self.y = Math.max(self.radius, Math.min(2732 - self.radius, self.y));
}
};
self.down = function (x, y, obj) {
// Circle was tapped
self.wasTapped = true;
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
self.floatSpeed = 2;
self.floatAngle = Math.random() * Math.PI * 2;
self.floatRadius = 100;
self.centerX = 0;
self.centerY = 0;
self.floatTime = 0;
// Create heart shape using two circles for the top lobes and a triangle-like bottom
var topLeft = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFF1744
});
topLeft.x = -12;
topLeft.y = -8;
var topRight = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFF1744
});
topRight.x = 12;
topRight.y = -8;
// Create bottom part using multiple small circles to form a point
var bottom1 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.35,
scaleY: 0.35,
tint: 0xFF1744
});
bottom1.x = 0;
bottom1.y = 5;
var bottom2 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
tint: 0xFF1744
});
bottom2.x = 0;
bottom2.y = 15;
var bottom3 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15,
tint: 0xFF1744
});
bottom3.x = 0;
bottom3.y = 22;
// Add middle fill circles for better shape
var middle1 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFF1744
});
middle1.x = -6;
middle1.y = 0;
var middle2 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFF1744
});
middle2.x = 6;
middle2.y = 0;
// Pulsing effect
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Repeat pulsing
if (!self.collected && self.parent) {
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
});
self.update = function () {
if (self.collected) return;
// Float in a smooth pattern
self.floatTime += 0.02;
var offsetX = Math.sin(self.floatTime) * self.floatRadius;
var offsetY = Math.cos(self.floatTime * 0.7) * self.floatRadius * 0.5;
self.x = self.centerX + offsetX;
self.y = self.centerY + offsetY;
// Slight rotation for visual interest
self.rotation = Math.sin(self.floatTime * 0.5) * 0.1;
};
self.down = function (x, y, obj) {
if (!self.collected) {
self.collected = true;
}
};
return self;
});
var TargetIndicator = Container.expand(function () {
var self = Container.call(this);
self.targetColor = 0xFFFFFF;
var ringGraphics = self.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5
});
var innerCircle = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.setTargetColor = function (color) {
self.targetColor = color;
innerCircle.tint = color;
ringGraphics.tint = color;
};
return self;
});
var TimeSlowCannon = Container.expand(function () {
var self = Container.call(this);
self.shootTimer = 0;
self.shootInterval = 900; // 15 seconds at 60fps
self.cannonColor = 0x4A90E2;
self.currentRotation = 0;
self.targetRotation = 0;
self.minRotation = -Math.PI / 4; // -45 degrees (aim up)
self.maxRotation = Math.PI / 4; // +45 degrees (aim down)
self.rotationSpeed = 0.05;
// Cannon base - sleek blue design
var cannonBase = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x2E5A8A
});
// Cannon barrel - elongated and futuristic
var cannonBarrel = self.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 2.0
});
cannonBarrel.tint = self.cannonColor;
// Energy core in center
var energyCore = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
tint: 0x00FFFF
});
// Pulsing energy core animation
tween(energyCore, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.7
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(energyCore, {
scaleX: 0.6,
scaleY: 0.6,
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
// Set cannon rotation to face right (90 degrees clockwise)
self.rotation = Math.PI / 2;
self.down = function (x, y, obj) {
// Calculate desired rotation based on mouse/touch Y position relative to cannon
var relativeY = y - self.y;
var normalizedY = Math.max(-300, Math.min(300, relativeY)); // Clamp range
self.targetRotation = normalizedY / 300 * (self.maxRotation - self.minRotation) / 2;
self.targetRotation = Math.max(self.minRotation, Math.min(self.maxRotation, self.targetRotation));
};
self.update = function () {
// Smoothly rotate cannon towards target rotation
if (Math.abs(self.targetRotation - self.currentRotation) > 0.01) {
var rotationDiff = self.targetRotation - self.currentRotation;
self.currentRotation += rotationDiff * self.rotationSpeed;
// Apply the rotation on top of the base 90-degree rotation
self.rotation = Math.PI / 2 + self.currentRotation;
}
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
self.shoot();
self.shootTimer = 0;
}
};
self.shoot = function () {
// Create electric charge buildup
var chargeEffect = game.addChild(new Container());
var charge = chargeEffect.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0,
tint: 0x00FFFF,
alpha: 0.0
});
chargeEffect.x = self.x;
chargeEffect.y = self.y;
// Charge buildup animation
tween(chargeEffect, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Create projectile instead of instant effect
var projectile = game.addChild(new TimeSlowProjectile());
projectile.x = self.x;
projectile.y = self.y;
// Calculate shooting direction based on cannon rotation
var shootingAngle = self.currentRotation; // Current cannon aim adjustment
var baseAngle = 0; // Shooting to the right (since cannon faces right)
projectile.angle = baseAngle + shootingAngle;
// Add projectile to tracking array
timeSlowProjectiles.push(projectile);
chargeEffect.destroy();
}
});
// Cannon recoil with electric sparks
var recoilSparks = game.addChild(new Container());
for (var s = 0; s < 8; s++) {
var spark = recoilSparks.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2,
tint: 0x00FFFF
});
spark.x = self.x + (Math.random() - 0.5) * 100;
spark.y = self.y + (Math.random() - 0.5) * 100;
tween(spark, {
alpha: 0,
scaleX: 0.4,
scaleY: 0.4
}, {
duration: 500,
easing: tween.easeOut
});
}
// Animate recoil sparks container
tween(recoilSparks, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
recoilSparks.destroy();
}
});
// Cannon recoil effect
tween(self, {
y: self.y + 30
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.y - 30
}, {
duration: 300,
easing: tween.bounceOut
});
}
});
};
return self;
});
var TimeSlowProjectile = Container.expand(function () {
var self = Container.call(this);
self.speed = 5;
self.angle = 0;
self.used = false;
// Create glowing projectile
var projectileCore = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x00FFFF
});
// Add outer glow ring
var glowRing = self.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FFFF,
alpha: 0.5
});
// Pulsing glow animation
tween(glowRing, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(glowRing, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.5
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
self.update = function () {
if (self.used) return;
// Move projectile
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
};
self.down = function (x, y, obj) {
if (self.used) return;
self.used = true;
// Lightning bolt effect - create zigzag pattern
for (var l = 0; l < 5; l++) {
var lightning = game.addChild(new Container());
var bolt = lightning.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 3.0,
tint: 0xFFFFFF
});
lightning.x = self.x + (Math.random() - 0.5) * 200;
lightning.y = self.y - l * 100;
lightning.rotation = (Math.random() - 0.5) * 0.5;
lightning.alpha = 0.8;
// Lightning flash effect
tween(lightning, {
alpha: 0,
scaleY: 4.0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
lightning.destroy();
}
});
}
// Screen flash for dramatic effect
LK.effects.flashScreen(0x00FFFF, 400);
// Time slow effect implementation
timeSlowActive = true;
timeSlowDuration = 300; // 5 seconds at 60fps
// Visual feedback for time slow
var timeIndicator = game.addChild(new Container());
var indicator = timeIndicator.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 8.0,
tint: 0x00FFFF,
alpha: 0.2
});
timeIndicator.x = 1024;
timeIndicator.y = 1366;
tween(timeIndicator, {
scaleX: 12.0,
scaleY: 12.0,
alpha: 0
}, {
duration: 5000,
easing: tween.easeOut,
onFinish: function onFinish() {
timeIndicator.destroy();
}
});
// Destroy projectile with explosion effect
tween(self, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
// Game variables
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
// Game variables
var circles = [];
var targetColor = 0xFFFFFF;
var gameRunning = true;
var combo = 0;
var level = 1;
var circleSpeed = 2;
var spawnTimer = 0;
var spawnInterval = 90; // 1.5 seconds at 60fps
var lives = 3;
var hearts = [];
var heartSpawnTimer = 0;
var heartSpawnInterval = 600; // 10 seconds at 60fps
var cannons = [];
var timeSlowProjectiles = [];
var timeSlowActive = false;
var timeSlowDuration = 0;
// Color palette
var colors = [0xFF6B6B,
// Red
0x4ECDC4,
// Cyan
0x45B7D1,
// Blue
0xF7DC6F,
// Yellow
0x58D68D,
// Green
0xBB8FCE,
// Purple
0xF8B500,
// Orange
0xE91E63 // Pink
];
// UI Elements
var scoreContainer = new Container();
LK.gui.top.addChild(scoreContainer);
scoreContainer.y = 50;
// Score background
var scoreBg = scoreContainer.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.8,
tint: 0x222222,
alpha: 0.8
});
// Score text with gradient-like effect
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFD700
});
scoreTxt.anchor.set(0.5, 0.5);
scoreContainer.addChild(scoreTxt);
// Score label
var scoreLabel = new Text2('SCORE', {
size: 30,
fill: 0xFFFFFF
});
scoreLabel.anchor.set(0.5, 0.5);
scoreLabel.y = -40;
scoreContainer.addChild(scoreLabel);
// Animated score value for smooth transitions
var displayedScore = 0;
var targetScore = 0;
var comboTxt = new Text2('', {
size: 50,
fill: 0xFFD700
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.y = 130;
var levelTxt = new Text2('Level 1', {
size: 40,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(levelTxt);
levelTxt.x = -200;
levelTxt.y = 50;
var livesTxt = new Text2('Lives: 3', {
size: 40,
fill: 0xFF6B6B
});
livesTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(livesTxt);
livesTxt.x = -200;
livesTxt.y = 100;
// Create target indicator
var targetIndicator = game.addChild(new TargetIndicator());
targetIndicator.x = 1024;
targetIndicator.y = 2600;
// Set initial target color
function setNewTargetColor() {
targetColor = colors[Math.floor(Math.random() * colors.length)];
targetIndicator.setTargetColor(targetColor);
// Pulse animation for new target
tween(targetIndicator, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(targetIndicator, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
function spawnHeart() {
if (!gameRunning || hearts.length > 0) return; // Only one heart at a time
var heart = game.addChild(new Heart());
// Random position within play area
heart.centerX = 200 + Math.random() * 1648;
heart.centerY = 400 + Math.random() * 1932;
heart.x = heart.centerX;
heart.y = heart.centerY;
hearts.push(heart);
// Auto-remove after 5 seconds if not collected
LK.setTimeout(function () {
if (!heart.collected && heart.parent) {
tween(heart, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 500,
onFinish: function onFinish() {
heart.destroy();
var index = hearts.indexOf(heart);
if (index > -1) {
hearts.splice(index, 1);
}
}
});
}
}, 5000);
}
function spawnCircle() {
if (!gameRunning) return;
var circle = game.addChild(new ColorCircle());
// Random position on edges
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// Top
circle.x = Math.random() * 2048;
circle.y = -50;
} else if (edge === 1) {
// Right
circle.x = 2098;
circle.y = Math.random() * 2732;
} else if (edge === 2) {
// Bottom
circle.x = Math.random() * 2048;
circle.y = 2782;
} else {
// Left
circle.x = -50;
circle.y = Math.random() * 2732;
}
// Set random color
var color = colors[Math.floor(Math.random() * colors.length)];
circle.setColor(color);
// Set speed based on level
circle.speed = circleSpeed + (level - 1) * 0.5;
// Aim towards center area with some randomness
var targetX = 1024 + (Math.random() - 0.5) * 800;
var targetY = 1366 + (Math.random() - 0.5) * 800;
circle.angle = Math.atan2(targetY - circle.y, targetX - circle.x);
circles.push(circle);
}
function checkCircleTap(circle) {
if (circle.colorValue === targetColor) {
// Correct tap
combo++;
var points = 10 * combo;
LK.setScore(LK.getScore() + points);
// Create floating score popup
var scorePopup = game.addChild(new Container());
var popupBg = scorePopup.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
tint: 0x000000,
alpha: 0.6
});
var popupText = new Text2('+' + points, {
size: 60,
fill: 0xFFD700
});
popupText.anchor.set(0.5, 0.5);
scorePopup.addChild(popupText);
scorePopup.x = circle.x;
scorePopup.y = circle.y;
scorePopup.alpha = 0;
// Animate floating score
tween(scorePopup, {
y: circle.y - 150,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scorePopup, {
y: circle.y - 200,
alpha: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
scorePopup.destroy();
}
});
}
});
if (combo % 5 === 0) {
LK.getSound('combo').play();
// Bonus flash
LK.effects.flashScreen(0xFFD700, 300);
} else {
LK.getSound('correct').play();
}
// Create particle burst effect
for (var p = 0; p < 8; p++) {
var particle = game.addChild(new Container());
var particleGraphics = particle.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particleGraphics.tint = circle.colorValue;
particle.x = circle.x;
particle.y = circle.y;
var angle = Math.PI * 2 / 8 * p;
var distance = 150;
tween(particle, {
x: circle.x + Math.cos(angle) * distance,
y: circle.y + Math.sin(angle) * distance,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Success animation with enhanced effects
tween(circle, {
scaleX: 2,
scaleY: 2,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
circle.destroy();
}
});
// Add ring pulse effect
var ringEffect = game.addChild(new Container());
var ring = ringEffect.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
ring.tint = circle.colorValue;
ringEffect.x = circle.x;
ringEffect.y = circle.y;
ringEffect.alpha = 0.8;
tween(ringEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
ringEffect.destroy();
}
});
// Update UI
targetScore = LK.getScore();
// Pulse effect on score change
tween(scoreContainer, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Flash score color
tween(scoreTxt, {
tint: 0xFFFFFF
}, {
duration: 100,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFD700
}, {
duration: 300
});
}
});
if (combo > 1) {
comboTxt.setText('Combo x' + combo);
// Combo text pulse effect
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(comboTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
// Target indicator celebration effect
tween(targetIndicator, {
rotation: Math.PI * 0.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(targetIndicator, {
rotation: -Math.PI * 0.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(targetIndicator, {
rotation: 0
}, {
duration: 100
});
}
});
}
});
// Set new target color
setNewTargetColor();
} else {
// Wrong tap
combo = 0;
lives--;
LK.getSound('wrong').play();
// Error animation
tween(circle, {
tint: 0x333333,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
circle.destroy();
}
});
// Flash screen red
LK.effects.flashScreen(0xFF0000, 500);
// Update UI
comboTxt.setText('');
livesTxt.setText('Lives: ' + lives);
if (lives <= 0) {
gameOver();
}
}
// Remove from array
var index = circles.indexOf(circle);
if (index > -1) {
circles.splice(index, 1);
}
}
function nextLevel() {
level++;
circleSpeed += 0.5;
spawnInterval = Math.max(30, spawnInterval - 10); // Spawn faster
// Clear all circles
for (var i = circles.length - 1; i >= 0; i--) {
circles[i].destroy();
}
circles = [];
// Level up effects
LK.getSound('levelup').play();
LK.effects.flashScreen(0x4ECDC4, 1000);
// Update UI
levelTxt.setText('Level ' + level);
// Show level notification
var levelNotification = new Text2('Level ' + level + '!', {
size: 100,
fill: 0x4ECDC4
});
levelNotification.anchor.set(0.5, 0.5);
levelNotification.x = 1024;
levelNotification.y = 1366;
game.addChild(levelNotification);
tween(levelNotification, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
levelNotification.destroy();
}
});
}
function gameOver() {
gameRunning = false;
// Clear all circles
for (var i = circles.length - 1; i >= 0; i--) {
circles[i].destroy();
}
// Check for win condition (reached level 10)
if (level >= 10) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
// Game update loop
game.update = function () {
if (!gameRunning) return;
// Handle time slow effect
if (timeSlowActive) {
timeSlowDuration--;
if (timeSlowDuration <= 0) {
timeSlowActive = false;
}
}
// Spawn circles (affected by time slow)
spawnTimer++;
var effectiveSpawnInterval = timeSlowActive ? spawnInterval * 2 : spawnInterval;
if (spawnTimer >= effectiveSpawnInterval) {
spawnCircle();
spawnTimer = 0;
}
// Spawn hearts
heartSpawnTimer++;
if (heartSpawnTimer >= heartSpawnInterval) {
spawnHeart();
heartSpawnTimer = 0;
// Randomize next spawn interval (10-20 seconds)
heartSpawnInterval = 600 + Math.floor(Math.random() * 600);
}
// Check for collected hearts
for (var h = hearts.length - 1; h >= 0; h--) {
var heart = hearts[h];
if (heart.collected) {
// Add life
lives = Math.min(lives + 1, 5); // Cap at 5 lives
livesTxt.setText('Lives: ' + lives);
// Collection effect - golden flash instead of red
LK.effects.flashScreen(0xFFD700, 300);
LK.getSound('levelup').play();
// Create enhanced sparkle effect with red particles
for (var s = 0; s < 16; s++) {
var sparkle = game.addChild(new Container());
var sparkleGraphics = sparkle.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFF0000
});
sparkle.x = heartX;
sparkle.y = heartY;
var angle = Math.PI * 2 / 16 * s;
var distance = 150 + Math.random() * 100;
// Add initial scale pulse
tween(sparkle, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(sparkle, {
x: heartX + Math.cos(angle) * distance,
y: heartY + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
sparkle.destroy();
}
});
}
});
}
// Store heart position for effects before removing it
var heartX = heart.x;
var heartY = heart.y;
// Remove heart from game immediately and destroy it
heart.destroy();
hearts.splice(h, 1);
// Special heart collection effect with red glow
// First create a red glow effect
var heartGlow = game.addChild(new Container());
var glowRing = heartGlow.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xFF0000,
alpha: 0.8
});
heartGlow.x = heartX;
heartGlow.y = heartY;
// Animate glow ring expanding
tween(heartGlow, {
scaleX: 4,
scaleY: 4,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
heartGlow.destroy();
}
});
}
}
// Despawn oldest circles if there are more than 8
while (circles.length > 8) {
var oldestCircle = circles.shift(); // Remove first (oldest) circle
oldestCircle.destroy();
}
// Check for tapped circles
for (var i = circles.length - 1; i >= 0; i--) {
var circle = circles[i];
if (circle.wasTapped) {
circle.wasTapped = false;
checkCircleTap(circle);
break; // Only process one tap per frame
}
// Remove circles that went off screen
if (circle.x < -100 || circle.x > 2148 || circle.y < -100 || circle.y > 2832) {
circles.splice(i, 1);
circle.destroy();
}
}
// Manage time slow projectiles
for (var p = timeSlowProjectiles.length - 1; p >= 0; p--) {
var projectile = timeSlowProjectiles[p];
// Remove projectiles that went off screen or were used
if (projectile.used || projectile.x < -100 || projectile.x > 2148 || projectile.y < -100 || projectile.y > 2832) {
timeSlowProjectiles.splice(p, 1);
if (!projectile.used) {
projectile.destroy();
}
}
}
// Smooth score counter animation
if (displayedScore < targetScore) {
var diff = targetScore - displayedScore;
var increment = Math.max(1, Math.floor(diff / 10));
displayedScore = Math.min(displayedScore + increment, targetScore);
scoreTxt.setText(displayedScore.toString());
// Add subtle scale effect while counting
var scaleFactor = 1 + diff / 1000 * 0.1;
scoreTxt.scale.set(Math.min(1.2, scaleFactor));
} else if (displayedScore > targetScore) {
displayedScore = targetScore;
scoreTxt.setText(displayedScore.toString());
scoreTxt.scale.set(1);
}
};
// Volume control variables
var currentVolume = storage.volume || 0.5;
var volumeSlider = null;
var volumeContainer = null;
var isPaused = false;
// Pause handler to show volume controls
LK.on('pause', function () {
isPaused = true;
// Create volume control container
volumeContainer = new Container();
LK.gui.center.addChild(volumeContainer);
// Background for volume controls
var volumeBg = volumeContainer.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 2,
tint: 0x222222,
alpha: 0.9
});
// Volume label
var volumeLabel = new Text2('VOLUME', {
size: 50,
fill: 0xFFFFFF
});
volumeLabel.anchor.set(0.5, 0.5);
volumeLabel.y = -50;
volumeContainer.addChild(volumeLabel);
// Volume slider background
var sliderBg = volumeContainer.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.3,
tint: 0x444444
});
sliderBg.y = 20;
// Volume slider handle
volumeSlider = volumeContainer.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0xFFD700
});
volumeSlider.y = 20;
volumeSlider.x = (currentVolume - 0.5) * 300; // Position based on volume (0-1 maps to -150 to +150)
// Volume percentage display
var volumePercent = new Text2(Math.round(currentVolume * 100) + '%', {
size: 40,
fill: 0xFFD700
});
volumePercent.anchor.set(0.5, 0.5);
volumePercent.y = 80;
volumeContainer.addChild(volumePercent);
// Handle volume slider interaction
volumeSlider.down = function (x, y, obj) {
volumeSlider.isDragging = true;
};
volumeContainer.move = function (x, y, obj) {
if (volumeSlider && volumeSlider.isDragging) {
// Convert global position to local slider position
var localX = Math.max(-150, Math.min(150, x - volumeContainer.x));
volumeSlider.x = localX;
// Update volume (slider range -150 to +150 maps to volume 0 to 1)
currentVolume = Math.max(0, Math.min(1, (localX + 150) / 300));
// Update display
volumePercent.setText(Math.round(currentVolume * 100) + '%');
// Save to storage
storage.volume = currentVolume;
// Update music volume immediately
LK.stopMusic();
LK.playMusic('calmMusic', {
volume: currentVolume
});
}
};
volumeContainer.up = function (x, y, obj) {
if (volumeSlider) {
volumeSlider.isDragging = false;
}
};
});
// Resume handler to clean up volume controls
LK.on('resume', function () {
isPaused = false;
if (volumeContainer) {
volumeContainer.destroy();
volumeContainer = null;
volumeSlider = null;
}
});
// Create single time slow cannon on left side
var timeSlowCannon = game.addChild(new TimeSlowCannon());
timeSlowCannon.x = 200;
timeSlowCannon.y = 1366; // Center vertically
cannons.push(timeSlowCannon);
// Initialize game
setNewTargetColor();
// Play calm background music
LK.playMusic('calmMusic', {
volume: currentVolume
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
volume: 0.5
});
/****
* Classes
****/
var ColorCircle = Container.expand(function () {
var self = Container.call(this);
self.colorValue = 0xFFFFFF;
self.radius = 75;
self.speed = 2;
self.angle = Math.random() * Math.PI * 2;
self.lastWasIntersecting = false;
var circleGraphics = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.setColor = function (color) {
self.colorValue = color;
circleGraphics.tint = color;
};
self.update = function () {
// Move in a circular pattern (affected by time slow)
var effectiveSpeed = timeSlowActive ? self.speed * 0.3 : self.speed;
self.x += Math.cos(self.angle) * effectiveSpeed;
self.y += Math.sin(self.angle) * effectiveSpeed;
// Bounce off walls
if (self.x < self.radius || self.x > 2048 - self.radius) {
self.angle = Math.PI - self.angle;
self.x = Math.max(self.radius, Math.min(2048 - self.radius, self.x));
}
if (self.y < self.radius || self.y > 2732 - self.radius) {
self.angle = -self.angle;
self.y = Math.max(self.radius, Math.min(2732 - self.radius, self.y));
}
};
self.down = function (x, y, obj) {
// Circle was tapped
self.wasTapped = true;
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
self.floatSpeed = 2;
self.floatAngle = Math.random() * Math.PI * 2;
self.floatRadius = 100;
self.centerX = 0;
self.centerY = 0;
self.floatTime = 0;
// Create heart shape using two circles for the top lobes and a triangle-like bottom
var topLeft = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFF1744
});
topLeft.x = -12;
topLeft.y = -8;
var topRight = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFF1744
});
topRight.x = 12;
topRight.y = -8;
// Create bottom part using multiple small circles to form a point
var bottom1 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.35,
scaleY: 0.35,
tint: 0xFF1744
});
bottom1.x = 0;
bottom1.y = 5;
var bottom2 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
tint: 0xFF1744
});
bottom2.x = 0;
bottom2.y = 15;
var bottom3 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15,
tint: 0xFF1744
});
bottom3.x = 0;
bottom3.y = 22;
// Add middle fill circles for better shape
var middle1 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFF1744
});
middle1.x = -6;
middle1.y = 0;
var middle2 = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFF1744
});
middle2.x = 6;
middle2.y = 0;
// Pulsing effect
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Repeat pulsing
if (!self.collected && self.parent) {
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
});
self.update = function () {
if (self.collected) return;
// Float in a smooth pattern
self.floatTime += 0.02;
var offsetX = Math.sin(self.floatTime) * self.floatRadius;
var offsetY = Math.cos(self.floatTime * 0.7) * self.floatRadius * 0.5;
self.x = self.centerX + offsetX;
self.y = self.centerY + offsetY;
// Slight rotation for visual interest
self.rotation = Math.sin(self.floatTime * 0.5) * 0.1;
};
self.down = function (x, y, obj) {
if (!self.collected) {
self.collected = true;
}
};
return self;
});
var TargetIndicator = Container.expand(function () {
var self = Container.call(this);
self.targetColor = 0xFFFFFF;
var ringGraphics = self.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5
});
var innerCircle = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.setTargetColor = function (color) {
self.targetColor = color;
innerCircle.tint = color;
ringGraphics.tint = color;
};
return self;
});
var TimeSlowCannon = Container.expand(function () {
var self = Container.call(this);
self.shootTimer = 0;
self.shootInterval = 900; // 15 seconds at 60fps
self.cannonColor = 0x4A90E2;
self.currentRotation = 0;
self.targetRotation = 0;
self.minRotation = -Math.PI / 4; // -45 degrees (aim up)
self.maxRotation = Math.PI / 4; // +45 degrees (aim down)
self.rotationSpeed = 0.05;
// Cannon base - sleek blue design
var cannonBase = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x2E5A8A
});
// Cannon barrel - elongated and futuristic
var cannonBarrel = self.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 2.0
});
cannonBarrel.tint = self.cannonColor;
// Energy core in center
var energyCore = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
tint: 0x00FFFF
});
// Pulsing energy core animation
tween(energyCore, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.7
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(energyCore, {
scaleX: 0.6,
scaleY: 0.6,
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
// Set cannon rotation to face right (90 degrees clockwise)
self.rotation = Math.PI / 2;
self.down = function (x, y, obj) {
// Calculate desired rotation based on mouse/touch Y position relative to cannon
var relativeY = y - self.y;
var normalizedY = Math.max(-300, Math.min(300, relativeY)); // Clamp range
self.targetRotation = normalizedY / 300 * (self.maxRotation - self.minRotation) / 2;
self.targetRotation = Math.max(self.minRotation, Math.min(self.maxRotation, self.targetRotation));
};
self.update = function () {
// Smoothly rotate cannon towards target rotation
if (Math.abs(self.targetRotation - self.currentRotation) > 0.01) {
var rotationDiff = self.targetRotation - self.currentRotation;
self.currentRotation += rotationDiff * self.rotationSpeed;
// Apply the rotation on top of the base 90-degree rotation
self.rotation = Math.PI / 2 + self.currentRotation;
}
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
self.shoot();
self.shootTimer = 0;
}
};
self.shoot = function () {
// Create electric charge buildup
var chargeEffect = game.addChild(new Container());
var charge = chargeEffect.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0,
tint: 0x00FFFF,
alpha: 0.0
});
chargeEffect.x = self.x;
chargeEffect.y = self.y;
// Charge buildup animation
tween(chargeEffect, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Create projectile instead of instant effect
var projectile = game.addChild(new TimeSlowProjectile());
projectile.x = self.x;
projectile.y = self.y;
// Calculate shooting direction based on cannon rotation
var shootingAngle = self.currentRotation; // Current cannon aim adjustment
var baseAngle = 0; // Shooting to the right (since cannon faces right)
projectile.angle = baseAngle + shootingAngle;
// Add projectile to tracking array
timeSlowProjectiles.push(projectile);
chargeEffect.destroy();
}
});
// Cannon recoil with electric sparks
var recoilSparks = game.addChild(new Container());
for (var s = 0; s < 8; s++) {
var spark = recoilSparks.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2,
tint: 0x00FFFF
});
spark.x = self.x + (Math.random() - 0.5) * 100;
spark.y = self.y + (Math.random() - 0.5) * 100;
tween(spark, {
alpha: 0,
scaleX: 0.4,
scaleY: 0.4
}, {
duration: 500,
easing: tween.easeOut
});
}
// Animate recoil sparks container
tween(recoilSparks, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
recoilSparks.destroy();
}
});
// Cannon recoil effect
tween(self, {
y: self.y + 30
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.y - 30
}, {
duration: 300,
easing: tween.bounceOut
});
}
});
};
return self;
});
var TimeSlowProjectile = Container.expand(function () {
var self = Container.call(this);
self.speed = 5;
self.angle = 0;
self.used = false;
// Create glowing projectile
var projectileCore = self.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x00FFFF
});
// Add outer glow ring
var glowRing = self.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0x00FFFF,
alpha: 0.5
});
// Pulsing glow animation
tween(glowRing, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(glowRing, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.5
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
});
self.update = function () {
if (self.used) return;
// Move projectile
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
};
self.down = function (x, y, obj) {
if (self.used) return;
self.used = true;
// Lightning bolt effect - create zigzag pattern
for (var l = 0; l < 5; l++) {
var lightning = game.addChild(new Container());
var bolt = lightning.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 3.0,
tint: 0xFFFFFF
});
lightning.x = self.x + (Math.random() - 0.5) * 200;
lightning.y = self.y - l * 100;
lightning.rotation = (Math.random() - 0.5) * 0.5;
lightning.alpha = 0.8;
// Lightning flash effect
tween(lightning, {
alpha: 0,
scaleY: 4.0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
lightning.destroy();
}
});
}
// Screen flash for dramatic effect
LK.effects.flashScreen(0x00FFFF, 400);
// Time slow effect implementation
timeSlowActive = true;
timeSlowDuration = 300; // 5 seconds at 60fps
// Visual feedback for time slow
var timeIndicator = game.addChild(new Container());
var indicator = timeIndicator.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 8.0,
tint: 0x00FFFF,
alpha: 0.2
});
timeIndicator.x = 1024;
timeIndicator.y = 1366;
tween(timeIndicator, {
scaleX: 12.0,
scaleY: 12.0,
alpha: 0
}, {
duration: 5000,
easing: tween.easeOut,
onFinish: function onFinish() {
timeIndicator.destroy();
}
});
// Destroy projectile with explosion effect
tween(self, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
// Game variables
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
// Game variables
var circles = [];
var targetColor = 0xFFFFFF;
var gameRunning = true;
var combo = 0;
var level = 1;
var circleSpeed = 2;
var spawnTimer = 0;
var spawnInterval = 90; // 1.5 seconds at 60fps
var lives = 3;
var hearts = [];
var heartSpawnTimer = 0;
var heartSpawnInterval = 600; // 10 seconds at 60fps
var cannons = [];
var timeSlowProjectiles = [];
var timeSlowActive = false;
var timeSlowDuration = 0;
// Color palette
var colors = [0xFF6B6B,
// Red
0x4ECDC4,
// Cyan
0x45B7D1,
// Blue
0xF7DC6F,
// Yellow
0x58D68D,
// Green
0xBB8FCE,
// Purple
0xF8B500,
// Orange
0xE91E63 // Pink
];
// UI Elements
var scoreContainer = new Container();
LK.gui.top.addChild(scoreContainer);
scoreContainer.y = 50;
// Score background
var scoreBg = scoreContainer.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.8,
tint: 0x222222,
alpha: 0.8
});
// Score text with gradient-like effect
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFD700
});
scoreTxt.anchor.set(0.5, 0.5);
scoreContainer.addChild(scoreTxt);
// Score label
var scoreLabel = new Text2('SCORE', {
size: 30,
fill: 0xFFFFFF
});
scoreLabel.anchor.set(0.5, 0.5);
scoreLabel.y = -40;
scoreContainer.addChild(scoreLabel);
// Animated score value for smooth transitions
var displayedScore = 0;
var targetScore = 0;
var comboTxt = new Text2('', {
size: 50,
fill: 0xFFD700
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.y = 130;
var levelTxt = new Text2('Level 1', {
size: 40,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(levelTxt);
levelTxt.x = -200;
levelTxt.y = 50;
var livesTxt = new Text2('Lives: 3', {
size: 40,
fill: 0xFF6B6B
});
livesTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(livesTxt);
livesTxt.x = -200;
livesTxt.y = 100;
// Create target indicator
var targetIndicator = game.addChild(new TargetIndicator());
targetIndicator.x = 1024;
targetIndicator.y = 2600;
// Set initial target color
function setNewTargetColor() {
targetColor = colors[Math.floor(Math.random() * colors.length)];
targetIndicator.setTargetColor(targetColor);
// Pulse animation for new target
tween(targetIndicator, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(targetIndicator, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
function spawnHeart() {
if (!gameRunning || hearts.length > 0) return; // Only one heart at a time
var heart = game.addChild(new Heart());
// Random position within play area
heart.centerX = 200 + Math.random() * 1648;
heart.centerY = 400 + Math.random() * 1932;
heart.x = heart.centerX;
heart.y = heart.centerY;
hearts.push(heart);
// Auto-remove after 5 seconds if not collected
LK.setTimeout(function () {
if (!heart.collected && heart.parent) {
tween(heart, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 500,
onFinish: function onFinish() {
heart.destroy();
var index = hearts.indexOf(heart);
if (index > -1) {
hearts.splice(index, 1);
}
}
});
}
}, 5000);
}
function spawnCircle() {
if (!gameRunning) return;
var circle = game.addChild(new ColorCircle());
// Random position on edges
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// Top
circle.x = Math.random() * 2048;
circle.y = -50;
} else if (edge === 1) {
// Right
circle.x = 2098;
circle.y = Math.random() * 2732;
} else if (edge === 2) {
// Bottom
circle.x = Math.random() * 2048;
circle.y = 2782;
} else {
// Left
circle.x = -50;
circle.y = Math.random() * 2732;
}
// Set random color
var color = colors[Math.floor(Math.random() * colors.length)];
circle.setColor(color);
// Set speed based on level
circle.speed = circleSpeed + (level - 1) * 0.5;
// Aim towards center area with some randomness
var targetX = 1024 + (Math.random() - 0.5) * 800;
var targetY = 1366 + (Math.random() - 0.5) * 800;
circle.angle = Math.atan2(targetY - circle.y, targetX - circle.x);
circles.push(circle);
}
function checkCircleTap(circle) {
if (circle.colorValue === targetColor) {
// Correct tap
combo++;
var points = 10 * combo;
LK.setScore(LK.getScore() + points);
// Create floating score popup
var scorePopup = game.addChild(new Container());
var popupBg = scorePopup.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
tint: 0x000000,
alpha: 0.6
});
var popupText = new Text2('+' + points, {
size: 60,
fill: 0xFFD700
});
popupText.anchor.set(0.5, 0.5);
scorePopup.addChild(popupText);
scorePopup.x = circle.x;
scorePopup.y = circle.y;
scorePopup.alpha = 0;
// Animate floating score
tween(scorePopup, {
y: circle.y - 150,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scorePopup, {
y: circle.y - 200,
alpha: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
scorePopup.destroy();
}
});
}
});
if (combo % 5 === 0) {
LK.getSound('combo').play();
// Bonus flash
LK.effects.flashScreen(0xFFD700, 300);
} else {
LK.getSound('correct').play();
}
// Create particle burst effect
for (var p = 0; p < 8; p++) {
var particle = game.addChild(new Container());
var particleGraphics = particle.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particleGraphics.tint = circle.colorValue;
particle.x = circle.x;
particle.y = circle.y;
var angle = Math.PI * 2 / 8 * p;
var distance = 150;
tween(particle, {
x: circle.x + Math.cos(angle) * distance,
y: circle.y + Math.sin(angle) * distance,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Success animation with enhanced effects
tween(circle, {
scaleX: 2,
scaleY: 2,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
circle.destroy();
}
});
// Add ring pulse effect
var ringEffect = game.addChild(new Container());
var ring = ringEffect.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
ring.tint = circle.colorValue;
ringEffect.x = circle.x;
ringEffect.y = circle.y;
ringEffect.alpha = 0.8;
tween(ringEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
ringEffect.destroy();
}
});
// Update UI
targetScore = LK.getScore();
// Pulse effect on score change
tween(scoreContainer, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Flash score color
tween(scoreTxt, {
tint: 0xFFFFFF
}, {
duration: 100,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFD700
}, {
duration: 300
});
}
});
if (combo > 1) {
comboTxt.setText('Combo x' + combo);
// Combo text pulse effect
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(comboTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
// Target indicator celebration effect
tween(targetIndicator, {
rotation: Math.PI * 0.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(targetIndicator, {
rotation: -Math.PI * 0.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(targetIndicator, {
rotation: 0
}, {
duration: 100
});
}
});
}
});
// Set new target color
setNewTargetColor();
} else {
// Wrong tap
combo = 0;
lives--;
LK.getSound('wrong').play();
// Error animation
tween(circle, {
tint: 0x333333,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
circle.destroy();
}
});
// Flash screen red
LK.effects.flashScreen(0xFF0000, 500);
// Update UI
comboTxt.setText('');
livesTxt.setText('Lives: ' + lives);
if (lives <= 0) {
gameOver();
}
}
// Remove from array
var index = circles.indexOf(circle);
if (index > -1) {
circles.splice(index, 1);
}
}
function nextLevel() {
level++;
circleSpeed += 0.5;
spawnInterval = Math.max(30, spawnInterval - 10); // Spawn faster
// Clear all circles
for (var i = circles.length - 1; i >= 0; i--) {
circles[i].destroy();
}
circles = [];
// Level up effects
LK.getSound('levelup').play();
LK.effects.flashScreen(0x4ECDC4, 1000);
// Update UI
levelTxt.setText('Level ' + level);
// Show level notification
var levelNotification = new Text2('Level ' + level + '!', {
size: 100,
fill: 0x4ECDC4
});
levelNotification.anchor.set(0.5, 0.5);
levelNotification.x = 1024;
levelNotification.y = 1366;
game.addChild(levelNotification);
tween(levelNotification, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
levelNotification.destroy();
}
});
}
function gameOver() {
gameRunning = false;
// Clear all circles
for (var i = circles.length - 1; i >= 0; i--) {
circles[i].destroy();
}
// Check for win condition (reached level 10)
if (level >= 10) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
// Game update loop
game.update = function () {
if (!gameRunning) return;
// Handle time slow effect
if (timeSlowActive) {
timeSlowDuration--;
if (timeSlowDuration <= 0) {
timeSlowActive = false;
}
}
// Spawn circles (affected by time slow)
spawnTimer++;
var effectiveSpawnInterval = timeSlowActive ? spawnInterval * 2 : spawnInterval;
if (spawnTimer >= effectiveSpawnInterval) {
spawnCircle();
spawnTimer = 0;
}
// Spawn hearts
heartSpawnTimer++;
if (heartSpawnTimer >= heartSpawnInterval) {
spawnHeart();
heartSpawnTimer = 0;
// Randomize next spawn interval (10-20 seconds)
heartSpawnInterval = 600 + Math.floor(Math.random() * 600);
}
// Check for collected hearts
for (var h = hearts.length - 1; h >= 0; h--) {
var heart = hearts[h];
if (heart.collected) {
// Add life
lives = Math.min(lives + 1, 5); // Cap at 5 lives
livesTxt.setText('Lives: ' + lives);
// Collection effect - golden flash instead of red
LK.effects.flashScreen(0xFFD700, 300);
LK.getSound('levelup').play();
// Create enhanced sparkle effect with red particles
for (var s = 0; s < 16; s++) {
var sparkle = game.addChild(new Container());
var sparkleGraphics = sparkle.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFF0000
});
sparkle.x = heartX;
sparkle.y = heartY;
var angle = Math.PI * 2 / 16 * s;
var distance = 150 + Math.random() * 100;
// Add initial scale pulse
tween(sparkle, {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(sparkle, {
x: heartX + Math.cos(angle) * distance,
y: heartY + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
sparkle.destroy();
}
});
}
});
}
// Store heart position for effects before removing it
var heartX = heart.x;
var heartY = heart.y;
// Remove heart from game immediately and destroy it
heart.destroy();
hearts.splice(h, 1);
// Special heart collection effect with red glow
// First create a red glow effect
var heartGlow = game.addChild(new Container());
var glowRing = heartGlow.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xFF0000,
alpha: 0.8
});
heartGlow.x = heartX;
heartGlow.y = heartY;
// Animate glow ring expanding
tween(heartGlow, {
scaleX: 4,
scaleY: 4,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
heartGlow.destroy();
}
});
}
}
// Despawn oldest circles if there are more than 8
while (circles.length > 8) {
var oldestCircle = circles.shift(); // Remove first (oldest) circle
oldestCircle.destroy();
}
// Check for tapped circles
for (var i = circles.length - 1; i >= 0; i--) {
var circle = circles[i];
if (circle.wasTapped) {
circle.wasTapped = false;
checkCircleTap(circle);
break; // Only process one tap per frame
}
// Remove circles that went off screen
if (circle.x < -100 || circle.x > 2148 || circle.y < -100 || circle.y > 2832) {
circles.splice(i, 1);
circle.destroy();
}
}
// Manage time slow projectiles
for (var p = timeSlowProjectiles.length - 1; p >= 0; p--) {
var projectile = timeSlowProjectiles[p];
// Remove projectiles that went off screen or were used
if (projectile.used || projectile.x < -100 || projectile.x > 2148 || projectile.y < -100 || projectile.y > 2832) {
timeSlowProjectiles.splice(p, 1);
if (!projectile.used) {
projectile.destroy();
}
}
}
// Smooth score counter animation
if (displayedScore < targetScore) {
var diff = targetScore - displayedScore;
var increment = Math.max(1, Math.floor(diff / 10));
displayedScore = Math.min(displayedScore + increment, targetScore);
scoreTxt.setText(displayedScore.toString());
// Add subtle scale effect while counting
var scaleFactor = 1 + diff / 1000 * 0.1;
scoreTxt.scale.set(Math.min(1.2, scaleFactor));
} else if (displayedScore > targetScore) {
displayedScore = targetScore;
scoreTxt.setText(displayedScore.toString());
scoreTxt.scale.set(1);
}
};
// Volume control variables
var currentVolume = storage.volume || 0.5;
var volumeSlider = null;
var volumeContainer = null;
var isPaused = false;
// Pause handler to show volume controls
LK.on('pause', function () {
isPaused = true;
// Create volume control container
volumeContainer = new Container();
LK.gui.center.addChild(volumeContainer);
// Background for volume controls
var volumeBg = volumeContainer.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 2,
tint: 0x222222,
alpha: 0.9
});
// Volume label
var volumeLabel = new Text2('VOLUME', {
size: 50,
fill: 0xFFFFFF
});
volumeLabel.anchor.set(0.5, 0.5);
volumeLabel.y = -50;
volumeContainer.addChild(volumeLabel);
// Volume slider background
var sliderBg = volumeContainer.attachAsset('targetRing', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.3,
tint: 0x444444
});
sliderBg.y = 20;
// Volume slider handle
volumeSlider = volumeContainer.attachAsset('circle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0xFFD700
});
volumeSlider.y = 20;
volumeSlider.x = (currentVolume - 0.5) * 300; // Position based on volume (0-1 maps to -150 to +150)
// Volume percentage display
var volumePercent = new Text2(Math.round(currentVolume * 100) + '%', {
size: 40,
fill: 0xFFD700
});
volumePercent.anchor.set(0.5, 0.5);
volumePercent.y = 80;
volumeContainer.addChild(volumePercent);
// Handle volume slider interaction
volumeSlider.down = function (x, y, obj) {
volumeSlider.isDragging = true;
};
volumeContainer.move = function (x, y, obj) {
if (volumeSlider && volumeSlider.isDragging) {
// Convert global position to local slider position
var localX = Math.max(-150, Math.min(150, x - volumeContainer.x));
volumeSlider.x = localX;
// Update volume (slider range -150 to +150 maps to volume 0 to 1)
currentVolume = Math.max(0, Math.min(1, (localX + 150) / 300));
// Update display
volumePercent.setText(Math.round(currentVolume * 100) + '%');
// Save to storage
storage.volume = currentVolume;
// Update music volume immediately
LK.stopMusic();
LK.playMusic('calmMusic', {
volume: currentVolume
});
}
};
volumeContainer.up = function (x, y, obj) {
if (volumeSlider) {
volumeSlider.isDragging = false;
}
};
});
// Resume handler to clean up volume controls
LK.on('resume', function () {
isPaused = false;
if (volumeContainer) {
volumeContainer.destroy();
volumeContainer = null;
volumeSlider = null;
}
});
// Create single time slow cannon on left side
var timeSlowCannon = game.addChild(new TimeSlowCannon());
timeSlowCannon.x = 200;
timeSlowCannon.y = 1366; // Center vertically
cannons.push(timeSlowCannon);
// Initialize game
setNewTargetColor();
// Play calm background music
LK.playMusic('calmMusic', {
volume: currentVolume
});