/****
* 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