User prompt
while the floor and the walls tend to bring fruits back to a halt, other fruits from the top keep aplying pressure on them, thus restarting their movement. once fruits have been brought to a halt, they shouldn't recieve inputs from other fruits
User prompt
fruits that are touching the ground or the walls, should recueve more friction and thus rest more naturally. right now they keep spinning because affected by other fruits, but the friction from the floor and walls should be more intense, since those are static boundaries that define the base friction applied to fruits
Code edit (1 edits merged)
Please save this source code
User prompt
Simplify Bounce Logic in CollisionComponent: Action: Remove the if (!fruit.hasBounced) condition wrapping the velocity reversal (fruit.vx = -fruit.vx * ... or fruit.vy = -fruit.vy * ...) within checkLeftWallCollision, checkRightWallCollision, and checkFloorCollision. Let the velocity reversal happen on every collision with a boundary. Action: Remove the logic that sets fruit.vx = 0 or fruit.vy = 0 in the else block associated with hasBounced. Reasoning: The hasBounced flag adds state complexity that might contribute to unexpected behavior. Relying on the elasticity property (which is already low) and natural friction/damping should be sufficient to prevent excessive bouncing without needing a hard stop after the first impact. This simplifies the collision response. Keep the sound playing only on the first effective bounce if desired, perhaps by checking if the incoming velocity was above a threshold before the collision response.
User prompt
ensure the 3 second delays applies to pineapples that fall from the left, as well
User prompt
fruits that colide with the game over line, should have a 3 second delay before they trigger the game over state
User prompt
Remove External Stuck Correction: Action: Delete the checkAndFixStuckFruits function entirely. Reasoning: The core physics loop should inherently prevent fruits from getting permanently stuck in mid-air if gravity is applied correctly. Relying on an external function to "nudge" stuck fruits is a workaround, not a robust solution. Simplifying the core physics makes this external check redundant. Refactor PhysicsComponent.stabilizeFruit - Prioritize Mid-Air State: Action: Modify the stabilizeFruit function. Add a check at the very beginning of the function: if the fruit is determined to be isMidAir (not touching floor or walls), the function should immediately return false (indicating not stabilized) without performing any stabilization calculations or velocity modifications. Reasoning: This ensures that fruits identified as being in mid-air are never subject to the stabilization logic that might incorrectly stop their vertical movement. Stabilization should only apply to fruits potentially resting on surfaces or against other fruits. Action: Remove any remaining fruit-level specific threshold adjustments or logic within stabilizeFruit. The stabilization thresholds and damping factors can still scale based on level (as they already do implicitly via massFactor or explicitly if desired for general effect), but there should be no if (fruitLevel === 4) or similar specific conditions. Refactor PhysicsComponent.apply - Guarantee Gravity & Minimum Fall Speed: Action: First, ensure the gravity application (fruit.vy += fruit.gravity * gravityMultiplier;) happens unconditionally for all non-static, non-merging fruits at the beginning of the apply function, before calling stabilizeFruit. Action: Second, after applying gravity, calling stabilizeFruit, applying friction, and handling velocity thresholds, add a final safety net check at the very end of the apply function, just before it finishes: Check if the fruit isMidAir. If it is mid-air AND its vertical velocity fruit.vy is less than a small positive constant (e.g., MIN_FALL_SPEED = 0.1), set fruit.vy = MIN_FALL_SPEED. Reasoning: This final check acts as an absolute guarantee. No matter what stabilization or friction calculations occurred, if a fruit is unsupported, it will always have at least a minimal downward velocity, preventing it from hovering. Action: Remove any if (fruitLevel === 4) conditions or other fruit-specific rules from the apply function.
User prompt
can you ensure the fruits rotation is directly corelated to it's movement? like, when fruits fall and rotate, rotation does happen but it seems to be slower than the actual rotation of the intended psysics. like, rotation seems to "lag" compared to what it should actually look like. and also fix the issue with fruits caught in between other fruits not touching walls or the floor, keep spinning exponentially faster. if anything, when in between more fruits, it should come to a erst faster as it has friction from multiple places
User prompt
nice job Ava, everything works sooo nice! now, can you ensure the fruits rotation is directly corlated to it's movement? like, when fruits fall and rotate, rotation does happen but it seems to be slower than the actual rotation of the intended psysics. like, rotation seems to "lag" compared to what it should actually look like
User prompt
Goal: Stop mid-air fruits from ever accumulating a high enough totalStabilizationScore to reach stabilization levels 1 or 2. How: Inside stabilizeFruit, before calculating the final stabilizationLevel based on thresholds, check if the fruit isMidAir. If true, artificially cap or drastically reduce the calculated totalStabilizationScore so it cannot possibly trigger level 1 or 2 stabilization.
User prompt
simplify the logic for how force is applied to larger fruits, so you only consider their level, and do a multiplier based on that. don't have tiers for fruits, remove tiers completeltly ,and simplify the logic
User prompt
remove all comments from the code
User prompt
use this color for the background HEX: #ffe122
User prompt
make the background color fully white
User prompt
make the background color fully black
User prompt
the orange still remains unaffected by grafity, fix that issue please
User prompt
centralize the code for all fruits logic, so the orange does not have special logic for it's behaviour, since that's the only fruti that remains in the air and not affected by gravity
User prompt
optimize, reduce and improve the readability of the code
User prompt
the orange fruit seems to be bugged and remain stuck in mid air and not affected by gravity after it stops moving. other fruits can still move it around, but gravity no logner affects it. fix this pls
User prompt
the smaller fruits still go straight to the ground, falling between the cracks, and higher level fruits still seem very light as they always tend to "float" towards the top staying on top of the smaller fruits. it should be the other way around
User prompt
the larger the fruit, the less it hsould be moves by smaller fruits. tie this to the fruit's level, so the higher it's level, the less it moves by smaller fruits
User prompt
right now, smaller level frutis seem to be heavier than larger fruits, being attacted towards the ground more. it should be the other way around, with larger fruits being heavier
User prompt
centralize the logic for how bouncing works, as it seems larger fruits still boucne more than just one time. smaller fruits seem to be fixed but the larger they become now, it appears they keep bouncing. ensure ALL fruits can only bounce once. remove any legacy code that's no longer required for this new mechanic. refactor the bouncing to only bounce once for all the frames
User prompt
the vibration was not fixed, it's even worse now, rethink the implementation logic. a fruit can only bounce once, afterwards it stops bouncing. it only bounces once if it never bounced, after the first bounce, stop bouncing it
User prompt
that didn't fix the problem, small fruits still bounce infinitely if caught in between fruits. after each bounce, reduce the force it's impaacted by, so each bounce has an exponentially lower effect on said fruit
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var ChargedBallUI = Container.expand(function () { var self = Container.call(this); self.chargeNeededForRelease = 15; self.currentCharge = 0; self.isReadyToRelease = false; self.countdownText = null; self.pulseAnimationActive = false; self.initialize = function () { self.portalAsset = self.attachAsset('portal', { anchorX: 0.5, anchorY: 0.5 }); self.portalAsset.x = gameWidth / 2 + 870; self.portalAsset.alpha = 0; self.portalAsset.scaleX = 0; self.portalAsset.scaleY = 0; self.y = 120; }; self.updateChargeDisplay = function (chargeCount) { self.currentCharge = chargeCount; var remainingCount = Math.max(0, self.chargeNeededForRelease - self.currentCharge); // No hue/tint changes for portal - keep original appearance // Calculate size based on charge progress var progressPercent = (self.chargeNeededForRelease - remainingCount) / self.chargeNeededForRelease; var targetScale = progressPercent; // Make portal visible once charging starts if (progressPercent > 0 && self.portalAsset.alpha === 0) { self.portalAsset.alpha = 1; } tween(self.portalAsset, { scaleX: targetScale, scaleY: targetScale, alpha: progressPercent // Gradually increase visibility }, { duration: 300, easing: tween.easeOut }); if (remainingCount === 0 && !self.isReadyToRelease) { self.setReadyState(true); } }; self.setReadyState = function (isReady) { self.isReadyToRelease = isReady; if (isReady) { tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0, rotation: Math.PI * 2 // Make portal spin clockwise 360 degrees }, { duration: 300, easing: tween.easeOut }); self.startPulseAnimation(); } }; self.startPulseAnimation = function () { if (self.pulseAnimationActive) { return; } self.pulseAnimationActive = true; self._pulseText(); }; self._pulseText = function () { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.3, scaleY: 1.3 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut, onFinish: self._pulseText }); } }); }; self.reset = function () { self.isReadyToRelease = false; self.currentCharge = 0; self.pulseAnimationActive = false; tween(self.portalAsset, { alpha: 0 }, { duration: 200, easing: tween.easeOut }); tween(self.portalAsset, { scaleX: 0, scaleY: 0 }, { duration: 200, easing: tween.easeOut }); }; return self; }); var CollisionComponent = Container.expand(function () { var self = Container.call(this); self.wallContactFrames = 0; self.checkBoundaryCollisions = function (fruit, walls, floor) { if (!walls || !walls.left || !walls.right || !floor) { return; } var fruitHalfWidth = fruit.width / 2; var fruitHalfHeight = fruit.height / 2; var fruitLevel = getFruitLevel(fruit); // Simplified size reduction based directly on level var sizeReduction = Math.max(0, fruitLevel - 4) * 2; fruitHalfWidth = Math.max(10, fruitHalfWidth - sizeReduction / 2); fruitHalfHeight = Math.max(10, fruitHalfHeight - sizeReduction / 2); var cosAngle = Math.abs(Math.cos(fruit.rotation)); var sinAngle = Math.abs(Math.sin(fruit.rotation)); var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle; var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle; fruit._boundaryContacts = fruit._boundaryContacts || { left: false, right: false, floor: false }; fruit._boundaryContacts.left = false; fruit._boundaryContacts.right = false; fruit._boundaryContacts.floor = false; self.checkLeftWallCollision(fruit, walls.left, effectiveWidth); self.checkRightWallCollision(fruit, walls.right, effectiveWidth); self.checkFloorCollision(fruit, floor, effectiveHeight); var isInContact = fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor; if (isInContact) { fruit.wallContactFrames++; // Simplified progressive friction var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01); fruit.angularVelocity *= progressiveFriction; if (fruit.wallContactFrames > 10) { fruit.vx *= 0.9; fruit.vy *= 0.9; } } else { fruit.wallContactFrames = Math.max(0, fruit.wallContactFrames - 1); } }; self.checkLeftWallCollision = function (fruit, leftWall, effectiveWidth) { var leftBoundary = leftWall.x + leftWall.width / 2 + effectiveWidth; if (fruit.x < leftBoundary) { fruit.x = leftBoundary; if (!fruit.hasBounced) { fruit.vx = -fruit.vx * fruit.elasticity * 0.7; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vx = 0; } var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; fruit._boundaryContacts.left = true; if (fruit.wallContactFrames > 3 && Math.abs(fruit.vx) < 0.8) { fruit.vx = 0; } } else if (fruit.x > leftWall.x + leftWall.width * 2) { if (fruit._boundaryContacts && !fruit._boundaryContacts.right && !fruit._boundaryContacts.floor) { fruit.wallContactFrames = Math.max(0, fruit.wallContactFrames - 1); } } }; self.checkRightWallCollision = function (fruit, rightWall, effectiveWidth) { var rightBoundary = rightWall.x - rightWall.width / 2 - effectiveWidth; if (fruit.x > rightBoundary) { fruit.x = rightBoundary; if (!fruit.hasBounced) { fruit.vx = -fruit.vx * fruit.elasticity * 0.7; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vx = 0; } var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; fruit._boundaryContacts.right = true; if (fruit.wallContactFrames > 3 && Math.abs(fruit.vx) < 0.8) { fruit.vx = 0; } } }; self.checkFloorCollision = function (fruit, floor, effectiveHeight) { var floorCollisionY = floor.y - floor.height / 2 - effectiveHeight; if (fruit.y > floorCollisionY) { fruit.y = floorCollisionY; if (!fruit.hasBounced) { fruit.vy = -fruit.vy * fruit.elasticity * 0.5; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vy = 0; } fruit._boundaryContacts.floor = true; if (Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity = fruit.vx * 0.01; } fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; var restThreshold = fruit.wallContactFrames > 5 ? 1.5 : 2.5; if (Math.abs(fruit.vy) < restThreshold) { fruit.vy = 0; fruit.vx *= 0.7; if (fruit.wallContactFrames > 8) { fruit.vx *= 0.85; } } var angularRestThreshold = fruit.wallContactFrames > 5 ? 0.015 : 0.025; if (Math.abs(fruit.angularVelocity) < angularRestThreshold) { fruit.angularVelocity = 0; } } }; return self; }); var DotPool = Container.expand(function (initialSize) { var self = Container.call(this); var pool = []; var activeObjects = []; self.initialize = function (size) { for (var i = 0; i < size; i++) { self.createObject(); } }; self.createObject = function () { var dot = new Container(); var dotGraphic = dot.attachAsset('trajectoryDot', { anchorX: 0.5, anchorY: 0.5 }); dotGraphic.tint = 0xFFFFFF; dot.scaleX = 0.8; dot.scaleY = 0.8; dot.visible = false; pool.push(dot); return dot; }; self.get = function () { var object = pool.length > 0 ? pool.pop() : self.createObject(); activeObjects.push(object); return object; }; self.release = function (object) { var index = activeObjects.indexOf(object); if (index !== -1) { activeObjects.splice(index, 1); object.visible = false; pool.push(object); } }; self.releaseAll = function () { while (activeObjects.length > 0) { var object = activeObjects.pop(); object.visible = false; pool.push(object); } }; if (initialSize) { self.initialize(initialSize); } return self; }); var EvolutionLine = Container.expand(function () { var self = Container.call(this); self.initialize = function () { self.y = 120; self.x = gameWidth / 2 + 350; // Moved 300 pixels to the right var fruitTypes = [FruitTypes.CHERRY, FruitTypes.GRAPE, FruitTypes.APPLE, FruitTypes.ORANGE, FruitTypes.WATERMELON, FruitTypes.PINEAPPLE, FruitTypes.MELON, FruitTypes.PEACH, FruitTypes.COCONUT, FruitTypes.DURIAN]; var totalWidth = 0; var fruitIcons = []; for (var i = 0; i < fruitTypes.length; i++) { var fruitType = fruitTypes[i]; var fruitIcon = LK.getAsset(fruitType.id, { anchorX: 0.5, anchorY: 0.5 }); var scale = Math.min(150 / fruitIcon.width, 150 / fruitIcon.height); fruitIcon.scaleX = scale; fruitIcon.scaleY = scale; totalWidth += fruitIcon.width * scale; if (i < fruitTypes.length - 1) { totalWidth += 100; // Increased spacing } fruitIcons.push(fruitIcon); } var currentX = -totalWidth / 2; for (var i = 0; i < fruitIcons.length; i++) { var icon = fruitIcons[i]; icon.x = currentX + icon.width * icon.scaleX / 2; icon.y = 0; self.addChild(icon); currentX += icon.width * icon.scaleX + 20; // Increased spacing } }; return self; }); var FireElement = Container.expand(function (initX, initY, zIndex) { var self = Container.call(this); // Configuration self.baseX = initX || 0; self.baseY = initY || 0; self.zIndex = zIndex || 0; self.movementRange = 30 + Math.random() * 20; self.movementSpeed = 0.3 + Math.random() * 0.4; self.direction = Math.random() > 0.5 ? 1 : -1; // Alpha range changed to 0.1-0.5 self.alphaMin = 0.1; self.alphaMax = 0.5; self.flickerSpeed = 500 + Math.random() * 300; self.frameIndex = 0; self.frameTimer = null; self.frameDuration = 200; // Switch frames every 300ms // Setup self.initialize = function () { // Create both fire frames (one visible, one hidden initially) self.fireAsset = self.attachAsset('fire', { anchorX: 0.5, anchorY: 1.0 // Anchor at bottom-center }); self.fireAsset2 = self.attachAsset('fire_2', { anchorX: 0.5, anchorY: 1.0 // Anchor at bottom-center }); self.fireAsset2.visible = false; // Start with frame 2 hidden self.x = self.baseX; self.y = self.baseY; // Start animations self.startAlphaFlicker(); self.startFrameAnimation(); }; // Horizontal sway animation self.update = function () { // Update horizontal position with sway animation self.x += self.movementSpeed * self.direction; // Check if we need to change direction if (Math.abs(self.x - self.baseX) > self.movementRange) { self.direction *= -1; } }; // Frame animation self.startFrameAnimation = function () { // Clear any existing frame timer if (self.frameTimer) { LK.clearInterval(self.frameTimer); } // Create new interval for frame switching self.frameTimer = LK.setInterval(function () { self.toggleFrame(); }, self.frameDuration); }; self.toggleFrame = function () { // Switch between frames self.frameIndex = (self.frameIndex + 1) % 2; self.fireAsset.visible = self.frameIndex === 0; self.fireAsset2.visible = self.frameIndex === 1; }; // Alpha flicker animation self.startAlphaFlicker = function () { if (self.flickerTween) { self.flickerTween.stop(); } // Randomize starting delay to avoid synchronized flickers var startDelay = Math.random() * 500; LK.setTimeout(function () { self.flickerToMax(); }, startDelay); }; self.flickerToMax = function () { // Apply alpha flicker to both frames self.flickerTween = tween(self, { alpha: self.alphaMax // Use class variable instead of hardcoded value }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMin }); }; self.flickerToMin = function () { // Apply alpha flicker to both frames self.flickerTween = tween(self, { alpha: self.alphaMin // Use class variable instead of hardcoded value }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMax }); }; self.destroy = function () { if (self.flickerTween) { self.flickerTween.stop(); } if (self.frameTimer) { LK.clearInterval(self.frameTimer); self.frameTimer = null; } Container.prototype.destroy.call(this); }; self.initialize(); return self; }); var Fruit = Container.expand(function (type) { var self = Container.call(this); self.id = 'fruit_' + Date.now() + '_' + Math.floor(Math.random() * 10000); self.type = type; var physics = new PhysicsComponent(); var collision = new CollisionComponent(); var mergeHandler = new MergeComponent(); var behaviorSystem = new FruitBehavior(); self.vx = physics.vx; self.vy = physics.vy; self.rotation = physics.rotation; self.angularVelocity = physics.angularVelocity; self.angularFriction = physics.angularFriction; self.groundAngularFriction = physics.groundAngularFriction; var currentLevel = getFruitLevel(self); self.gravity = physics.gravity * (1 + (currentLevel - 1) * 0.3); self.friction = physics.friction; self.rotationRestCounter = physics.rotationRestCounter; self.maxAngularVelocity = physics.maxAngularVelocity; self.isStatic = physics.isStatic; self.elasticity = currentLevel <= 3 ? 0.85 : 0.8 - (currentLevel - 1) * (0.2 / 9); self.wallContactFrames = collision.wallContactFrames; self.merging = mergeHandler.merging; self.mergeGracePeriodActive = mergeHandler.mergeGracePeriodActive; self.fromChargedRelease = mergeHandler.fromChargedRelease; self.safetyPeriod = false; self.immuneToGameOver = false; self.hasBounced = false; // Single flag for bounce tracking self.behavior = type && type.id ? behaviorSystem.getMergeHandler(type.id) : null; if (self.type && self.type.id && self.type.points && self.type.size) { var fruitGraphics = self.attachAsset(self.type.id, { anchorX: 0.5, anchorY: 0.5 }); self.width = fruitGraphics.width; self.height = fruitGraphics.height; if (self.behavior && self.behavior.onSpawn) { self.behavior.onSpawn(self); } } else { console.log("Warning: Fruit type not available yet or missing required properties"); } self.updatePhysics = function () { physics.apply(self); }; self.checkBoundaries = function (walls, floor) { collision.checkBoundaryCollisions(self, walls, floor); if (self.safetyPeriod === false && self.vy <= 0.1) { self.safetyPeriod = undefined; } }; self.merge = function (otherFruit) { mergeHandler.beginMerge(self, otherFruit); }; return self; }); var FruitBehavior = Container.expand(function () { var self = Container.call(this); self.behaviors = { CHERRY: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, GRAPE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, APPLE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, ORANGE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, WATERMELON: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, PINEAPPLE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); }, onSpawn: function onSpawn() {} }, MELON: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.getSound('Smartz').play(); return self.standardMerge(fruit1, fruit2, posX, posY); } }, PEACH: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.getSound('stonks').play(); return self.standardMerge(fruit1, fruit2, posX, posY); } }, COCONUT: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.getSound('ThisIsFine').play(); return self.standardMerge(fruit1, fruit2, posX, posY); }, onSpawn: function onSpawn() { LK.getSound('stonks').play(); } }, DURIAN: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.setScore(LK.getScore() + fruit1.type.points); updateScoreDisplay(); removeFruitFromGame(fruit1); removeFruitFromGame(fruit2); releasePineappleOnMerge(); return null; } } }; self.getMergeHandler = function (fruitTypeId) { if (!fruitTypeId) { return self.behaviors.CHERRY; } var upperTypeId = fruitTypeId.toUpperCase(); return self.behaviors[upperTypeId] || self.behaviors.CHERRY; }; self.standardMerge = function (fruit1, fruit2, posX, posY) { var nextType = FruitTypes[fruit1.type.next.toUpperCase()]; releasePineappleOnMerge(); return self.createNextLevelFruit(fruit1, nextType, posX, posY); }; self.createNextLevelFruit = function (sourceFruit, nextType, posX, posY) { var newFruit = new Fruit(nextType); newFruit.x = posX; newFruit.y = posY; newFruit.scaleX = 0.5; newFruit.scaleY = 0.5; game.addChild(newFruit); fruits.push(newFruit); spatialGrid.insertObject(newFruit); LK.setScore(LK.getScore() + nextType.points); updateScoreDisplay(); tween(newFruit, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); return newFruit; }; self.playSoundEffect = function (soundId) { if (soundId) { LK.getSound(soundId).play(); } }; return self; }); var Line = Container.expand(function () { var self = Container.call(this); var lineGraphics = self.attachAsset('floor', { anchorX: 0.5, anchorY: 0.5 }); lineGraphics.tint = 0xff0000; lineGraphics.height = 20; return self; }); var MergeComponent = Container.expand(function () { var self = Container.call(this); self.merging = false; self.mergeGracePeriodActive = false; self.fromChargedRelease = false; self.fruitBehavior = new FruitBehavior(); self.beginMerge = function (fruit1, fruit2) { if (fruit1.merging) { return; } fruit1.merging = true; fruit2.merging = true; var midX = (fruit1.x + fruit2.x) / 2; var midY = (fruit1.y + fruit2.y) / 2; self.animateMerge(fruit1, fruit2, midX, midY); }; self.animateMerge = function (fruit1, fruit2, midX, midY) { tween(fruit1, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut }); tween(fruit2, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.completeMerge(fruit1, fruit2, midX, midY); } }); }; self.completeMerge = function (fruit1, fruit2, midX, midY) { LK.getSound('merge').play(); self.trackMerge(fruit1, fruit2); var behaviorHandler = self.fruitBehavior.getMergeHandler(fruit1.type.id); behaviorHandler.onMerge(fruit1, fruit2, midX, midY); if (fruit1 && fruits.includes(fruit1) && fruit1.type.id.toUpperCase() !== 'DURIAN') { removeFruitFromGame(fruit1); } if (fruit2 && fruits.includes(fruit2)) { removeFruitFromGame(fruit2); } }; self.trackMerge = function (fruit1, fruit2) { var fromReleasedFruits = fruit1.fromChargedRelease || fruit2.fromChargedRelease; var isPlayerDroppedFruitMerge = !fromReleasedFruits && (fruit1 === lastDroppedFruit || fruit2 === lastDroppedFruit) && !lastDroppedHasMerged; var fruitHasMergeGracePeriod = fruit1.mergeGracePeriodActive || fruit2.mergeGracePeriodActive; if (isPlayerDroppedFruitMerge || fruitHasMergeGracePeriod) { lastDroppedHasMerged = true; } }; return self; }); var PhysicsComponent = Container.expand(function () { var self = Container.call(this); self.vx = 0; self.vy = 0; self.gravity = 5.0; self.friction = 0.92; self.elasticity = 0.4; self.isStatic = false; self.rotation = 0; self.angularVelocity = 0; self.angularFriction = 0.85; self.groundAngularFriction = 0.6; self.maxAngularVelocity = 0.08; self.rotationRestCounter = 0; self.lastVx = 0; self.lastVy = 0; self.hasBounced = false; // Single flag to track if the fruit has bounced self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) { var stabilizationLevel = 0; var fruitLevel = getFruitLevel(fruit); if (!fruit._stabilizeMetrics) { fruit._stabilizeMetrics = { consecutiveSmallMovements: 0, wallContactDuration: 0, surroundedDuration: 0, restingDuration: 0 }; } // Simplified mass factor based directly on level var massFactor = fruitLevel * 0.18; // Simplified thresholds based directly on level var movementThreshold = 0.7 - fruitLevel * 0.08; var angularThreshold = 0.07 - fruitLevel * 0.006; // Apply general stuck detection for all fruits if (Math.abs(fruit.vx) < 0.1 && Math.abs(fruit.vy) < 0.1 && !fruit._boundaryContacts && (!fruit.neighborContacts || fruit.neighborContacts.length < 2)) { // Apply a small random impulse to any fruit that appears stuck in mid-air // Scale impulse based on fruit level var impulseStrength = 0.2 + fruitLevel * 0.04; fruit.vx += (Math.random() * 2 - 1) * impulseStrength; // Always ensure downward bias for mid-air fruits, scaled by fruit level var downwardBias = 0.05 + fruitLevel * 0.03; fruit.vy += Math.random() * 0.25 + downwardBias; } if (movementMagnitude < movementThreshold && Math.abs(fruit.angularVelocity) < angularThreshold) { // Simplified stabilization metrics - direct level-based adjustment var stabilizationRate = 1.5 + fruitLevel * 0.2; fruit._stabilizeMetrics.consecutiveSmallMovements += stabilizationRate; fruit._stabilizeMetrics.restingDuration += stabilizationRate; } else { fruit._stabilizeMetrics.consecutiveSmallMovements = 0; fruit._stabilizeMetrics.restingDuration = Math.max(0, fruit._stabilizeMetrics.restingDuration - 2); } if (fruit.wallContactFrames > 0) { // Simplified wall contact stabilization fruit._stabilizeMetrics.wallContactDuration += 1.2 + fruitLevel * 0.1; } else { fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1); } if (fruit.surroundedFrames > 0) { // Simplified surrounded stabilization fruit._stabilizeMetrics.surroundedDuration += 1.5 + fruitLevel * 0.15; } else { fruit._stabilizeMetrics.surroundedDuration = Math.max(0, fruit._stabilizeMetrics.surroundedDuration - 1); } // Simplified stabilization score calculation var levelFactor = fruitLevel * 0.1; var totalStabilizationScore = fruit._stabilizeMetrics.consecutiveSmallMovements * (1.0 + levelFactor) + fruit._stabilizeMetrics.wallContactDuration * (0.8 + levelFactor) + fruit._stabilizeMetrics.surroundedDuration * (1.2 + levelFactor) + fruit._stabilizeMetrics.restingDuration * (0.5 + levelFactor); // Check if fruit is in mid-air var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; // For mid-air fruits, drastically reduce stabilization score to prevent stabilization if (isMidAir) { // Cap stabilization score for mid-air fruits to prevent reaching stabilization levels totalStabilizationScore = Math.min(totalStabilizationScore, 3.0); } // Simplified stabilization thresholds based directly on level var fullStabilizationThreshold = 15 - fruitLevel * 0.8; var partialStabilizationThreshold = 8 - fruitLevel * 0.4; if (totalStabilizationScore > fullStabilizationThreshold) { stabilizationLevel = 2; } else if (totalStabilizationScore > partialStabilizationThreshold) { stabilizationLevel = 1; } if (stabilizationLevel > 0) { // Simplified damping factor calculation based directly on level var dampFactor = stabilizationLevel === 2 ? Math.max(0.4, 0.6 - fruitLevel * 0.03) : Math.max(0.6, 0.85 - fruitLevel * 0.02); fruit.vx *= dampFactor; fruit.vy *= dampFactor; fruit.angularVelocity *= dampFactor * 0.9; // Simplified stop threshold var stopThreshold = 0.15 - fruitLevel * 0.01; if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) { // Check if the fruit is in mid-air var isInMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; if (isInMidAir) { // Ensure minimal movement for all fruits in mid-air var minVx = (Math.random() - 0.5) * (0.2 + fruitLevel * 0.02); var minVy = 0.2 + fruitLevel * 0.05; // Strong downward movement fruit.vx = minVx; fruit.vy = minVy; return false; } else { fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; return true; } } } return false; }; self.apply = function (fruit) { if (fruit.isStatic || fruit.merging) { return; } fruit.lastVx = fruit.vx; fruit.lastVy = fruit.vy; var fruitLevel = getFruitLevel(fruit); // Check if fruit is in mid-air var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; var isStuck = Math.abs(fruit.vx) < 0.2 && Math.abs(fruit.vy) < 0.2; var isAtRest = Math.abs(fruit.vx) < 0.01 && Math.abs(fruit.vy) < 0.01; // Force movement for any stuck fruit in mid-air if ((isStuck || isAtRest) && isMidAir) { // Simplified: Apply consistent force based on level var forceMultiplier = 1.0 + fruitLevel * 0.2; fruit.vx += (Math.random() * 0.4 - 0.2) * forceMultiplier; // Ensure consistent downward movement for all fruits fruit.vy = 0.3 + fruitLevel * 0.08; } // Simplified gravity calculation using direct level multiplier var gravityMultiplier = 1 + fruitLevel * 0.4; fruit.vy += fruit.gravity * gravityMultiplier; // Simplified max velocity calculation - heavier fruits move slower var maxVelocity = 65 - fruitLevel * 5; if (Math.abs(fruit.vx) > maxVelocity) { fruit.vx = fruit.vx > 0 ? maxVelocity : -maxVelocity; } if (Math.abs(fruit.vy) > maxVelocity) { fruit.vy = fruit.vy > 0 ? maxVelocity : -maxVelocity; } // Simplified level-based adjustments // Add downward force based on level consistently fruit.vy += fruitLevel * 0.1; // Apply inertia based directly on level if (fruit.lastVx !== undefined && fruit.lastVy !== undefined) { // Calculate velocity changes var dvx = fruit.vx - fruit.lastVx; var dvy = fruit.vy - fruit.lastVy; // Apply inertia proportional to level var inertiaResistance = fruitLevel * 0.05; inertiaResistance = Math.min(0.9, inertiaResistance); // Cap at 0.9 fruit.vx = fruit.lastVx + dvx * (1 - inertiaResistance); fruit.vy = fruit.lastVy + dvy * (1 - inertiaResistance); } // Add consistent minimum falling speed for all fruits in mid-air if (isMidAir && fruit.vy < 0.5) { fruit.vy = Math.max(fruit.vy, 0.3 + fruitLevel * 0.05); } fruit.x += fruit.vx; fruit.y += fruit.vy; var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); var velocityChange = Math.abs(fruit.vx - fruit.lastVx) + Math.abs(fruit.vy - fruit.lastVy); var isFullyStabilized = self.stabilizeFruit(fruit, movementMagnitude, velocityChange); if (!isFullyStabilized) { if (movementMagnitude > 0.5 || velocityChange > 0.3) { // Simplified rotation calculation based directly on level var rotationFactor = 0.015 / (1 + fruitLevel * 0.1); var targetAngularVelocity = fruit.vx * rotationFactor; fruit.angularVelocity = fruit.angularVelocity * 0.8 + targetAngularVelocity * 0.2; } else { fruit.angularVelocity *= 0.8; } fruit.rotation += fruit.angularVelocity; // Simplified friction based directly on level var frictionModifier = 0.99 + fruitLevel * 0.003; fruit.vx *= fruit.friction * frictionModifier; fruit.vy *= fruit.friction * frictionModifier * 0.99; // Simplified stop thresholds based directly on level var stopThreshold = 0.15 - fruitLevel * 0.01; stopThreshold = Math.max(0.05, stopThreshold); if (Math.abs(fruit.vx) < stopThreshold) { fruit.vx = 0; } // Never stop vertical movement for fruits in mid-air if (Math.abs(fruit.vy) < stopThreshold && fruit.y > gameHeight - 300 && !isMidAir) { fruit.vy = 0; } self.handleRotationDamping(fruit); } }; self.handleRotationDamping = function (fruit) { var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); var fruitLevel = getFruitLevel(fruit); var isSmallFruit = fruitLevel <= 3; var isLargeFruit = fruitLevel >= 6; // More damping for larger fruits (less rotation) and less damping for small fruits (more rotation) var sizeBasedDamp = isSmallFruit ? -0.15 : isLargeFruit ? 0.25 : 0; var rotationDampFactor = 0.9; if (movementMagnitude < 0.05) { rotationDampFactor = 0.6 - sizeBasedDamp; } else if (movementMagnitude < 0.3) { rotationDampFactor = 0.7 - sizeBasedDamp; } else if (movementMagnitude < 0.5) { rotationDampFactor = 0.8 - sizeBasedDamp * 0.7; } else if (movementMagnitude < 0.8) { rotationDampFactor = 0.85 - sizeBasedDamp * 0.5; } fruit.angularVelocity *= rotationDampFactor; if (movementMagnitude > 0.3) { var targetDirection = fruit.vx > 0 ? 1 : -1; var currentDirection = fruit.angularVelocity > 0 ? 1 : -1; var correctionFactor = isSmallFruit ? 0.6 : 0.7; if (targetDirection !== currentDirection && Math.abs(fruit.vx) > 1.0) { fruit.angularVelocity *= correctionFactor; } } // Make small fruits come to rest more quickly var angularThreshold = isSmallFruit ? 0.012 : 0.008; var restFramesThreshold = isSmallFruit ? 4 : 6; // Enhanced damping for small fruits that have bounced if (fruit.hasBounced && isSmallFruit) { var bounceDampFactor = 0.6; fruit.angularVelocity *= bounceDampFactor; // Hard stop for bounced fruits with low angular velocity if (Math.abs(fruit.angularVelocity) < angularThreshold * 2) { fruit.angularVelocity = 0; } } if (Math.abs(fruit.angularVelocity) < angularThreshold) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > restFramesThreshold) { fruit.angularVelocity = 0; } } else { fruit.rotationRestCounter = 0; } // Limit max angular velocity slightly more for smaller fruits var maxAngularMultiplier = isSmallFruit ? 1.0 : 1.2; fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity * maxAngularMultiplier), fruit.maxAngularVelocity * maxAngularMultiplier); }; return self; }); var SpatialGrid = Container.expand(function (cellSize) { var self = Container.call(this); self.cellSize = cellSize || 200; self.grid = {}; self.lastRebuildTime = Date.now(); self.rebuildInterval = 60000; self.insertObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) { return; } obj._currentCells = obj._currentCells || []; var cells = self.getCellsForObject(obj); obj._currentCells = cells.slice(); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (!self.grid[cellKey]) { self.grid[cellKey] = []; } if (self.grid[cellKey].indexOf(obj) === -1) { self.grid[cellKey].push(obj); } } }; self.removeObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var cells = obj._currentCells || self.getCellsForObject(obj); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { var cellIndex = self.grid[cellKey].indexOf(obj); if (cellIndex !== -1) { self.grid[cellKey].splice(cellIndex, 1); } if (self.grid[cellKey].length === 0) { delete self.grid[cellKey]; } } } obj._currentCells = []; }; self.getCellsForObject = function (obj) { if (!obj || typeof obj.x !== 'number' || typeof obj.y !== 'number' || typeof obj.width !== 'number' || typeof obj.height !== 'number') { return []; } var cells = []; var halfWidth = obj.width / 2; var halfHeight = obj.height / 2; var minCellX = Math.floor((obj.x - halfWidth) / self.cellSize); var maxCellX = Math.floor((obj.x + halfWidth) / self.cellSize); var minCellY = Math.floor((obj.y - halfHeight) / self.cellSize); var maxCellY = Math.floor((obj.y + halfHeight) / self.cellSize); for (var cellX = minCellX; cellX <= maxCellX; cellX++) { for (var cellY = minCellY; cellY <= maxCellY; cellY++) { cells.push(cellX + "," + cellY); } } return cells; }; self.updateObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var newCells = self.getCellsForObject(obj); var oldCells = obj._currentCells || []; var cellsChanged = false; if (oldCells.length !== newCells.length) { cellsChanged = true; } else { for (var i = 0; i < newCells.length; i++) { if (oldCells.indexOf(newCells[i]) === -1) { cellsChanged = true; break; } } } if (cellsChanged) { self.removeObject(obj); self.insertObject(obj); } }; self.getPotentialCollisions = function (obj) { var candidates = []; var cells = self.getCellsForObject(obj); var addedObjects = {}; for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { for (var j = 0; j < self.grid[cellKey].length; j++) { var otherObj = self.grid[cellKey][j]; if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) { candidates.push(otherObj); addedObjects[otherObj.id] = true; } } } } return candidates; }; self.clear = function () { self.grid = {}; self.lastRebuildTime = Date.now(); }; self.rebuildGrid = function (allObjects) { self.grid = {}; self.lastRebuildTime = Date.now(); if (Array.isArray(allObjects)) { for (var i = 0; i < allObjects.length; i++) { if (allObjects[i] && !allObjects[i].merging && !allObjects[i].isStatic) { self.insertObject(allObjects[i]); } } } }; return self; }); var TrajectoryLine = Container.expand(function () { var self = Container.call(this); self.dotPool = new DotPool(100); self.activeDots = []; self.dots = []; self.dotSpacing = 10; self.dotSize = 15; self.maxDots = 100; self.createDots = function () { self.clearDots(); self.dotPool.initialize(self.maxDots); }; self.clearDots = function () { for (var i = 0; i < self.activeDots.length; i++) { if (self.activeDots[i]) { self.removeChild(self.activeDots[i]); self.dotPool.release(self.activeDots[i]); } } self.activeDots = []; }; self.updateTrajectory = function (startX, startY) { if (!activeFruit) { return; } self.clearDots(); var dotY = startY; var dotSpacing = 25; var dotCount = 0; var hitDetected = false; while (dotCount < self.maxDots && !hitDetected) { var dot = self.dotPool.get(); self.addChild(dot); self.activeDots.push(dot); dot.x = startX; dot.y = dotY; dot.visible = true; dot.alpha = 1.0; dotCount++; dotY += dotSpacing; var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; if (dotY > floorCollisionY) { if (dotCount > 0) { self.activeDots[dotCount - 1].y = floorCollisionY; } hitDetected = true; break; } var potentialHits = spatialGrid.getPotentialCollisions({ x: startX, y: dotY, width: activeFruit.width, height: activeFruit.height, id: 'trajectory_check' }); for (var j = 0; j < potentialHits.length; j++) { var fruit = potentialHits[j]; if (fruit && fruit !== activeFruit && !fruit.merging && fruit.width && fruit.height) { if (self.wouldIntersectFruit(fruit.x, fruit.y, startX, dotY, activeFruit, fruit)) { if (dotCount > 0) { var dx = fruit.x - startX; var dy = fruit.y - dotY; var dist = Math.sqrt(dx * dx + dy * dy); var overlap = activeFruit.width / 2 + fruit.width / 2 - dist; if (dist > 0) { self.activeDots[dotCount - 1].y = dotY - dy / dist * overlap; } } hitDetected = true; break; } } } } }; self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) { var dx = fruitX - dropX; var dy = fruitY - dropY; var distanceSquared = dx * dx + dy * dy; var activeFruitLevel = getFruitLevel(activeFruitObj); var targetFruitLevel = getFruitLevel(targetFruitObj); var activeFruitRadius = activeFruitObj.width / 2; var targetFruitRadius = targetFruitObj.width / 2; // Simplified hitbox reduction based directly on level var hitboxReduction = (Math.max(0, activeFruitLevel - 4) + Math.max(0, targetFruitLevel - 4)) * 2; var combinedRadii = activeFruitRadius + targetFruitRadius - hitboxReduction; var minDistanceSquared = combinedRadii * combinedRadii; if (distanceSquared < minDistanceSquared) { return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xffe122 }); /**** * Game Code ****/ var gameOverLine; var pineapple; var pineappleActive = false; var pineapplePushCount = 0; var readyToReleaseCharged = false; var trajectoryLine; var chargedFruitIconScale = 0.3; var isClickable = true; var evolutionLine; var fireContainer; var activeFireElements = []; var BASE_FIRES = 3; var FRUITS_PER_ADDITIONAL_FIRE = 3; var FIRE_Y_START_OFFSET = 50; var FIRE_Y_STACK_OFFSET = 100; var FIRE_X_SPREAD = 500; var MAX_FIRES = 15; var fruitLevels = { 'CHERRY': 1, 'GRAPE': 2, 'APPLE': 3, 'ORANGE': 4, 'WATERMELON': 5, 'PINEAPPLE': 6, 'MELON': 7, 'PEACH': 8, 'COCONUT': 9, 'DURIAN': 10 }; function getFruitLevel(fruit) { if (!fruit || !fruit.type || !fruit.type.id) { return 10; } return fruitLevels[fruit.type.id.toUpperCase()] || 10; } function releasePineappleOnMerge() { mergeCounter++; pushPineapple(); if (mergeCounter >= 15 && !pineappleActive && pineapple) { pineappleActive = true; pineapple.isStatic = false; applyDropPhysics(pineapple, 2.5); fruits.push(pineapple); if (spatialGrid) { spatialGrid.insertObject(pineapple); } LK.setTimeout(function () { if (pineapple && fruits.includes(pineapple)) { pineapple.immuneToGameOver = false; } }, 2000); setupPineapple(); mergeCounter = 0; } } var FruitTypes = { CHERRY: { id: 'cherry', size: 150, points: 1, next: 'grape' }, GRAPE: { id: 'grape', size: 200, points: 2, next: 'apple' }, APPLE: { id: 'apple', size: 250, points: 3, next: 'orange' }, ORANGE: { id: 'orange', size: 200, points: 5, next: 'watermelon' }, WATERMELON: { id: 'watermelon', size: 350, points: 8, next: 'pineapple' }, PINEAPPLE: { id: 'pineapple', size: 400, points: 13, next: 'melon' }, MELON: { id: 'melon', size: 450, points: 21, next: 'peach' }, PEACH: { id: 'peach', size: 500, points: 34, next: 'coconut' }, COCONUT: { id: 'coconut', size: 550, points: 55, next: 'durian' }, DURIAN: { id: 'durian', size: 600, points: 89, next: null } }; var gameWidth = 2048; var gameHeight = 2732; var fruits = []; var nextFruitType = null; var activeFruit = null; var wallLeft, wallRight, gameFloor; var dropPointY = 200; var gameOver = false; var scoreText; var isDragging = false; var chargedBallUI = null; var chargeCounter = 0; var mergeCounter = 0; var lastDroppedFruit = null; var lastDroppedHasMerged = false; var spatialGrid = null; function setupBoundaries() { wallLeft = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallLeft.x = 0; wallLeft.y = gameHeight / 2; wallLeft.alpha = 0; wallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallRight.x = gameWidth; wallRight.y = gameHeight / 2; wallRight.alpha = 0; gameFloor = game.addChild(LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5 })); gameFloor.x = gameWidth / 2; gameFloor.y = gameHeight; gameFloor.alpha = 0; gameOverLine = game.addChild(new Line()); gameOverLine.x = gameWidth / 2; gameOverLine.y = 550; gameOverLine.scaleX = 1; gameOverLine.scaleY = 0.2; gameOverLine.alpha = 1; } function createNextFruit() { var fruitProbability = Math.random(); var fruitType = fruitProbability < 0.6 ? FruitTypes.CHERRY : FruitTypes.GRAPE; nextFruitType = fruitType; activeFruit = new Fruit(nextFruitType); activeFruit.x = lastDroppedFruit && lastDroppedFruit.x ? lastDroppedFruit.x : gameWidth / 2; activeFruit.y = dropPointY + 200; activeFruit.isStatic = true; activeFruit.hasBounced = false; // Initialize bounce tracking with single flag game.addChild(activeFruit); if (trajectoryLine) { trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y); } } function dropFruit() { if (gameOver || !activeFruit || !isClickable) { return; } isClickable = false; LK.setTimeout(function () { isClickable = true; }, 300); activeFruit.isStatic = false; applyDropPhysics(activeFruit, 3.5); fruits.push(activeFruit); spatialGrid.insertObject(activeFruit); lastDroppedFruit = activeFruit; lastDroppedHasMerged = false; chargeCounter++; updateChargedBallDisplay(); if (chargeCounter >= 15 && !readyToReleaseCharged) { releaseChargedBalls(); } activeFruit.mergeGracePeriodActive = true; LK.setTimeout(function () { if (activeFruit && fruits.includes(activeFruit)) { activeFruit.mergeGracePeriodActive = false; } }, 2000); if (trajectoryLine && trajectoryLine.dots && trajectoryLine.dots.length) { for (var i = 0; i < trajectoryLine.dots.length; i++) { trajectoryLine.dots[i].visible = false; } } for (var i = 0; i < fruits.length; i++) { if (fruits[i] && fruits[i].fromChargedRelease) { fruits[i].fromChargedRelease = false; } } LK.getSound('drop').play(); if (readyToReleaseCharged && chargeCounter >= 15) { LK.getSound('pickleRick').play(); var orange = new Fruit(FruitTypes.ORANGE); var minX = wallLeft.x + wallLeft.width / 2 + orange.width / 2 + 50; var maxX = wallRight.x - wallRight.width / 2 - orange.width / 2 - 50; orange.x = minX + Math.random() * (maxX - minX); orange.y = -orange.height; orange.isStatic = false; var forceMultiplier = 3.5 + (Math.random() * 1 - 0.5); applyDropPhysics(orange, forceMultiplier); orange.fromChargedRelease = true; game.addChild(orange); fruits.push(orange); spatialGrid.insertObject(orange); chargeCounter = 0; resetChargedBalls(); readyToReleaseCharged = false; } activeFruit = null; createNextFruit(); } function applyDropPhysics(fruit, forceMultiplier) { var fruitLevel = getFruitLevel(fruit); // Simplify with direct level-based multiplier for force forceMultiplier *= 1.4; var levelAdjustedForce = forceMultiplier * (1 - fruitLevel * 0.05); // Apply simple random angle var angle = (Math.random() * 20 - 10) * (Math.PI / 180); fruit.vx = Math.sin(angle) * levelAdjustedForce; fruit.vy = Math.abs(Math.cos(angle) * levelAdjustedForce) * 1.5; // Simplified gravity calculation based only on level fruit.gravity = 13.0 * (1 + fruitLevel * 0.3); fruit.safetyPeriod = false; fruit.immuneToGameOver = true; LK.setTimeout(function () { if (fruit && fruits.includes(fruit)) { fruit.immuneToGameOver = false; } }, 1000); } function updateScoreDisplay() { scoreText.setText(LK.getScore()); } function setupUI() { scoreText = new Text2("0", { size: 120, fill: 0x000000 }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = 400; setupChargedBallDisplay(); } function setupChargedBallDisplay() { chargedBallUI = new ChargedBallUI(); chargedBallUI.initialize(); game.addChild(chargedBallUI); } function updateChargedBallDisplay() { chargedBallUI && chargedBallUI.updateChargeDisplay(chargeCounter); } function releaseChargedBalls() { readyToReleaseCharged = true; chargedBallUI && chargedBallUI.setReadyState(true); } function resetChargedBalls() { chargedBallUI && chargedBallUI.reset(); } function checkFruitCollisions() { for (var k = 0; k < fruits.length; k++) { if (fruits[k]) { fruits[k].neighboringFruits = 0; fruits[k].neighborContacts = fruits[k].neighborContacts || []; fruits[k].neighborContacts = []; } } outerLoop: for (var i = fruits.length - 1; i >= 0; i--) { var fruit1 = fruits[i]; if (!fruit1 || fruit1 === activeFruit || fruit1.merging || fruit1.isStatic) { continue; } var candidates = spatialGrid.getPotentialCollisions(fruit1); for (var j = 0; j < candidates.length; j++) { var fruit2 = candidates[j]; if (!fruit2 || fruit2 === activeFruit || fruit2.merging || fruit2.isStatic || fruit1 === fruit2) { continue; } if (fruits.indexOf(fruit2) === -1) { continue; } var dx = fruit2.x - fruit1.x; var dy = fruit2.y - fruit1.y; var distanceSquared = dx * dx + dy * dy; var distance = Math.sqrt(distanceSquared); var fruit1HalfWidth = fruit1.width / 2; var fruit1HalfHeight = fruit1.height / 2; var fruit2HalfWidth = fruit2.width / 2; var fruit2HalfHeight = fruit2.height / 2; var absDistanceX = Math.abs(dx); var absDistanceY = Math.abs(dy); var level1 = getFruitLevel(fruit1); var level2 = getFruitLevel(fruit2); var hitboxReduction = 0; if (level1 > 5) { hitboxReduction += (level1 - 5) * 3; } if (level2 > 5) { hitboxReduction += (level2 - 5) * 3; } var combinedHalfWidths = fruit1HalfWidth + fruit2HalfWidth - hitboxReduction / 2; var combinedHalfHeights = fruit1HalfHeight + fruit2HalfHeight - hitboxReduction / 2; var colliding = absDistanceX < combinedHalfWidths && absDistanceY < combinedHalfHeights; if (colliding) { if (fruit1.type === fruit2.type) { fruit1.merge(fruit2); continue outerLoop; } else { if (distance === 0) { distance = 0.1; dx = distance; dy = 0; } var overlap = combinedHalfWidths - absDistanceX; if (absDistanceY < combinedHalfHeights && absDistanceX < combinedHalfWidths) { overlap = Math.min(combinedHalfWidths - absDistanceX, combinedHalfHeights - absDistanceY); } var normalizeX = dx / distance; var normalizeY = dy / distance; var moveX = overlap / 2 * normalizeX; var moveY = overlap / 2 * normalizeY; var separationFactor = 1.05; fruit1.x -= moveX * separationFactor; fruit1.y -= moveY * separationFactor; fruit2.x += moveX * separationFactor; fruit2.y += moveY * separationFactor; var rvX = fruit2.vx - fruit1.vx; var rvY = fruit2.vy - fruit1.vy; var contactVelocity = rvX * normalizeX + rvY * normalizeY; if (contactVelocity < 0) { var collisionElasticity = Math.max(fruit1.elasticity, fruit2.elasticity); // If either fruit has already bounced, make them non-bouncy if (fruit1.hasBounced || fruit2.hasBounced) { collisionElasticity = 0; } var impulse = -(1 + collisionElasticity) * contactVelocity; var level1 = getFruitLevel(fruit1); var level2 = getFruitLevel(fruit2); var mass1 = Math.pow(level1, 3.0) * Math.pow(fruit1.type.size, 1.5); var mass2 = Math.pow(level2, 3.0) * Math.pow(fruit2.type.size, 1.5); var totalMass = mass1 + mass2; var impulseRatio1 = totalMass > 0 ? mass2 / totalMass : 0.5; var impulseRatio2 = totalMass > 0 ? mass1 / totalMass : 0.5; var levelDifference = Math.abs(level1 - level2); // Extreme impact multiplier based on level difference var levelImpactMultiplier = 1 + levelDifference * 0.15; // Higher maximum impulse ratio for more dramatic collisions var maxImpulseRatio = 3.0; if (level1 > level2) { // Larger fruit pushes smaller fruit strongly impulseRatio2 = Math.min(impulseRatio2 * levelImpactMultiplier * 2.5, maxImpulseRatio * impulseRatio2); // Drastically reduce how much smaller fruits move larger fruits impulseRatio1 *= Math.max(0.05, 1 / (1 + Math.pow(level1 - level2, 3.0) * 0.5)); } else if (level2 > level1) { // Larger fruit pushes smaller fruit strongly impulseRatio1 = Math.min(impulseRatio1 * levelImpactMultiplier * 2.5, maxImpulseRatio * impulseRatio1); // Drastically reduce how much smaller fruits move larger fruits impulseRatio2 *= Math.max(0.05, 1 / (1 + Math.pow(level2 - level1, 3.0) * 0.5)); } var velocityMagnitude = Math.sqrt(rvX * rvX + rvY * rvY); var velocityDampening = 1.0; if (velocityMagnitude > 5) { velocityDampening = 0.5 + 0.5 * (5 / velocityMagnitude); } var impulse1 = impulse * impulseRatio1 * velocityDampening; var impulse2 = impulse * impulseRatio2 * velocityDampening; var sizeDifference = Math.abs(fruit1.type.size - fruit2.type.size) / Math.max(fruit1.type.size, fruit2.type.size, 1); var currentTime = Date.now(); // Only allow a bounce on initial contact if (!fruit1.hasBounced || !fruit2.hasBounced) { if (!fruit1.hasBounced && velocityMagnitude > 0.5) { fruit1.hasBounced = true; LK.getSound('bounce').play(); } if (!fruit2.hasBounced && velocityMagnitude > 0.5) { fruit2.hasBounced = true; LK.getSound('bounce').play(); } } // Apply consistent bounce damping that doesn't depend on bounce count var bounce1Damping = fruit1.hasBounced ? 0.3 : 1.0; var bounce2Damping = fruit2.hasBounced ? 0.3 : 1.0; // Dramatically enhanced size-based bouncing effect if (fruit1.type.size < fruit2.type.size) { // Smaller fruit bounces much more aggressively against larger fruit var levelDiffBonus = Math.pow(level2 - level1, 1.2); impulse1 *= (1 + sizeDifference * 6.5 + levelDiffBonus * 0.3) * bounce1Damping; fruit1._lastBounceTime = currentTime; // Apply extremely strong upward bounce for small fruits to prevent them from sinking if (level1 < level2) { // Scale upward force exponentially based on level difference var levelRatio = level2 / level1; var upwardForce = Math.min(12.0, (level2 - level1) * 2.2 * Math.pow(levelRatio, 1.5)) * bounce1Damping; // Massive extra upward force when small fruit is below larger fruit if (fruit1.y > fruit2.y) { upwardForce *= 2.5 + (level2 - level1) * 0.25; } fruit1.vy -= upwardForce; // Apply extreme horizontal resistance for large fruits (almost impossible to push sideways) if (level2 >= 6) { var horizontalResistance = Math.min(0.9, (level2 - 5) * 0.2); fruit2.vx *= 1 - horizontalResistance; } } } else if (fruit2.type.size < fruit1.type.size) { // Smaller fruit bounces much more aggressively against larger fruit var levelDiffBonus = Math.pow(level1 - level2, 1.2); impulse2 *= (1 + sizeDifference * 6.5 + levelDiffBonus * 0.3) * bounce2Damping; fruit2._lastBounceTime = currentTime; // Apply extremely strong upward bounce for small fruits to prevent them from sinking if (level2 < level1) { // Scale upward force exponentially based on level difference var levelRatio = level1 / level2; var upwardForce = Math.min(12.0, (level1 - level2) * 2.2 * Math.pow(levelRatio, 1.5)) * bounce2Damping; // Massive extra upward force when small fruit is below larger fruit if (fruit2.y > fruit1.y) { upwardForce *= 2.5 + (level1 - level2) * 0.25; } fruit2.vy -= upwardForce; // Apply extreme horizontal resistance for large fruits (almost impossible to push sideways) if (level1 >= 6) { var horizontalResistance = Math.min(0.9, (level1 - 5) * 0.2); fruit1.vx *= 1 - horizontalResistance; } } } // Apply strong damping if fruit has already bounced var dampFactor1 = fruit1.hasBounced ? 0.3 : 1.0; var dampFactor2 = fruit2.hasBounced ? 0.3 : 1.0; fruit1.vx -= impulse1 * normalizeX * dampFactor1; fruit1.vy -= impulse1 * normalizeY * dampFactor1; fruit2.vx += impulse2 * normalizeX * dampFactor2; fruit2.vy += impulse2 * normalizeY * dampFactor2; // If already bounced, apply additional friction to quickly stop movement if (fruit1.hasBounced) { fruit1.vx *= 0.7; fruit1.vy *= 0.7; fruit1.angularVelocity *= 0.5; } if (fruit2.hasBounced) { fruit2.vx *= 0.7; fruit2.vy *= 0.7; fruit2.angularVelocity *= 0.5; } var tangentX = -normalizeY; var tangentY = normalizeX; var tangentVelocity = rvX * tangentX + rvY * tangentY; var frictionImpulse = -tangentVelocity * 0.1; fruit1.vx -= frictionImpulse * tangentX * impulseRatio1; fruit1.vy -= frictionImpulse * tangentY * impulseRatio1; fruit2.vx += frictionImpulse * tangentX * impulseRatio2; fruit2.vy += frictionImpulse * tangentY * impulseRatio2; var rotationTransferFactor = 0.01; var tangentialComponent = rvX * tangentX + rvY * tangentY; var inertia1 = mass1 * Math.pow(fruit1.width / 2, 2); var inertia2 = mass2 * Math.pow(fruit2.width / 2, 2); var angularImpulse = tangentialComponent * rotationTransferFactor; fruit1.angularVelocity += inertia2 > 0 ? angularImpulse * (inertia1 / (inertia1 + inertia2)) : angularImpulse * 0.5; fruit2.angularVelocity -= inertia1 > 0 ? angularImpulse * (inertia2 / (inertia1 + inertia2)) : angularImpulse * 0.5; fruit1.angularVelocity *= 0.95; fruit2.angularVelocity *= 0.95; fruit1.angularVelocity = Math.min(Math.max(fruit1.angularVelocity, -fruit1.maxAngularVelocity), fruit1.maxAngularVelocity); fruit2.angularVelocity = Math.min(Math.max(fruit2.angularVelocity, -fruit2.maxAngularVelocity), fruit2.maxAngularVelocity); fruit1.neighboringFruits = (fruit1.neighboringFruits || 0) + 1; fruit2.neighboringFruits = (fruit2.neighboringFruits || 0) + 1; fruit1.neighborContacts = fruit1.neighborContacts || []; fruit2.neighborContacts = fruit2.neighborContacts || []; if (fruit1.neighborContacts.indexOf(fruit2.id) === -1) { fruit1.neighborContacts.push(fruit2.id); } if (fruit2.neighborContacts.indexOf(fruit1.id) === -1) { fruit2.neighborContacts.push(fruit1.id); } var currentTime = Date.now(); fruit1._collisionHistory = fruit1._collisionHistory || {}; fruit2._collisionHistory = fruit2._collisionHistory || {}; var recentCollision = false; if (fruit1._collisionHistory[fruit2.id] && currentTime - fruit1._collisionHistory[fruit2.id] < 300) { recentCollision = true; } fruit1._collisionHistory[fruit2.id] = currentTime; fruit2._collisionHistory[fruit1.id] = currentTime; // Apply stronger damping for fruits that have already bounced var extraDamp1 = fruit1.hasBounced ? 0.5 : 0; var extraDamp2 = fruit2.hasBounced ? 0.5 : 0; if (recentCollision || fruit1.bounceCount > 3 || fruit2.bounceCount > 3) { // Calculate damping factors based on fruit levels var level1DampFactor = 0.6 + (level1 - 1) * 0.04; // Higher damping for larger fruits var level2DampFactor = 0.6 + (level2 - 1) * 0.04; // Apply stronger damping to larger fruits and fruits that have bounced a lot var fruit1Damp = level1 > level2 ? 0.5 : 0.7; var fruit2Damp = level2 > level1 ? 0.5 : 0.7; // Extra damping for fruits that have bounced too much fruit1Damp *= 1 - extraDamp1; fruit2Damp *= 1 - extraDamp2; fruit1.vx *= fruit1Damp; fruit1.vy *= fruit1Damp; fruit2.vx *= fruit2Damp; fruit2.vy *= fruit2Damp; // Stronger angular damping for smaller fruits to reduce wiggling var angular1Damp = level1 <= 3 ? 0.45 : 0.65; var angular2Damp = level2 <= 3 ? 0.45 : 0.65; // Extra angular damping for fruits that have bounced too much angular1Damp *= 1 - extraDamp1 * 0.5; angular2Damp *= 1 - extraDamp2 * 0.5; fruit1.angularVelocity *= angular1Damp; fruit2.angularVelocity *= angular2Damp; // Hard stop for small fruits that have already bounced if (level1 <= 3 && fruit1.hasBounced && Math.abs(fruit1.vx) < 1.0 && Math.abs(fruit1.vy) < 1.0) { fruit1.vx *= 0.2; fruit1.vy *= 0.2; fruit1.angularVelocity *= 0.2; } if (level2 <= 3 && fruit2.hasBounced && Math.abs(fruit2.vx) < 1.0 && Math.abs(fruit2.vy) < 1.0) { fruit2.vx *= 0.2; fruit2.vy *= 0.2; fruit2.angularVelocity *= 0.2; } } if (fruit1.neighborContacts.length >= 3 || fruit2.neighborContacts.length >= 3) { var stabilizeFactor = 0.6; fruit1.vx *= stabilizeFactor; fruit1.vy *= stabilizeFactor; fruit2.vx *= stabilizeFactor; fruit2.vy *= stabilizeFactor; fruit1.angularVelocity *= 0.75; fruit2.angularVelocity *= 0.75; } // Stop vibrating small fruits that have already bounced var isVibrating1 = fruit1.hasBounced && Math.abs(fruit1.vx) > 0.2 && Math.abs(fruit1.vy) > 0.2 && level1 <= 3; var isVibrating2 = fruit2.hasBounced && Math.abs(fruit2.vx) > 0.2 && Math.abs(fruit2.vy) > 0.2 && level2 <= 3; // Apply hard stop to vibrating fruits if (isVibrating1 || isVibrating2) { var hardStopFactor = 0.1; if (isVibrating1) { fruit1.vx *= hardStopFactor; fruit1.vy *= hardStopFactor; fruit1.angularVelocity *= hardStopFactor; // Complete stop for slow-moving small fruits if (Math.abs(fruit1.vx) < 0.7 && Math.abs(fruit1.vy) < 0.7) { fruit1.vx = 0; fruit1.vy = 0; fruit1.angularVelocity = 0; } } if (isVibrating2) { fruit2.vx *= hardStopFactor; fruit2.vy *= hardStopFactor; fruit2.angularVelocity *= hardStopFactor; // Complete stop for slow-moving small fruits if (Math.abs(fruit2.vx) < 0.7 && Math.abs(fruit2.vy) < 0.7) { fruit2.vx = 0; fruit2.vy = 0; fruit2.angularVelocity = 0; } } } } } } } } } function checkGameOver() { if (gameOver) { return; } for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit || fruit === activeFruit || fruit.merging || fruit.isStatic) { continue; } var fruitHalfHeight = fruit.height / 2; var fruitHalfWidth = fruit.width / 2; var fruitLevel = getFruitLevel(fruit); // Simplified size reduction based directly on level var sizeReduction = Math.max(0, fruitLevel - 4) * 2; fruitHalfHeight = Math.max(10, fruitHalfHeight - sizeReduction / 2); fruitHalfWidth = Math.max(10, fruitHalfWidth - sizeReduction / 2); var cosAngle = Math.abs(Math.cos(fruit.rotation)); var sinAngle = Math.abs(Math.sin(fruit.rotation)); var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle; var fruitTopY = fruit.y - effectiveHeight; var lineBottomY = gameOverLine.y + gameOverLine.height / 2; if (fruitTopY <= lineBottomY) { var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle; var fruitLeftX = fruit.x - effectiveWidth; var fruitRightX = fruit.x + effectiveWidth; var lineLeftX = gameOverLine.x - gameOverLine.width * gameOverLine.scaleX / 2; var lineRightX = gameOverLine.x + gameOverLine.width * gameOverLine.scaleX / 2; var horizontalOverlap = !(fruitRightX < lineLeftX || fruitLeftX > lineRightX); if (horizontalOverlap) { if (fruit.immuneToGameOver) { continue; } var stableOrSlowing = Math.abs(fruit.vy) < 1.0 || fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor); if (stableOrSlowing) { gameOver = true; LK.showGameOver(); return; } } } else { fruit.safetyPeriod = undefined; } } } function setupPineapple() { pineapple = new Fruit(FruitTypes.PINEAPPLE); pineapple.x = -pineapple.width / 2; pineapple.y = 200; pineapple.isStatic = true; pineappleActive = false; pineapplePushCount = 0; game.addChild(pineapple); } function pushPineapple() { if (!pineappleActive && pineapple) { var step = mergeCounter; var totalSteps = 15; var percentage = Math.min(step / totalSteps, 1.0); var startPos = -pineapple.width / 2; var endPos = gameWidth * 0.16; var newX = startPos + percentage * (endPos - startPos); tween(pineapple, { x: newX }, { duration: 300, easing: tween.bounceOut }); } } function initGame() { LK.setScore(0); gameOver = false; fruits = []; chargeCounter = 0; if (chargedBallUI) { chargedBallUI.destroy(); } chargedBallUI = null; readyToReleaseCharged = false; lastScoreCheckForCoconut = 0; lastDroppedFruit = null; lastDroppedHasMerged = false; mergeCounter = 0; isClickable = true; // Initialize fire background if (fireContainer) { // Clean up existing fire elements for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) { activeFireElements[i].destroy(); } } fireContainer.destroy(); } fireContainer = new Container(); game.addChildAt(fireContainer, 0); // Add at bottom layer activeFireElements = []; if (spatialGrid) { spatialGrid.clear(); } else { var avgFruitSize = 0; var fruitCount = 0; for (var fruitType in FruitTypes) { if (FruitTypes.hasOwnProperty(fruitType)) { avgFruitSize += FruitTypes[fruitType].size; fruitCount++; } } avgFruitSize = fruitCount > 0 ? avgFruitSize / fruitCount : 300; var optimalCellSize = Math.ceil(avgFruitSize * 1.1); spatialGrid = new SpatialGrid(optimalCellSize); } LK.playMusic('bgmusic'); if (wallLeft) { wallLeft.destroy(); } if (wallRight) { wallRight.destroy(); } if (gameFloor) { gameFloor.destroy(); } if (gameOverLine) { gameOverLine.destroy(); } if (pineapple) { pineapple.destroy(); } if (trajectoryLine) { trajectoryLine.destroy(); } if (evolutionLine) { evolutionLine.destroy(); } setupBoundaries(); setupUI(); setupPineapple(); updateFireBackground(); // Initial setup of fire elements trajectoryLine = game.addChild(new TrajectoryLine()); trajectoryLine.createDots(); evolutionLine = game.addChild(new EvolutionLine()); evolutionLine.initialize(); updateScoreDisplay(); activeFruit = null; createNextFruit(); resetChargedBalls(); updateFireBackground(); // Initial setup of fire elements } function spawnCoconut() { var coconut = new Fruit(FruitTypes.COCONUT); var minX = wallLeft.x + wallLeft.width / 2 + coconut.width / 2 + 50; var maxX = wallRight.x - wallRight.width / 2 - coconut.width / 2 - 50; coconut.x = minX + Math.random() * (maxX - minX); coconut.y = gameHeight + coconut.height / 2; coconut.isStatic = true; game.addChild(coconut); fruits.push(coconut); coconut.safetyPeriod = false; coconut.immuneToGameOver = true; var targetY = gameHeight - gameFloor.height / 2 - coconut.height / 2 - 10; tween(coconut, { y: targetY }, { duration: 1200, easing: tween.easeIn, onFinish: function onFinish() { if (!coconut || !fruits.includes(coconut)) { return; } coconut.isStatic = false; coconut.vy = -2; coconut.vx = (Math.random() * 2 - 1) * 1.5; spatialGrid.insertObject(coconut); LK.setTimeout(function () { if (coconut && fruits.includes(coconut)) { coconut.immuneToGameOver = false; } }, 1000); } }); } var lastScoreCheckForCoconut = 0; game.down = function (x, y) { if (activeFruit && !gameOver) { isDragging = true; game.move(x, y); } }; game.move = function (x, y) { if (isDragging && activeFruit && !gameOver) { var fruitRadius = activeFruit.width / 2; var minX = wallLeft.x + wallLeft.width / 2 + fruitRadius; var maxX = wallRight.x - wallRight.width / 2 - fruitRadius; activeFruit.x = Math.max(minX, Math.min(maxX, x)); activeFruit.y = dropPointY + 200; if (trajectoryLine) { trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y); } } }; game.up = function () { if (isDragging && activeFruit && isClickable && !gameOver) { dropFruit(); } isDragging = false; }; function updatePhysics() { if (spatialGrid && Date.now() - spatialGrid.lastRebuildTime > spatialGrid.rebuildInterval) { spatialGrid.rebuildGrid(fruits); } // Check for and fix stuck fruits checkAndFixStuckFruits(); for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (!fruit || fruit.isStatic || fruit.merging) { continue; } fruit.updatePhysics(); var walls = { left: wallLeft, right: wallRight }; fruit.checkBoundaries(walls, gameFloor); } checkFruitCollisions(); for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (fruit && !fruit.isStatic && !fruit.merging) { if (fruit.surroundedFrames === undefined) { fruit.surroundedFrames = 0; } var boundaryContact = fruit.wallContactFrames > 0; var floorProximity = fruit.y + fruit.height / 2 >= gameFloor.y - gameFloor.height / 2 - 10; var hasMultipleNeighbors = fruit.neighborContacts && fruit.neighborContacts.length >= 2; var isSlowMoving = Math.abs(fruit.vx) < 0.3 && Math.abs(fruit.vy) < 0.3; var hasGapBelow = false; if (floorProximity || hasMultipleNeighbors) { var potentialNeighbors = spatialGrid.getPotentialCollisions({ x: fruit.x, y: fruit.y + fruit.height / 2 + 20, width: fruit.width * 0.6, height: 20, id: 'gap_check_' + fruit.id }); hasGapBelow = potentialNeighbors.length === 0 && !floorProximity; if (hasGapBelow && isSlowMoving) { var leftCount = 0, rightCount = 0; for (var j = 0; j < fruit.neighborContacts.length; j++) { var neighborId = fruit.neighborContacts[j]; for (var k = 0; k < fruits.length; k++) { if (fruits[k] && fruits[k].id === neighborId) { if (fruits[k].x < fruit.x) { leftCount++; } else if (fruits[k].x > fruit.x) { rightCount++; } break; } } } if (leftCount < rightCount) { fruit.vx -= 0.05; } else if (rightCount < leftCount) { fruit.vx += 0.05; } else { fruit.vx += Math.random() * 0.1 - 0.05; } } } var stabilizationScenario = boundaryContact ? "boundary" : floorProximity ? "floor" : hasMultipleNeighbors ? "surrounded" : "free"; var fruitLevel = getFruitLevel(fruit); var isSmallFruit = fruitLevel <= 3; switch (stabilizationScenario) { case "boundary": fruit.surroundedFrames = 0; break; case "floor": fruit.surroundedFrames = 0; break; case "surrounded": fruit.surroundedFrames++; // Much stronger stabilization for smaller fruits to reduce vibration var sizeBonus = isSmallFruit ? 0.15 : fruitLevel >= 7 ? -0.05 : 0; var stabilizationStrength = Math.min(0.95, 0.75 + fruit.surroundedFrames * (0.022 + sizeBonus)); // Additional stabilization boost when a fruit has multiple neighbors if (fruit.neighborContacts.length >= 3) { var contactBonus = isSmallFruit ? 0.1 : 0.03; stabilizationStrength = Math.min(0.98, stabilizationStrength + contactBonus); } // Apply even stronger damping to angular velocity for small fruits var angularStabilization = isSmallFruit ? stabilizationStrength * 0.9 : stabilizationStrength; fruit.vx *= stabilizationStrength; fruit.vy *= stabilizationStrength; fruit.angularVelocity *= angularStabilization; // Small fruits settle faster when surrounded var thresholdFrames = isSmallFruit ? 8 : 10; var thresholdFramesForced = isSmallFruit ? 15 : 18; if (fruit.surroundedFrames > thresholdFrames && isSlowMoving || fruit.surroundedFrames > thresholdFramesForced) { // Use larger threshold for small fruits to help them settle faster var velocityThreshold = isSmallFruit ? 0.08 : 0.05; var angularThreshold = isSmallFruit ? 0.008 : 0.005; if (Math.abs(fruit.vx) < velocityThreshold) { fruit.vx = 0; } if (Math.abs(fruit.vy) < velocityThreshold) { fruit.vy = 0; } if (Math.abs(fruit.angularVelocity) < angularThreshold) { fruit.angularVelocity = 0; } } break; case "free": fruit.surroundedFrames = 0; break; } spatialGrid.updateObject(fruit); } } } game.update = function () { if (gameOver) { return; } var currentScore = LK.getScore(); if (currentScore >= lastScoreCheckForCoconut + 1000) { lastScoreCheckForCoconut = Math.floor(currentScore / 1000) * 1000; spawnCoconut(); } else { if (currentScore > lastScoreCheckForCoconut) { lastScoreCheckForCoconut = Math.floor(currentScore / 1000) * 1000; } } updatePhysics(); checkGameOver(); // Update fire background updateFireBackground(); // Animate individual fire elements for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) { activeFireElements[i].update(); } } }; initGame(); function updateFireBackground() { // Track unique fruit types on the board var uniqueFruitTypes = {}; for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (fruit && !fruit.isStatic && !fruit.merging && fruit.type && fruit.type.id) { uniqueFruitTypes[fruit.type.id] = true; } } // Count the number of unique fruit types var uniqueTypeCount = Object.keys(uniqueFruitTypes).length; // Calculate target number of fire elements: BASE_FIRES + one for each unique fruit type var targetFireCount = BASE_FIRES + uniqueTypeCount; targetFireCount = Math.min(targetFireCount, MAX_FIRES); // Create or remove fires to match target count if (targetFireCount > activeFireElements.length) { // Need to add more fires var newFiresCount = targetFireCount - activeFireElements.length; for (var i = 0; i < newFiresCount; i++) { // Create fire at appropriate position createFireElement(activeFireElements.length); } } else if (targetFireCount < activeFireElements.length) { // Need to remove excess fires var fireToRemoveCount = activeFireElements.length - targetFireCount; for (var i = 0; i < fireToRemoveCount; i++) { var fire = activeFireElements.pop(); // Remove from end (highest/last added fires) if (fire) { fire.destroy(); // Ensure destroy() is called to clean up resources fireContainer.removeChild(fire); } } } } // Helper function to create a single fire element at the specified index function createFireElement(index) { // Calculate y position (higher fires appear behind/further back) var yPos = gameHeight + FIRE_Y_START_OFFSET - index * FIRE_Y_STACK_OFFSET; // Randomize x position around center of screen var xPos = gameWidth / 2 + (Math.random() * FIRE_X_SPREAD - FIRE_X_SPREAD / 2); // Create new fire element var newFire = new FireElement(xPos, yPos); // Alternate x-axis flip for all fires including the first three if (index % 2 === 1) { newFire.fireAsset.scaleX = -1; // Flip every other fire } // Add at index 0 to render behind existing fires fireContainer.addChildAt(newFire, 0); activeFireElements.push(newFire); } function removeFruitFromGame(fruit) { var index = fruits.indexOf(fruit); if (index !== -1) { fruits.splice(index, 1); } spatialGrid.removeObject(fruit); fruit.destroy(); } function checkAndFixStuckFruits() { // Current time to track stuck duration var currentTime = Date.now(); for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit || fruit.isStatic || fruit.merging) { continue; } // Create tracking object if it doesn't exist if (!fruit._stuckTracking) { fruit._stuckTracking = { lastPosition: { x: fruit.x, y: fruit.y }, stuckTime: 0, lastCheck: currentTime, lastForceApplied: 0 }; } // Distance moved since last check var dx = fruit.x - fruit._stuckTracking.lastPosition.x; var dy = fruit.y - fruit._stuckTracking.lastPosition.y; var distMoved = Math.sqrt(dx * dx + dy * dy); // Time since last check var timeDelta = currentTime - fruit._stuckTracking.lastCheck; // Update tracking fruit._stuckTracking.lastPosition.x = fruit.x; fruit._stuckTracking.lastPosition.y = fruit.y; fruit._stuckTracking.lastCheck = currentTime; var fruitLevel = getFruitLevel(fruit); // Determine if the fruit is stuck var isOnFloor = fruit._boundaryContacts && fruit._boundaryContacts.floor; var isAtWall = fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right); var isMidAir = !isOnFloor && !isAtWall; // Enhanced stuck detection - more sensitive for mid-air fruits var movementThreshold = isMidAir ? 0.3 : 0.5; var velocityThreshold = isMidAir ? 0.2 : 0.4; var isLowVelocity = Math.abs(fruit.vx) < velocityThreshold && Math.abs(fruit.vy) < velocityThreshold; // Check if fruit is stuck or hovering in mid-air if ((distMoved < movementThreshold || isLowVelocity) && isMidAir) { fruit._stuckTracking.stuckTime += timeDelta; // Simplified stuck time threshold based directly on level var stuckTimeThreshold = 1000 - fruitLevel * 100; stuckTimeThreshold = Math.max(300, stuckTimeThreshold); // Simplified force interval based directly on level var forceIntervalThreshold = 800 - fruitLevel * 80; forceIntervalThreshold = Math.max(300, forceIntervalThreshold); // If stuck for too long and not been nudged recently if (fruit._stuckTracking.stuckTime > stuckTimeThreshold && currentTime - fruit._stuckTracking.lastForceApplied > forceIntervalThreshold) { // Simplified force multiplier based directly on level var forceMultiplier = 1.0 + fruitLevel * 0.3; forceMultiplier = Math.min(4.0, forceMultiplier); // Ensure consistent downward momentum for all fruits fruit.vy = Math.max(fruit.vy, 0.5 + fruitLevel * 0.2); // Apply consistent force with downward bias fruit.vx += (Math.random() * 2.0 - 1.0) * forceMultiplier; fruit.vy += (Math.random() * 1.0 + 1.0) * forceMultiplier; // Reset stuck tracking fruit._stuckTracking.stuckTime = 0; fruit._stuckTracking.lastForceApplied = currentTime; } } else { // Fruit is moving or touching boundaries, reset stuck counter fruit._stuckTracking.stuckTime = 0; } // Special handling for fruits with zero velocity in mid-air if (isMidAir && Math.abs(fruit.vx) < 0.01 && Math.abs(fruit.vy) < 0.01) { // Force minimal downward movement for zero-velocity fruits based on level fruit.vy = Math.max(0.5, fruitLevel * 0.15); fruit.vx += (Math.random() - 0.5) * 0.2; } } }
===================================================================
--- original.js
+++ change.js
@@ -717,8 +717,15 @@
}
// Simplified stabilization score calculation
var levelFactor = fruitLevel * 0.1;
var totalStabilizationScore = fruit._stabilizeMetrics.consecutiveSmallMovements * (1.0 + levelFactor) + fruit._stabilizeMetrics.wallContactDuration * (0.8 + levelFactor) + fruit._stabilizeMetrics.surroundedDuration * (1.2 + levelFactor) + fruit._stabilizeMetrics.restingDuration * (0.5 + levelFactor);
+ // Check if fruit is in mid-air
+ var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right;
+ // For mid-air fruits, drastically reduce stabilization score to prevent stabilization
+ if (isMidAir) {
+ // Cap stabilization score for mid-air fruits to prevent reaching stabilization levels
+ totalStabilizationScore = Math.min(totalStabilizationScore, 3.0);
+ }
// Simplified stabilization thresholds based directly on level
var fullStabilizationThreshold = 15 - fruitLevel * 0.8;
var partialStabilizationThreshold = 8 - fruitLevel * 0.4;
if (totalStabilizationScore > fullStabilizationThreshold) {