/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (targetX, targetY, startX, startY) {
var self = Container.call(this);
// Attach arrow asset
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate direction and speed
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Set base speed and apply power-up multiplier
var baseSpeed = 8;
var speed = baseSpeed;
if (extraPowerActive) speed *= 1.8;
if (speedBoostActive) speed *= 1.5;
// Cache velocity calculations
var velocityMultiplier = speed / distance;
self.velocityX = dx * velocityMultiplier;
self.velocityY = dy * velocityMultiplier;
// Set rotation to point toward target
arrowGraphics.rotation = Math.atan2(dy, dx);
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
self.x += self.velocityX;
self.y += self.velocityY;
// Remove arrow if it goes off screen (check this first for early exit)
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
return;
}
// Only check collisions every 6 frames to optimize performance while maintaining responsiveness
if (LK.ticks % 6 === 0) {
// Check collision with balls (optimized)
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
// Quick distance check before expensive intersection test
var dx = self.x - ball.x;
var dy = self.y - ball.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < ballCollisionRange) {
// 80px collision range - optimized
if (self.intersects(ball)) {
// Trigger ball hit
ball.hit();
self.destroy();
return;
}
}
}
// Check collision with bombs (optimized)
for (var j = bombs.length - 1; j >= 0; j--) {
var bomb = bombs[j];
// Quick distance check before expensive intersection test
var dx2 = self.x - bomb.x;
var dy2 = self.y - bomb.y;
var distanceSquared2 = dx2 * dx2 + dy2 * dy2;
if (distanceSquared2 < bombCollisionRange) {
// 80px collision range - optimized
if (self.intersects(bomb)) {
// Trigger bomb hit
bomb.hit();
self.destroy();
return;
}
}
}
}
};
return self;
});
var Ball = Container.expand(function (ballType) {
var self = Container.call(this);
// Store ball type and set properties based on type
self.ballType = ballType;
var assetId, points;
switch (ballType) {
case 'red':
assetId = 'redBall';
points = 30;
break;
case 'yellow':
assetId = 'yellowBall';
points = 30;
break;
case 'blue':
assetId = 'blueBall';
points = 30;
break;
}
self.points = points;
// Attach the appropriate ball asset
var ballGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply subtle scale effect instead of color tinting
var randomScale = 0.9 + Math.random() * 0.2; // Scale between 0.9 and 1.1
ballGraphics.scaleX = randomScale;
ballGraphics.scaleY = randomScale;
// Add floating animation
self.floatDirection = Math.random() > 0.5 ? 1 : -1;
self.floatSpeed = 0.5 + Math.random() * 0.5;
self.floatOffset = 0;
// Add horizontal movement
self.horizontalDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 4 + Math.random() * 3;
// Add vertical movement
self.verticalDirection = Math.random() > 0.5 ? 1 : -1;
self.verticalSpeed = 1.5 + Math.random() * 2;
// Initialize falling properties
self.isHit = false;
self.isFalling = false;
self.fallSpeed = 0;
self.gravity = 0.5;
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
// If ball is falling after being hit, apply gravity
if (self.isFalling) {
self.fallSpeed += self.gravity;
self.y += self.fallSpeed;
// Check if ball hits the ground (bottom of screen)
if (self.y >= 2600) {
// Simply destroy the ball without effects
self.destroy();
return;
}
} else {
// Normal ball movement when not hit
// Only update floating animation every 20 frames to reduce CPU load
if (LK.ticks % 20 === 0) {
self.floatOffset += self.floatSpeed;
self.y += Math.sin(self.floatOffset) * self.floatDirection * 0.2;
}
// Cache movement calculations for better performance
var horizontalMovement = self.horizontalSpeed * self.horizontalDirection;
var verticalMovement = self.verticalSpeed * self.verticalDirection;
// Add horizontal movement in both directions
self.x += horizontalMovement;
// Add vertical movement for diagonal motion
self.y += verticalMovement;
// Keep balls in upper area - restrict Y position for portrait screen
if (self.y > 1800) {
self.y = 1800;
self.verticalDirection = -1; // Bounce upward
}
if (self.y < 100) {
self.y = 100;
self.verticalDirection = 1; // Bounce downward
}
// Bounce off screen edges - keep balls in central area for portrait screen
if (self.x <= 200 || self.x >= 1848) {
self.horizontalDirection *= -1; // Reverse horizontal direction
if (self.x <= 200) self.x = 200;
if (self.x >= 1848) self.x = 1848;
}
}
};
self.hit = function () {
// Award points and remove ball
var basePoints = bowPointBonuses[bowLevel - 1]; // Use bow level bonus instead of default
var finalPoints = basePoints;
// Apply extra power bonus if active
if (extraPowerActive) {
finalPoints += 30;
}
var oldScore = LK.getScore();
LK.setScore(oldScore + finalPoints);
scoreTxt.setText(LK.getScore());
// Increment kill count
killCount++;
killTxt.setText('Kill: ' + killCount);
// Award gold coins
goldCoins += 10;
coinTxt.setText('Coin: ' + goldCoins);
// Add coin scatter effect
createCoinScatterEffect(self.x, self.y);
// Add colorful effect to coin text
tween.stop(coinTxt, {
tint: true,
scaleX: true,
scaleY: true
});
tween(coinTxt, {
tint: 0xFFD700
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(coinTxt, {
tint: 0xFFD700
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
// Add scale pulse effect to coin counter
coinTxt.scaleX = 1.3;
coinTxt.scaleY = 1.3;
tween(coinTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.bounceOut
});
// Add colorful effect to kill count
// Stop any existing tween on kill text
tween.stop(killTxt, {
tint: true,
scaleX: true,
scaleY: true
});
// Apply rainbow color effect based on kill count
var killEffectColor;
switch (killCount % 6) {
case 1:
killEffectColor = 0xFF0000;
break;
// Red
case 2:
killEffectColor = 0x00FF00;
break;
// Green
case 3:
killEffectColor = 0x0000FF;
break;
// Blue
case 4:
killEffectColor = 0xFFFF00;
break;
// Yellow
case 5:
killEffectColor = 0xFF00FF;
break;
// Magenta
case 0:
killEffectColor = 0x00FFFF;
break;
// Cyan
}
// Apply color tint effect to kill counter
tween(killTxt, {
tint: killEffectColor
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fade back to white
tween(killTxt, {
tint: 0xFFFFFF
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Add scale pulse effect to kill counter
killTxt.scaleX = 1.4;
killTxt.scaleY = 1.4;
tween(killTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.bounceOut
});
// Add colorful effect to score based on ball type
var effectColor;
switch (self.ballType) {
case 'red':
effectColor = 0xFF4444;
break;
case 'yellow':
effectColor = 0xFFFF44;
break;
case 'blue':
effectColor = 0x4444FF;
break;
}
// Stop any existing tween on score text
tween.stop(scoreTxt, {
tint: true,
scaleX: true,
scaleY: true
});
// Apply color tint effect
tween(scoreTxt, {
tint: effectColor
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fade back to white
tween(scoreTxt, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
// Add scale pulse effect
scoreTxt.scaleX = 1.3;
scoreTxt.scaleY = 1.3;
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.bounceOut
});
updateRankDisplay();
try {
LK.getSound('ballHit').play();
} catch (e) {
console.log('Ball hit sound failed to play');
}
// Check for extra life every 5000 points
var newScore = LK.getScore();
var oldLifeBonuses = Math.floor(oldScore / 5000);
var newLifeBonuses = Math.floor(newScore / 5000);
if (newLifeBonuses > oldLifeBonuses) {
lives++;
updateHeartsDisplay();
LK.effects.flashScreen(0x00FF00, 800); // Green flash for extra life
}
// Check for arrow speed boost every 10000 points
var oldSpeedBonuses = Math.floor(oldScore / 10000);
var newSpeedBonuses = Math.floor(newScore / 10000);
if (newSpeedBonuses > oldSpeedBonuses) {
// Activate speed boost for 1 minute
speedBoostActive = true;
speedBoostEndTime = remainingTime - 60; // 1 minute duration
LK.effects.flashScreen(0x0080FF, 800); // Blue flash for speed boost
}
// 1% chance to gain extra life on ball hit
if (Math.random() < 0.01) {
lives++;
updateHeartsDisplay();
LK.effects.flashScreen(0x00FF00, 800); // Green flash for extra life
}
// 6% chance to activate power-up
if (Math.random() < 0.06) {
// 50% chance for each power-up type
if (Math.random() < 0.5) {
// Activate extra power (+30 points)
extraPowerActive = true;
extraPowerEndTime = remainingTime - powerUpDuration;
} else {
// Activate dual shot
dualShotActive = true;
dualShotEndTime = remainingTime - powerUpDuration;
}
}
// Create tree bark merging effect before falling
createMergingEffect(self.x, self.y, 'ball');
// Mark ball as hit and start falling
self.isHit = true;
self.isFalling = true;
self.fallSpeed = 0;
self.gravity = 0.5;
// Remove from balls array
for (var i = balls.length - 1; i >= 0; i--) {
if (balls[i] === self) {
balls.splice(i, 1);
break;
}
}
// Spawn a new random ball at a random position for portrait screen
var randomBallType = ballTypes[Math.floor(Math.random() * ballTypes.length)];
var newBall = new Ball(randomBallType);
// Random position in the game area (avoiding edges) for portrait screen
newBall.x = 300 + Math.random() * 1448;
newBall.y = 300 + Math.random() * 1200;
balls.push(newBall);
game.addChild(newBall);
};
return self;
});
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Attach bomb asset
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply subtle alpha effect instead of color tinting
var randomAlpha = 0.85 + Math.random() * 0.15; // Alpha between 0.85 and 1.0
bombGraphics.alpha = randomAlpha;
// Add pulsing animation to make it look dangerous
self.pulseOffset = Math.random() * Math.PI * 2;
// Add horizontal movement
self.horizontalDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 4 + Math.random() * 3;
// Add vertical movement
self.verticalDirection = Math.random() > 0.5 ? 1 : -1;
self.verticalSpeed = 1.5 + Math.random() * 2;
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
// Only update pulsing animation every 15 frames to reduce CPU load
if (LK.ticks % 15 === 0) {
self.pulseOffset += 0.08;
var scale = 1 + Math.sin(self.pulseOffset) * 0.08;
bombGraphics.scaleX = scale;
bombGraphics.scaleY = scale;
}
// Cache movement calculations for better performance
var horizontalMovement = self.horizontalSpeed * self.horizontalDirection;
var verticalMovement = self.verticalSpeed * self.verticalDirection;
// Add horizontal movement in both directions
self.x += horizontalMovement;
// Add vertical movement for diagonal motion
self.y += verticalMovement;
// Keep bombs in upper area - restrict Y position for portrait screen
if (self.y > 1800) {
self.y = 1800;
self.verticalDirection = -1; // Bounce upward
}
if (self.y < 100) {
self.y = 100;
self.verticalDirection = 1; // Bounce downward
}
// Bounce off screen edges - keep bombs in central area for portrait screen
if (self.x <= 200 || self.x >= 1848) {
self.horizontalDirection *= -1; // Reverse horizontal direction
if (self.x <= 200) self.x = 200;
if (self.x >= 1848) self.x = 1848;
}
};
self.hit = function () {
// Play bomb sound effect with error handling
try {
LK.getSound('bombSound').play();
} catch (e) {
console.log('Bomb sound failed to play');
}
// Deduct points and lose a life for hitting bomb
LK.setScore(Math.max(0, LK.getScore() - 10));
scoreTxt.setText(LK.getScore());
updateRankDisplay();
lives--;
updateHeartsDisplay();
bombHitCount++;
// Create tree bark merging effect
createMergingEffect(self.x, self.y, 'bomb');
// Flash screen red to indicate penalty
LK.effects.flashScreen(0xff0000, 500);
// Check if 5 bombs hit - restart game
if (bombHitCount >= 5) {
LK.showGameOver();
return;
}
// Check game over
if (lives <= 0) {
LK.showGameOver();
return;
}
// Remove from bombs array
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i] === self) {
bombs.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var TreeBark = Container.expand(function (startX, startY, targetX, targetY) {
var self = Container.call(this);
// Attach tree bark asset
var barkGraphics = self.attachAsset('treeBark', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position
self.x = startX;
self.y = startY;
// Calculate movement direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Set speed and direction
self.speed = 6;
if (distance > 0) {
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
} else {
self.velocityX = 0;
self.velocityY = 0;
}
// Add spinning animation
barkGraphics.rotation = Math.random() * Math.PI * 2;
self.rotationSpeed = (Math.random() - 0.5) * 0.3;
// Set alpha and scale
barkGraphics.alpha = 0.8;
barkGraphics.scaleX = 0.8 + Math.random() * 0.4;
barkGraphics.scaleY = barkGraphics.scaleX;
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
// Move towards target
self.x += self.velocityX;
self.y += self.velocityY;
// Rotate
barkGraphics.rotation += self.rotationSpeed;
// Fade out over time
barkGraphics.alpha -= 0.01;
// Remove when fully transparent or off screen
if (barkGraphics.alpha <= 0 || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
// Game arrays to track objects
var game = new LK.Game({
backgroundColor: 0x0080FF
});
/****
* Game Code
****/
// Initialize ball assets with different colors
// Game arrays to track objects
var balls = [];
var bombs = [];
var lives = 5;
var hearts = [];
var bombHitCount = 0;
// Timer variables (30 minutes = 1800 seconds)
var totalGameTime = 1800; // 30 minutes in seconds
var remainingTime = totalGameTime;
var lastSecondTick = 0;
// Power-up system variables
var extraPowerActive = false;
var dualShotActive = false;
var speedBoostActive = false;
var extraPowerEndTime = 0;
var dualShotEndTime = 0;
var speedBoostEndTime = 0;
var powerUpDuration = 180; // 3 minutes in seconds
// Performance optimization: cached calculations
var screenCenterX = 1024;
var screenCenterY = 1366;
var maxHandDistance = 90;
var ballCollisionRange = 6400;
var bombCollisionRange = 6400;
// Object pooling for better performance
var arrowPool = [];
var maxArrows = 50;
var destroyedObjects = [];
// CS2 Rank System - 3 Categories
var rankThresholds = [{
name: 'Gümüş Seviyeler',
min: 0,
max: 4910,
color: 0x808080,
asset: 'rankSilver'
}, {
name: 'Altın Nova Seviyeler',
min: 4911,
max: 9734,
color: 0xFFD700,
asset: 'rankGoldNova'
}, {
name: 'Supreme Seviyeler',
min: 9735,
max: 999999,
color: 0xFF0000,
asset: 'rankSupreme'
}];
var currentRank = rankThresholds[0];
var rankIcon = null;
var rankNameTxt = null;
// Kill tracking system
var killCount = 0;
var killTxt = null;
// Currency system
var goldCoins = 0;
var coinTxt = null;
// Bow upgrade system
var bowLevel = 1;
var bowLevelTxt = null;
var bowUpgradeCosts = [200, 500, 750, 950, 1500]; // Costs for levels 1-5
var bowPointBonuses = [30, 50, 70, 90, 120]; // Points for levels 1-5
// Score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0);
scoreTxt.x = -20;
scoreTxt.y = 20;
LK.gui.topRight.addChild(scoreTxt);
// Timer display
var timerTxt = new Text2('30:00', {
size: 80,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 0;
timerTxt.y = 20;
LK.gui.top.addChild(timerTxt);
// Power-up display texts
var extraPowerTxt = new Text2('', {
size: 50,
fill: 0x00FF00
});
extraPowerTxt.anchor.set(1, 0);
extraPowerTxt.x = -20;
extraPowerTxt.y = 120;
LK.gui.topRight.addChild(extraPowerTxt);
var dualShotTxt = new Text2('', {
size: 50,
fill: 0x00FFFF
});
dualShotTxt.anchor.set(1, 0);
dualShotTxt.x = -20;
dualShotTxt.y = 180;
LK.gui.topRight.addChild(dualShotTxt);
var speedBoostTxt = new Text2('', {
size: 50,
fill: 0x0080FF
});
speedBoostTxt.anchor.set(1, 0);
speedBoostTxt.x = -20;
speedBoostTxt.y = 240;
LK.gui.topRight.addChild(speedBoostTxt);
// Initialize rank display in bottom left corner
rankNameTxt = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
rankNameTxt.anchor.set(0, 1);
rankNameTxt.x = 540;
rankNameTxt.y = 2712;
game.addChild(rankNameTxt);
// Initialize kill counter display next to rank
killTxt = new Text2('Kill: 0', {
size: 50,
fill: 0xFFFFFF
});
killTxt.anchor.set(0, 1);
killTxt.x = 540;
killTxt.y = 2670;
game.addChild(killTxt);
// Initialize gold coin display next to rank
coinTxt = new Text2('Coin: 0', {
size: 50,
fill: 0xFFD700
});
coinTxt.anchor.set(0, 1);
coinTxt.x = 540;
coinTxt.y = 2620;
game.addChild(coinTxt);
// Initialize bow level display below score
bowLevelTxt = new Text2('Yay Seviyesi: 1', {
size: 50,
fill: 0xFFFFFF
});
bowLevelTxt.anchor.set(1, 0);
bowLevelTxt.x = -20;
bowLevelTxt.y = 300;
bowLevelTxt.visible = true;
LK.gui.topRight.addChild(bowLevelTxt);
// Initialize bow level indicators (1, 2, 3, 4, 5) above rank
var bowLevelIndicators = [];
var bowUpgradeButtons = [];
var bowUpgradeLabels = [];
var bowUpgradePrices = [200, 500, 750, 950, 1500]; // Updated prices
for (var levelNum = 1; levelNum <= 5; levelNum++) {
// Create bow upgrade button text (Yay 1, Yay 2, etc)
var bowButton = new Text2('Yay ' + levelNum, {
size: 64,
fill: bowLevel >= levelNum ? 0x00FF00 : 0xFFFFFF
});
bowButton.anchor.set(0.5, 0.5);
bowButton.x = 425 + (levelNum - 1) * 200;
bowButton.y = 2360;
bowButton.levelNum = levelNum; // Store level number for reference
game.addChild(bowButton);
bowUpgradeButtons.push(bowButton);
// Create price label next to each button
var priceLabel = new Text2(bowUpgradePrices[levelNum - 1] + ' coin', {
size: 24,
fill: 0xFFD700
});
priceLabel.anchor.set(0.5, 0.5);
priceLabel.x = 425 + (levelNum - 1) * 200;
priceLabel.y = 2320;
priceLabel.levelNum = levelNum;
game.addChild(priceLabel);
bowUpgradeLabels.push(priceLabel);
// Create original level indicators below buttons
var levelIndicator = new Text2(levelNum.toString(), {
size: 40,
fill: levelNum === 1 ? 0x00FF00 : 0x666666 // Active level is green, inactive are gray
});
levelIndicator.anchor.set(0.5, 1);
levelIndicator.x = 270 + (levelNum - 3) * 50; // Center around rank icon with 50px spacing
levelIndicator.y = 2500; // Position below buttons
game.addChild(levelIndicator);
bowLevelIndicators.push(levelIndicator);
}
// Fire effects variables
var fireEffects = [];
// Merging system variables
var treeBarkParticles = [];
var mergingBalls = [];
var mergingBombs = [];
var mergeRange = 150;
// Function to add fire effects around rank icon
function addFireEffectsToRank() {
// Remove existing fire effects
if (fireEffects && fireEffects.length > 0) {
for (var i = 0; i < fireEffects.length; i++) {
if (fireEffects[i]) {
fireEffects[i].destroy();
}
}
}
fireEffects = [];
if (!rankIcon) return;
// Create fire particles around the rank icon
var numFireParticles = 1;
var iconCenterX = rankIcon.x + 250; // Center of rank icon
var iconCenterY = rankIcon.y - 250; // Center of rank icon
var radius = 280; // Distance from center
for (var i = 0; i < numFireParticles; i++) {
var angle = i / numFireParticles * Math.PI * 2;
var fireX = iconCenterX + Math.cos(angle) * radius;
var fireY = iconCenterY + Math.sin(angle) * radius;
// Create fire particle using a small red/orange shape
var fireParticle = LK.getAsset('redBall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
fireParticle.tint = 0xFF4400; // Orange-red color
fireParticle.x = fireX;
fireParticle.y = fireY;
fireParticle.alpha = 0.8;
fireParticle.baseX = fireX;
fireParticle.baseY = fireY;
fireParticle.animationOffset = i * 0.5; // Stagger animation
game.addChild(fireParticle);
fireEffects.push(fireParticle);
// Start flickering animation
animateFireParticle(fireParticle);
}
}
// Function to animate individual fire particle
function animateFireParticle(particle) {
if (!particle) return;
// Random flicker effect
var flickerDuration = 200 + Math.random() * 300;
var targetAlpha = 0.3 + Math.random() * 0.7;
var targetScale = 0.2 + Math.random() * 0.3;
var targetTint = Math.random() > 0.5 ? 0xFF4400 : 0xFF6600;
tween(particle, {
alpha: targetAlpha,
scaleX: targetScale,
scaleY: targetScale,
tint: targetTint
}, {
duration: flickerDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue animation loop
animateFireParticle(particle);
}
});
}
// Initialize rank display
updateRankDisplay();
// Function to get current rank based on score
function getCurrentRank(score) {
for (var i = rankThresholds.length - 1; i >= 0; i--) {
if (score >= rankThresholds[i].min) {
return rankThresholds[i];
}
}
return rankThresholds[0];
}
// Function to get rank level within current rank category
function getRankLevel(score, rank) {
var rangeSize = rank.max - rank.min + 1;
var relativeScore = score - rank.min;
var level = Math.floor(relativeScore / rangeSize * 10) + 1;
return Math.min(level, 10);
}
// Function to update rank display
function updateRankDisplay() {
var newRank = getCurrentRank(LK.getScore());
var rankLevel = getRankLevel(LK.getScore(), newRank);
// Always update rank display to ensure it's visible
currentRank = newRank;
// Remove old rank icon if exists
if (rankIcon) {
rankIcon.destroy();
}
// Create new rank icon in bottom left corner
rankIcon = LK.getAsset(currentRank.asset, {
anchorX: 0,
anchorY: 1
});
rankIcon.x = 20;
rankIcon.y = 2712;
game.addChild(rankIcon);
// Update rank name text
if (rankNameTxt) {
rankNameTxt.setText(currentRank.name + ' - Seviye ' + rankLevel);
rankNameTxt.tint = currentRank.color;
}
// Add fire effects around rank icon
addFireEffectsToRank();
// Flash screen with rank color when rank changes (except first time)
if (LK.getScore() > 0) {
LK.effects.flashScreen(currentRank.color, 1000);
}
}
// Initialize hearts display
function updateHeartsDisplay() {
// Clear existing hearts
for (var h = 0; h < hearts.length; h++) {
hearts[h].destroy();
}
hearts = [];
// Create new hearts based on current lives
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = -120 - i * 70;
heart.y = -120;
LK.gui.bottomRight.addChild(heart);
hearts.push(heart);
}
}
// Function to update bow level indicators
function updateBowLevelIndicators() {
for (var i = 0; i < bowLevelIndicators.length; i++) {
var indicator = bowLevelIndicators[i];
var levelNum = i + 1;
if (levelNum <= bowLevel) {
// Active level - green and larger
indicator.tint = 0x00FF00;
indicator.scaleX = 1.2;
indicator.scaleY = 1.2;
} else {
// Inactive level - gray and normal size
indicator.tint = 0x666666;
indicator.scaleX = 1.0;
indicator.scaleY = 1.0;
}
}
// Update bow upgrade buttons
for (var j = 0; j < bowUpgradeButtons.length; j++) {
var button = bowUpgradeButtons[j];
var label = bowUpgradeLabels[j];
var levelNum = j + 1;
if (bowLevel >= levelNum) {
// Already purchased - show as "Alındı" (Purchased)
button.setText('Yay ' + levelNum);
button.tint = 0x00FF00;
label.setText('Alındı');
label.tint = 0x00FF00;
} else if (bowLevel === levelNum - 1) {
// Next available upgrade
button.setText('Yay ' + levelNum);
button.tint = goldCoins >= bowUpgradePrices[levelNum - 1] ? 0xFFFFFF : 0x666666;
label.setText(bowUpgradePrices[levelNum - 1] + ' coin');
label.tint = goldCoins >= bowUpgradePrices[levelNum - 1] ? 0xFFD700 : 0x666666;
} else {
// Locked upgrade
button.setText('Yay ' + levelNum);
button.tint = 0x666666;
label.setText(bowUpgradePrices[levelNum - 1] + ' coin');
label.tint = 0x666666;
}
}
}
// Function to create coin scatter effect
function createCoinScatterEffect(centerX, centerY) {
// Create multiple coin particles scattered around the hit point
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var distance = 50 + Math.random() * 50;
var targetX = centerX + Math.cos(angle) * distance;
var targetY = centerY + Math.sin(angle) * distance;
// Create coin particle using yellow ball asset
var coinParticle = LK.getAsset('yellowBall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
coinParticle.tint = 0xFFD700; // Gold color
coinParticle.x = centerX;
coinParticle.y = centerY;
coinParticle.alpha = 0.9;
game.addChild(coinParticle);
// Animate coin to target position
tween(coinParticle, {
x: targetX,
y: targetY,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (coinParticle && !coinParticle.destroyed) {
coinParticle.destroy();
}
}
});
}
}
// Function to create tree bark merging effect
function createMergingEffect(centerX, centerY, objectType) {
// Find nearby objects to merge with
var nearbyObjects = [];
var color = objectType === 'ball' ? 0x8B4513 : 0x654321; // Brown colors
// Check for nearby balls
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
var distance = Math.sqrt((ball.x - centerX) * (ball.x - centerX) + (ball.y - centerY) * (ball.y - centerY));
if (distance < mergeRange && !ball.isHit) {
nearbyObjects.push(ball);
}
}
// Check for nearby bombs
for (var j = 0; j < bombs.length; j++) {
var bomb = bombs[j];
var distance2 = Math.sqrt((bomb.x - centerX) * (bomb.x - centerX) + (bomb.y - centerY) * (bomb.y - centerY));
if (distance2 < mergeRange) {
nearbyObjects.push(bomb);
}
}
// Create tree bark particles flowing between objects
for (var k = 0; k < nearbyObjects.length; k++) {
var target = nearbyObjects[k];
// Create multiple bark particles flowing from center to target
for (var p = 0; p < 3; p++) {
var bark = new TreeBark(centerX, centerY, target.x, target.y);
bark.tint = color;
treeBarkParticles.push(bark);
game.addChild(bark);
// Add slight delay for each particle
tween(bark, {
alpha: 0.9
}, {
duration: 100 + p * 50,
onFinish: function onFinish() {
// Create merging glow effect on target
if (target && !target.destroyed) {
tween(target, {
tint: color
}, {
duration: 300,
onFinish: function onFinish() {
tween(target, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
}
});
}
}
});
}
// Create reverse flow particles from target to center
for (var r = 0; r < 2; r++) {
var reverseBark = new TreeBark(target.x, target.y, centerX, centerY);
reverseBark.tint = color;
reverseBark.alpha = 0.6;
treeBarkParticles.push(reverseBark);
game.addChild(reverseBark);
}
}
// Create explosion of bark particles around the center
for (var e = 0; e < 8; e++) {
var angle = e / 8 * Math.PI * 2;
var explosionX = centerX + Math.cos(angle) * 100;
var explosionY = centerY + Math.sin(angle) * 100;
var explosionBark = new TreeBark(centerX, centerY, explosionX, explosionY);
explosionBark.tint = color;
explosionBark.alpha = 0.7;
treeBarkParticles.push(explosionBark);
game.addChild(explosionBark);
}
}
// Initialize hearts display
updateHeartsDisplay();
// Ball types for spawning
var ballTypes = ['red', 'yellow', 'blue'];
// Create initial grid of balls and bombs arranged for portrait layout
var currentX = 200;
var currentY = 150;
var itemsPerRow = 10;
var spacing = 150;
var itemCount = 0;
// Create equal amounts of balls and bombs in a grid
for (var row = 0; row < 10; row++) {
for (var col = 0; col < itemsPerRow; col++) {
var x = currentX + col * spacing;
var y = currentY + row * spacing;
if (itemCount % 6 === 5) {
// Every 6th item is a bomb
var bomb = new Bomb();
bomb.x = x;
bomb.y = y;
bombs.push(bomb);
game.addChild(bomb);
} else {
// Create balls in sequence: red, yellow, blue
var ballType = ballTypes[itemCount % 3];
var ball = new Ball(ballType);
ball.x = x;
ball.y = y;
balls.push(ball);
game.addChild(ball);
}
itemCount++;
}
}
// Create hand launcher at bottom center
var hand = game.addChild(LK.getAsset('hand', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
}));
hand.x = 1024;
hand.y = 2400;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
game.down = function (x, y, obj) {
// Don't allow shooting during countdown
if (countdownActive) return;
// Check if touch is on any bow upgrade button
for (var buttonIndex = 0; buttonIndex < bowUpgradeButtons.length; buttonIndex++) {
var button = bowUpgradeButtons[buttonIndex];
var buttonLevelNum = button.levelNum;
// Check if clicked on this button (within 100px range)
var buttonDistance = Math.sqrt((x - button.x) * (x - button.x) + (y - button.y) * (y - button.y));
if (buttonDistance < 100) {
// Check if this is the next available upgrade
if (bowLevel === buttonLevelNum - 1 && bowLevel < 5) {
var upgradeCost = bowUpgradeCosts[buttonLevelNum - 1];
if (goldCoins >= upgradeCost) {
// Deduct coins and upgrade bow
goldCoins -= upgradeCost;
bowLevel = buttonLevelNum;
// Update displays
coinTxt.setText('Coin: ' + goldCoins);
bowLevelTxt.setText('Yay Seviyesi: ' + bowLevel);
// Update bow level indicators
updateBowLevelIndicators();
// Add level up effect
tween.stop(bowLevelTxt, {
tint: true,
scaleX: true,
scaleY: true
});
// Rainbow color effect for level up
var levelUpColor;
switch (bowLevel) {
case 1:
levelUpColor = 0xFFFFFF;
break;
case 2:
levelUpColor = 0x00FF00;
break;
// Green
case 3:
levelUpColor = 0x0080FF;
break;
// Blue
case 4:
levelUpColor = 0xFF8000;
break;
// Orange
case 5:
levelUpColor = 0xFF0080;
break;
// Pink
default:
levelUpColor = 0xFFFFFF;
break;
}
bowLevelTxt.scaleX = 1.5;
bowLevelTxt.scaleY = 1.5;
tween(bowLevelTxt, {
tint: levelUpColor,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(bowLevelTxt, {
tint: 0x00FF00
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
// Flash screen with upgrade color
LK.effects.flashScreen(levelUpColor, 600);
// Add special effect to the upgraded button
tween(button, {
scaleX: 1.3,
scaleY: 1.3,
tint: levelUpColor
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(button, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0x00FF00
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
return;
}
}
break; // Exit the loop once we found a button click
}
}
// Check if touch is near the hand
var distance = Math.sqrt((x - hand.x) * (x - hand.x) + (y - hand.y) * (y - hand.y));
if (distance < 100) {
isDragging = true;
dragStartX = x;
dragStartY = y;
}
};
game.move = function (x, y, obj) {
if (isDragging) {
// Move hand slightly toward drag direction
var dx = x - dragStartX;
var dy = y - dragStartY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
// Limit movement using cached values for better performance
var targetX = screenCenterX + dx * 0.3;
var targetY = 2400 + dy * 0.3;
var distanceFromCenter = Math.sqrt((targetX - screenCenterX) * (targetX - screenCenterX) + (targetY - 2400) * (targetY - 2400));
if (distanceFromCenter > maxHandDistance) {
var ratio = maxHandDistance / distanceFromCenter;
targetX = screenCenterX + (targetX - screenCenterX) * ratio;
targetY = 2400 + (targetY - 2400) * ratio;
}
hand.x = targetX;
hand.y = targetY;
// Calculate rotation angle based on drag direction
var targetRotation = Math.atan2(dy, dx);
// Smooth rotation using tween
tween.stop(hand, {
rotation: true
});
tween(hand, {
rotation: targetRotation
}, {
duration: 200,
easing: tween.easeOut
});
}
}
};
game.up = function (x, y, obj) {
if (isDragging) {
// Calculate launch direction (upward from hand position)
var dx = x - hand.x;
var dy = y - hand.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 20) {
// Create and launch arrow toward touch point
var arrow = new Arrow(x, y, hand.x, hand.y);
arrow.x = hand.x;
arrow.y = hand.y;
game.addChild(arrow);
// If dual shot is active, create a second arrow with slight offset
if (dualShotActive) {
var arrow2 = new Arrow(x + 50, y, hand.x, hand.y);
arrow2.x = hand.x;
arrow2.y = hand.y;
game.addChild(arrow2);
}
}
// Reset hand position and rotation
hand.x = 1024;
hand.y = 2400;
// Smooth rotation back to neutral position
tween.stop(hand, {
rotation: true
});
tween(hand, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
isDragging = false;
}
};
game.update = function () {
// Timer logic - update every second (60 ticks = 1 second at 60 FPS)
if (LK.ticks - lastSecondTick >= 60) {
remainingTime--;
lastSecondTick = LK.ticks;
// Format and display time as MM:SS with optimized string creation
var minutes = Math.floor(remainingTime / 60);
var seconds = remainingTime % 60;
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeString);
// Check if time is up
if (remainingTime <= 0) {
LK.showYouWin(); // Player survives 30 minutes = win condition
return;
}
// Change timer color when less than 5 minutes remain (warning)
if (remainingTime <= 300) {
// 5 minutes = 300 seconds
timerTxt.tint = 0xFF0000; // Red color for urgency
} else if (remainingTime <= 600) {
// 10 minutes = 600 seconds
timerTxt.tint = 0xFFFF00; // Yellow color for caution
}
}
// Check and update power-up timers
if (extraPowerActive && remainingTime <= extraPowerEndTime) {
extraPowerActive = false;
extraPowerTxt.setText('');
}
if (dualShotActive && remainingTime <= dualShotEndTime) {
dualShotActive = false;
dualShotTxt.setText('');
}
if (speedBoostActive && remainingTime <= speedBoostEndTime) {
speedBoostActive = false;
speedBoostTxt.setText('');
}
// Update power-up display texts only when needed
if (extraPowerActive) {
var extraTimeLeft = extraPowerEndTime - remainingTime;
var extraMinutes = Math.floor(Math.abs(extraTimeLeft) / 60);
var extraSeconds = Math.abs(extraTimeLeft) % 60;
var extraTimeString = extraMinutes + ':' + (extraSeconds < 10 ? '0' : '') + extraSeconds;
extraPowerTxt.setText('Güç +30: ' + extraTimeString);
}
if (dualShotActive) {
var dualTimeLeft = dualShotEndTime - remainingTime;
var dualMinutes = Math.floor(Math.abs(dualTimeLeft) / 60);
var dualSeconds = Math.abs(dualTimeLeft) % 60;
var dualTimeString = dualMinutes + ':' + (dualSeconds < 10 ? '0' : '') + dualSeconds;
dualShotTxt.setText('Çift Ok: ' + dualTimeString);
}
if (speedBoostActive) {
var speedTimeLeft = speedBoostEndTime - remainingTime;
var speedMinutes = Math.floor(Math.abs(speedTimeLeft) / 60);
var speedSeconds = Math.abs(speedTimeLeft) % 60;
var speedTimeString = speedMinutes + ':' + (speedSeconds < 10 ? '0' : '') + speedSeconds;
speedBoostTxt.setText('Hız Artışı: ' + speedTimeString);
}
// Clean up destroyed tree bark particles and other objects every 10 frames
if (LK.ticks % 10 === 0) {
for (var b = treeBarkParticles.length - 1; b >= 0; b--) {
var bark = treeBarkParticles[b];
if (!bark || bark.destroyed) {
treeBarkParticles.splice(b, 1);
}
}
// Clean up any other destroyed objects
destroyedObjects = [];
}
// Update fire effects animation every 30 frames for better performance
if (LK.ticks % 30 === 0) {
for (var i = 0; i < fireEffects.length; i++) {
var fire = fireEffects[i];
if (fire && fire.baseX !== undefined && fire.baseY !== undefined) {
// Add slight floating movement
fire.x = fire.baseX + Math.sin((LK.ticks + fire.animationOffset) * 0.04) * 8;
fire.y = fire.baseY + Math.cos((LK.ticks + fire.animationOffset) * 0.025) * 6;
}
}
}
// Score is updated only when it changes in Ball.hit() and Bomb.hit()
// No need for redundant updates here
};
// Game state variables
var gameStarted = false;
var countdownActive = true;
var countdownValue = 5;
var countdownTxt = new Text2('5', {
size: 400,
fill: 0x000000
});
countdownTxt.anchor.set(0.5, 0.5);
countdownTxt.x = 1024;
countdownTxt.y = 1366;
game.addChild(countdownTxt);
// Countdown logic
var countdownTimer = LK.setInterval(function () {
countdownValue--;
if (countdownValue > 0) {
countdownTxt.setText(countdownValue.toString());
// Scale animation for countdown
countdownTxt.scaleX = 1.5;
countdownTxt.scaleY = 1.5;
tween(countdownTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut
});
} else {
countdownTxt.setText('BAŞLA!');
// Final animation
tween(countdownTxt, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownTxt.destroy();
gameStarted = true;
countdownActive = false;
}
});
LK.clearInterval(countdownTimer);
}
}, 1000);
// Start background music immediately when game loads
try {
LK.playMusic('backgroundMusic', {
loop: true,
fade: {
start: 0,
end: 0.15,
duration: 1000
}
});
} catch (e) {
console.log('Background music failed to play:', e);
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (targetX, targetY, startX, startY) {
var self = Container.call(this);
// Attach arrow asset
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
// Calculate direction and speed
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Set base speed and apply power-up multiplier
var baseSpeed = 8;
var speed = baseSpeed;
if (extraPowerActive) speed *= 1.8;
if (speedBoostActive) speed *= 1.5;
// Cache velocity calculations
var velocityMultiplier = speed / distance;
self.velocityX = dx * velocityMultiplier;
self.velocityY = dy * velocityMultiplier;
// Set rotation to point toward target
arrowGraphics.rotation = Math.atan2(dy, dx);
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
self.x += self.velocityX;
self.y += self.velocityY;
// Remove arrow if it goes off screen (check this first for early exit)
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
return;
}
// Only check collisions every 6 frames to optimize performance while maintaining responsiveness
if (LK.ticks % 6 === 0) {
// Check collision with balls (optimized)
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
// Quick distance check before expensive intersection test
var dx = self.x - ball.x;
var dy = self.y - ball.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < ballCollisionRange) {
// 80px collision range - optimized
if (self.intersects(ball)) {
// Trigger ball hit
ball.hit();
self.destroy();
return;
}
}
}
// Check collision with bombs (optimized)
for (var j = bombs.length - 1; j >= 0; j--) {
var bomb = bombs[j];
// Quick distance check before expensive intersection test
var dx2 = self.x - bomb.x;
var dy2 = self.y - bomb.y;
var distanceSquared2 = dx2 * dx2 + dy2 * dy2;
if (distanceSquared2 < bombCollisionRange) {
// 80px collision range - optimized
if (self.intersects(bomb)) {
// Trigger bomb hit
bomb.hit();
self.destroy();
return;
}
}
}
}
};
return self;
});
var Ball = Container.expand(function (ballType) {
var self = Container.call(this);
// Store ball type and set properties based on type
self.ballType = ballType;
var assetId, points;
switch (ballType) {
case 'red':
assetId = 'redBall';
points = 30;
break;
case 'yellow':
assetId = 'yellowBall';
points = 30;
break;
case 'blue':
assetId = 'blueBall';
points = 30;
break;
}
self.points = points;
// Attach the appropriate ball asset
var ballGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply subtle scale effect instead of color tinting
var randomScale = 0.9 + Math.random() * 0.2; // Scale between 0.9 and 1.1
ballGraphics.scaleX = randomScale;
ballGraphics.scaleY = randomScale;
// Add floating animation
self.floatDirection = Math.random() > 0.5 ? 1 : -1;
self.floatSpeed = 0.5 + Math.random() * 0.5;
self.floatOffset = 0;
// Add horizontal movement
self.horizontalDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 4 + Math.random() * 3;
// Add vertical movement
self.verticalDirection = Math.random() > 0.5 ? 1 : -1;
self.verticalSpeed = 1.5 + Math.random() * 2;
// Initialize falling properties
self.isHit = false;
self.isFalling = false;
self.fallSpeed = 0;
self.gravity = 0.5;
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
// If ball is falling after being hit, apply gravity
if (self.isFalling) {
self.fallSpeed += self.gravity;
self.y += self.fallSpeed;
// Check if ball hits the ground (bottom of screen)
if (self.y >= 2600) {
// Simply destroy the ball without effects
self.destroy();
return;
}
} else {
// Normal ball movement when not hit
// Only update floating animation every 20 frames to reduce CPU load
if (LK.ticks % 20 === 0) {
self.floatOffset += self.floatSpeed;
self.y += Math.sin(self.floatOffset) * self.floatDirection * 0.2;
}
// Cache movement calculations for better performance
var horizontalMovement = self.horizontalSpeed * self.horizontalDirection;
var verticalMovement = self.verticalSpeed * self.verticalDirection;
// Add horizontal movement in both directions
self.x += horizontalMovement;
// Add vertical movement for diagonal motion
self.y += verticalMovement;
// Keep balls in upper area - restrict Y position for portrait screen
if (self.y > 1800) {
self.y = 1800;
self.verticalDirection = -1; // Bounce upward
}
if (self.y < 100) {
self.y = 100;
self.verticalDirection = 1; // Bounce downward
}
// Bounce off screen edges - keep balls in central area for portrait screen
if (self.x <= 200 || self.x >= 1848) {
self.horizontalDirection *= -1; // Reverse horizontal direction
if (self.x <= 200) self.x = 200;
if (self.x >= 1848) self.x = 1848;
}
}
};
self.hit = function () {
// Award points and remove ball
var basePoints = bowPointBonuses[bowLevel - 1]; // Use bow level bonus instead of default
var finalPoints = basePoints;
// Apply extra power bonus if active
if (extraPowerActive) {
finalPoints += 30;
}
var oldScore = LK.getScore();
LK.setScore(oldScore + finalPoints);
scoreTxt.setText(LK.getScore());
// Increment kill count
killCount++;
killTxt.setText('Kill: ' + killCount);
// Award gold coins
goldCoins += 10;
coinTxt.setText('Coin: ' + goldCoins);
// Add coin scatter effect
createCoinScatterEffect(self.x, self.y);
// Add colorful effect to coin text
tween.stop(coinTxt, {
tint: true,
scaleX: true,
scaleY: true
});
tween(coinTxt, {
tint: 0xFFD700
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(coinTxt, {
tint: 0xFFD700
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
// Add scale pulse effect to coin counter
coinTxt.scaleX = 1.3;
coinTxt.scaleY = 1.3;
tween(coinTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.bounceOut
});
// Add colorful effect to kill count
// Stop any existing tween on kill text
tween.stop(killTxt, {
tint: true,
scaleX: true,
scaleY: true
});
// Apply rainbow color effect based on kill count
var killEffectColor;
switch (killCount % 6) {
case 1:
killEffectColor = 0xFF0000;
break;
// Red
case 2:
killEffectColor = 0x00FF00;
break;
// Green
case 3:
killEffectColor = 0x0000FF;
break;
// Blue
case 4:
killEffectColor = 0xFFFF00;
break;
// Yellow
case 5:
killEffectColor = 0xFF00FF;
break;
// Magenta
case 0:
killEffectColor = 0x00FFFF;
break;
// Cyan
}
// Apply color tint effect to kill counter
tween(killTxt, {
tint: killEffectColor
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fade back to white
tween(killTxt, {
tint: 0xFFFFFF
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
// Add scale pulse effect to kill counter
killTxt.scaleX = 1.4;
killTxt.scaleY = 1.4;
tween(killTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.bounceOut
});
// Add colorful effect to score based on ball type
var effectColor;
switch (self.ballType) {
case 'red':
effectColor = 0xFF4444;
break;
case 'yellow':
effectColor = 0xFFFF44;
break;
case 'blue':
effectColor = 0x4444FF;
break;
}
// Stop any existing tween on score text
tween.stop(scoreTxt, {
tint: true,
scaleX: true,
scaleY: true
});
// Apply color tint effect
tween(scoreTxt, {
tint: effectColor
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fade back to white
tween(scoreTxt, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
// Add scale pulse effect
scoreTxt.scaleX = 1.3;
scoreTxt.scaleY = 1.3;
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.bounceOut
});
updateRankDisplay();
try {
LK.getSound('ballHit').play();
} catch (e) {
console.log('Ball hit sound failed to play');
}
// Check for extra life every 5000 points
var newScore = LK.getScore();
var oldLifeBonuses = Math.floor(oldScore / 5000);
var newLifeBonuses = Math.floor(newScore / 5000);
if (newLifeBonuses > oldLifeBonuses) {
lives++;
updateHeartsDisplay();
LK.effects.flashScreen(0x00FF00, 800); // Green flash for extra life
}
// Check for arrow speed boost every 10000 points
var oldSpeedBonuses = Math.floor(oldScore / 10000);
var newSpeedBonuses = Math.floor(newScore / 10000);
if (newSpeedBonuses > oldSpeedBonuses) {
// Activate speed boost for 1 minute
speedBoostActive = true;
speedBoostEndTime = remainingTime - 60; // 1 minute duration
LK.effects.flashScreen(0x0080FF, 800); // Blue flash for speed boost
}
// 1% chance to gain extra life on ball hit
if (Math.random() < 0.01) {
lives++;
updateHeartsDisplay();
LK.effects.flashScreen(0x00FF00, 800); // Green flash for extra life
}
// 6% chance to activate power-up
if (Math.random() < 0.06) {
// 50% chance for each power-up type
if (Math.random() < 0.5) {
// Activate extra power (+30 points)
extraPowerActive = true;
extraPowerEndTime = remainingTime - powerUpDuration;
} else {
// Activate dual shot
dualShotActive = true;
dualShotEndTime = remainingTime - powerUpDuration;
}
}
// Create tree bark merging effect before falling
createMergingEffect(self.x, self.y, 'ball');
// Mark ball as hit and start falling
self.isHit = true;
self.isFalling = true;
self.fallSpeed = 0;
self.gravity = 0.5;
// Remove from balls array
for (var i = balls.length - 1; i >= 0; i--) {
if (balls[i] === self) {
balls.splice(i, 1);
break;
}
}
// Spawn a new random ball at a random position for portrait screen
var randomBallType = ballTypes[Math.floor(Math.random() * ballTypes.length)];
var newBall = new Ball(randomBallType);
// Random position in the game area (avoiding edges) for portrait screen
newBall.x = 300 + Math.random() * 1448;
newBall.y = 300 + Math.random() * 1200;
balls.push(newBall);
game.addChild(newBall);
};
return self;
});
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Attach bomb asset
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply subtle alpha effect instead of color tinting
var randomAlpha = 0.85 + Math.random() * 0.15; // Alpha between 0.85 and 1.0
bombGraphics.alpha = randomAlpha;
// Add pulsing animation to make it look dangerous
self.pulseOffset = Math.random() * Math.PI * 2;
// Add horizontal movement
self.horizontalDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 4 + Math.random() * 3;
// Add vertical movement
self.verticalDirection = Math.random() > 0.5 ? 1 : -1;
self.verticalSpeed = 1.5 + Math.random() * 2;
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
// Only update pulsing animation every 15 frames to reduce CPU load
if (LK.ticks % 15 === 0) {
self.pulseOffset += 0.08;
var scale = 1 + Math.sin(self.pulseOffset) * 0.08;
bombGraphics.scaleX = scale;
bombGraphics.scaleY = scale;
}
// Cache movement calculations for better performance
var horizontalMovement = self.horizontalSpeed * self.horizontalDirection;
var verticalMovement = self.verticalSpeed * self.verticalDirection;
// Add horizontal movement in both directions
self.x += horizontalMovement;
// Add vertical movement for diagonal motion
self.y += verticalMovement;
// Keep bombs in upper area - restrict Y position for portrait screen
if (self.y > 1800) {
self.y = 1800;
self.verticalDirection = -1; // Bounce upward
}
if (self.y < 100) {
self.y = 100;
self.verticalDirection = 1; // Bounce downward
}
// Bounce off screen edges - keep bombs in central area for portrait screen
if (self.x <= 200 || self.x >= 1848) {
self.horizontalDirection *= -1; // Reverse horizontal direction
if (self.x <= 200) self.x = 200;
if (self.x >= 1848) self.x = 1848;
}
};
self.hit = function () {
// Play bomb sound effect with error handling
try {
LK.getSound('bombSound').play();
} catch (e) {
console.log('Bomb sound failed to play');
}
// Deduct points and lose a life for hitting bomb
LK.setScore(Math.max(0, LK.getScore() - 10));
scoreTxt.setText(LK.getScore());
updateRankDisplay();
lives--;
updateHeartsDisplay();
bombHitCount++;
// Create tree bark merging effect
createMergingEffect(self.x, self.y, 'bomb');
// Flash screen red to indicate penalty
LK.effects.flashScreen(0xff0000, 500);
// Check if 5 bombs hit - restart game
if (bombHitCount >= 5) {
LK.showGameOver();
return;
}
// Check game over
if (lives <= 0) {
LK.showGameOver();
return;
}
// Remove from bombs array
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i] === self) {
bombs.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var TreeBark = Container.expand(function (startX, startY, targetX, targetY) {
var self = Container.call(this);
// Attach tree bark asset
var barkGraphics = self.attachAsset('treeBark', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position
self.x = startX;
self.y = startY;
// Calculate movement direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Set speed and direction
self.speed = 6;
if (distance > 0) {
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
} else {
self.velocityX = 0;
self.velocityY = 0;
}
// Add spinning animation
barkGraphics.rotation = Math.random() * Math.PI * 2;
self.rotationSpeed = (Math.random() - 0.5) * 0.3;
// Set alpha and scale
barkGraphics.alpha = 0.8;
barkGraphics.scaleX = 0.8 + Math.random() * 0.4;
barkGraphics.scaleY = barkGraphics.scaleX;
self.update = function () {
// Don't update if countdown is active
if (countdownActive) return;
// Move towards target
self.x += self.velocityX;
self.y += self.velocityY;
// Rotate
barkGraphics.rotation += self.rotationSpeed;
// Fade out over time
barkGraphics.alpha -= 0.01;
// Remove when fully transparent or off screen
if (barkGraphics.alpha <= 0 || self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
// Game arrays to track objects
var game = new LK.Game({
backgroundColor: 0x0080FF
});
/****
* Game Code
****/
// Initialize ball assets with different colors
// Game arrays to track objects
var balls = [];
var bombs = [];
var lives = 5;
var hearts = [];
var bombHitCount = 0;
// Timer variables (30 minutes = 1800 seconds)
var totalGameTime = 1800; // 30 minutes in seconds
var remainingTime = totalGameTime;
var lastSecondTick = 0;
// Power-up system variables
var extraPowerActive = false;
var dualShotActive = false;
var speedBoostActive = false;
var extraPowerEndTime = 0;
var dualShotEndTime = 0;
var speedBoostEndTime = 0;
var powerUpDuration = 180; // 3 minutes in seconds
// Performance optimization: cached calculations
var screenCenterX = 1024;
var screenCenterY = 1366;
var maxHandDistance = 90;
var ballCollisionRange = 6400;
var bombCollisionRange = 6400;
// Object pooling for better performance
var arrowPool = [];
var maxArrows = 50;
var destroyedObjects = [];
// CS2 Rank System - 3 Categories
var rankThresholds = [{
name: 'Gümüş Seviyeler',
min: 0,
max: 4910,
color: 0x808080,
asset: 'rankSilver'
}, {
name: 'Altın Nova Seviyeler',
min: 4911,
max: 9734,
color: 0xFFD700,
asset: 'rankGoldNova'
}, {
name: 'Supreme Seviyeler',
min: 9735,
max: 999999,
color: 0xFF0000,
asset: 'rankSupreme'
}];
var currentRank = rankThresholds[0];
var rankIcon = null;
var rankNameTxt = null;
// Kill tracking system
var killCount = 0;
var killTxt = null;
// Currency system
var goldCoins = 0;
var coinTxt = null;
// Bow upgrade system
var bowLevel = 1;
var bowLevelTxt = null;
var bowUpgradeCosts = [200, 500, 750, 950, 1500]; // Costs for levels 1-5
var bowPointBonuses = [30, 50, 70, 90, 120]; // Points for levels 1-5
// Score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0);
scoreTxt.x = -20;
scoreTxt.y = 20;
LK.gui.topRight.addChild(scoreTxt);
// Timer display
var timerTxt = new Text2('30:00', {
size: 80,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 0;
timerTxt.y = 20;
LK.gui.top.addChild(timerTxt);
// Power-up display texts
var extraPowerTxt = new Text2('', {
size: 50,
fill: 0x00FF00
});
extraPowerTxt.anchor.set(1, 0);
extraPowerTxt.x = -20;
extraPowerTxt.y = 120;
LK.gui.topRight.addChild(extraPowerTxt);
var dualShotTxt = new Text2('', {
size: 50,
fill: 0x00FFFF
});
dualShotTxt.anchor.set(1, 0);
dualShotTxt.x = -20;
dualShotTxt.y = 180;
LK.gui.topRight.addChild(dualShotTxt);
var speedBoostTxt = new Text2('', {
size: 50,
fill: 0x0080FF
});
speedBoostTxt.anchor.set(1, 0);
speedBoostTxt.x = -20;
speedBoostTxt.y = 240;
LK.gui.topRight.addChild(speedBoostTxt);
// Initialize rank display in bottom left corner
rankNameTxt = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
rankNameTxt.anchor.set(0, 1);
rankNameTxt.x = 540;
rankNameTxt.y = 2712;
game.addChild(rankNameTxt);
// Initialize kill counter display next to rank
killTxt = new Text2('Kill: 0', {
size: 50,
fill: 0xFFFFFF
});
killTxt.anchor.set(0, 1);
killTxt.x = 540;
killTxt.y = 2670;
game.addChild(killTxt);
// Initialize gold coin display next to rank
coinTxt = new Text2('Coin: 0', {
size: 50,
fill: 0xFFD700
});
coinTxt.anchor.set(0, 1);
coinTxt.x = 540;
coinTxt.y = 2620;
game.addChild(coinTxt);
// Initialize bow level display below score
bowLevelTxt = new Text2('Yay Seviyesi: 1', {
size: 50,
fill: 0xFFFFFF
});
bowLevelTxt.anchor.set(1, 0);
bowLevelTxt.x = -20;
bowLevelTxt.y = 300;
bowLevelTxt.visible = true;
LK.gui.topRight.addChild(bowLevelTxt);
// Initialize bow level indicators (1, 2, 3, 4, 5) above rank
var bowLevelIndicators = [];
var bowUpgradeButtons = [];
var bowUpgradeLabels = [];
var bowUpgradePrices = [200, 500, 750, 950, 1500]; // Updated prices
for (var levelNum = 1; levelNum <= 5; levelNum++) {
// Create bow upgrade button text (Yay 1, Yay 2, etc)
var bowButton = new Text2('Yay ' + levelNum, {
size: 64,
fill: bowLevel >= levelNum ? 0x00FF00 : 0xFFFFFF
});
bowButton.anchor.set(0.5, 0.5);
bowButton.x = 425 + (levelNum - 1) * 200;
bowButton.y = 2360;
bowButton.levelNum = levelNum; // Store level number for reference
game.addChild(bowButton);
bowUpgradeButtons.push(bowButton);
// Create price label next to each button
var priceLabel = new Text2(bowUpgradePrices[levelNum - 1] + ' coin', {
size: 24,
fill: 0xFFD700
});
priceLabel.anchor.set(0.5, 0.5);
priceLabel.x = 425 + (levelNum - 1) * 200;
priceLabel.y = 2320;
priceLabel.levelNum = levelNum;
game.addChild(priceLabel);
bowUpgradeLabels.push(priceLabel);
// Create original level indicators below buttons
var levelIndicator = new Text2(levelNum.toString(), {
size: 40,
fill: levelNum === 1 ? 0x00FF00 : 0x666666 // Active level is green, inactive are gray
});
levelIndicator.anchor.set(0.5, 1);
levelIndicator.x = 270 + (levelNum - 3) * 50; // Center around rank icon with 50px spacing
levelIndicator.y = 2500; // Position below buttons
game.addChild(levelIndicator);
bowLevelIndicators.push(levelIndicator);
}
// Fire effects variables
var fireEffects = [];
// Merging system variables
var treeBarkParticles = [];
var mergingBalls = [];
var mergingBombs = [];
var mergeRange = 150;
// Function to add fire effects around rank icon
function addFireEffectsToRank() {
// Remove existing fire effects
if (fireEffects && fireEffects.length > 0) {
for (var i = 0; i < fireEffects.length; i++) {
if (fireEffects[i]) {
fireEffects[i].destroy();
}
}
}
fireEffects = [];
if (!rankIcon) return;
// Create fire particles around the rank icon
var numFireParticles = 1;
var iconCenterX = rankIcon.x + 250; // Center of rank icon
var iconCenterY = rankIcon.y - 250; // Center of rank icon
var radius = 280; // Distance from center
for (var i = 0; i < numFireParticles; i++) {
var angle = i / numFireParticles * Math.PI * 2;
var fireX = iconCenterX + Math.cos(angle) * radius;
var fireY = iconCenterY + Math.sin(angle) * radius;
// Create fire particle using a small red/orange shape
var fireParticle = LK.getAsset('redBall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
fireParticle.tint = 0xFF4400; // Orange-red color
fireParticle.x = fireX;
fireParticle.y = fireY;
fireParticle.alpha = 0.8;
fireParticle.baseX = fireX;
fireParticle.baseY = fireY;
fireParticle.animationOffset = i * 0.5; // Stagger animation
game.addChild(fireParticle);
fireEffects.push(fireParticle);
// Start flickering animation
animateFireParticle(fireParticle);
}
}
// Function to animate individual fire particle
function animateFireParticle(particle) {
if (!particle) return;
// Random flicker effect
var flickerDuration = 200 + Math.random() * 300;
var targetAlpha = 0.3 + Math.random() * 0.7;
var targetScale = 0.2 + Math.random() * 0.3;
var targetTint = Math.random() > 0.5 ? 0xFF4400 : 0xFF6600;
tween(particle, {
alpha: targetAlpha,
scaleX: targetScale,
scaleY: targetScale,
tint: targetTint
}, {
duration: flickerDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue animation loop
animateFireParticle(particle);
}
});
}
// Initialize rank display
updateRankDisplay();
// Function to get current rank based on score
function getCurrentRank(score) {
for (var i = rankThresholds.length - 1; i >= 0; i--) {
if (score >= rankThresholds[i].min) {
return rankThresholds[i];
}
}
return rankThresholds[0];
}
// Function to get rank level within current rank category
function getRankLevel(score, rank) {
var rangeSize = rank.max - rank.min + 1;
var relativeScore = score - rank.min;
var level = Math.floor(relativeScore / rangeSize * 10) + 1;
return Math.min(level, 10);
}
// Function to update rank display
function updateRankDisplay() {
var newRank = getCurrentRank(LK.getScore());
var rankLevel = getRankLevel(LK.getScore(), newRank);
// Always update rank display to ensure it's visible
currentRank = newRank;
// Remove old rank icon if exists
if (rankIcon) {
rankIcon.destroy();
}
// Create new rank icon in bottom left corner
rankIcon = LK.getAsset(currentRank.asset, {
anchorX: 0,
anchorY: 1
});
rankIcon.x = 20;
rankIcon.y = 2712;
game.addChild(rankIcon);
// Update rank name text
if (rankNameTxt) {
rankNameTxt.setText(currentRank.name + ' - Seviye ' + rankLevel);
rankNameTxt.tint = currentRank.color;
}
// Add fire effects around rank icon
addFireEffectsToRank();
// Flash screen with rank color when rank changes (except first time)
if (LK.getScore() > 0) {
LK.effects.flashScreen(currentRank.color, 1000);
}
}
// Initialize hearts display
function updateHeartsDisplay() {
// Clear existing hearts
for (var h = 0; h < hearts.length; h++) {
hearts[h].destroy();
}
hearts = [];
// Create new hearts based on current lives
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = -120 - i * 70;
heart.y = -120;
LK.gui.bottomRight.addChild(heart);
hearts.push(heart);
}
}
// Function to update bow level indicators
function updateBowLevelIndicators() {
for (var i = 0; i < bowLevelIndicators.length; i++) {
var indicator = bowLevelIndicators[i];
var levelNum = i + 1;
if (levelNum <= bowLevel) {
// Active level - green and larger
indicator.tint = 0x00FF00;
indicator.scaleX = 1.2;
indicator.scaleY = 1.2;
} else {
// Inactive level - gray and normal size
indicator.tint = 0x666666;
indicator.scaleX = 1.0;
indicator.scaleY = 1.0;
}
}
// Update bow upgrade buttons
for (var j = 0; j < bowUpgradeButtons.length; j++) {
var button = bowUpgradeButtons[j];
var label = bowUpgradeLabels[j];
var levelNum = j + 1;
if (bowLevel >= levelNum) {
// Already purchased - show as "Alındı" (Purchased)
button.setText('Yay ' + levelNum);
button.tint = 0x00FF00;
label.setText('Alındı');
label.tint = 0x00FF00;
} else if (bowLevel === levelNum - 1) {
// Next available upgrade
button.setText('Yay ' + levelNum);
button.tint = goldCoins >= bowUpgradePrices[levelNum - 1] ? 0xFFFFFF : 0x666666;
label.setText(bowUpgradePrices[levelNum - 1] + ' coin');
label.tint = goldCoins >= bowUpgradePrices[levelNum - 1] ? 0xFFD700 : 0x666666;
} else {
// Locked upgrade
button.setText('Yay ' + levelNum);
button.tint = 0x666666;
label.setText(bowUpgradePrices[levelNum - 1] + ' coin');
label.tint = 0x666666;
}
}
}
// Function to create coin scatter effect
function createCoinScatterEffect(centerX, centerY) {
// Create multiple coin particles scattered around the hit point
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var distance = 50 + Math.random() * 50;
var targetX = centerX + Math.cos(angle) * distance;
var targetY = centerY + Math.sin(angle) * distance;
// Create coin particle using yellow ball asset
var coinParticle = LK.getAsset('yellowBall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
coinParticle.tint = 0xFFD700; // Gold color
coinParticle.x = centerX;
coinParticle.y = centerY;
coinParticle.alpha = 0.9;
game.addChild(coinParticle);
// Animate coin to target position
tween(coinParticle, {
x: targetX,
y: targetY,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (coinParticle && !coinParticle.destroyed) {
coinParticle.destroy();
}
}
});
}
}
// Function to create tree bark merging effect
function createMergingEffect(centerX, centerY, objectType) {
// Find nearby objects to merge with
var nearbyObjects = [];
var color = objectType === 'ball' ? 0x8B4513 : 0x654321; // Brown colors
// Check for nearby balls
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
var distance = Math.sqrt((ball.x - centerX) * (ball.x - centerX) + (ball.y - centerY) * (ball.y - centerY));
if (distance < mergeRange && !ball.isHit) {
nearbyObjects.push(ball);
}
}
// Check for nearby bombs
for (var j = 0; j < bombs.length; j++) {
var bomb = bombs[j];
var distance2 = Math.sqrt((bomb.x - centerX) * (bomb.x - centerX) + (bomb.y - centerY) * (bomb.y - centerY));
if (distance2 < mergeRange) {
nearbyObjects.push(bomb);
}
}
// Create tree bark particles flowing between objects
for (var k = 0; k < nearbyObjects.length; k++) {
var target = nearbyObjects[k];
// Create multiple bark particles flowing from center to target
for (var p = 0; p < 3; p++) {
var bark = new TreeBark(centerX, centerY, target.x, target.y);
bark.tint = color;
treeBarkParticles.push(bark);
game.addChild(bark);
// Add slight delay for each particle
tween(bark, {
alpha: 0.9
}, {
duration: 100 + p * 50,
onFinish: function onFinish() {
// Create merging glow effect on target
if (target && !target.destroyed) {
tween(target, {
tint: color
}, {
duration: 300,
onFinish: function onFinish() {
tween(target, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
}
});
}
}
});
}
// Create reverse flow particles from target to center
for (var r = 0; r < 2; r++) {
var reverseBark = new TreeBark(target.x, target.y, centerX, centerY);
reverseBark.tint = color;
reverseBark.alpha = 0.6;
treeBarkParticles.push(reverseBark);
game.addChild(reverseBark);
}
}
// Create explosion of bark particles around the center
for (var e = 0; e < 8; e++) {
var angle = e / 8 * Math.PI * 2;
var explosionX = centerX + Math.cos(angle) * 100;
var explosionY = centerY + Math.sin(angle) * 100;
var explosionBark = new TreeBark(centerX, centerY, explosionX, explosionY);
explosionBark.tint = color;
explosionBark.alpha = 0.7;
treeBarkParticles.push(explosionBark);
game.addChild(explosionBark);
}
}
// Initialize hearts display
updateHeartsDisplay();
// Ball types for spawning
var ballTypes = ['red', 'yellow', 'blue'];
// Create initial grid of balls and bombs arranged for portrait layout
var currentX = 200;
var currentY = 150;
var itemsPerRow = 10;
var spacing = 150;
var itemCount = 0;
// Create equal amounts of balls and bombs in a grid
for (var row = 0; row < 10; row++) {
for (var col = 0; col < itemsPerRow; col++) {
var x = currentX + col * spacing;
var y = currentY + row * spacing;
if (itemCount % 6 === 5) {
// Every 6th item is a bomb
var bomb = new Bomb();
bomb.x = x;
bomb.y = y;
bombs.push(bomb);
game.addChild(bomb);
} else {
// Create balls in sequence: red, yellow, blue
var ballType = ballTypes[itemCount % 3];
var ball = new Ball(ballType);
ball.x = x;
ball.y = y;
balls.push(ball);
game.addChild(ball);
}
itemCount++;
}
}
// Create hand launcher at bottom center
var hand = game.addChild(LK.getAsset('hand', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
}));
hand.x = 1024;
hand.y = 2400;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
game.down = function (x, y, obj) {
// Don't allow shooting during countdown
if (countdownActive) return;
// Check if touch is on any bow upgrade button
for (var buttonIndex = 0; buttonIndex < bowUpgradeButtons.length; buttonIndex++) {
var button = bowUpgradeButtons[buttonIndex];
var buttonLevelNum = button.levelNum;
// Check if clicked on this button (within 100px range)
var buttonDistance = Math.sqrt((x - button.x) * (x - button.x) + (y - button.y) * (y - button.y));
if (buttonDistance < 100) {
// Check if this is the next available upgrade
if (bowLevel === buttonLevelNum - 1 && bowLevel < 5) {
var upgradeCost = bowUpgradeCosts[buttonLevelNum - 1];
if (goldCoins >= upgradeCost) {
// Deduct coins and upgrade bow
goldCoins -= upgradeCost;
bowLevel = buttonLevelNum;
// Update displays
coinTxt.setText('Coin: ' + goldCoins);
bowLevelTxt.setText('Yay Seviyesi: ' + bowLevel);
// Update bow level indicators
updateBowLevelIndicators();
// Add level up effect
tween.stop(bowLevelTxt, {
tint: true,
scaleX: true,
scaleY: true
});
// Rainbow color effect for level up
var levelUpColor;
switch (bowLevel) {
case 1:
levelUpColor = 0xFFFFFF;
break;
case 2:
levelUpColor = 0x00FF00;
break;
// Green
case 3:
levelUpColor = 0x0080FF;
break;
// Blue
case 4:
levelUpColor = 0xFF8000;
break;
// Orange
case 5:
levelUpColor = 0xFF0080;
break;
// Pink
default:
levelUpColor = 0xFFFFFF;
break;
}
bowLevelTxt.scaleX = 1.5;
bowLevelTxt.scaleY = 1.5;
tween(bowLevelTxt, {
tint: levelUpColor,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(bowLevelTxt, {
tint: 0x00FF00
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
// Flash screen with upgrade color
LK.effects.flashScreen(levelUpColor, 600);
// Add special effect to the upgraded button
tween(button, {
scaleX: 1.3,
scaleY: 1.3,
tint: levelUpColor
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(button, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0x00FF00
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
return;
}
}
break; // Exit the loop once we found a button click
}
}
// Check if touch is near the hand
var distance = Math.sqrt((x - hand.x) * (x - hand.x) + (y - hand.y) * (y - hand.y));
if (distance < 100) {
isDragging = true;
dragStartX = x;
dragStartY = y;
}
};
game.move = function (x, y, obj) {
if (isDragging) {
// Move hand slightly toward drag direction
var dx = x - dragStartX;
var dy = y - dragStartY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
// Limit movement using cached values for better performance
var targetX = screenCenterX + dx * 0.3;
var targetY = 2400 + dy * 0.3;
var distanceFromCenter = Math.sqrt((targetX - screenCenterX) * (targetX - screenCenterX) + (targetY - 2400) * (targetY - 2400));
if (distanceFromCenter > maxHandDistance) {
var ratio = maxHandDistance / distanceFromCenter;
targetX = screenCenterX + (targetX - screenCenterX) * ratio;
targetY = 2400 + (targetY - 2400) * ratio;
}
hand.x = targetX;
hand.y = targetY;
// Calculate rotation angle based on drag direction
var targetRotation = Math.atan2(dy, dx);
// Smooth rotation using tween
tween.stop(hand, {
rotation: true
});
tween(hand, {
rotation: targetRotation
}, {
duration: 200,
easing: tween.easeOut
});
}
}
};
game.up = function (x, y, obj) {
if (isDragging) {
// Calculate launch direction (upward from hand position)
var dx = x - hand.x;
var dy = y - hand.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 20) {
// Create and launch arrow toward touch point
var arrow = new Arrow(x, y, hand.x, hand.y);
arrow.x = hand.x;
arrow.y = hand.y;
game.addChild(arrow);
// If dual shot is active, create a second arrow with slight offset
if (dualShotActive) {
var arrow2 = new Arrow(x + 50, y, hand.x, hand.y);
arrow2.x = hand.x;
arrow2.y = hand.y;
game.addChild(arrow2);
}
}
// Reset hand position and rotation
hand.x = 1024;
hand.y = 2400;
// Smooth rotation back to neutral position
tween.stop(hand, {
rotation: true
});
tween(hand, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
isDragging = false;
}
};
game.update = function () {
// Timer logic - update every second (60 ticks = 1 second at 60 FPS)
if (LK.ticks - lastSecondTick >= 60) {
remainingTime--;
lastSecondTick = LK.ticks;
// Format and display time as MM:SS with optimized string creation
var minutes = Math.floor(remainingTime / 60);
var seconds = remainingTime % 60;
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeString);
// Check if time is up
if (remainingTime <= 0) {
LK.showYouWin(); // Player survives 30 minutes = win condition
return;
}
// Change timer color when less than 5 minutes remain (warning)
if (remainingTime <= 300) {
// 5 minutes = 300 seconds
timerTxt.tint = 0xFF0000; // Red color for urgency
} else if (remainingTime <= 600) {
// 10 minutes = 600 seconds
timerTxt.tint = 0xFFFF00; // Yellow color for caution
}
}
// Check and update power-up timers
if (extraPowerActive && remainingTime <= extraPowerEndTime) {
extraPowerActive = false;
extraPowerTxt.setText('');
}
if (dualShotActive && remainingTime <= dualShotEndTime) {
dualShotActive = false;
dualShotTxt.setText('');
}
if (speedBoostActive && remainingTime <= speedBoostEndTime) {
speedBoostActive = false;
speedBoostTxt.setText('');
}
// Update power-up display texts only when needed
if (extraPowerActive) {
var extraTimeLeft = extraPowerEndTime - remainingTime;
var extraMinutes = Math.floor(Math.abs(extraTimeLeft) / 60);
var extraSeconds = Math.abs(extraTimeLeft) % 60;
var extraTimeString = extraMinutes + ':' + (extraSeconds < 10 ? '0' : '') + extraSeconds;
extraPowerTxt.setText('Güç +30: ' + extraTimeString);
}
if (dualShotActive) {
var dualTimeLeft = dualShotEndTime - remainingTime;
var dualMinutes = Math.floor(Math.abs(dualTimeLeft) / 60);
var dualSeconds = Math.abs(dualTimeLeft) % 60;
var dualTimeString = dualMinutes + ':' + (dualSeconds < 10 ? '0' : '') + dualSeconds;
dualShotTxt.setText('Çift Ok: ' + dualTimeString);
}
if (speedBoostActive) {
var speedTimeLeft = speedBoostEndTime - remainingTime;
var speedMinutes = Math.floor(Math.abs(speedTimeLeft) / 60);
var speedSeconds = Math.abs(speedTimeLeft) % 60;
var speedTimeString = speedMinutes + ':' + (speedSeconds < 10 ? '0' : '') + speedSeconds;
speedBoostTxt.setText('Hız Artışı: ' + speedTimeString);
}
// Clean up destroyed tree bark particles and other objects every 10 frames
if (LK.ticks % 10 === 0) {
for (var b = treeBarkParticles.length - 1; b >= 0; b--) {
var bark = treeBarkParticles[b];
if (!bark || bark.destroyed) {
treeBarkParticles.splice(b, 1);
}
}
// Clean up any other destroyed objects
destroyedObjects = [];
}
// Update fire effects animation every 30 frames for better performance
if (LK.ticks % 30 === 0) {
for (var i = 0; i < fireEffects.length; i++) {
var fire = fireEffects[i];
if (fire && fire.baseX !== undefined && fire.baseY !== undefined) {
// Add slight floating movement
fire.x = fire.baseX + Math.sin((LK.ticks + fire.animationOffset) * 0.04) * 8;
fire.y = fire.baseY + Math.cos((LK.ticks + fire.animationOffset) * 0.025) * 6;
}
}
}
// Score is updated only when it changes in Ball.hit() and Bomb.hit()
// No need for redundant updates here
};
// Game state variables
var gameStarted = false;
var countdownActive = true;
var countdownValue = 5;
var countdownTxt = new Text2('5', {
size: 400,
fill: 0x000000
});
countdownTxt.anchor.set(0.5, 0.5);
countdownTxt.x = 1024;
countdownTxt.y = 1366;
game.addChild(countdownTxt);
// Countdown logic
var countdownTimer = LK.setInterval(function () {
countdownValue--;
if (countdownValue > 0) {
countdownTxt.setText(countdownValue.toString());
// Scale animation for countdown
countdownTxt.scaleX = 1.5;
countdownTxt.scaleY = 1.5;
tween(countdownTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut
});
} else {
countdownTxt.setText('BAŞLA!');
// Final animation
tween(countdownTxt, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownTxt.destroy();
gameStarted = true;
countdownActive = false;
}
});
LK.clearInterval(countdownTimer);
}
}, 1000);
// Start background music immediately when game loads
try {
LK.playMusic('backgroundMusic', {
loop: true,
fade: {
start: 0,
end: 0.15,
duration: 1000
}
});
} catch (e) {
console.log('Background music failed to play:', e);
}