/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ball = Container.expand(function (color) { var self = Container.call(this); self.ballColor = color || 'red'; self.ballGraphics = self.attachAsset('ball_' + self.ballColor, { anchorX: 0.5, anchorY: 0.5 }); self.chainIndex = 0; self.pathPosition = 0; self.isExploding = false; self.isBigComboBall = false; // Default: not a big combo ball self.lastPathPosition = 0; self.isOffScreen = false; self.lastX = 0; self.lastY = 0; self.checkOffScreen = function () { // Check if ball has moved completely off screen var margin = 100; // Extra margin to ensure ball is truly gone var wasOnScreen = self.lastX >= -margin && self.lastX <= 2048 + margin && self.lastY >= -margin && self.lastY <= 2732 + margin; var isNowOffScreen = self.x < -margin || self.x > 2048 + margin || self.y < -margin || self.y > 2732 + margin; // Detect transition from on-screen to off-screen if (wasOnScreen && isNowOffScreen && !self.isOffScreen) { self.isOffScreen = true; } // Update last known positions self.lastX = self.x; self.lastY = self.y; }; self.explode = function () { if (self.isExploding) return; self.isExploding = true; tween(self.ballGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); LK.getSound('explosion').play(); }; self.destroyOnHit = function () { // 2x big combo balls: require 3 hits, persist longer if (self.isBigComboBall) { if (!self.bigHitCount) self.bigHitCount = 0; self.bigHitCount++; // Flash effect to show hit tween(self.ballGraphics, { tint: 0xFFD700, scaleX: 2.2, scaleY: 2.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self.ballGraphics, { tint: 0xFFFFFF, scaleX: 2.0, scaleY: 2.0 }, { duration: 100, easing: tween.easeIn }); } }); // Only destroy after 3 hits if (self.bigHitCount >= 3) { // Fade out and remove after a longer time tween(self.ballGraphics, { alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); self.isExploding = true; LK.getSound('explosion').play(); // If this was the persistent big combo ball, clear the reference if (typeof persistentBigComboBall !== "undefined" && persistentBigComboBall === self) { persistentBigComboBall = null; persistentBigComboBallColor = null; } return true; } else { // Persist on screen, not destroyed yet return false; } } // Simple effect: fade out the ballGraphics and remove the ball tween(self.ballGraphics, { alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); // Purple balls require multiple hits, others destroyed immediately if (self.ballColor === 'purple') { if (!self.hitCount) self.hitCount = 0; self.hitCount++; if (self.hitCount >= 2) { // Mor top tamamen yok edildiğinde: bir kaos patlaması gibi zincire çok ve karışık top ekle if (typeof addBallToChain === "function" && typeof addBallGroup === "function") { // 4-7 arası rastgele top ekle var extraBalls = 4 + Math.floor(Math.random() * 4); for (var i = 0; i < extraBalls; i++) { if (Math.random() < 0.5) { addBallToChain(); } else { addBallGroup(); } } } // Mor top yok edildiğinde purpleFrenzy başlat if (typeof triggerPurpleFrenzy === "function") { triggerPurpleFrenzy(); } // --- Spawn 5+ slow balls in all directions if purple, red, or yellow ball destroyed --- if (self.ballColor === 'purple' || self.ballColor === 'red' || self.ballColor === 'yellow') { var spawnCount = 5 + Math.floor(Math.random() * 3); // 5-7 balls var angleStep = Math.PI * 2 / spawnCount; for (var spawnIdx = 0; spawnIdx < spawnCount; spawnIdx++) { var angle = angleStep * spawnIdx + Math.random() * 0.2; // small random offset var colorChoices = ['purple', 'red', 'yellow']; var spawnColor = colorChoices[Math.floor(Math.random() * colorChoices.length)]; var newBall = new Ball(spawnColor); // Place at current position newBall.x = self.x; newBall.y = self.y; // Give a unique scatter direction and radius newBall.scatterDirection = angle; newBall.scatterRadius = 100 + Math.random() * 60; // Place at the end of the chain, but visually outside newBall.chainIndex = ballChain.length; newBall.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; // Add to chain and game ballChain.push(newBall); if (typeof game !== "undefined") game.addChild(newBall); // Set very slow movement for these spawned balls newBall.isSpawnedSlow = true; newBall.slowMoveTicks = 0; // Overwrite updateBallPosition for this ball to move slowly outward newBall.update = function () { // Move outward in a straight line, very slowly var speed = 1.2; // very slow this.x += Math.cos(this.scatterDirection) * speed; this.y += Math.sin(this.scatterDirection) * speed; this.slowMoveTicks++; // After 180 ticks (~3s), let it join normal chain movement if (this.slowMoveTicks > 180) { // Remove custom update, let normal updateBallPosition take over delete this.update; } }; } } self.explode(); return true; } else { // Flash purple ball to show it was hit tween(self.ballGraphics, { tint: 0xFF00FF, scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self.ballGraphics, { tint: 0xFFFFFF, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } }); return false; } } else { // Subtle pop effect for every destroyed ball var pop = LK.getAsset('ball_' + self.ballColor, { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.7, scaleY: 0.7, alpha: 0.7 }); if (self.parent) self.parent.addChild(pop); tween(pop, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 220, easing: tween.easeOut, onFinish: function onFinish() { if (pop.parent) pop.parent.removeChild(pop); } }); self.explode(); return true; } }; return self; }); var Frog = Container.expand(function () { var self = Container.call(this); self.frogGraphics = self.attachAsset('frog', { anchorX: 0.5, anchorY: 0.5 }); self.currentBallColor = ballColors[Math.floor(Math.random() * ballColors.length)]; self.nextBallColor = ballColors[Math.floor(Math.random() * ballColors.length)]; // Create a dedicated preview area container above the frog self.previewArea = new Container(); self.previewArea.x = 0; self.previewArea.y = -260; // Place well above the frog's head, visually separated self.addChild(self.previewArea); // Add a subtle background for the preview area to make it distinct self.previewBg = LK.getAsset('bud', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.18, scaleY: 1.18, alpha: 0.92, // normal tone, not too dark, visually separated but not harsh tint: 0x6b8e23 // olive green, natural and soft }); self.previewArea.addChild(self.previewBg); // Add the preview ball inside the preview area self.currentBall = self.attachAsset('ball_' + self.currentBallColor, { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 0.7, scaleY: 0.7 }); self.previewArea.addChild(self.currentBall); // Removed highlight circle behind frog self.aimAngle = 0; self.canShoot = true; self.updateFrogColor = function () { // Color mapping for frog tinting based on ball color var colorMap = { 'red': 0xff8888, 'blue': 0x8888ff, 'yellow': 0xffff88, 'green': 0x88ff88, 'purple': 0xff88ff }; self.frogGraphics.tint = colorMap[self.currentBallColor] || 0xffffff; }; // Initialize frog color self.updateFrogColor(); self.updateAim = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; self.aimAngle = Math.atan2(dy, dx); self.frogGraphics.rotation = self.aimAngle + Math.PI / 2; }; self.shoot = function () { if (!self.canShoot) return null; var projectile = new Projectile(self.currentBallColor); projectile.x = self.x; projectile.y = self.y; projectile.velocityX = Math.cos(self.aimAngle) * projectile.speed; projectile.velocityY = Math.sin(self.aimAngle) * projectile.speed; // Add visual effect when projectile is fired // Create shooting effect with glow and scale animation tween(projectile.projectileGraphics, { scaleX: 1.2, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(projectile.projectileGraphics, { scaleX: 0.8, scaleY: 0.8, tint: 0xFFFFFF }, { duration: 100, easing: tween.easeIn }); } }); // Create muzzle flash effect at frog's mouth var muzzleFlash = LK.getAsset('ball_yellow', { anchorX: 0.5, anchorY: 0.5, x: self.x + Math.cos(self.aimAngle) * 30, y: self.y + Math.sin(self.aimAngle) * 30, scaleX: 0.4, scaleY: 0.4, alpha: 0.8, tint: 0xFFD700 }); game.addChild(muzzleFlash); // Animate muzzle flash tween(muzzleFlash, { scaleX: 0.8, scaleY: 0.8, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { if (muzzleFlash.parent) { muzzleFlash.parent.removeChild(muzzleFlash); } } }); // Update frog's current ball self.currentBallColor = self.nextBallColor; self.nextBallColor = ballColors[Math.floor(Math.random() * ballColors.length)]; if (self.currentBall.parent) { self.currentBall.parent.removeChild(self.currentBall); } // Add the new preview ball to the preview area, keeping it visually separated self.currentBall = self.attachAsset('ball_' + self.currentBallColor, { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 0.7, scaleY: 0.7 }); self.previewArea.addChild(self.currentBall); // Removed highlight circle and pulsing effect logic // Update frog color to match new ball self.updateFrogColor(); LK.getSound('shoot').play(); return projectile; }; return self; }); // Game constants var Projectile = Container.expand(function (color) { var self = Container.call(this); self.ballColor = color; self.projectileGraphics = self.attachAsset('ball_' + self.ballColor, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.velocityX = 0; self.velocityY = 0; self.speed = 12; self.active = true; self.update = function () { if (!self.active) return; self.x += self.velocityX; self.y += self.velocityY; // Check bounds if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) { self.active = false; if (self.parent) { self.parent.removeChild(self); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d5016 }); /**** * Game Code ****/ // Initialize game assets with Maya theme and retro pixel style // Game constants var ballColors = ['red', 'blue', 'yellow', 'green', 'purple']; var pathRadius = 400; var centerX = 2048 / 2; var centerY = 2732 / 2; var chainSpeed = 0.22; // Slower chain movement for longer play var maxChainLength = 220; // Allow even more balls in chain var MAX_SPEED = 8; // Maximum allowed speed for balls // Game variables var ballChain = []; var projectiles = []; var gameRunning = true; var chainProgress = 0; var matchCombo = 0; // Combo tracking variables var comboCount = 0; var comboColor = undefined; // Add green leafy background image (fills the screen, behind all game elements) var leafyBg = LK.getAsset('path_marker', { anchorX: 0.5, anchorY: 0.5, x: centerX, y: centerY, scaleX: 2048 / 3000, scaleY: 2732 / 3000, alpha: 0.7 // subtle, not too strong }); game.addChild(leafyBg); // Create temple center var templeCenter = LK.getAsset('temple_center', { anchorX: 0.5, anchorY: 0.5, x: centerX, y: centerY }); game.addChild(templeCenter); // Create frog var frog = new Frog(); frog.x = centerX; frog.y = centerY; game.addChild(frog); // Create score display var scoreTxt = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Create ball usage counter display (top right) var ballUsageTxt = new Text2('Top Used: 0', { size: 60, fill: 0xFFFF00 }); ballUsageTxt.anchor.set(1, 0); ballUsageTxt.x = -50; // Will be positioned relative to gui.topRight ballUsageTxt.y = 50; LK.gui.topRight.addChild(ballUsageTxt); // Remove chainTxt from top left (no clain text) // Initialize ball chain function createInitialChain() { for (var i = 0; i < 130; i++) { // Increased from 90 to 130 for longer play var color = ballColors[Math.floor(Math.random() * ballColors.length)]; var ball = new Ball(color); ball.chainIndex = i; ball.pathPosition = i * 70; ballChain.push(ball); game.addChild(ball); updateBallPosition(ball); } } // Update ball position along scattered path with orderly movement function updateBallPosition(ball) { if (!ball.scatterDirection) { // Initialize scatter direction for each ball var angleOffset = ball.chainIndex * 137.5 % 360; // Golden angle for even distribution ball.scatterDirection = angleOffset * Math.PI / 180; ball.scatterRadius = 200 + ball.chainIndex % 3 * 50; // Varying distances } var totalProgress = chainProgress + ball.pathPosition; // If this is a 2x big combo ball, move it at least 5x slower var moveSpeedFactor = ball.isBigComboBall ? 0.1 : 0.5; // 0.1 is 5x slower than 0.5 var moveDistance = totalProgress * moveSpeedFactor; var targetX = centerX + Math.cos(ball.scatterDirection) * (ball.scatterRadius + moveDistance); var targetY = centerY + Math.sin(ball.scatterDirection) * (ball.scatterRadius + moveDistance); // Smooth movement towards target position // For big combo balls, also move more slowly towards their target var lerpFactor = ball.isBigComboBall ? 0.02 : 0.1; ball.x = ball.x + (targetX - ball.x) * lerpFactor; ball.y = ball.y + (targetY - ball.y) * lerpFactor; } // Add new ball to front of chain function addBallToChain() { if (ballChain.length >= maxChainLength) return; var color = ballColors[Math.floor(Math.random() * ballColors.length)]; var ball = new Ball(color); ball.chainIndex = ballChain.length; ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; ballChain.push(ball); game.addChild(ball); updateBallPosition(ball); } // Add group of same color balls occasionally function addBallGroup() { if (ballChain.length >= maxChainLength - 5) return; var groupColor = ballColors[Math.floor(Math.random() * ballColors.length)]; var groupSize = 3 + Math.floor(Math.random() * 3); // 3-5 balls for (var i = 0; i < groupSize; i++) { if (ballChain.length >= maxChainLength) break; var ball = new Ball(groupColor); ball.chainIndex = ballChain.length; ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; ballChain.push(ball); game.addChild(ball); updateBallPosition(ball); } } // Check for matches in chain function checkMatches() { var matches = []; var currentColor = null; var currentGroup = []; for (var i = 0; i < ballChain.length; i++) { var ball = ballChain[i]; if (ball.isExploding) continue; if (ball.ballColor === currentColor) { currentGroup.push(ball); } else { if (currentGroup.length >= 3) { matches = matches.concat(currentGroup); } currentColor = ball.ballColor; currentGroup = [ball]; } } // Check last group if (currentGroup.length >= 3) { matches = matches.concat(currentGroup); } if (matches.length > 0) { // Award points var points = matches.length * 10; if (matchCombo > 0) { points *= matchCombo + 1; } LK.setScore(LK.getScore() + points); matchCombo++; // Explode matched balls with Maya-themed effects for (var j = 0; j < matches.length; j++) { var matchedBall = matches[j]; // Create temple-themed glow effect tween(matchedBall.ballGraphics, { tint: 0xFFD700, // Golden glow scaleX: 1.3, scaleY: 1.3 }, { duration: 150, easing: tween.easeOut }); // Add screen flash effect for larger matches if (matches.length >= 5) { LK.effects.flashScreen(0xFFD700, 400); // Golden flash } matchedBall.explode(); } // Remove exploded balls from chain ballChain = ballChain.filter(function (ball) { return !ball.isExploding; }); // Reindex remaining balls for (var k = 0; k < ballChain.length; k++) { ballChain[k].chainIndex = k; } // Create energy burst effect at match location if (matches.length > 0) { var centerMatchX = 0; var centerMatchY = 0; for (var effectIndex = 0; effectIndex < matches.length; effectIndex++) { centerMatchX += matches[effectIndex].x; centerMatchY += matches[effectIndex].y; } centerMatchX /= matches.length; centerMatchY /= matches.length; // Create energy orbs radiating from match center for (var orbIndex = 0; orbIndex < 6; orbIndex++) { var energyOrb = LK.getAsset('ball_yellow', { anchorX: 0.5, anchorY: 0.5, x: centerMatchX, y: centerMatchY, scaleX: 0.3, scaleY: 0.3, alpha: 0.8 }); game.addChild(energyOrb); var angle = orbIndex * 60 * Math.PI / 180; var distance = 120 + Math.random() * 80; tween(energyOrb, { x: centerMatchX + Math.cos(angle) * distance, y: centerMatchY + Math.sin(angle) * distance, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { if (energyOrb.parent) { energyOrb.parent.removeChild(energyOrb); } } }); } } LK.getSound('match').play(); // Add new balls after successful matches to increase difficulty // Add more balls for larger matches to reward skillful play var ballsToAdd = 3 + Math.floor(matches.length / 3); // Base 3 + bonus for larger matches for (var addCount = 0; addCount < ballsToAdd; addCount++) { addBallToChain(); } // Check for chain reactions LK.setTimeout(function () { checkMatches(); }, 300); } else { matchCombo = 0; } } // Insert projectile into chain function insertProjectileIntoChain(projectile, insertIndex) { var newBall = new Ball(projectile.ballColor); newBall.chainIndex = insertIndex; // Adjust positions of balls after insertion point for (var i = insertIndex; i < ballChain.length; i++) { ballChain[i].chainIndex++; ballChain[i].pathPosition += 70; } // Set position for new ball if (insertIndex < ballChain.length) { newBall.pathPosition = ballChain[insertIndex].pathPosition - 70; } else { newBall.pathPosition = insertIndex * 70; } ballChain.splice(insertIndex, 0, newBall); game.addChild(newBall); updateBallPosition(newBall); // Remove projectile if (projectile.parent) { projectile.parent.removeChild(projectile); } // Check for matches LK.setTimeout(function () { checkMatches(); }, 100); } function handleBallHit(projectileColor, targetBallColor) { if (targetBallColor === "purple") { // Mor topa vurulunca 20 puan ekle LK.setScore(LK.getScore() + 20); LK.getSound('explosion').play(); // sinek efekti return; } if (projectileColor === targetBallColor) { // Doğru renge vurulduysa // Check if the hit ball is a big combo ball (2x size, 50 points) if (typeof hitBall !== "undefined" && hitBall.isBigComboBall) { LK.setScore(LK.getScore() + 50); } else { LK.setScore(LK.getScore() + 5); } LK.getSound('match').play(); } else { // Yanlış renge vurulduysa LK.setScore(Math.max(0, LK.getScore() - 5)); LK.getSound('shoot').play(); } } // Track number of balls used (shot) if (typeof ballsUsed === "undefined") var ballsUsed = 0; // Handle touch input game.down = function (x, y, obj) { if (!gameRunning) return; frog.updateAim(x, y); var projectile = frog.shoot(); if (projectile) { projectiles.push(projectile); game.addChild(projectile); ballsUsed++; ballUsageTxt.setText('Top Used: ' + ballsUsed); } }; game.move = function (x, y, obj) { if (!gameRunning) return; frog.updateAim(x, y); }; // Main game update loop game.update = function () { if (!gameRunning) return; // Gradually accelerate chain speed up to MAX_SPEED if (typeof chainSpeedAccel === "undefined") { var chainSpeedAccel = 0.00012; // Acceleration per tick } if (chainSpeed < MAX_SPEED * 0.18) { // Cap normal speed to 18% of MAX_SPEED chainSpeed += chainSpeedAccel; if (chainSpeed > MAX_SPEED * 0.18) chainSpeed = MAX_SPEED * 0.18; } // Update chain progress chainProgress += chainSpeed; // Update ball positions for (var i = 0; i < ballChain.length; i++) { var ball = ballChain[i]; if (!ball.isExploding) { ball.lastPathPosition = ball.pathPosition; var lastX = ball.x; var lastY = ball.y; // If this is a slow spawned ball with custom update, call it if (typeof ball.isSpawnedSlow !== "undefined" && ball.isSpawnedSlow && typeof ball.update === "function") { ball.update(); } else { updateBallPosition(ball); } // Check off-screen status, but do not remove persistent big combo ball if (!(typeof persistentBigComboBall !== "undefined" && ball === persistentBigComboBall)) { var wasOffScreen = ball.isOffScreen; ball.checkOffScreen(); // If the ball just went off screen this frame, spawn 2 new balls if (!wasOffScreen && ball.isOffScreen) { // Only spawn if game is running and not at max chain length if (gameRunning && ballChain.length < maxChainLength - 1) { addBallToChain(); addBallToChain(); } } } // Detect very fast moving balls and occasionally destroy them var deltaX = ball.x - lastX; var deltaY = ball.y - lastY; var speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Limit ball speed to MAX_SPEED if (speed > MAX_SPEED) { var speedRatio = MAX_SPEED / speed; var limitedDeltaX = deltaX * speedRatio; var limitedDeltaY = deltaY * speedRatio; ball.x = lastX + limitedDeltaX; ball.y = lastY + limitedDeltaY; speed = MAX_SPEED; } if (speed > 8 && Math.random() < 0.02) { // 2% chance to destroy fast balls, but not persistent big combo ball if (!(typeof persistentBigComboBall !== "undefined" && ball === persistentBigComboBall)) { ball.explode(); ballChain.splice(i, 1); i--; // Adjust index after removal // Reindex remaining balls for (var reindexI = 0; reindexI < ballChain.length; reindexI++) { ballChain[reindexI].chainIndex = reindexI; } continue; } } // Check if chain reached center (game over condition) var totalProgress = chainProgress + ball.pathPosition; var distanceFromCenter = Math.sqrt((ball.x - centerX) * (ball.x - centerX) + (ball.y - centerY) * (ball.y - centerY)); if (distanceFromCenter <= 120 && ball.chainIndex === 0) { gameRunning = false; LK.showGameOver(); return; } } } // Update projectiles for (var j = projectiles.length - 1; j >= 0; j--) { var projectile = projectiles[j]; if (!projectile.active) { projectiles.splice(j, 1); continue; } // Check collision with chain balls var hitBall = null; var insertIndex = -1; for (var k = 0; k < ballChain.length; k++) { var chainBall = ballChain[k]; if (chainBall.isExploding) continue; var dx = projectile.x - chainBall.x; var dy = projectile.y - chainBall.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 55) { hitBall = chainBall; insertIndex = k; break; } } if (hitBall) { // Check if projectile color matches hit ball color for scoring var isColorMatch = projectile.ballColor === hitBall.ballColor; var isPurpleBall = hitBall.ballColor === 'purple'; // Destroy ball on hit (purple balls need multiple hits) var ballDestroyed = hitBall.destroyOnHit(); if (ballDestroyed) { // Remove destroyed ball from chain, but keep persistent big combo ball until 3 hits for (var removeIndex = 0; removeIndex < ballChain.length; removeIndex++) { if (ballChain[removeIndex] === hitBall) { // If this is the persistent big combo ball and it hasn't reached 3 hits, do not remove if (!(typeof persistentBigComboBall !== "undefined" && hitBall === persistentBigComboBall && (!hitBall.isExploding || hitBall.bigHitCount < 3))) { ballChain.splice(removeIndex, 1); } break; } } // Reindex remaining balls for (var reindexK = 0; reindexK < ballChain.length; reindexK++) { ballChain[reindexK].chainIndex = reindexK; } // Use centralized scoring function handleBallHit(projectile.ballColor, hitBall.ballColor); } // Only create special effects for matching colors (excluding purple) if (isColorMatch && !isPurpleBall && hitBall && hitBall.ballGraphics) { // Create Maya-themed matching effect tween(hitBall.ballGraphics, { tint: 0xFFD700, // Golden glow scaleX: 1.4, scaleY: 1.4 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { if (hitBall && hitBall.ballGraphics) { tween(hitBall.ballGraphics, { tint: 0xFFFFFF, // Return to normal scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeIn }); } } }); // Create energy burst effect around the hit ball for (var effectIndex = 0; effectIndex < 4; effectIndex++) { var energyParticle = LK.getAsset('ball_yellow', { anchorX: 0.5, anchorY: 0.5, x: hitBall.x, y: hitBall.y, scaleX: 0.2, scaleY: 0.2, alpha: 0.9 }); game.addChild(energyParticle); var angle = effectIndex * 90 * Math.PI / 180; var distance = 80; tween(energyParticle, { x: hitBall.x + Math.cos(angle) * distance, y: hitBall.y + Math.sin(angle) * distance, alpha: 0, scaleX: 0.05, scaleY: 0.05 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (energyParticle.parent) { energyParticle.parent.removeChild(energyParticle); } } }); } // Combo logic: Now also applies to purple balls! if (isColorMatch) { // --- Combo logic: Only count combo if same color as previous hit --- if (typeof comboCount === "undefined") comboCount = 1; if (typeof comboColor === "undefined") comboColor = hitBall.ballColor; if (comboColor === hitBall.ballColor) { comboCount++; } else { // Only reset combo if the color actually changed if (comboCount > 1) { // Optionally, you can trigger a combo break effect here if needed } comboCount = 1; comboColor = hitBall.ballColor; } // --- Combo Popup and Effects --- if (comboCount > 1) { // Show combo popup text at hitBall position var comboText = new Text2('COMBO x' + comboCount + '!', { size: 90 + Math.min(comboCount * 10, 120), fill: 0xFFD700, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); comboText.anchor.set(0.5, 0.5); comboText.x = hitBall.x; comboText.y = hitBall.y - 80; game.addChild(comboText); // Animate popup: scale up, float up, fade out tween(comboText, { scaleX: 1.4, scaleY: 1.4, y: comboText.y - 120, alpha: 0 }, { duration: 1400, // Increased from 700 to 1400ms for longer visibility easing: tween.easeOut, onFinish: function onFinish() { if (comboText.parent) comboText.parent.removeChild(comboText); } }); // Flash screen for high combos if (comboCount >= 5) { LK.effects.flashScreen(0xFFD700, 200 + comboCount * 30); } // Add a little shake to the game for big combos if (comboCount >= 7) { var shakeAmount = Math.min(comboCount * 2, 20); var originalX = game.x || 0; var originalY = game.y || 0; var shakeTicks = 12; var shakeTimer = LK.setInterval(function () { game.x = originalX + (Math.random() - 0.5) * shakeAmount; game.y = originalY + (Math.random() - 0.5) * shakeAmount; shakeTicks--; if (shakeTicks <= 0) { LK.clearInterval(shakeTimer); game.x = originalX; game.y = originalY; } }, 16); } // --- Extra balls for combos: spawn extra balls for each combo popup --- var extraComboBalls = Math.min(2 + Math.floor(comboCount / 2), 8); for (var extraComboI = 0; extraComboI < extraComboBalls; extraComboI++) { addBallToChain(); } } // Her doğru vuruştan sonra ortaya çıkan top sayısını artır (kombo stili) var ballsToAdd = Math.min(5 + comboCount, 20); // Kombo ile artan, bolca top for (var comboI = 0; comboI < ballsToAdd; comboI++) { addBallToChain(); } // Her doğru vuruşta toplar çok yavaş hareket etsin chainSpeed = 0.05; // Çok yavaş hareket // --- Combo: Track per-color hit counts for 2x big, 50-point ball after 3 hits of same color --- // Only increment color hit count if there is no persistent big combo ball on screen if (typeof colorHitCounts === "undefined") colorHitCounts = {}; if (typeof persistentBigComboBall === "undefined" || !persistentBigComboBall) { // Initialize color hit count if not present if (!colorHitCounts[hitBall.ballColor]) { colorHitCounts[hitBall.ballColor] = 0; } colorHitCounts[hitBall.ballColor]++; } // Track persistent 2x big combo ball if (typeof persistentBigComboBall === "undefined") persistentBigComboBall = null; if (typeof persistentBigComboBallColor === "undefined") persistentBigComboBallColor = null; // When 3 hits of the same color (at any time), spawn a 2x big random color ball and keep it until next 3 hits of any color if (colorHitCounts[hitBall.ballColor] === 3) { // Remove previous persistent big combo ball if it exists if (persistentBigComboBall && persistentBigComboBall.parent) { persistentBigComboBall.parent.removeChild(persistentBigComboBall); } // Pick a random color for the big ball var bigColors = ballColors.slice(); var randomBigColor = bigColors[Math.floor(Math.random() * bigColors.length)]; var bigBall = new Ball(randomBigColor); bigBall.chainIndex = ballChain.length; bigBall.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; // Büyük top için scale artır bigBall.ballGraphics.scaleX = 2.0; bigBall.ballGraphics.scaleY = 2.0; // Mark as big and worth 50 points, and require 3 hits to destroy bigBall.isBigComboBall = true; bigBall.bigHitCount = 0; // Track hits for this big ball // Mark as persistent bigBall.isPersistentBigComboBall = true; persistentBigComboBall = bigBall; persistentBigComboBallColor = hitBall.ballColor; ballChain.push(bigBall); game.addChild(bigBall); updateBallPosition(bigBall); // 50 puan ekle LK.setScore(LK.getScore() + 50); // Reset this color's hit count colorHitCounts[hitBall.ballColor] = 0; } // Remove persistent big combo ball only if it was hit 3 times if (persistentBigComboBall && persistentBigComboBall.isExploding) { persistentBigComboBall = null; persistentBigComboBallColor = null; } } else { // Reset combo on wrong color or purple if (typeof comboCount !== "undefined" && comboCount > 2) { // Combo break effect: speed up chain and burst color chainSpeed = 0.35 + Math.random() * 0.1; // Color burst at frog var burstColors = [0xff4444, 0x44ff44, 0x4444ff, 0xffff44, 0xff44ff]; for (var burstI = 0; burstI < 7; burstI++) { var burst = LK.getAsset('ball_' + ballColors[Math.floor(Math.random() * ballColors.length)], { anchorX: 0.5, anchorY: 0.5, x: frog.x, y: frog.y, scaleX: 0.3, scaleY: 0.3, alpha: 0.8, tint: burstColors[burstI % burstColors.length] }); game.addChild(burst); var angle = Math.random() * Math.PI * 2; var dist = 120 + Math.random() * 60; tween(burst, { x: frog.x + Math.cos(angle) * dist, y: frog.y + Math.sin(angle) * dist, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (burst.parent) burst.parent.removeChild(burst); } }); } } comboCount = 0; comboColor = undefined; // Reset unique color combo if (typeof colorComboHits !== "undefined") colorComboHits = {}; if (typeof colorComboOrder !== "undefined") colorComboOrder = []; } } // Remove projectile immediately after hit projectile.active = false; if (projectile.parent) { projectile.parent.removeChild(projectile); } projectiles.splice(j, 1); if (!ballDestroyed) { insertProjectileIntoChain(projectile, insertIndex); } } } // --- Continuous, looping ball and group spawns until score is zero --- // Mor top yendikten sonra bir süre mor topların spawn oranını ve karmaşıklığını artır if (typeof purpleFrenzyTicks === "undefined") { var purpleFrenzyTicks = 0; var purpleFrenzyEndTick = 0; } if (typeof lastPurpleFrenzyTriggerTick === "undefined") { var lastPurpleFrenzyTriggerTick = -10000; } // Mor top yendikten sonra tetiklenecek fonksiyon if (typeof triggerPurpleFrenzy === "undefined") { var triggerPurpleFrenzy = function triggerPurpleFrenzy() { purpleFrenzyTicks = 0; purpleFrenzyEndTick = LK.ticks + 600 + Math.floor(Math.random() * 300); // 10-15 saniye lastPurpleFrenzyTriggerTick = LK.ticks; // Speed up chain and shake screen for a moment chainSpeed = 0.45; LK.effects.flashScreen(0xAA00FF, 400); var shakeTicks = 18; var originalX = game.x || 0; var originalY = game.y || 0; var shakeTimer = LK.setInterval(function () { game.x = originalX + (Math.random() - 0.5) * 18; game.y = originalY + (Math.random() - 0.5) * 18; shakeTicks--; if (shakeTicks <= 0) { LK.clearInterval(shakeTimer); game.x = originalX; game.y = originalY; } }, 16); }; } // Mor top yendikten sonra Ball.destroyOnHit içinde triggerPurpleFrenzy() çağrılır if (LK.getScore() > 0) { // Timers for next ball and group spawn if (typeof nextBallSpawnTick === "undefined") { var nextBallSpawnTick = LK.ticks + 10 + Math.floor(Math.random() * 30); // 0.16-0.66s (was 0.5-2s) var nextBallGroupSpawnTick = LK.ticks + 40 + Math.floor(Math.random() * 60); // 0.66-1.66s (was 2-5s) var nextChaosSpawnTick = LK.ticks + 60 + Math.floor(Math.random() * 100); // 1-2.66s (was 3-8s) } // Mor top fazlalığı aktif mi? var purpleFrenzyActive = purpleFrenzyEndTick > LK.ticks; // Random single ball spawns if (LK.ticks >= nextBallSpawnTick && ballChain.length < maxChainLength) { var ballsToAdd = purpleFrenzyActive ? 4 + Math.floor(Math.random() * 3) : 2 + Math.floor(Math.random() * 3); // 4-6 balls if frenzy, else 2-4 for (var i = 0; i < ballsToAdd; i++) { // Frenzy sırasında %40 mor top, %60 random if (purpleFrenzyActive && Math.random() < 0.4) { var ball = new Ball('purple'); ball.chainIndex = ballChain.length; ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; ballChain.push(ball); game.addChild(ball); updateBallPosition(ball); } else { addBallToChain(); } } // Next spawn in 0.1-0.33s if frenzy, else 0.16-0.66s nextBallSpawnTick = LK.ticks + (purpleFrenzyActive ? 6 + Math.floor(Math.random() * 14) : 10 + Math.floor(Math.random() * 30)); } // Random group spawns if (LK.ticks >= nextBallGroupSpawnTick && ballChain.length < maxChainLength - 5) { var doGroup = Math.random() < (purpleFrenzyActive ? 0.95 : 0.85); // 95% group if frenzy, 85% else if (doGroup) { // Frenzy sırasında grup içinde mor toplar karışık if (purpleFrenzyActive && Math.random() < 0.5) { var groupSize = 5 + Math.floor(Math.random() * 3); // 5-7 balls for (var gi = 0; gi < groupSize; gi++) { var color = Math.random() < 0.5 ? 'purple' : ballColors[Math.floor(Math.random() * ballColors.length)]; var ball = new Ball(color); ball.chainIndex = ballChain.length; ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; ballChain.push(ball); game.addChild(ball); updateBallPosition(ball); } } else { addBallGroup(); } } else { addBallToChain(); addBallToChain(); addBallToChain(); } // Next group in 0.5-1.25s if frenzy, else 0.66-1.66s nextBallGroupSpawnTick = LK.ticks + (purpleFrenzyActive ? 30 + Math.floor(Math.random() * 45) : 40 + Math.floor(Math.random() * 60)); } // Rare chaos burst: lots of balls at once, unpredictable if (LK.ticks >= nextChaosSpawnTick && ballChain.length < maxChainLength - 8) { var chaosCount = purpleFrenzyActive ? 10 + Math.floor(Math.random() * 6) : 6 + Math.floor(Math.random() * 5); // 10-15 balls if frenzy, else 6-10 for (var i = 0; i < chaosCount; i++) { if (purpleFrenzyActive && Math.random() < 0.5) { var color = Math.random() < 0.7 ? 'purple' : ballColors[Math.floor(Math.random() * ballColors.length)]; var ball = new Ball(color); ball.chainIndex = ballChain.length; ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0; ballChain.push(ball); game.addChild(ball); updateBallPosition(ball); } else if (Math.random() < 0.5) { addBallToChain(); } else { addBallGroup(); } } // Next chaos in 0.66-1.33s if frenzy, else 1-2.66s nextChaosSpawnTick = LK.ticks + (purpleFrenzyActive ? 40 + Math.floor(Math.random() * 40) : 60 + Math.floor(Math.random() * 100)); } } else { // If score is zero, stop all new ball spawns // Optionally, you could clear timers or handle end-of-loop logic here } // Update UI scoreTxt.setText('Score: ' + LK.getScore()); ballUsageTxt.setText('Top Used: ' + (typeof ballsUsed !== "undefined" ? ballsUsed : 0)); // Check if all balls are scattered off screen (game over condition) var allBallsOffScreen = ballChain.length > 0; for (var m = 0; m < ballChain.length; m++) { if (!ballChain[m].isOffScreen && !ballChain[m].isExploding) { allBallsOffScreen = false; break; } } if (allBallsOffScreen && ballChain.length > 0) { gameRunning = false; // Custom "The End" screen with green frame and music showTheEndScreen(); return; } // End the game if ballsUsed reaches 50 if (typeof ballsUsed !== "undefined" && ballsUsed >= 50) { gameRunning = false; showTheEndScreen(); return; } // Win condition - clear all balls if (ballChain.length === 0) { gameRunning = false; // Reset color hit counts and persistent big combo ball on win if (typeof colorHitCounts !== "undefined") colorHitCounts = {}; if (typeof persistentBigComboBall !== "undefined") persistentBigComboBall = null; if (typeof persistentBigComboBallColor !== "undefined") persistentBigComboBallColor = null; // Rainbow burst effect at center for (var rb = 0; rb < 12; rb++) { var colorIdx = rb % ballColors.length; var burst = LK.getAsset('ball_' + ballColors[colorIdx], { anchorX: 0.5, anchorY: 0.5, x: centerX, y: centerY, scaleX: 0.5, scaleY: 0.5, alpha: 1 }); game.addChild(burst); var angle = rb * (Math.PI * 2 / 12); var dist = 400 + Math.random() * 120; tween(burst, { x: centerX + Math.cos(angle) * dist, y: centerY + Math.sin(angle) * dist, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 1200, easing: tween.easeOut, onFinish: function onFinish() { if (burst.parent) burst.parent.removeChild(burst); } }); } // Slow motion effect before win var oldChainSpeed = chainSpeed; chainSpeed = 0.01; LK.setTimeout(function () { chainSpeed = oldChainSpeed; LK.setScore(LK.getScore() + 1000); // Bonus for clearing showTheEndScreen(); }, 900); return; } // --- Custom "The End" screen with green frame and music --- function showTheEndScreen() { // Stop all music and play end music (use 'final' sound as end music) LK.stopMusic(); LK.playMusic('final', { loop: false, fade: { start: 0, end: 1, duration: 1200 } }); // Overlay: green frame var frameThickness = 32; var frameColor = 0x00ff44; var frameAlpha = 0.85; // Top var topFrame = LK.getAsset('bud', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: frameThickness, tint: frameColor, alpha: frameAlpha }); // Bottom var bottomFrame = LK.getAsset('bud', { anchorX: 0, anchorY: 0, x: 0, y: 2732 - frameThickness, width: 2048, height: frameThickness, tint: frameColor, alpha: frameAlpha }); // Left var leftFrame = LK.getAsset('bud', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: frameThickness, height: 2732, tint: frameColor, alpha: frameAlpha }); // Right var rightFrame = LK.getAsset('bud', { anchorX: 0, anchorY: 0, x: 2048 - frameThickness, y: 0, width: frameThickness, height: 2732, tint: frameColor, alpha: frameAlpha }); // "The End" text var theEndText = new Text2('THE END', { size: 260, fill: 0x00FF44, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); theEndText.anchor.set(0.5, 0.5); theEndText.x = 2048 / 2; theEndText.y = 2732 / 2; // Add to game overlay game.addChild(topFrame); game.addChild(bottomFrame); game.addChild(leftFrame); game.addChild(rightFrame); game.addChild(theEndText); // Animate "The End" text (pulse) tween(theEndText, { scaleX: 1.18, scaleY: 1.18 }, { duration: 900, yoyo: true, repeat: 2, easing: tween.easeInOut }); // Optionally, fade out all gameplay elements for (var i = 0; i < game.children.length; i++) { var obj = game.children[i]; if (obj !== topFrame && obj !== bottomFrame && obj !== leftFrame && obj !== rightFrame && obj !== theEndText) { tween(obj, { alpha: 0.18 }, { duration: 900, easing: tween.easeOut }); } } } }; // Initialize the game createInitialChain(); ; // Play background music 'amazon' LK.playMusic('amazon'); // Minimalistic tween library for animations
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function (color) {
var self = Container.call(this);
self.ballColor = color || 'red';
self.ballGraphics = self.attachAsset('ball_' + self.ballColor, {
anchorX: 0.5,
anchorY: 0.5
});
self.chainIndex = 0;
self.pathPosition = 0;
self.isExploding = false;
self.isBigComboBall = false; // Default: not a big combo ball
self.lastPathPosition = 0;
self.isOffScreen = false;
self.lastX = 0;
self.lastY = 0;
self.checkOffScreen = function () {
// Check if ball has moved completely off screen
var margin = 100; // Extra margin to ensure ball is truly gone
var wasOnScreen = self.lastX >= -margin && self.lastX <= 2048 + margin && self.lastY >= -margin && self.lastY <= 2732 + margin;
var isNowOffScreen = self.x < -margin || self.x > 2048 + margin || self.y < -margin || self.y > 2732 + margin;
// Detect transition from on-screen to off-screen
if (wasOnScreen && isNowOffScreen && !self.isOffScreen) {
self.isOffScreen = true;
}
// Update last known positions
self.lastX = self.x;
self.lastY = self.y;
};
self.explode = function () {
if (self.isExploding) return;
self.isExploding = true;
tween(self.ballGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
LK.getSound('explosion').play();
};
self.destroyOnHit = function () {
// 2x big combo balls: require 3 hits, persist longer
if (self.isBigComboBall) {
if (!self.bigHitCount) self.bigHitCount = 0;
self.bigHitCount++;
// Flash effect to show hit
tween(self.ballGraphics, {
tint: 0xFFD700,
scaleX: 2.2,
scaleY: 2.2
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.ballGraphics, {
tint: 0xFFFFFF,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Only destroy after 3 hits
if (self.bigHitCount >= 3) {
// Fade out and remove after a longer time
tween(self.ballGraphics, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
self.isExploding = true;
LK.getSound('explosion').play();
// If this was the persistent big combo ball, clear the reference
if (typeof persistentBigComboBall !== "undefined" && persistentBigComboBall === self) {
persistentBigComboBall = null;
persistentBigComboBallColor = null;
}
return true;
} else {
// Persist on screen, not destroyed yet
return false;
}
}
// Simple effect: fade out the ballGraphics and remove the ball
tween(self.ballGraphics, {
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
// Purple balls require multiple hits, others destroyed immediately
if (self.ballColor === 'purple') {
if (!self.hitCount) self.hitCount = 0;
self.hitCount++;
if (self.hitCount >= 2) {
// Mor top tamamen yok edildiğinde: bir kaos patlaması gibi zincire çok ve karışık top ekle
if (typeof addBallToChain === "function" && typeof addBallGroup === "function") {
// 4-7 arası rastgele top ekle
var extraBalls = 4 + Math.floor(Math.random() * 4);
for (var i = 0; i < extraBalls; i++) {
if (Math.random() < 0.5) {
addBallToChain();
} else {
addBallGroup();
}
}
}
// Mor top yok edildiğinde purpleFrenzy başlat
if (typeof triggerPurpleFrenzy === "function") {
triggerPurpleFrenzy();
}
// --- Spawn 5+ slow balls in all directions if purple, red, or yellow ball destroyed ---
if (self.ballColor === 'purple' || self.ballColor === 'red' || self.ballColor === 'yellow') {
var spawnCount = 5 + Math.floor(Math.random() * 3); // 5-7 balls
var angleStep = Math.PI * 2 / spawnCount;
for (var spawnIdx = 0; spawnIdx < spawnCount; spawnIdx++) {
var angle = angleStep * spawnIdx + Math.random() * 0.2; // small random offset
var colorChoices = ['purple', 'red', 'yellow'];
var spawnColor = colorChoices[Math.floor(Math.random() * colorChoices.length)];
var newBall = new Ball(spawnColor);
// Place at current position
newBall.x = self.x;
newBall.y = self.y;
// Give a unique scatter direction and radius
newBall.scatterDirection = angle;
newBall.scatterRadius = 100 + Math.random() * 60;
// Place at the end of the chain, but visually outside
newBall.chainIndex = ballChain.length;
newBall.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
// Add to chain and game
ballChain.push(newBall);
if (typeof game !== "undefined") game.addChild(newBall);
// Set very slow movement for these spawned balls
newBall.isSpawnedSlow = true;
newBall.slowMoveTicks = 0;
// Overwrite updateBallPosition for this ball to move slowly outward
newBall.update = function () {
// Move outward in a straight line, very slowly
var speed = 1.2; // very slow
this.x += Math.cos(this.scatterDirection) * speed;
this.y += Math.sin(this.scatterDirection) * speed;
this.slowMoveTicks++;
// After 180 ticks (~3s), let it join normal chain movement
if (this.slowMoveTicks > 180) {
// Remove custom update, let normal updateBallPosition take over
delete this.update;
}
};
}
}
self.explode();
return true;
} else {
// Flash purple ball to show it was hit
tween(self.ballGraphics, {
tint: 0xFF00FF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.ballGraphics, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
return false;
}
} else {
// Subtle pop effect for every destroyed ball
var pop = LK.getAsset('ball_' + self.ballColor, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 0.7,
scaleY: 0.7,
alpha: 0.7
});
if (self.parent) self.parent.addChild(pop);
tween(pop, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 220,
easing: tween.easeOut,
onFinish: function onFinish() {
if (pop.parent) pop.parent.removeChild(pop);
}
});
self.explode();
return true;
}
};
return self;
});
var Frog = Container.expand(function () {
var self = Container.call(this);
self.frogGraphics = self.attachAsset('frog', {
anchorX: 0.5,
anchorY: 0.5
});
self.currentBallColor = ballColors[Math.floor(Math.random() * ballColors.length)];
self.nextBallColor = ballColors[Math.floor(Math.random() * ballColors.length)];
// Create a dedicated preview area container above the frog
self.previewArea = new Container();
self.previewArea.x = 0;
self.previewArea.y = -260; // Place well above the frog's head, visually separated
self.addChild(self.previewArea);
// Add a subtle background for the preview area to make it distinct
self.previewBg = LK.getAsset('bud', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1.18,
scaleY: 1.18,
alpha: 0.92,
// normal tone, not too dark, visually separated but not harsh
tint: 0x6b8e23 // olive green, natural and soft
});
self.previewArea.addChild(self.previewBg);
// Add the preview ball inside the preview area
self.currentBall = self.attachAsset('ball_' + self.currentBallColor, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 0.7,
scaleY: 0.7
});
self.previewArea.addChild(self.currentBall);
// Removed highlight circle behind frog
self.aimAngle = 0;
self.canShoot = true;
self.updateFrogColor = function () {
// Color mapping for frog tinting based on ball color
var colorMap = {
'red': 0xff8888,
'blue': 0x8888ff,
'yellow': 0xffff88,
'green': 0x88ff88,
'purple': 0xff88ff
};
self.frogGraphics.tint = colorMap[self.currentBallColor] || 0xffffff;
};
// Initialize frog color
self.updateFrogColor();
self.updateAim = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
self.aimAngle = Math.atan2(dy, dx);
self.frogGraphics.rotation = self.aimAngle + Math.PI / 2;
};
self.shoot = function () {
if (!self.canShoot) return null;
var projectile = new Projectile(self.currentBallColor);
projectile.x = self.x;
projectile.y = self.y;
projectile.velocityX = Math.cos(self.aimAngle) * projectile.speed;
projectile.velocityY = Math.sin(self.aimAngle) * projectile.speed;
// Add visual effect when projectile is fired
// Create shooting effect with glow and scale animation
tween(projectile.projectileGraphics, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(projectile.projectileGraphics, {
scaleX: 0.8,
scaleY: 0.8,
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Create muzzle flash effect at frog's mouth
var muzzleFlash = LK.getAsset('ball_yellow', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x + Math.cos(self.aimAngle) * 30,
y: self.y + Math.sin(self.aimAngle) * 30,
scaleX: 0.4,
scaleY: 0.4,
alpha: 0.8,
tint: 0xFFD700
});
game.addChild(muzzleFlash);
// Animate muzzle flash
tween(muzzleFlash, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (muzzleFlash.parent) {
muzzleFlash.parent.removeChild(muzzleFlash);
}
}
});
// Update frog's current ball
self.currentBallColor = self.nextBallColor;
self.nextBallColor = ballColors[Math.floor(Math.random() * ballColors.length)];
if (self.currentBall.parent) {
self.currentBall.parent.removeChild(self.currentBall);
}
// Add the new preview ball to the preview area, keeping it visually separated
self.currentBall = self.attachAsset('ball_' + self.currentBallColor, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 0.7,
scaleY: 0.7
});
self.previewArea.addChild(self.currentBall);
// Removed highlight circle and pulsing effect logic
// Update frog color to match new ball
self.updateFrogColor();
LK.getSound('shoot').play();
return projectile;
};
return self;
});
// Game constants
var Projectile = Container.expand(function (color) {
var self = Container.call(this);
self.ballColor = color;
self.projectileGraphics = self.attachAsset('ball_' + self.ballColor, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 12;
self.active = true;
self.update = function () {
if (!self.active) return;
self.x += self.velocityX;
self.y += self.velocityY;
// Check bounds
if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
self.active = false;
if (self.parent) {
self.parent.removeChild(self);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d5016
});
/****
* Game Code
****/
// Initialize game assets with Maya theme and retro pixel style
// Game constants
var ballColors = ['red', 'blue', 'yellow', 'green', 'purple'];
var pathRadius = 400;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var chainSpeed = 0.22; // Slower chain movement for longer play
var maxChainLength = 220; // Allow even more balls in chain
var MAX_SPEED = 8; // Maximum allowed speed for balls
// Game variables
var ballChain = [];
var projectiles = [];
var gameRunning = true;
var chainProgress = 0;
var matchCombo = 0;
// Combo tracking variables
var comboCount = 0;
var comboColor = undefined;
// Add green leafy background image (fills the screen, behind all game elements)
var leafyBg = LK.getAsset('path_marker', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
scaleX: 2048 / 3000,
scaleY: 2732 / 3000,
alpha: 0.7 // subtle, not too strong
});
game.addChild(leafyBg);
// Create temple center
var templeCenter = LK.getAsset('temple_center', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY
});
game.addChild(templeCenter);
// Create frog
var frog = new Frog();
frog.x = centerX;
frog.y = centerY;
game.addChild(frog);
// Create score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create ball usage counter display (top right)
var ballUsageTxt = new Text2('Top Used: 0', {
size: 60,
fill: 0xFFFF00
});
ballUsageTxt.anchor.set(1, 0);
ballUsageTxt.x = -50; // Will be positioned relative to gui.topRight
ballUsageTxt.y = 50;
LK.gui.topRight.addChild(ballUsageTxt);
// Remove chainTxt from top left (no clain text)
// Initialize ball chain
function createInitialChain() {
for (var i = 0; i < 130; i++) {
// Increased from 90 to 130 for longer play
var color = ballColors[Math.floor(Math.random() * ballColors.length)];
var ball = new Ball(color);
ball.chainIndex = i;
ball.pathPosition = i * 70;
ballChain.push(ball);
game.addChild(ball);
updateBallPosition(ball);
}
}
// Update ball position along scattered path with orderly movement
function updateBallPosition(ball) {
if (!ball.scatterDirection) {
// Initialize scatter direction for each ball
var angleOffset = ball.chainIndex * 137.5 % 360; // Golden angle for even distribution
ball.scatterDirection = angleOffset * Math.PI / 180;
ball.scatterRadius = 200 + ball.chainIndex % 3 * 50; // Varying distances
}
var totalProgress = chainProgress + ball.pathPosition;
// If this is a 2x big combo ball, move it at least 5x slower
var moveSpeedFactor = ball.isBigComboBall ? 0.1 : 0.5; // 0.1 is 5x slower than 0.5
var moveDistance = totalProgress * moveSpeedFactor;
var targetX = centerX + Math.cos(ball.scatterDirection) * (ball.scatterRadius + moveDistance);
var targetY = centerY + Math.sin(ball.scatterDirection) * (ball.scatterRadius + moveDistance);
// Smooth movement towards target position
// For big combo balls, also move more slowly towards their target
var lerpFactor = ball.isBigComboBall ? 0.02 : 0.1;
ball.x = ball.x + (targetX - ball.x) * lerpFactor;
ball.y = ball.y + (targetY - ball.y) * lerpFactor;
}
// Add new ball to front of chain
function addBallToChain() {
if (ballChain.length >= maxChainLength) return;
var color = ballColors[Math.floor(Math.random() * ballColors.length)];
var ball = new Ball(color);
ball.chainIndex = ballChain.length;
ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
ballChain.push(ball);
game.addChild(ball);
updateBallPosition(ball);
}
// Add group of same color balls occasionally
function addBallGroup() {
if (ballChain.length >= maxChainLength - 5) return;
var groupColor = ballColors[Math.floor(Math.random() * ballColors.length)];
var groupSize = 3 + Math.floor(Math.random() * 3); // 3-5 balls
for (var i = 0; i < groupSize; i++) {
if (ballChain.length >= maxChainLength) break;
var ball = new Ball(groupColor);
ball.chainIndex = ballChain.length;
ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
ballChain.push(ball);
game.addChild(ball);
updateBallPosition(ball);
}
}
// Check for matches in chain
function checkMatches() {
var matches = [];
var currentColor = null;
var currentGroup = [];
for (var i = 0; i < ballChain.length; i++) {
var ball = ballChain[i];
if (ball.isExploding) continue;
if (ball.ballColor === currentColor) {
currentGroup.push(ball);
} else {
if (currentGroup.length >= 3) {
matches = matches.concat(currentGroup);
}
currentColor = ball.ballColor;
currentGroup = [ball];
}
}
// Check last group
if (currentGroup.length >= 3) {
matches = matches.concat(currentGroup);
}
if (matches.length > 0) {
// Award points
var points = matches.length * 10;
if (matchCombo > 0) {
points *= matchCombo + 1;
}
LK.setScore(LK.getScore() + points);
matchCombo++;
// Explode matched balls with Maya-themed effects
for (var j = 0; j < matches.length; j++) {
var matchedBall = matches[j];
// Create temple-themed glow effect
tween(matchedBall.ballGraphics, {
tint: 0xFFD700,
// Golden glow
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut
});
// Add screen flash effect for larger matches
if (matches.length >= 5) {
LK.effects.flashScreen(0xFFD700, 400); // Golden flash
}
matchedBall.explode();
}
// Remove exploded balls from chain
ballChain = ballChain.filter(function (ball) {
return !ball.isExploding;
});
// Reindex remaining balls
for (var k = 0; k < ballChain.length; k++) {
ballChain[k].chainIndex = k;
}
// Create energy burst effect at match location
if (matches.length > 0) {
var centerMatchX = 0;
var centerMatchY = 0;
for (var effectIndex = 0; effectIndex < matches.length; effectIndex++) {
centerMatchX += matches[effectIndex].x;
centerMatchY += matches[effectIndex].y;
}
centerMatchX /= matches.length;
centerMatchY /= matches.length;
// Create energy orbs radiating from match center
for (var orbIndex = 0; orbIndex < 6; orbIndex++) {
var energyOrb = LK.getAsset('ball_yellow', {
anchorX: 0.5,
anchorY: 0.5,
x: centerMatchX,
y: centerMatchY,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.8
});
game.addChild(energyOrb);
var angle = orbIndex * 60 * Math.PI / 180;
var distance = 120 + Math.random() * 80;
tween(energyOrb, {
x: centerMatchX + Math.cos(angle) * distance,
y: centerMatchY + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (energyOrb.parent) {
energyOrb.parent.removeChild(energyOrb);
}
}
});
}
}
LK.getSound('match').play();
// Add new balls after successful matches to increase difficulty
// Add more balls for larger matches to reward skillful play
var ballsToAdd = 3 + Math.floor(matches.length / 3); // Base 3 + bonus for larger matches
for (var addCount = 0; addCount < ballsToAdd; addCount++) {
addBallToChain();
}
// Check for chain reactions
LK.setTimeout(function () {
checkMatches();
}, 300);
} else {
matchCombo = 0;
}
}
// Insert projectile into chain
function insertProjectileIntoChain(projectile, insertIndex) {
var newBall = new Ball(projectile.ballColor);
newBall.chainIndex = insertIndex;
// Adjust positions of balls after insertion point
for (var i = insertIndex; i < ballChain.length; i++) {
ballChain[i].chainIndex++;
ballChain[i].pathPosition += 70;
}
// Set position for new ball
if (insertIndex < ballChain.length) {
newBall.pathPosition = ballChain[insertIndex].pathPosition - 70;
} else {
newBall.pathPosition = insertIndex * 70;
}
ballChain.splice(insertIndex, 0, newBall);
game.addChild(newBall);
updateBallPosition(newBall);
// Remove projectile
if (projectile.parent) {
projectile.parent.removeChild(projectile);
}
// Check for matches
LK.setTimeout(function () {
checkMatches();
}, 100);
}
function handleBallHit(projectileColor, targetBallColor) {
if (targetBallColor === "purple") {
// Mor topa vurulunca 20 puan ekle
LK.setScore(LK.getScore() + 20);
LK.getSound('explosion').play(); // sinek efekti
return;
}
if (projectileColor === targetBallColor) {
// Doğru renge vurulduysa
// Check if the hit ball is a big combo ball (2x size, 50 points)
if (typeof hitBall !== "undefined" && hitBall.isBigComboBall) {
LK.setScore(LK.getScore() + 50);
} else {
LK.setScore(LK.getScore() + 5);
}
LK.getSound('match').play();
} else {
// Yanlış renge vurulduysa
LK.setScore(Math.max(0, LK.getScore() - 5));
LK.getSound('shoot').play();
}
}
// Track number of balls used (shot)
if (typeof ballsUsed === "undefined") var ballsUsed = 0;
// Handle touch input
game.down = function (x, y, obj) {
if (!gameRunning) return;
frog.updateAim(x, y);
var projectile = frog.shoot();
if (projectile) {
projectiles.push(projectile);
game.addChild(projectile);
ballsUsed++;
ballUsageTxt.setText('Top Used: ' + ballsUsed);
}
};
game.move = function (x, y, obj) {
if (!gameRunning) return;
frog.updateAim(x, y);
};
// Main game update loop
game.update = function () {
if (!gameRunning) return;
// Gradually accelerate chain speed up to MAX_SPEED
if (typeof chainSpeedAccel === "undefined") {
var chainSpeedAccel = 0.00012; // Acceleration per tick
}
if (chainSpeed < MAX_SPEED * 0.18) {
// Cap normal speed to 18% of MAX_SPEED
chainSpeed += chainSpeedAccel;
if (chainSpeed > MAX_SPEED * 0.18) chainSpeed = MAX_SPEED * 0.18;
}
// Update chain progress
chainProgress += chainSpeed;
// Update ball positions
for (var i = 0; i < ballChain.length; i++) {
var ball = ballChain[i];
if (!ball.isExploding) {
ball.lastPathPosition = ball.pathPosition;
var lastX = ball.x;
var lastY = ball.y;
// If this is a slow spawned ball with custom update, call it
if (typeof ball.isSpawnedSlow !== "undefined" && ball.isSpawnedSlow && typeof ball.update === "function") {
ball.update();
} else {
updateBallPosition(ball);
}
// Check off-screen status, but do not remove persistent big combo ball
if (!(typeof persistentBigComboBall !== "undefined" && ball === persistentBigComboBall)) {
var wasOffScreen = ball.isOffScreen;
ball.checkOffScreen();
// If the ball just went off screen this frame, spawn 2 new balls
if (!wasOffScreen && ball.isOffScreen) {
// Only spawn if game is running and not at max chain length
if (gameRunning && ballChain.length < maxChainLength - 1) {
addBallToChain();
addBallToChain();
}
}
}
// Detect very fast moving balls and occasionally destroy them
var deltaX = ball.x - lastX;
var deltaY = ball.y - lastY;
var speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Limit ball speed to MAX_SPEED
if (speed > MAX_SPEED) {
var speedRatio = MAX_SPEED / speed;
var limitedDeltaX = deltaX * speedRatio;
var limitedDeltaY = deltaY * speedRatio;
ball.x = lastX + limitedDeltaX;
ball.y = lastY + limitedDeltaY;
speed = MAX_SPEED;
}
if (speed > 8 && Math.random() < 0.02) {
// 2% chance to destroy fast balls, but not persistent big combo ball
if (!(typeof persistentBigComboBall !== "undefined" && ball === persistentBigComboBall)) {
ball.explode();
ballChain.splice(i, 1);
i--; // Adjust index after removal
// Reindex remaining balls
for (var reindexI = 0; reindexI < ballChain.length; reindexI++) {
ballChain[reindexI].chainIndex = reindexI;
}
continue;
}
}
// Check if chain reached center (game over condition)
var totalProgress = chainProgress + ball.pathPosition;
var distanceFromCenter = Math.sqrt((ball.x - centerX) * (ball.x - centerX) + (ball.y - centerY) * (ball.y - centerY));
if (distanceFromCenter <= 120 && ball.chainIndex === 0) {
gameRunning = false;
LK.showGameOver();
return;
}
}
}
// Update projectiles
for (var j = projectiles.length - 1; j >= 0; j--) {
var projectile = projectiles[j];
if (!projectile.active) {
projectiles.splice(j, 1);
continue;
}
// Check collision with chain balls
var hitBall = null;
var insertIndex = -1;
for (var k = 0; k < ballChain.length; k++) {
var chainBall = ballChain[k];
if (chainBall.isExploding) continue;
var dx = projectile.x - chainBall.x;
var dy = projectile.y - chainBall.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 55) {
hitBall = chainBall;
insertIndex = k;
break;
}
}
if (hitBall) {
// Check if projectile color matches hit ball color for scoring
var isColorMatch = projectile.ballColor === hitBall.ballColor;
var isPurpleBall = hitBall.ballColor === 'purple';
// Destroy ball on hit (purple balls need multiple hits)
var ballDestroyed = hitBall.destroyOnHit();
if (ballDestroyed) {
// Remove destroyed ball from chain, but keep persistent big combo ball until 3 hits
for (var removeIndex = 0; removeIndex < ballChain.length; removeIndex++) {
if (ballChain[removeIndex] === hitBall) {
// If this is the persistent big combo ball and it hasn't reached 3 hits, do not remove
if (!(typeof persistentBigComboBall !== "undefined" && hitBall === persistentBigComboBall && (!hitBall.isExploding || hitBall.bigHitCount < 3))) {
ballChain.splice(removeIndex, 1);
}
break;
}
}
// Reindex remaining balls
for (var reindexK = 0; reindexK < ballChain.length; reindexK++) {
ballChain[reindexK].chainIndex = reindexK;
}
// Use centralized scoring function
handleBallHit(projectile.ballColor, hitBall.ballColor);
}
// Only create special effects for matching colors (excluding purple)
if (isColorMatch && !isPurpleBall && hitBall && hitBall.ballGraphics) {
// Create Maya-themed matching effect
tween(hitBall.ballGraphics, {
tint: 0xFFD700,
// Golden glow
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (hitBall && hitBall.ballGraphics) {
tween(hitBall.ballGraphics, {
tint: 0xFFFFFF,
// Return to normal
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
}
});
// Create energy burst effect around the hit ball
for (var effectIndex = 0; effectIndex < 4; effectIndex++) {
var energyParticle = LK.getAsset('ball_yellow', {
anchorX: 0.5,
anchorY: 0.5,
x: hitBall.x,
y: hitBall.y,
scaleX: 0.2,
scaleY: 0.2,
alpha: 0.9
});
game.addChild(energyParticle);
var angle = effectIndex * 90 * Math.PI / 180;
var distance = 80;
tween(energyParticle, {
x: hitBall.x + Math.cos(angle) * distance,
y: hitBall.y + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (energyParticle.parent) {
energyParticle.parent.removeChild(energyParticle);
}
}
});
}
// Combo logic: Now also applies to purple balls!
if (isColorMatch) {
// --- Combo logic: Only count combo if same color as previous hit ---
if (typeof comboCount === "undefined") comboCount = 1;
if (typeof comboColor === "undefined") comboColor = hitBall.ballColor;
if (comboColor === hitBall.ballColor) {
comboCount++;
} else {
// Only reset combo if the color actually changed
if (comboCount > 1) {
// Optionally, you can trigger a combo break effect here if needed
}
comboCount = 1;
comboColor = hitBall.ballColor;
}
// --- Combo Popup and Effects ---
if (comboCount > 1) {
// Show combo popup text at hitBall position
var comboText = new Text2('COMBO x' + comboCount + '!', {
size: 90 + Math.min(comboCount * 10, 120),
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
comboText.anchor.set(0.5, 0.5);
comboText.x = hitBall.x;
comboText.y = hitBall.y - 80;
game.addChild(comboText);
// Animate popup: scale up, float up, fade out
tween(comboText, {
scaleX: 1.4,
scaleY: 1.4,
y: comboText.y - 120,
alpha: 0
}, {
duration: 1400,
// Increased from 700 to 1400ms for longer visibility
easing: tween.easeOut,
onFinish: function onFinish() {
if (comboText.parent) comboText.parent.removeChild(comboText);
}
});
// Flash screen for high combos
if (comboCount >= 5) {
LK.effects.flashScreen(0xFFD700, 200 + comboCount * 30);
}
// Add a little shake to the game for big combos
if (comboCount >= 7) {
var shakeAmount = Math.min(comboCount * 2, 20);
var originalX = game.x || 0;
var originalY = game.y || 0;
var shakeTicks = 12;
var shakeTimer = LK.setInterval(function () {
game.x = originalX + (Math.random() - 0.5) * shakeAmount;
game.y = originalY + (Math.random() - 0.5) * shakeAmount;
shakeTicks--;
if (shakeTicks <= 0) {
LK.clearInterval(shakeTimer);
game.x = originalX;
game.y = originalY;
}
}, 16);
}
// --- Extra balls for combos: spawn extra balls for each combo popup ---
var extraComboBalls = Math.min(2 + Math.floor(comboCount / 2), 8);
for (var extraComboI = 0; extraComboI < extraComboBalls; extraComboI++) {
addBallToChain();
}
}
// Her doğru vuruştan sonra ortaya çıkan top sayısını artır (kombo stili)
var ballsToAdd = Math.min(5 + comboCount, 20); // Kombo ile artan, bolca top
for (var comboI = 0; comboI < ballsToAdd; comboI++) {
addBallToChain();
}
// Her doğru vuruşta toplar çok yavaş hareket etsin
chainSpeed = 0.05; // Çok yavaş hareket
// --- Combo: Track per-color hit counts for 2x big, 50-point ball after 3 hits of same color ---
// Only increment color hit count if there is no persistent big combo ball on screen
if (typeof colorHitCounts === "undefined") colorHitCounts = {};
if (typeof persistentBigComboBall === "undefined" || !persistentBigComboBall) {
// Initialize color hit count if not present
if (!colorHitCounts[hitBall.ballColor]) {
colorHitCounts[hitBall.ballColor] = 0;
}
colorHitCounts[hitBall.ballColor]++;
}
// Track persistent 2x big combo ball
if (typeof persistentBigComboBall === "undefined") persistentBigComboBall = null;
if (typeof persistentBigComboBallColor === "undefined") persistentBigComboBallColor = null;
// When 3 hits of the same color (at any time), spawn a 2x big random color ball and keep it until next 3 hits of any color
if (colorHitCounts[hitBall.ballColor] === 3) {
// Remove previous persistent big combo ball if it exists
if (persistentBigComboBall && persistentBigComboBall.parent) {
persistentBigComboBall.parent.removeChild(persistentBigComboBall);
}
// Pick a random color for the big ball
var bigColors = ballColors.slice();
var randomBigColor = bigColors[Math.floor(Math.random() * bigColors.length)];
var bigBall = new Ball(randomBigColor);
bigBall.chainIndex = ballChain.length;
bigBall.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
// Büyük top için scale artır
bigBall.ballGraphics.scaleX = 2.0;
bigBall.ballGraphics.scaleY = 2.0;
// Mark as big and worth 50 points, and require 3 hits to destroy
bigBall.isBigComboBall = true;
bigBall.bigHitCount = 0; // Track hits for this big ball
// Mark as persistent
bigBall.isPersistentBigComboBall = true;
persistentBigComboBall = bigBall;
persistentBigComboBallColor = hitBall.ballColor;
ballChain.push(bigBall);
game.addChild(bigBall);
updateBallPosition(bigBall);
// 50 puan ekle
LK.setScore(LK.getScore() + 50);
// Reset this color's hit count
colorHitCounts[hitBall.ballColor] = 0;
}
// Remove persistent big combo ball only if it was hit 3 times
if (persistentBigComboBall && persistentBigComboBall.isExploding) {
persistentBigComboBall = null;
persistentBigComboBallColor = null;
}
} else {
// Reset combo on wrong color or purple
if (typeof comboCount !== "undefined" && comboCount > 2) {
// Combo break effect: speed up chain and burst color
chainSpeed = 0.35 + Math.random() * 0.1;
// Color burst at frog
var burstColors = [0xff4444, 0x44ff44, 0x4444ff, 0xffff44, 0xff44ff];
for (var burstI = 0; burstI < 7; burstI++) {
var burst = LK.getAsset('ball_' + ballColors[Math.floor(Math.random() * ballColors.length)], {
anchorX: 0.5,
anchorY: 0.5,
x: frog.x,
y: frog.y,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.8,
tint: burstColors[burstI % burstColors.length]
});
game.addChild(burst);
var angle = Math.random() * Math.PI * 2;
var dist = 120 + Math.random() * 60;
tween(burst, {
x: frog.x + Math.cos(angle) * dist,
y: frog.y + Math.sin(angle) * dist,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (burst.parent) burst.parent.removeChild(burst);
}
});
}
}
comboCount = 0;
comboColor = undefined;
// Reset unique color combo
if (typeof colorComboHits !== "undefined") colorComboHits = {};
if (typeof colorComboOrder !== "undefined") colorComboOrder = [];
}
}
// Remove projectile immediately after hit
projectile.active = false;
if (projectile.parent) {
projectile.parent.removeChild(projectile);
}
projectiles.splice(j, 1);
if (!ballDestroyed) {
insertProjectileIntoChain(projectile, insertIndex);
}
}
}
// --- Continuous, looping ball and group spawns until score is zero ---
// Mor top yendikten sonra bir süre mor topların spawn oranını ve karmaşıklığını artır
if (typeof purpleFrenzyTicks === "undefined") {
var purpleFrenzyTicks = 0;
var purpleFrenzyEndTick = 0;
}
if (typeof lastPurpleFrenzyTriggerTick === "undefined") {
var lastPurpleFrenzyTriggerTick = -10000;
}
// Mor top yendikten sonra tetiklenecek fonksiyon
if (typeof triggerPurpleFrenzy === "undefined") {
var triggerPurpleFrenzy = function triggerPurpleFrenzy() {
purpleFrenzyTicks = 0;
purpleFrenzyEndTick = LK.ticks + 600 + Math.floor(Math.random() * 300); // 10-15 saniye
lastPurpleFrenzyTriggerTick = LK.ticks;
// Speed up chain and shake screen for a moment
chainSpeed = 0.45;
LK.effects.flashScreen(0xAA00FF, 400);
var shakeTicks = 18;
var originalX = game.x || 0;
var originalY = game.y || 0;
var shakeTimer = LK.setInterval(function () {
game.x = originalX + (Math.random() - 0.5) * 18;
game.y = originalY + (Math.random() - 0.5) * 18;
shakeTicks--;
if (shakeTicks <= 0) {
LK.clearInterval(shakeTimer);
game.x = originalX;
game.y = originalY;
}
}, 16);
};
}
// Mor top yendikten sonra Ball.destroyOnHit içinde triggerPurpleFrenzy() çağrılır
if (LK.getScore() > 0) {
// Timers for next ball and group spawn
if (typeof nextBallSpawnTick === "undefined") {
var nextBallSpawnTick = LK.ticks + 10 + Math.floor(Math.random() * 30); // 0.16-0.66s (was 0.5-2s)
var nextBallGroupSpawnTick = LK.ticks + 40 + Math.floor(Math.random() * 60); // 0.66-1.66s (was 2-5s)
var nextChaosSpawnTick = LK.ticks + 60 + Math.floor(Math.random() * 100); // 1-2.66s (was 3-8s)
}
// Mor top fazlalığı aktif mi?
var purpleFrenzyActive = purpleFrenzyEndTick > LK.ticks;
// Random single ball spawns
if (LK.ticks >= nextBallSpawnTick && ballChain.length < maxChainLength) {
var ballsToAdd = purpleFrenzyActive ? 4 + Math.floor(Math.random() * 3) : 2 + Math.floor(Math.random() * 3); // 4-6 balls if frenzy, else 2-4
for (var i = 0; i < ballsToAdd; i++) {
// Frenzy sırasında %40 mor top, %60 random
if (purpleFrenzyActive && Math.random() < 0.4) {
var ball = new Ball('purple');
ball.chainIndex = ballChain.length;
ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
ballChain.push(ball);
game.addChild(ball);
updateBallPosition(ball);
} else {
addBallToChain();
}
}
// Next spawn in 0.1-0.33s if frenzy, else 0.16-0.66s
nextBallSpawnTick = LK.ticks + (purpleFrenzyActive ? 6 + Math.floor(Math.random() * 14) : 10 + Math.floor(Math.random() * 30));
}
// Random group spawns
if (LK.ticks >= nextBallGroupSpawnTick && ballChain.length < maxChainLength - 5) {
var doGroup = Math.random() < (purpleFrenzyActive ? 0.95 : 0.85); // 95% group if frenzy, 85% else
if (doGroup) {
// Frenzy sırasında grup içinde mor toplar karışık
if (purpleFrenzyActive && Math.random() < 0.5) {
var groupSize = 5 + Math.floor(Math.random() * 3); // 5-7 balls
for (var gi = 0; gi < groupSize; gi++) {
var color = Math.random() < 0.5 ? 'purple' : ballColors[Math.floor(Math.random() * ballColors.length)];
var ball = new Ball(color);
ball.chainIndex = ballChain.length;
ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
ballChain.push(ball);
game.addChild(ball);
updateBallPosition(ball);
}
} else {
addBallGroup();
}
} else {
addBallToChain();
addBallToChain();
addBallToChain();
}
// Next group in 0.5-1.25s if frenzy, else 0.66-1.66s
nextBallGroupSpawnTick = LK.ticks + (purpleFrenzyActive ? 30 + Math.floor(Math.random() * 45) : 40 + Math.floor(Math.random() * 60));
}
// Rare chaos burst: lots of balls at once, unpredictable
if (LK.ticks >= nextChaosSpawnTick && ballChain.length < maxChainLength - 8) {
var chaosCount = purpleFrenzyActive ? 10 + Math.floor(Math.random() * 6) : 6 + Math.floor(Math.random() * 5); // 10-15 balls if frenzy, else 6-10
for (var i = 0; i < chaosCount; i++) {
if (purpleFrenzyActive && Math.random() < 0.5) {
var color = Math.random() < 0.7 ? 'purple' : ballColors[Math.floor(Math.random() * ballColors.length)];
var ball = new Ball(color);
ball.chainIndex = ballChain.length;
ball.pathPosition = ballChain.length > 0 ? ballChain[ballChain.length - 1].pathPosition + 70 : 0;
ballChain.push(ball);
game.addChild(ball);
updateBallPosition(ball);
} else if (Math.random() < 0.5) {
addBallToChain();
} else {
addBallGroup();
}
}
// Next chaos in 0.66-1.33s if frenzy, else 1-2.66s
nextChaosSpawnTick = LK.ticks + (purpleFrenzyActive ? 40 + Math.floor(Math.random() * 40) : 60 + Math.floor(Math.random() * 100));
}
} else {
// If score is zero, stop all new ball spawns
// Optionally, you could clear timers or handle end-of-loop logic here
}
// Update UI
scoreTxt.setText('Score: ' + LK.getScore());
ballUsageTxt.setText('Top Used: ' + (typeof ballsUsed !== "undefined" ? ballsUsed : 0));
// Check if all balls are scattered off screen (game over condition)
var allBallsOffScreen = ballChain.length > 0;
for (var m = 0; m < ballChain.length; m++) {
if (!ballChain[m].isOffScreen && !ballChain[m].isExploding) {
allBallsOffScreen = false;
break;
}
}
if (allBallsOffScreen && ballChain.length > 0) {
gameRunning = false;
// Custom "The End" screen with green frame and music
showTheEndScreen();
return;
}
// End the game if ballsUsed reaches 50
if (typeof ballsUsed !== "undefined" && ballsUsed >= 50) {
gameRunning = false;
showTheEndScreen();
return;
}
// Win condition - clear all balls
if (ballChain.length === 0) {
gameRunning = false;
// Reset color hit counts and persistent big combo ball on win
if (typeof colorHitCounts !== "undefined") colorHitCounts = {};
if (typeof persistentBigComboBall !== "undefined") persistentBigComboBall = null;
if (typeof persistentBigComboBallColor !== "undefined") persistentBigComboBallColor = null;
// Rainbow burst effect at center
for (var rb = 0; rb < 12; rb++) {
var colorIdx = rb % ballColors.length;
var burst = LK.getAsset('ball_' + ballColors[colorIdx], {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
scaleX: 0.5,
scaleY: 0.5,
alpha: 1
});
game.addChild(burst);
var angle = rb * (Math.PI * 2 / 12);
var dist = 400 + Math.random() * 120;
tween(burst, {
x: centerX + Math.cos(angle) * dist,
y: centerY + Math.sin(angle) * dist,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (burst.parent) burst.parent.removeChild(burst);
}
});
}
// Slow motion effect before win
var oldChainSpeed = chainSpeed;
chainSpeed = 0.01;
LK.setTimeout(function () {
chainSpeed = oldChainSpeed;
LK.setScore(LK.getScore() + 1000); // Bonus for clearing
showTheEndScreen();
}, 900);
return;
}
// --- Custom "The End" screen with green frame and music ---
function showTheEndScreen() {
// Stop all music and play end music (use 'final' sound as end music)
LK.stopMusic();
LK.playMusic('final', {
loop: false,
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// Overlay: green frame
var frameThickness = 32;
var frameColor = 0x00ff44;
var frameAlpha = 0.85;
// Top
var topFrame = LK.getAsset('bud', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: frameThickness,
tint: frameColor,
alpha: frameAlpha
});
// Bottom
var bottomFrame = LK.getAsset('bud', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - frameThickness,
width: 2048,
height: frameThickness,
tint: frameColor,
alpha: frameAlpha
});
// Left
var leftFrame = LK.getAsset('bud', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: frameThickness,
height: 2732,
tint: frameColor,
alpha: frameAlpha
});
// Right
var rightFrame = LK.getAsset('bud', {
anchorX: 0,
anchorY: 0,
x: 2048 - frameThickness,
y: 0,
width: frameThickness,
height: 2732,
tint: frameColor,
alpha: frameAlpha
});
// "The End" text
var theEndText = new Text2('THE END', {
size: 260,
fill: 0x00FF44,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
theEndText.anchor.set(0.5, 0.5);
theEndText.x = 2048 / 2;
theEndText.y = 2732 / 2;
// Add to game overlay
game.addChild(topFrame);
game.addChild(bottomFrame);
game.addChild(leftFrame);
game.addChild(rightFrame);
game.addChild(theEndText);
// Animate "The End" text (pulse)
tween(theEndText, {
scaleX: 1.18,
scaleY: 1.18
}, {
duration: 900,
yoyo: true,
repeat: 2,
easing: tween.easeInOut
});
// Optionally, fade out all gameplay elements
for (var i = 0; i < game.children.length; i++) {
var obj = game.children[i];
if (obj !== topFrame && obj !== bottomFrame && obj !== leftFrame && obj !== rightFrame && obj !== theEndText) {
tween(obj, {
alpha: 0.18
}, {
duration: 900,
easing: tween.easeOut
});
}
}
}
};
// Initialize the game
createInitialChain();
;
// Play background music 'amazon'
LK.playMusic('amazon');
// Minimalistic tween library for animations
kocaman gözleri olan ve ağzında olan her renge göre renk değiştiren bir bukalemun. In-Game asset. 2d. High contrast. No shadows
minik ve çok sevimli bir yusufçuk. In-Game asset. 2d. High contrast. No shadows
minik sevimli bir tırtıl. In-Game asset. 2d. High contrast. No shadows
çirkin ve sevimsiz kara sinek mor renkli bir kısmı. In-Game asset. 2d. High contrast. No shadows
çok güzel renkleri olan ağırlıklı olarak kırmızı renkli kelebek. In-Game asset. 2d. High contrast. No shadows
yeşil yapraklar ve ağaçlar olan orta bir alan. In-Game asset. 2d. High contrast. No shadows
oval kütük küçük bir meydan gibi üstten görünümlü. In-Game asset. 2d. High contrast. No shadows
çember şeklinde bir görseli çerçeve içine alan çevresi yeşillik çerçeve. In-Game asset. 2d. High contrast. No shadows