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; // Start invisible 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); var sizeReduction = 0; if (fruitLevel > 5) { sizeReduction = (fruitLevel - 5) * 3; } 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; var wasInContact = fruit.wallContactFrames > 0; 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++; 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; // Only bounce if this is the first bounce if (!fruit.hasBounced) { fruit.vx = -fruit.vx * fruit.elasticity * 0.7; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vx = 0; // Stop movement immediately after first bounce } 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; // Only bounce if this is the first bounce if (!fruit.hasBounced) { fruit.vx = -fruit.vx * fruit.elasticity * 0.7; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vx = 0; // Stop movement immediately after first bounce } 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; var oldVy = fruit.vy; // Only bounce if this is the first bounce if (!fruit.hasBounced) { fruit.vy = -fruit.vy * fruit.elasticity * 0.5; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vy = 0; // Stop vertical movement immediately after first bounce } 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; 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 shouldStabilize = false; var stabilizationLevel = 0; var fruitLevel = getFruitLevel(fruit); if (!fruit._stabilizeMetrics) { fruit._stabilizeMetrics = { consecutiveSmallMovements: 0, wallContactDuration: 0, surroundedDuration: 0, restingDuration: 0 }; } var massFactor = Math.pow(fruitLevel, 1.8) / 10; // Increased power to amplify mass difference // Lower thresholds for large fruits (harder to move) and higher for small fruits (easier to move) var movementThreshold = 0.7 - (fruitLevel - 1) * 0.08; // Make large fruits stabilize their rotation more quickly var angularThreshold = 0.07 - (fruitLevel - 1) * 0.006; // Specific oranges fix - add periodic small impulse for mid-air stuck fruit if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE' && 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 oranges that appear stuck fruit.vx += Math.random() * 0.4 - 0.2; fruit.vy += Math.random() * 0.3 - 0.05; // Bias slightly downward } if (movementMagnitude < movementThreshold && Math.abs(fruit.angularVelocity) < angularThreshold) { // Small fruits take longer to stabilize, while large fruits stabilize faster var smallFruitBonus = fruitLevel <= 3 ? 0.3 : 0; var largeFruitBonus = fruitLevel >= 6 ? (fruitLevel - 5) * 0.4 : 0; fruit._stabilizeMetrics.consecutiveSmallMovements += 1.5 + massFactor * 0.3 + largeFruitBonus - smallFruitBonus; fruit._stabilizeMetrics.restingDuration += 1.5 + massFactor * 0.3 + largeFruitBonus - smallFruitBonus; } else { fruit._stabilizeMetrics.consecutiveSmallMovements = 0; fruit._stabilizeMetrics.restingDuration = Math.max(0, fruit._stabilizeMetrics.restingDuration - 2); } if (fruit.wallContactFrames > 0) { // Increase wall contact stabilization for smaller fruits var wallContactBonus = fruitLevel <= 3 ? 0.4 : 0; fruit._stabilizeMetrics.wallContactDuration += 1.2 + massFactor * 0.15 + wallContactBonus; } else { fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1); } if (fruit.surroundedFrames > 0) { // Increase surrounded stabilization for smaller fruits var surroundedBonus = fruitLevel <= 3 ? 0.6 : 0; fruit._stabilizeMetrics.surroundedDuration += 1.5 + massFactor * 0.25 + surroundedBonus; } else { fruit._stabilizeMetrics.surroundedDuration = Math.max(0, fruit._stabilizeMetrics.surroundedDuration - 1); } var levelFactor = (fruitLevel - 1) * 0.12; 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); // Lower stabilization thresholds for small fruits to make them stabilize more quickly var sizeAdjustment = fruitLevel <= 3 ? 3 : 0; var fullStabilizationThreshold = 15 - (fruitLevel - 1) * 1.0 - sizeAdjustment; var partialStabilizationThreshold = 8 - (fruitLevel - 1) * 0.6 - sizeAdjustment * 0.5; if (totalStabilizationScore > fullStabilizationThreshold) { stabilizationLevel = 2; } else if (totalStabilizationScore > partialStabilizationThreshold) { stabilizationLevel = 1; } if (stabilizationLevel > 0) { // Larger fruits stabilize more quickly (harder to move) var baseDampFactor = stabilizationLevel === 2 ? 0.6 : 0.85; // Larger fruits have stronger mass-based damping var massDampFactor = baseDampFactor - massFactor * 0.04; // Make it harder for larger fruits to be displaced var dampFactor = Math.max(0.4, massDampFactor - (fruitLevel - 1) * 0.03); // Apply stabilization differently based on size if (fruitLevel <= 3) { // Small fruits stabilize faster to reduce vibration dampFactor = Math.min(dampFactor * 0.85, 0.85); } else if (fruitLevel >= 7) { // Very large fruits (levels 7+) are more stable, harder to move dampFactor = Math.max(0.3, dampFactor - 0.1); } fruit.vx *= dampFactor; fruit.vy *= dampFactor; fruit.angularVelocity *= dampFactor * 0.9; // Reduce angular velocity more aggressively // More aggressive stop threshold for smaller fruits var sizeStopBonus = fruitLevel <= 3 ? 0.05 : 0; var stopThreshold = 0.15 - (fruitLevel - 1) * 0.01 + sizeStopBonus; if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) { 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); // Significantly stronger gravity multiplier for larger fruits var gravityMultiplier = 1 + (fruitLevel - 1) * 1.5; fruit.vy += fruit.gravity * gravityMultiplier * 5; // More aggressive max velocity reduction for larger fruits to make them feel heavier var maxVelocity = 65 - Math.min(25, (fruitLevel - 1) * 3.0); 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; } // Substantially enhanced downward force for larger fruits if (fruitLevel > 3) { var bottomTendencyForce = (fruitLevel - 2) * 0.15; fruit.vy += bottomTendencyForce; } else if (fruitLevel <= 2) { // Small buoyancy for smaller fruits to make them float up slightly fruit.vy -= 0.15; } 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) { // Larger fruits rotate less easily var rotationFactor = 0.015 / (1 + (fruitLevel - 1) * 0.18); var targetAngularVelocity = fruit.vx * rotationFactor; fruit.angularVelocity = fruit.angularVelocity * 0.8 + targetAngularVelocity * 0.2; } else { fruit.angularVelocity *= 0.8; } fruit.rotation += fruit.angularVelocity; // Heavier fruits have more momentum (less friction) var frictionModifier = 0.99 + (fruitLevel - 1) * 0.005; fruit.vx *= fruit.friction * frictionModifier; fruit.vy *= fruit.friction * frictionModifier * 0.99; // Larger fruits come to rest more decisively var horizontalStopThreshold = Math.max(0.05, 0.15 - (fruitLevel - 1) * 0.01); var verticalStopThreshold = Math.max(0.05, 0.15 - (fruitLevel - 1) * 0.01); if (Math.abs(fruit.vx) < horizontalStopThreshold) { fruit.vx = 0; } if (Math.abs(fruit.vy) < verticalStopThreshold && fruit.y > gameHeight - 300) { 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 fruits that have already bounced if (fruit.hasBounced && isSmallFruit) { // Enhanced rotation damping for small fruits that have bounced 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; var hitboxReduction = 0; if (activeFruitLevel > 5) { hitboxReduction += (activeFruitLevel - 5) * 3; } if (targetFruitLevel > 5) { hitboxReduction += (targetFruitLevel - 5) * 3; } 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: 0xf6e58d }); /**** * 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; // Initialize bounce tracking activeFruit.hasBounced = false; 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); forceMultiplier *= 1.4; var levelAdjustedForce = forceMultiplier * (1 + (fruitLevel - 1) * 0.02); var mobilityReduction = Math.max(0, (fruitLevel - 2) * 0.08); levelAdjustedForce *= 1 - mobilityReduction; 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; fruit.gravity = 13.0 * (1 + (fruitLevel - 1) * 0.4); if (fruitLevel >= 5) { fruit.gravity *= 1 + (fruitLevel - 5) * 0.08; } 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) { collisionElasticity = 0; } if (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); // Enhanced impact multiplier based on level difference var levelImpactMultiplier = 1 + levelDifference * 0.08; // Higher maximum impulse ratio allows more dramatic bounces var maxImpulseRatio = 2.2; if (level1 > level2) { // Larger fruit pushes smaller fruit more strongly impulseRatio2 = Math.min(impulseRatio2 * levelImpactMultiplier * 1.6, maxImpulseRatio * impulseRatio2); } else if (level2 > level1) { // Larger fruit pushes smaller fruit more strongly impulseRatio1 = Math.min(impulseRatio1 * levelImpactMultiplier * 1.6, maxImpulseRatio * impulseRatio1); } 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 stronger upward bounce for small fruits to prevent them from sinking if (level1 < level2) { var upwardForce = Math.min(7.0, (level2 - level1) * 1.4) * bounce1Damping; // Extra upward force when small fruit is below larger fruit if (fruit1.y > fruit2.y) { upwardForce *= 1.5; } fruit1.vy -= upwardForce; } } 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 stronger upward bounce for small fruits to prevent them from sinking if (level2 < level1) { var upwardForce = Math.min(7.0, (level1 - level2) * 1.4) * bounce2Damping; // Extra upward force when small fruit is below larger fruit if (fruit2.y > fruit1.y) { upwardForce *= 1.5; } fruit2.vy -= upwardForce; } } // Apply strong damping if fruit has already bounced once 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 bring to rest 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; } // Detect vibration pattern - fruits that have already bounced and are still moving var isVibrating1 = fruit1.hasBounced && Math.abs(fruit1.vx) > 0.2 && Math.abs(fruit1.vy) > 0.2; var isVibrating2 = fruit2.hasBounced && Math.abs(fruit2.vx) > 0.2 && Math.abs(fruit2.vy) > 0.2; // Special case for small fruits that are vibrating too much if (isVibrating1 && level1 <= 3 || isVibrating2 && level2 <= 3) { // Apply strong stabilization to immediately reduce vibration var hardStopFactor = 0.1; if (isVibrating1 && level1 <= 3) { fruit1.vx *= hardStopFactor; fruit1.vy *= hardStopFactor; fruit1.angularVelocity *= hardStopFactor; // If velocity is already small, completely stop the fruit if (Math.abs(fruit1.vx) < 0.7 && Math.abs(fruit1.vy) < 0.7) { fruit1.vx = 0; fruit1.vy = 0; fruit1.angularVelocity = 0; } } if (isVibrating2 && level2 <= 3) { fruit2.vx *= hardStopFactor; fruit2.vy *= hardStopFactor; fruit2.angularVelocity *= hardStopFactor; // If velocity is already small, completely stop the fruit 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); var sizeReduction = 0; if (fruitLevel > 5) { sizeReduction = (fruitLevel - 5) * 3; } 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); } // Reset bounce state fruit.hasBounced = false; 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); var movementThreshold = 0.5; // If fruit hasn't moved significantly and is not touching boundaries var isOnFloor = fruit._boundaryContacts && fruit._boundaryContacts.floor; var isAtWall = fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right); // Check if fruit is stuck in mid-air if (distMoved < movementThreshold && !isOnFloor && !isAtWall) { fruit._stuckTracking.stuckTime += timeDelta; // If stuck for more than 2 seconds and not been nudged recently if (fruit._stuckTracking.stuckTime > 2000 && currentTime - fruit._stuckTracking.lastForceApplied > 1500) { // Apply a force based on fruit type - stronger for smaller fruits var forceMultiplier = 1.0; if (fruitLevel <= 4) { // Stronger nudge for smaller fruits, especially oranges forceMultiplier = 2.0; if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE') { forceMultiplier = 2.5; } } // Apply force with downward bias fruit.vx += (Math.random() * 2.0 - 1.0) * forceMultiplier; fruit.vy += (Math.random() * 1.0 + 0.5) * 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; } } }
===================================================================
--- original.js
+++ change.js
@@ -441,9 +441,9 @@
self.angularVelocity = physics.angularVelocity;
self.angularFriction = physics.angularFriction;
self.groundAngularFriction = physics.groundAngularFriction;
var currentLevel = getFruitLevel(self);
- self.gravity = physics.gravity * (1 + (currentLevel - 1) * 0.15);
+ 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;
@@ -769,26 +769,25 @@
fruit.lastVx = fruit.vx;
fruit.lastVy = fruit.vy;
var fruitLevel = getFruitLevel(fruit);
// Significantly stronger gravity multiplier for larger fruits
- var gravityMultiplier = 1 + (fruitLevel - 1) * 0.8;
+ var gravityMultiplier = 1 + (fruitLevel - 1) * 1.5;
fruit.vy += fruit.gravity * gravityMultiplier * 5;
- var fruitLevel = getFruitLevel(fruit);
// More aggressive max velocity reduction for larger fruits to make them feel heavier
- var maxVelocity = 65 - Math.min(25, (fruitLevel - 1) * 2.0);
+ var maxVelocity = 65 - Math.min(25, (fruitLevel - 1) * 3.0);
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;
}
// Substantially enhanced downward force for larger fruits
if (fruitLevel > 3) {
- var bottomTendencyForce = (fruitLevel - 2) * 0.08;
+ var bottomTendencyForce = (fruitLevel - 2) * 0.15;
fruit.vy += bottomTendencyForce;
} else if (fruitLevel <= 2) {
- // Slight upward force for very small fruits to help them stay on top
- fruit.vy -= 0.02;
+ // Small buoyancy for smaller fruits to make them float up slightly
+ fruit.vy -= 0.15;
}
fruit.x += fruit.vx;
fruit.y += fruit.vy;
var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy);
@@ -824,10 +823,10 @@
var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy);
var fruitLevel = getFruitLevel(fruit);
var isSmallFruit = fruitLevel <= 3;
var isLargeFruit = fruitLevel >= 6;
- // Less damping for smaller fruits (stay in motion longer) and more damping for larger fruits (settle faster)
- var sizeBasedDamp = isSmallFruit ? -0.05 : isLargeFruit ? 0.12 : 0;
+ // 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) {
@@ -1336,17 +1335,17 @@
}
function applyDropPhysics(fruit, forceMultiplier) {
var fruitLevel = getFruitLevel(fruit);
forceMultiplier *= 1.4;
- var levelAdjustedForce = forceMultiplier * (1 + (fruitLevel - 1) * 0.05);
- var mobilityReduction = Math.max(0, (fruitLevel - 2) * 0.03);
+ var levelAdjustedForce = forceMultiplier * (1 + (fruitLevel - 1) * 0.02);
+ var mobilityReduction = Math.max(0, (fruitLevel - 2) * 0.08);
levelAdjustedForce *= 1 - mobilityReduction;
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;
- fruit.gravity = 13.0 * (1 + (fruitLevel - 1) * 0.2);
+ fruit.gravity = 13.0 * (1 + (fruitLevel - 1) * 0.4);
if (fruitLevel >= 5) {
- fruit.gravity *= 1 + (fruitLevel - 5) * 0.03;
+ fruit.gravity *= 1 + (fruitLevel - 5) * 0.08;
}
fruit.safetyPeriod = false;
fruit.immuneToGameOver = true;
LK.setTimeout(function () {
@@ -1464,10 +1463,10 @@
}
var impulse = -(1 + collisionElasticity) * contactVelocity;
var level1 = getFruitLevel(fruit1);
var level2 = getFruitLevel(fruit2);
- var mass1 = Math.pow(level1, 2.0) * Math.pow(fruit1.type.size, 1.5);
- var mass2 = Math.pow(level2, 2.0) * Math.pow(fruit2.type.size, 1.5);
+ 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);