User prompt
Oyunda bi topu vurduğumuzda yanda %6 şansla sadece 3 dk boyunca +30 daha fazla güç veya aynı anda 2 ok firlatma aktive gelsin ekranda sağ köşede bu özelliklerin dakikası ve bitme zamanı ve özellikle ismi gözüksün ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Oyuna süre zamanı ekle toplam 30 dk olsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Toplar ve bombalar sağa sola ve çapraz hareket etsin hepsi farklı yerlere hareket etsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Toplar ve bombalar sadece yukarda dursun ve sağa sağa hareket edebilsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Oyunu optimize et ve düzelt
User prompt
Bombalar ve topları yukarda hareket ettie ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Sadece yukarda hareket edebilsinler
User prompt
Ele kadar kaçamasınlar ortada en fazla
User prompt
Bombalar toplar aşağıdada çok aşağı olmadan hareket edebilsin biraz hizlandir hepsini ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Arka plana cs2 Dust 2 fotoğrafını ekle
User prompt
Ok hızını çok az hizlandir
User prompt
Bombalar ve toplar hareket etsin hızları orta olsun
User prompt
Can barlarini aşağı al diğer seçenek ise oyuna başlamadan önce play ekranı olsun
User prompt
Eklediğim müziği oyuna ekle
User prompt
Yok olan topları rastgele oluştur diğer bir seçenek ise topa vurduğumuzda puan sesi ekle bombaya vurdugumuzda bomba sesi ekle.
User prompt
Eli yukari doğru kaldıramayak tamamen onu sadece 3 adım ilerletebilek lütfen köşeye can puanını rkle 5 kere bombaya basarsak oyunu sıfırdan başlat
User prompt
Oyun donuyor optimizasyon yap
User prompt
Toplar yan yana birleşik olmasın aralarında mesefe olsun lütfen
User prompt
Toplar dokunmatik ekranla patlatilmasin ve toplar çok fazla olsun ok yönüde yukari doğru olsun bombayı patlatinca can gitsin toplamda 5 can olsun sağ yukarda kalpler gözüksün.
User prompt
Topların hepsi yan yana dursun ve patlamadan yenisi gelmesin topları vurmak için aşağıdan el çıksın ve ona basılı tutarak ok fırlatsın
User prompt
Şimdi Bir top patlatma oyunu yapicaz kırmızı toplar sarı toplar mavi toplar olacak aralarında bomba olsun onları patlatırsak -30 puan alıyoruz, kırmızı toplar 40 puan sarı toplar 60 puan mavi toplar 70 puan olsun, top sayıları eşit olsun.
User prompt
Bu oyunu tamamen sil
Code edit (1 edits merged)
Please save this source code
User prompt
Garden Defense: Plants vs Invaders
Initial prompt
Plants Vs Zombies oyunundaki gibi bir bahçe ardından savunmak için bitkileri ve arkasında bahçe makinesi toplamda oyun başladıktan sonra 30 sn sonra zombiler gelsin.
/****
* 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);
}