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; // Single bounce handling pattern if (!fruit.hasBounced) { fruit.vx = -fruit.vx * fruit.elasticity * 0.7; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vx = 0; // Stop movement 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; // Single bounce handling pattern if (!fruit.hasBounced) { fruit.vx = -fruit.vx * fruit.elasticity * 0.7; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vx = 0; // Stop 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; // Single bounce handling pattern if (!fruit.hasBounced) { fruit.vy = -fruit.vy * fruit.elasticity * 0.5; fruit.hasBounced = true; LK.getSound('bounce').play(); } else { fruit.vy = 0; // Stop 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; // 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 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) { // Special case for oranges - never let them completely stop in mid-air if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE' && !fruit._boundaryContacts && (!fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right)) { // Add a tiny bit of movement to prevent complete stoppage fruit.vx = (Math.random() - 0.5) * 0.2; fruit.vy = Math.max(0.1, Math.random() * 0.3); // Always some downward movement return false; // Don't consider it fully stabilized } 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); // Always ensure gravity is applied to any fruit with zero velocity if (fruit.vy === 0 && fruit.vx === 0) { // Force minimal movement to any stuck fruit with zero velocity fruit.vy = 0.1; // Extra force for oranges which are especially prone to getting stuck if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE') { fruit.vy = 0.3; } } // Extreme gravity multiplier for larger fruits - quadratic scaling to make them much heavier var gravityMultiplier = 1 + Math.pow(fruitLevel, 2) * 0.3; fruit.vy += fruit.gravity * gravityMultiplier * 5; // Dramatically reduce max velocity for larger fruits to make them feel heavier var maxVelocity = 65 - Math.min(40, Math.pow(fruitLevel, 1.5) * 1.2); 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; } // Extreme mass and inertia properties for larger fruits if (fruitLevel > 3) { // Dramatically increase downward force for larger fruits - exponential scaling var bottomTendencyForce = (fruitLevel - 2) * 0.35; fruit.vy += bottomTendencyForce; // Apply extreme inertia modifier to make larger fruits much harder to move if (fruit.lastVx !== undefined && fruit.lastVy !== undefined) { // Calculate acceleration/changes in velocity var dvx = fruit.vx - fruit.lastVx; var dvy = fruit.vy - fruit.lastVy; // Much stronger inertia resistance for larger fruits var inertiaResistance = Math.min(0.9, (fruitLevel - 3) * 0.18); // Apply resistance to velocity changes (larger fruits resist change much more) fruit.vx = fruit.lastVx + dvx * (1 - inertiaResistance); fruit.vy = fruit.lastVy + dvy * (1 - inertiaResistance); } } else if (fruitLevel <= 2) { // Increased buoyancy for smaller fruits to make them float up more noticeably fruit.vy -= 0.35; } 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 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; 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; 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); 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 || 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); 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); } 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; // Handle stuck fruits, with special case for oranges var isOrange = fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE'; var stuckTimeThreshold = isOrange ? 700 : 2000; // Check oranges even sooner var forceIntervalThreshold = isOrange ? 600 : 1500; // Apply force more frequently for oranges // If stuck for too long and not been nudged recently if (fruit._stuckTracking.stuckTime > stuckTimeThreshold && currentTime - fruit._stuckTracking.lastForceApplied > forceIntervalThreshold) { // Calculate force based on fruit type var forceMultiplier = isOrange ? 4.0 : fruitLevel <= 4 ? 2.0 : 1.0; // Always ensure oranges have downward momentum if (isOrange) { fruit.vy = Math.max(fruit.vy, 1.5); } // Apply 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; } } }
===================================================================
--- original.js
+++ change.js
@@ -165,15 +165,15 @@
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
+ // Single bounce handling pattern
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
+ fruit.vx = 0; // Stop movement 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;
@@ -190,15 +190,15 @@
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
+ // Single bounce handling pattern
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
+ fruit.vx = 0; // Stop 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;
@@ -211,16 +211,15 @@
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
+ // Single bounce handling pattern
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.vy = 0; // Stop after first bounce
}
fruit._boundaryContacts.floor = true;
if (Math.abs(fruit.vx) > 0.5) {
fruit.angularVelocity = fruit.vx * 0.01;
@@ -453,9 +452,9 @@
self.mergeGracePeriodActive = mergeHandler.mergeGracePeriodActive;
self.fromChargedRelease = mergeHandler.fromChargedRelease;
self.safetyPeriod = false;
self.immuneToGameOver = false;
- self.hasBounced = 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,
@@ -776,12 +775,16 @@
}
fruit.lastVx = fruit.vx;
fruit.lastVy = fruit.vy;
var fruitLevel = getFruitLevel(fruit);
- // Always ensure gravity is applied, even for oranges
- if (fruit.vy === 0 && fruit.vx === 0 && fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE') {
- // Force gravity to be applied to fruits that are stuck in mid-air with no velocity
+ // Always ensure gravity is applied to any fruit with zero velocity
+ if (fruit.vy === 0 && fruit.vx === 0) {
+ // Force minimal movement to any stuck fruit with zero velocity
fruit.vy = 0.1;
+ // Extra force for oranges which are especially prone to getting stuck
+ if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE') {
+ fruit.vy = 0.3;
+ }
}
// Extreme gravity multiplier for larger fruits - quadratic scaling to make them much heavier
var gravityMultiplier = 1 + Math.pow(fruitLevel, 2) * 0.3;
fruit.vy += fruit.gravity * gravityMultiplier * 5;
@@ -872,11 +875,10 @@
}
// 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
+ // Enhanced damping for small fruits that have 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) {
@@ -1293,10 +1295,9 @@
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;
+ activeFruit.hasBounced = false; // Initialize bounce tracking with single flag
game.addChild(activeFruit);
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
@@ -1479,14 +1480,11 @@
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) {
+ if (fruit1.hasBounced || fruit2.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);
@@ -1494,23 +1492,21 @@
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 - much more dramatic effect
+ // 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 extremely strongly
+ // Larger fruit pushes smaller fruit strongly
impulseRatio2 = Math.min(impulseRatio2 * levelImpactMultiplier * 2.5, maxImpulseRatio * impulseRatio2);
// Drastically reduce how much smaller fruits move larger fruits
- // Apply cubic reduction to impulse based on level difference
impulseRatio1 *= Math.max(0.05, 1 / (1 + Math.pow(level1 - level2, 3.0) * 0.5));
} else if (level2 > level1) {
- // Larger fruit pushes smaller fruit extremely strongly
+ // Larger fruit pushes smaller fruit strongly
impulseRatio1 = Math.min(impulseRatio1 * levelImpactMultiplier * 2.5, maxImpulseRatio * impulseRatio1);
// Drastically reduce how much smaller fruits move larger fruits
- // Apply cubic reduction to impulse based on level difference
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;
@@ -1578,16 +1574,16 @@
fruit1.vx *= 1 - horizontalResistance;
}
}
}
- // Apply strong damping if fruit has already bounced once
+ // 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 bring to rest
+ // 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;
@@ -1680,31 +1676,30 @@
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
+ // 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 && level1 <= 3) {
+ if (isVibrating1) {
fruit1.vx *= hardStopFactor;
fruit1.vy *= hardStopFactor;
fruit1.angularVelocity *= hardStopFactor;
- // If velocity is already small, completely stop the fruit
+ // 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 && level2 <= 3) {
+ if (isVibrating2) {
fruit2.vx *= hardStopFactor;
fruit2.vy *= hardStopFactor;
fruit2.angularVelocity *= hardStopFactor;
- // If velocity is already small, completely stop the fruit
+ // 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;
@@ -2119,10 +2114,8 @@
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() {
@@ -2162,29 +2155,23 @@
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;
- // Special case for oranges that appear stuck
+ // Handle stuck fruits, with special case for oranges
var isOrange = fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE';
- var stuckTimeThreshold = isOrange ? 1000 : 2000; // Check oranges sooner
- var forceIntervalThreshold = isOrange ? 800 : 1500; // Apply force more frequently for oranges
+ var stuckTimeThreshold = isOrange ? 700 : 2000; // Check oranges even sooner
+ var forceIntervalThreshold = isOrange ? 600 : 1500; // Apply force more frequently for oranges
// If stuck for too long and not been nudged recently
if (fruit._stuckTracking.stuckTime > stuckTimeThreshold && currentTime - fruit._stuckTracking.lastForceApplied > forceIntervalThreshold) {
- // Apply a force based on fruit type - stronger for smaller fruits
- var forceMultiplier = 1.0;
- if (fruitLevel <= 4) {
- // Stronger nudge for smaller fruits
- forceMultiplier = 2.0;
- // Special case for oranges that often get stuck
- if (isOrange) {
- forceMultiplier = 3.5; // Much stronger force for oranges
- // Ensure it has some downward momentum
- fruit.vy = Math.max(fruit.vy, 1.0);
- }
+ // Calculate force based on fruit type
+ var forceMultiplier = isOrange ? 4.0 : fruitLevel <= 4 ? 2.0 : 1.0;
+ // Always ensure oranges have downward momentum
+ if (isOrange) {
+ fruit.vy = Math.max(fruit.vy, 1.5);
}
// Apply force with downward bias
fruit.vx += (Math.random() * 2.0 - 1.0) * forceMultiplier;
- fruit.vy += (Math.random() * 1.0 + 1.0) * forceMultiplier; // More downward bias
+ fruit.vy += (Math.random() * 1.0 + 1.0) * forceMultiplier;
// Reset stuck tracking
fruit._stuckTracking.stuckTime = 0;
fruit._stuckTracking.lastForceApplied = currentTime;
}