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