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); var progressPercent = (self.chargeNeededForRelease - remainingCount) / self.chargeNeededForRelease; var targetScale = progressPercent; if (progressPercent > 0 && self.portalAsset.alpha === 0) { self.portalAsset.alpha = 1; } tween(self.portalAsset, { scaleX: targetScale, scaleY: targetScale, alpha: progressPercent }, { 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 }, { duration: 300, easing: tween.easeOut }); self.startPulseAnimation(); } }; self.startPulseAnimation = function () { if (self.pulseAnimationActive) { return; } self.pulseAnimationActive = true; self._pulseText(); }; self._pulseText = function () { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.3, scaleY: 1.3 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut, onFinish: self._pulseText }); } }); }; self.reset = function () { self.isReadyToRelease = false; self.currentCharge = 0; self.pulseAnimationActive = false; tween(self.portalAsset, { alpha: 0 }, { duration: 200, easing: tween.easeOut }); tween(self.portalAsset, { scaleX: 0, scaleY: 0 }, { duration: 200, easing: tween.easeOut }); }; return self; }); var CollisionComponent = Container.expand(function () { var self = Container.call(this); self.wallContactFrames = 0; self.checkBoundaryCollisions = function (fruit, walls, floor) { if (!walls || !walls.left || !walls.right || !floor) { return; } var fruitHalfWidth = fruit.width / 2; var fruitHalfHeight = fruit.height / 2; var fruitLevel = getFruitLevel(fruit); var sizeReduction = 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++; var progressiveFriction = Math.min(0.75, 0.55 + fruit.wallContactFrames * 0.015); fruit.angularVelocity *= progressiveFriction; // More aggressive damping with increasing contact time if (fruit.wallContactFrames > 15) { fruit.vx *= 0.75; fruit.vy *= 0.75; fruit.angularVelocity *= 0.5; } else if (fruit.wallContactFrames > 10) { fruit.vx *= 0.8; fruit.vy *= 0.85; fruit.angularVelocity *= 0.7; } else if (fruit.wallContactFrames > 5) { 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) { var incomingVx = fruit.vx; fruit.x = leftBoundary; fruit.vx = -incomingVx * fruit.elasticity * 0.7; // Reverse based on incoming velocity if (!fruit.hasBounced && Math.abs(incomingVx) > 0.5) { // Check incoming speed for sound fruit.hasBounced = true; // Still track first significant bounce for sound/potential effects LK.getSound('bounce').play(); } var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= fruit.groundAngularFriction * 0.4; // Increased wall friction fruit._boundaryContacts.left = true; } 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) { var incomingVx = fruit.vx; fruit.x = rightBoundary; fruit.vx = -incomingVx * fruit.elasticity * 0.7; if (!fruit.hasBounced && Math.abs(incomingVx) > 0.5) { fruit.hasBounced = true; LK.getSound('bounce').play(); } var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= fruit.groundAngularFriction * 0.4; // Increased wall friction fruit._boundaryContacts.right = true; } }; self.checkFloorCollision = function (fruit, floor, effectiveHeight) { var floorCollisionY = floor.y - floor.height / 2 - effectiveHeight; if (fruit.y > floorCollisionY) { var incomingVy = fruit.vy; fruit.y = floorCollisionY; fruit.vy = -incomingVy * fruit.elasticity * 0.5; if (!fruit.hasBounced && Math.abs(incomingVy) > 0.5) { fruit.hasBounced = true; LK.getSound('bounce').play(); } fruit._boundaryContacts.floor = true; if (Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity = fruit.vx * 0.008; // Reduced floor rolling effect } fruit.angularVelocity *= fruit.groundAngularFriction * 0.4; // Increased floor friction var restThreshold = fruit.wallContactFrames > 5 ? 1.5 : 2.5; if (Math.abs(fruit.vy) < restThreshold) { fruit.vy = 0; fruit.vx *= 0.6; // Increased horizontal damping if (fruit.wallContactFrames > 8) { fruit.vx *= 0.75; } } var angularRestThreshold = fruit.wallContactFrames > 5 ? 0.012 : 0.018; // Lower threshold 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); 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; self.alphaMin = 0.1; self.alphaMax = 0.5; self.flickerSpeed = 500 + Math.random() * 300; self.frameIndex = 0; self.frameTimer = null; self.frameDuration = 200; self.initialize = function () { self.fireAsset = self.attachAsset('fire', { anchorX: 0.5, anchorY: 1.0 }); self.fireAsset2 = self.attachAsset('fire_2', { anchorX: 0.5, anchorY: 1.0 }); self.fireAsset2.visible = false; self.x = self.baseX; self.y = self.baseY; self.startAlphaFlicker(); self.startFrameAnimation(); }; self.update = function () { self.x += self.movementSpeed * self.direction; if (Math.abs(self.x - self.baseX) > self.movementRange) { self.direction *= -1; } }; self.startFrameAnimation = function () { if (self.frameTimer) { LK.clearInterval(self.frameTimer); } self.frameTimer = LK.setInterval(function () { self.toggleFrame(); }, self.frameDuration); }; self.toggleFrame = function () { self.frameIndex = (self.frameIndex + 1) % 2; self.fireAsset.visible = self.frameIndex === 0; self.fireAsset2.visible = self.frameIndex === 1; }; self.startAlphaFlicker = function () { if (self.flickerTween) { self.flickerTween.stop(); } var startDelay = Math.random() * 500; LK.setTimeout(function () { self.flickerToMax(); }, startDelay); }; self.flickerToMax = function () { self.flickerTween = tween(self, { alpha: self.alphaMax }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMin }); }; self.flickerToMin = function () { self.flickerTween = tween(self, { alpha: self.alphaMin }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMax }); }; self.destroy = function () { if (self.flickerTween) { self.flickerTween.stop(); } if (self.frameTimer) { LK.clearInterval(self.frameTimer); self.frameTimer = null; } Container.prototype.destroy.call(this); }; self.initialize(); return self; }); var Fruit = Container.expand(function (type) { var self = Container.call(this); self.id = 'fruit_' + Date.now() + '_' + Math.floor(Math.random() * 10000); self.type = type; var physics = new PhysicsComponent(); var collision = new CollisionComponent(); var mergeHandler = new MergeComponent(); var behaviorSystem = new FruitBehavior(); self.vx = physics.vx; self.vy = physics.vy; self.rotation = physics.rotation; self.angularVelocity = physics.angularVelocity; self.angularFriction = physics.angularFriction; self.groundAngularFriction = physics.groundAngularFriction; var currentLevel = getFruitLevel(self); self.gravity = physics.gravity * (1 + (currentLevel - 1) * 0.3); self.friction = physics.friction; self.rotationRestCounter = physics.rotationRestCounter; self.maxAngularVelocity = physics.maxAngularVelocity; self.isStatic = physics.isStatic; self.elasticity = currentLevel <= 3 ? 0.85 : 0.8 - (currentLevel - 1) * (0.2 / 9); self.wallContactFrames = collision.wallContactFrames; self.merging = mergeHandler.merging; self.mergeGracePeriodActive = mergeHandler.mergeGracePeriodActive; self.fromChargedRelease = mergeHandler.fromChargedRelease; self.safetyPeriod = false; self.immuneToGameOver = false; self.hasBounced = false; self.isFullyStabilized = false; self.behavior = type && type.id ? behaviorSystem.getMergeHandler(type.id) : null; if (self.type && self.type.id && self.type.points && self.type.size) { var fruitGraphics = self.attachAsset(self.type.id, { anchorX: 0.5, anchorY: 0.5 }); self.width = fruitGraphics.width; self.height = fruitGraphics.height; if (self.behavior && self.behavior.onSpawn) { self.behavior.onSpawn(self); } } else { console.log("Warning: Fruit type not available yet or missing required properties"); } self.updatePhysics = function () { physics.apply(self); }; self.checkBoundaries = function (walls, floor) { collision.checkBoundaryCollisions(self, walls, floor); if (self.safetyPeriod === false && self.vy <= 0.1) { self.safetyPeriod = undefined; } }; self.merge = function (otherFruit) { mergeHandler.beginMerge(self, otherFruit); }; return self; }); var FruitBehavior = Container.expand(function () { var self = Container.call(this); self.behaviors = { CHERRY: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, GRAPE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, APPLE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, ORANGE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, WATERMELON: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); } }, PINEAPPLE: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { return self.standardMerge(fruit1, fruit2, posX, posY); }, onSpawn: function onSpawn() {} }, MELON: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.getSound('Smartz').play(); return self.standardMerge(fruit1, fruit2, posX, posY); } }, PEACH: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.getSound('stonks').play(); return self.standardMerge(fruit1, fruit2, posX, posY); } }, COCONUT: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.getSound('ThisIsFine').play(); return self.standardMerge(fruit1, fruit2, posX, posY); }, onSpawn: function onSpawn() { LK.getSound('stonks').play(); } }, DURIAN: { onMerge: function onMerge(fruit1, fruit2, posX, posY) { LK.setScore(LK.getScore() + fruit1.type.points); updateScoreDisplay(); removeFruitFromGame(fruit1); removeFruitFromGame(fruit2); releasePineappleOnMerge(); return null; } } }; self.getMergeHandler = function (fruitTypeId) { if (!fruitTypeId) { return self.behaviors.CHERRY; } var upperTypeId = fruitTypeId.toUpperCase(); return self.behaviors[upperTypeId] || self.behaviors.CHERRY; }; self.standardMerge = function (fruit1, fruit2, posX, posY) { var nextType = FruitTypes[fruit1.type.next.toUpperCase()]; releasePineappleOnMerge(); return self.createNextLevelFruit(fruit1, nextType, posX, posY); }; self.createNextLevelFruit = function (sourceFruit, nextType, posX, posY) { var newFruit = new Fruit(nextType); newFruit.x = posX; newFruit.y = posY; newFruit.scaleX = 0.5; newFruit.scaleY = 0.5; game.addChild(newFruit); fruits.push(newFruit); spatialGrid.insertObject(newFruit); LK.setScore(LK.getScore() + nextType.points); updateScoreDisplay(); tween(newFruit, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); return newFruit; }; self.playSoundEffect = function (soundId) { if (soundId) { LK.getSound(soundId).play(); } }; return self; }); var Line = Container.expand(function () { var self = Container.call(this); var lineGraphics = self.attachAsset('floor', { anchorX: 0.5, anchorY: 0.5 }); lineGraphics.tint = 0xff0000; lineGraphics.height = 20; return self; }); var MergeComponent = Container.expand(function () { var self = Container.call(this); self.merging = false; self.mergeGracePeriodActive = false; self.fromChargedRelease = false; self.fruitBehavior = new FruitBehavior(); self.beginMerge = function (fruit1, fruit2) { if (fruit1.merging) { return; } fruit1.merging = true; fruit2.merging = true; var midX = (fruit1.x + fruit2.x) / 2; var midY = (fruit1.y + fruit2.y) / 2; self.animateMerge(fruit1, fruit2, midX, midY); }; self.animateMerge = function (fruit1, fruit2, midX, midY) { tween(fruit1, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut }); tween(fruit2, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.completeMerge(fruit1, fruit2, midX, midY); } }); }; self.completeMerge = function (fruit1, fruit2, midX, midY) { LK.getSound('merge').play(); self.trackMerge(fruit1, fruit2); var behaviorHandler = self.fruitBehavior.getMergeHandler(fruit1.type.id); behaviorHandler.onMerge(fruit1, fruit2, midX, midY); if (fruit1 && fruits.includes(fruit1) && fruit1.type.id.toUpperCase() !== 'DURIAN') { removeFruitFromGame(fruit1); } if (fruit2 && fruits.includes(fruit2)) { removeFruitFromGame(fruit2); } }; self.trackMerge = function (fruit1, fruit2) { var fromReleasedFruits = fruit1.fromChargedRelease || fruit2.fromChargedRelease; var isPlayerDroppedFruitMerge = !fromReleasedFruits && (fruit1 === lastDroppedFruit || fruit2 === lastDroppedFruit) && !lastDroppedHasMerged; var fruitHasMergeGracePeriod = fruit1.mergeGracePeriodActive || fruit2.mergeGracePeriodActive; if (isPlayerDroppedFruitMerge || fruitHasMergeGracePeriod) { lastDroppedHasMerged = true; } }; return self; }); var PhysicsComponent = Container.expand(function () { var self = Container.call(this); var MIN_FALL_SPEED = 0.1; // Define minimum fall speed constant 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.15; self.rotationRestCounter = 0; self.lastVx = 0; self.lastVy = 0; self.hasBounced = false; self.isFullyStabilized = false; self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) { var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; if (isMidAir) { return false; // Never stabilize fruits in mid-air } var stabilizationLevel = 0; var fruitLevel = getFruitLevel(fruit); if (!fruit._stabilizeMetrics) { fruit._stabilizeMetrics = { consecutiveSmallMovements: 0, wallContactDuration: 0, surroundedDuration: 0, restingDuration: 0 }; } var movementThreshold = 0.7 - fruitLevel * 0.08; var angularThreshold = 0.07 - fruitLevel * 0.006; if (movementMagnitude < movementThreshold && Math.abs(fruit.angularVelocity) < angularThreshold) { 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) { fruit._stabilizeMetrics.wallContactDuration += 1.2 + fruitLevel * 0.1; } else { fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1); } if (fruit.surroundedFrames > 0) { fruit._stabilizeMetrics.surroundedDuration += 1.5 + fruitLevel * 0.15; } else { fruit._stabilizeMetrics.surroundedDuration = Math.max(0, fruit._stabilizeMetrics.surroundedDuration - 1); } 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); 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) { 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; var stopThreshold = 0.15 - fruitLevel * 0.01; if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) { fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; fruit.isFullyStabilized = true; // Mark fruit as fully stabilized return true; } } return false; }; self.apply = function (fruit) { if (fruit.isStatic || fruit.merging) { return; } // Reset fully stabilized flag if the fruit is moving again (unless it's at boundary) var isMoving = Math.abs(fruit.vx) > 0.2 || Math.abs(fruit.vy) > 0.2 || Math.abs(fruit.angularVelocity) > 0.01; if (fruit.isFullyStabilized && isMoving && !fruit._boundaryContacts) { fruit.isFullyStabilized = false; } fruit.lastVx = fruit.vx; fruit.lastVy = fruit.vy; var fruitLevel = getFruitLevel(fruit); var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; var gravityMultiplier = 1 + fruitLevel * 0.4; fruit.vy += fruit.gravity * gravityMultiplier; 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; } fruit.vy += fruitLevel * 0.1; if (fruit.lastVx !== undefined && fruit.lastVy !== undefined) { var dvx = fruit.vx - fruit.lastVx; var dvy = fruit.vy - fruit.lastVy; var inertiaResistance = fruitLevel * 0.05; inertiaResistance = Math.min(0.9, inertiaResistance); fruit.vx = fruit.lastVx + dvx * (1 - inertiaResistance); fruit.vy = fruit.lastVy + dvy * (1 - inertiaResistance); } 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) { var rotationFactor = 0.035 / (1 + fruitLevel * 0.08); var targetAngularVelocity = fruit.vx * rotationFactor; fruit.angularVelocity = fruit.angularVelocity * 0.4 + targetAngularVelocity * 0.6; } else { fruit.angularVelocity *= 0.8; } fruit.rotation += fruit.angularVelocity; var frictionModifier = 0.99 + fruitLevel * 0.003; fruit.vx *= fruit.friction * frictionModifier; fruit.vy *= fruit.friction * frictionModifier * 0.99; var stopThreshold = 0.15 - fruitLevel * 0.01; stopThreshold = Math.max(0.05, stopThreshold); if (Math.abs(fruit.vx) < stopThreshold) { fruit.vx = 0; } // Only stop vertical velocity if NOT in mid-air if (Math.abs(fruit.vy) < stopThreshold && !isMidAir) { fruit.vy = 0; } self.handleRotationDamping(fruit); } // Update position AFTER all velocity adjustments fruit.x += fruit.vx; fruit.y += fruit.vy; // Final safety net: Ensure mid-air fruits always fall if (isMidAir) { var currentMinFallSpeed = MIN_FALL_SPEED + fruitLevel * 0.02; // Scale min speed slightly by level if (fruit.vy < currentMinFallSpeed) { fruit.vy = currentMinFallSpeed; } } }; 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; var rotationFactor = 0.04 / (1 + fruitLevel * 0.07); var targetAngularVelocity = fruit.vx * rotationFactor; // Reset fully stabilized status if fruit has significant movement if (fruit.isFullyStabilized && (movementMagnitude > 0.5 || Math.abs(fruit.angularVelocity) > 0.02)) { fruit.isFullyStabilized = false; } // Enhanced neighborhood awareness for rotation damping var hasNeighbors = fruit.neighborContacts && fruit.neighborContacts.length > 0; var hasManyNeighbors = fruit.neighborContacts && fruit.neighborContacts.length >= 3; var neighborFactor = hasManyNeighbors ? 0.4 : hasNeighbors ? 0.7 : 1.0; if (movementMagnitude > 0.3) { // Adjust based on neighborhood var blendRatio = hasNeighbors ? 0.5 : 0.7; fruit.angularVelocity = fruit.angularVelocity * (1 - blendRatio) + targetAngularVelocity * blendRatio * neighborFactor; var targetDirection = fruit.vx > 0 ? 1 : -1; var currentDirection = fruit.angularVelocity > 0 ? 1 : -1; if (targetDirection !== currentDirection && Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity *= 0.3; } } var sizeBasedDamp = isSmallFruit ? -0.15 : isLargeFruit ? 0.25 : 0; // Enhanced damping when surrounded by neighbors var rotationDampFactor = 0.95; if (hasManyNeighbors) { // Strong damping when surrounded rotationDampFactor = 0.5 - sizeBasedDamp * 0.5; } else if (hasNeighbors) { // Moderate damping with some neighbors rotationDampFactor = 0.65 - sizeBasedDamp * 0.75; } else 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 (fruit.lastVx !== undefined && Math.abs(fruit.vx - fruit.lastVx) > 0.5) { fruit.angularVelocity -= (fruit.vx - fruit.lastVx) * 0.02; } // Enhanced floor contact handling if (fruit._boundaryContacts) { if (fruit._boundaryContacts.floor && Math.abs(fruit.vx) > 0.2) { var rollFactor = 0.025 - fruitLevel * 0.001; // Reduced rolling factor var idealRollingVelocity = fruit.vx * rollFactor; fruit.angularVelocity = fruit.angularVelocity * 0.75 + idealRollingVelocity * 0.25; } // Additional damping for wall contacts if ((fruit._boundaryContacts.left || fruit._boundaryContacts.right) && Math.abs(fruit.angularVelocity) > 0.01) { fruit.angularVelocity *= 0.8; } } var angularThreshold = isSmallFruit ? 0.01 : 0.006; // Lower threshold var restFramesThreshold = isSmallFruit ? 3 : 5; // Faster resting if (Math.abs(fruit.angularVelocity) < angularThreshold) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > restFramesThreshold) { fruit.angularVelocity = 0; } } else { fruit.rotationRestCounter = 0; } var maxAngularMultiplier = isSmallFruit ? 1.2 : 1.4; fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity * maxAngularMultiplier), fruit.maxAngularVelocity * maxAngularMultiplier); }; return self; }); var SpatialGrid = Container.expand(function (cellSize) { var self = Container.call(this); self.cellSize = cellSize || 200; self.grid = {}; self.lastRebuildTime = Date.now(); self.rebuildInterval = 60000; self.insertObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) { return; } obj._currentCells = obj._currentCells || []; var cells = self.getCellsForObject(obj); obj._currentCells = cells.slice(); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (!self.grid[cellKey]) { self.grid[cellKey] = []; } if (self.grid[cellKey].indexOf(obj) === -1) { self.grid[cellKey].push(obj); } } }; self.removeObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var cells = obj._currentCells || self.getCellsForObject(obj); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { var cellIndex = self.grid[cellKey].indexOf(obj); if (cellIndex !== -1) { self.grid[cellKey].splice(cellIndex, 1); } if (self.grid[cellKey].length === 0) { delete self.grid[cellKey]; } } } obj._currentCells = []; }; self.getCellsForObject = function (obj) { if (!obj || typeof obj.x !== 'number' || typeof obj.y !== 'number' || typeof obj.width !== 'number' || typeof obj.height !== 'number') { return []; } var cells = []; var halfWidth = obj.width / 2; var halfHeight = obj.height / 2; var minCellX = Math.floor((obj.x - halfWidth) / self.cellSize); var maxCellX = Math.floor((obj.x + halfWidth) / self.cellSize); var minCellY = Math.floor((obj.y - halfHeight) / self.cellSize); var maxCellY = Math.floor((obj.y + halfHeight) / self.cellSize); for (var cellX = minCellX; cellX <= maxCellX; cellX++) { for (var cellY = minCellY; cellY <= maxCellY; cellY++) { cells.push(cellX + "," + cellY); } } return cells; }; self.updateObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var newCells = self.getCellsForObject(obj); var oldCells = obj._currentCells || []; var cellsChanged = false; if (oldCells.length !== newCells.length) { cellsChanged = true; } else { for (var i = 0; i < newCells.length; i++) { if (oldCells.indexOf(newCells[i]) === -1) { cellsChanged = true; break; } } } if (cellsChanged) { self.removeObject(obj); self.insertObject(obj); } }; self.getPotentialCollisions = function (obj) { var candidates = []; var cells = self.getCellsForObject(obj); var addedObjects = {}; for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { for (var j = 0; j < self.grid[cellKey].length; j++) { var otherObj = self.grid[cellKey][j]; if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) { candidates.push(otherObj); addedObjects[otherObj.id] = true; } } } } return candidates; }; self.clear = function () { self.grid = {}; self.lastRebuildTime = Date.now(); }; self.rebuildGrid = function (allObjects) { self.grid = {}; self.lastRebuildTime = Date.now(); if (Array.isArray(allObjects)) { for (var i = 0; i < allObjects.length; i++) { if (allObjects[i] && !allObjects[i].merging && !allObjects[i].isStatic) { self.insertObject(allObjects[i]); } } } }; return self; }); var TrajectoryLine = Container.expand(function () { var self = Container.call(this); self.dotPool = new DotPool(100); self.activeDots = []; self.dots = []; self.dotSpacing = 10; self.dotSize = 15; self.maxDots = 100; self.createDots = function () { self.clearDots(); self.dotPool.initialize(self.maxDots); }; self.clearDots = function () { for (var i = 0; i < self.activeDots.length; i++) { if (self.activeDots[i]) { self.removeChild(self.activeDots[i]); self.dotPool.release(self.activeDots[i]); } } self.activeDots = []; }; self.updateTrajectory = function (startX, startY) { if (!activeFruit) { return; } self.clearDots(); var dotY = startY; var dotSpacing = 25; var dotCount = 0; var hitDetected = false; while (dotCount < self.maxDots && !hitDetected) { var dot = self.dotPool.get(); self.addChild(dot); self.activeDots.push(dot); dot.x = startX; dot.y = dotY; dot.visible = true; dot.alpha = 1.0; dotCount++; dotY += dotSpacing; var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; if (dotY > floorCollisionY) { if (dotCount > 0) { self.activeDots[dotCount - 1].y = floorCollisionY; } hitDetected = true; break; } var potentialHits = spatialGrid.getPotentialCollisions({ x: startX, y: dotY, width: activeFruit.width, height: activeFruit.height, id: 'trajectory_check' }); for (var j = 0; j < potentialHits.length; j++) { var fruit = potentialHits[j]; if (fruit && fruit !== activeFruit && !fruit.merging && fruit.width && fruit.height) { if (self.wouldIntersectFruit(fruit.x, fruit.y, startX, dotY, activeFruit, fruit)) { if (dotCount > 0) { var dx = fruit.x - startX; var dy = fruit.y - dotY; var dist = Math.sqrt(dx * dx + dy * dy); var overlap = activeFruit.width / 2 + fruit.width / 2 - dist; if (dist > 0) { self.activeDots[dotCount - 1].y = dotY - dy / dist * overlap; } } hitDetected = true; break; } } } } }; self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) { var dx = fruitX - dropX; var dy = fruitY - dropY; var distanceSquared = dx * dx + dy * dy; var activeFruitLevel = getFruitLevel(activeFruitObj); var targetFruitLevel = getFruitLevel(targetFruitObj); var activeFruitRadius = activeFruitObj.width / 2; var targetFruitRadius = targetFruitObj.width / 2; var hitboxReduction = (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; pineapple.immuneToGameOver = true; applyDropPhysics(pineapple, 2.5); fruits.push(pineapple); if (spatialGrid) { spatialGrid.insertObject(pineapple); } LK.setTimeout(function () { if (pineapple && fruits.includes(pineapple)) { pineapple.immuneToGameOver = false; } }, 3000); 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; 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); orange.hasBounced = false; // Ensure consistent initialization var minX = wallLeft.x + wallLeft.width / 2 + orange.width / 2 + 50; var maxX = wallRight.x - wallRight.width / 2 - orange.width / 2 - 50; orange.x = minX + Math.random() * (maxX - minX); orange.y = -orange.height; orange.isStatic = false; var forceMultiplier = 3.5 + (Math.random() * 1 - 0.5); applyDropPhysics(orange, forceMultiplier); orange.fromChargedRelease = true; game.addChild(orange); fruits.push(orange); spatialGrid.insertObject(orange); chargeCounter = 0; resetChargedBalls(); readyToReleaseCharged = false; } activeFruit = null; createNextFruit(); } function applyDropPhysics(fruit, forceMultiplier) { var fruitLevel = getFruitLevel(fruit); forceMultiplier *= 1.4; var levelAdjustedForce = forceMultiplier * (1 - fruitLevel * 0.05); 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 * 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); 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 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; // Don't apply impulses to fully stabilized fruits if (!fruit1.isFullyStabilized) { fruit1.vx -= impulse1 * normalizeX; fruit1.vy -= impulse1 * normalizeY; } if (!fruit2.isFullyStabilized) { fruit2.vx += impulse2 * normalizeX; fruit2.vy += impulse2 * normalizeY; } var tangentX = -normalizeY; var tangentY = normalizeX; var tangentVelocity = rvX * tangentX + rvY * tangentY; var frictionImpulse = -tangentVelocity * 0.1; // Only apply friction impulses to non-stabilized fruits if (!fruit1.isFullyStabilized) { fruit1.vx -= frictionImpulse * tangentX * impulseRatio1; fruit1.vy -= frictionImpulse * tangentY * impulseRatio1; } if (!fruit2.isFullyStabilized) { fruit2.vx += frictionImpulse * tangentX * impulseRatio2; fruit2.vy += frictionImpulse * tangentY * impulseRatio2; } // If collision force is significant, reset stabilized state var impact1 = Math.abs(impulse1 * velocityDampening); var impact2 = Math.abs(impulse2 * velocityDampening); if (fruit1.isFullyStabilized && impact1 > 1.5) { fruit1.isFullyStabilized = false; } if (fruit2.isFullyStabilized && impact2 > 1.5) { fruit2.isFullyStabilized = false; } 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; // Only apply angular velocity changes to non-stabilized fruits if (!fruit1.isFullyStabilized) { fruit1.angularVelocity += inertia2 > 0 ? angularImpulse * (inertia1 / (inertia1 + inertia2)) : angularImpulse * 0.5; fruit1.angularVelocity *= 0.95; } if (!fruit2.isFullyStabilized) { fruit2.angularVelocity -= inertia1 > 0 ? angularImpulse * (inertia2 / (inertia1 + inertia2)) : angularImpulse * 0.5; fruit2.angularVelocity *= 0.95; } // Reset stabilization if angular impact is significant var angularImpactMagnitude = Math.abs(angularImpulse); if (fruit1.isFullyStabilized && angularImpactMagnitude > 0.015) { fruit1.isFullyStabilized = false; } if (fruit2.isFullyStabilized && angularImpactMagnitude > 0.015) { fruit2.isFullyStabilized = false; } 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; if (recentCollision) { var level1DampFactor = 0.6 + (level1 - 1) * 0.04; var level2DampFactor = 0.6 + (level2 - 1) * 0.04; var fruit1Damp = level1 > level2 ? 0.5 : 0.7; var fruit2Damp = level2 > level1 ? 0.5 : 0.7; fruit1.vx *= fruit1Damp; fruit1.vy *= fruit1Damp; fruit2.vx *= fruit2Damp; fruit2.vy *= fruit2Damp; var angular1Damp = level1 <= 3 ? 0.45 : 0.65; var angular2Damp = level2 <= 3 ? 0.45 : 0.65; fruit1.angularVelocity *= angular1Damp; fruit2.angularVelocity *= angular2Damp; } 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; } } } } } } } function checkGameOver() { if (gameOver) { return; } for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit || fruit === activeFruit || fruit.merging || fruit.isStatic) { continue; } var fruitHalfHeight = fruit.height / 2; var fruitHalfWidth = fruit.width / 2; var fruitLevel = getFruitLevel(fruit); var sizeReduction = 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) { if (!fruit.gameOverTimer) { fruit.gameOverTimer = Date.now(); tween(fruit, { alpha: 0.5 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(fruit, { alpha: 1 }, { duration: 500, easing: tween.easeInOut, repeat: 2 }); } }); continue; } var currentTime = Date.now(); if (currentTime - fruit.gameOverTimer >= 3000) { gameOver = true; LK.showGameOver(); return; } } } } else { fruit.safetyPeriod = undefined; if (fruit.gameOverTimer) { fruit.gameOverTimer = null; fruit.alpha = 1; } } } } 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; if (fireContainer) { for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) { activeFireElements[i].destroy(); } } fireContainer.destroy(); } fireContainer = new Container(); game.addChildAt(fireContainer, 0); 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(); trajectoryLine = game.addChild(new TrajectoryLine()); trajectoryLine.createDots(); evolutionLine = game.addChild(new EvolutionLine()); evolutionLine.initialize(); updateScoreDisplay(); activeFruit = null; createNextFruit(); resetChargedBalls(); updateFireBackground(); } 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); } 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++; var sizeBonus = isSmallFruit ? 0.15 : fruitLevel >= 7 ? -0.05 : 0; var stabilizationStrength = Math.min(0.95, 0.75 + fruit.surroundedFrames * (0.022 + sizeBonus)); if (fruit.neighborContacts.length >= 3) { var contactBonus = isSmallFruit ? 0.1 : 0.03; stabilizationStrength = Math.min(0.98, stabilizationStrength + contactBonus); } var angularStabilization = isSmallFruit ? stabilizationStrength * 0.9 : stabilizationStrength; fruit.vx *= stabilizationStrength; fruit.vy *= stabilizationStrength; fruit.angularVelocity *= angularStabilization; var thresholdFrames = isSmallFruit ? 8 : 10; var thresholdFramesForced = isSmallFruit ? 15 : 18; if (fruit.surroundedFrames > thresholdFrames && isSlowMoving || fruit.surroundedFrames > thresholdFramesForced) { 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(); updateFireBackground(); for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) { activeFireElements[i].update(); } } }; initGame(); function updateFireBackground() { 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; } } var uniqueTypeCount = Object.keys(uniqueFruitTypes).length; var targetFireCount = BASE_FIRES + uniqueTypeCount; targetFireCount = Math.min(targetFireCount, MAX_FIRES); if (targetFireCount > activeFireElements.length) { var newFiresCount = targetFireCount - activeFireElements.length; for (var i = 0; i < newFiresCount; i++) { createFireElement(activeFireElements.length); } } else if (targetFireCount < activeFireElements.length) { var fireToRemoveCount = activeFireElements.length - targetFireCount; for (var i = 0; i < fireToRemoveCount; i++) { var fire = activeFireElements.pop(); if (fire) { fire.destroy(); fireContainer.removeChild(fire); } } } } function createFireElement(index) { var yPos = gameHeight + FIRE_Y_START_OFFSET - index * FIRE_Y_STACK_OFFSET; var xPos = gameWidth / 2 + (Math.random() * FIRE_X_SPREAD - FIRE_X_SPREAD / 2); var newFire = new FireElement(xPos, yPos); if (index % 2 === 1) { newFire.fireAsset.scaleX = -1; } 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(); }
===================================================================
--- original.js
+++ change.js
@@ -428,8 +428,9 @@
self.fromChargedRelease = mergeHandler.fromChargedRelease;
self.safetyPeriod = false;
self.immuneToGameOver = false;
self.hasBounced = false;
+ self.isFullyStabilized = false;
self.behavior = type && type.id ? behaviorSystem.getMergeHandler(type.id) : null;
if (self.type && self.type.id && self.type.points && self.type.size) {
var fruitGraphics = self.attachAsset(self.type.id, {
anchorX: 0.5,
@@ -648,8 +649,9 @@
self.rotationRestCounter = 0;
self.lastVx = 0;
self.lastVy = 0;
self.hasBounced = false;
+ self.isFullyStabilized = false;
self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) {
var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right;
if (isMidAir) {
return false; // Never stabilize fruits in mid-air
@@ -702,8 +704,9 @@
if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) {
fruit.vx = 0;
fruit.vy = 0;
fruit.angularVelocity = 0;
+ fruit.isFullyStabilized = true; // Mark fruit as fully stabilized
return true;
}
}
return false;
@@ -711,8 +714,13 @@
self.apply = function (fruit) {
if (fruit.isStatic || fruit.merging) {
return;
}
+ // Reset fully stabilized flag if the fruit is moving again (unless it's at boundary)
+ var isMoving = Math.abs(fruit.vx) > 0.2 || Math.abs(fruit.vy) > 0.2 || Math.abs(fruit.angularVelocity) > 0.01;
+ if (fruit.isFullyStabilized && isMoving && !fruit._boundaryContacts) {
+ fruit.isFullyStabilized = false;
+ }
fruit.lastVx = fruit.vx;
fruit.lastVy = fruit.vy;
var fruitLevel = getFruitLevel(fruit);
var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right;
@@ -777,8 +785,12 @@
var isSmallFruit = fruitLevel <= 3;
var isLargeFruit = fruitLevel >= 6;
var rotationFactor = 0.04 / (1 + fruitLevel * 0.07);
var targetAngularVelocity = fruit.vx * rotationFactor;
+ // Reset fully stabilized status if fruit has significant movement
+ if (fruit.isFullyStabilized && (movementMagnitude > 0.5 || Math.abs(fruit.angularVelocity) > 0.02)) {
+ fruit.isFullyStabilized = false;
+ }
// Enhanced neighborhood awareness for rotation damping
var hasNeighbors = fruit.neighborContacts && fruit.neighborContacts.length > 0;
var hasManyNeighbors = fruit.neighborContacts && fruit.neighborContacts.length >= 3;
var neighborFactor = hasManyNeighbors ? 0.4 : hasNeighbors ? 0.7 : 1.0;
@@ -1427,29 +1439,61 @@
velocityDampening = 0.5 + 0.5 * (5 / velocityMagnitude);
}
var impulse1 = impulse * impulseRatio1 * velocityDampening;
var impulse2 = impulse * impulseRatio2 * velocityDampening;
- fruit1.vx -= impulse1 * normalizeX;
- fruit1.vy -= impulse1 * normalizeY;
- fruit2.vx += impulse2 * normalizeX;
- fruit2.vy += impulse2 * normalizeY;
+ // Don't apply impulses to fully stabilized fruits
+ if (!fruit1.isFullyStabilized) {
+ fruit1.vx -= impulse1 * normalizeX;
+ fruit1.vy -= impulse1 * normalizeY;
+ }
+ if (!fruit2.isFullyStabilized) {
+ fruit2.vx += impulse2 * normalizeX;
+ fruit2.vy += impulse2 * normalizeY;
+ }
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;
+ // Only apply friction impulses to non-stabilized fruits
+ if (!fruit1.isFullyStabilized) {
+ fruit1.vx -= frictionImpulse * tangentX * impulseRatio1;
+ fruit1.vy -= frictionImpulse * tangentY * impulseRatio1;
+ }
+ if (!fruit2.isFullyStabilized) {
+ fruit2.vx += frictionImpulse * tangentX * impulseRatio2;
+ fruit2.vy += frictionImpulse * tangentY * impulseRatio2;
+ }
+ // If collision force is significant, reset stabilized state
+ var impact1 = Math.abs(impulse1 * velocityDampening);
+ var impact2 = Math.abs(impulse2 * velocityDampening);
+ if (fruit1.isFullyStabilized && impact1 > 1.5) {
+ fruit1.isFullyStabilized = false;
+ }
+ if (fruit2.isFullyStabilized && impact2 > 1.5) {
+ fruit2.isFullyStabilized = false;
+ }
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;
+ // Only apply angular velocity changes to non-stabilized fruits
+ if (!fruit1.isFullyStabilized) {
+ fruit1.angularVelocity += inertia2 > 0 ? angularImpulse * (inertia1 / (inertia1 + inertia2)) : angularImpulse * 0.5;
+ fruit1.angularVelocity *= 0.95;
+ }
+ if (!fruit2.isFullyStabilized) {
+ fruit2.angularVelocity -= inertia1 > 0 ? angularImpulse * (inertia2 / (inertia1 + inertia2)) : angularImpulse * 0.5;
+ fruit2.angularVelocity *= 0.95;
+ }
+ // Reset stabilization if angular impact is significant
+ var angularImpactMagnitude = Math.abs(angularImpulse);
+ if (fruit1.isFullyStabilized && angularImpactMagnitude > 0.015) {
+ fruit1.isFullyStabilized = false;
+ }
+ if (fruit2.isFullyStabilized && angularImpactMagnitude > 0.015) {
+ fruit2.isFullyStabilized = false;
+ }
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;