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
User prompt
small fruits vibrate and bounce around too much when caught in between other fruits. create a mechanism where the fruit stops after a while, so they can bounce a max amount of times, but bounce less and less after each interaction with other fruits, so after a while it bounces less
User prompt
some fruits now remain stuck in mid air, especially the orange, fix this please
User prompt
smaller fruits seem to be heavier than larger fruit, going in between larger fruits very easy, all ending up at the bottom of the screen. fix this so it's the other way around, and larger fruits level are heavier, and smaller fruits are lighter and tend to stay on top
User prompt
smaller fruits still vibrate a LOT when in between larger fruits, make them vibrate less or change how they work so they reach a halt faster
User prompt
small fruits tend to slide to the floor between the larger fruits way too smoothly, as if they are heavier than the larger ones, ending up always going to the bottom and ending up merging since they all meet there. they should be so heavy, matter a fact it hsould be the other way, the larger the fruit level, the heavier and the harder to push by smaller fruits
User prompt
ensure the fires logic works as intended. always start rom 3 fires, but then with the addition of each new fruit type on the board, increase the number of fires. or decrease them if that certain fruit type was merges and is no longer present on the board
Code edit (2 edits merged)
Please save this source code
User prompt
altrnate fires frame every 300 miliseconds
User prompt
create a new asset named fire_2 and add it as a second frame to the fire. each fire is composed of 2 alternating frames once every 100 miliseconds
Code edit (1 edits merged)
Please save this source code
User prompt
move all the fires 500 pixels higher
User prompt
Instead of dynamically creating/destroying fires, pre-populate the maximum possible number of fire elements at the start of the game. These fires will be layered, with the "first" created being at the highest visual point (rendered first, thus appearing furthest back/top) and subsequent ones added progressively lower. The visibility of these pre-created fires will then be controlled based on the game state (number of unique fruit types). II. FireElement Class (Largely Unchanged, but note z-ordering): The FireElement class itself (handling individual asset, sway, alpha flicker) can remain mostly the same. Crucially, the order in which FireElement instances are added to their parent fireContainer will now directly determine their visual layering. The first one added will be at the "back" (highest on screen if y values are managed accordingly). III. Global Variables for Fire Management (Adjustments): fireContainer: Still needed. activeFireElements: This array will now hold all pre-created fire elements, regardless of their current visibility. BASE_FIRES_VISIBLE: Change BASE_FIRES to this, representing the number of fires visible when 0 unique fruit types are present (value: 3). FRUITS_TYPE_PER_ADDITIONAL_FIRE_VISIBLE: New constant. This dictates how many unique fruit types need to be present to reveal one additional fire beyond the base. (value: 1, as per "another added for each type of fruit existing"). MAX_POSSIBLE_FIRES: Define the absolute maximum number of fire elements to create at startup (e.g., 13, or BASE_FIRES_VISIBLE + number of unique fruit types in the game). FIRE_Y_START_TOP: New constant. This will be the y position for the highest (first created) fire. It should be high enough on the screen (e.g., gameOverLine.y + some_offset_above_line). FIRE_Y_STACK_OFFSET_DOWNWARDS: New constant. This will be the positive offset to position each subsequent fire lower than the previous one (e.g., 100 pixels). FIRE_X_SPREAD: Still relevant for horizontal placement. IV. Initialization Logic in initGame(): Create fireContainer: As before, add it to the game at a low z-index. Pre-Populate All Fires: Loop from i = 0 to MAX_POSSIBLE_FIRES - 1. Inside the loop, instantiate a FireElement. Y-Positioning (Reversed Order): The fire at index i should be positioned at FIRE_Y_START_TOP + (i * FIRE_Y_STACK_OFFSET_DOWNWARDS). This makes fire[0] the highest, fire[1] 100px below it, and so on. X-Positioning: Randomize horizontally using FIRE_X_SPREAD around gameWidth / 2. X-Axis Flipping: Apply scaleX = -1 to the fireAsset if i % 2 === 1 (for the 2nd, 4th, 6th, etc., fire created, which will be visually lower ones). Initial Visibility: Set newFire.visible = false; for all fires initially. Add the newFire to fireContainer (the order of addChild matters for default layering if not using addChildAt). Store the newFire in the activeFireElements array. Initial Visibility Update: After creating all fires, call the revised updateFireVisibility() function (see V) once to set the initial visible fires based on zero fruits. V. updateFireVisibility() Function (Replaces updateFireBackground()): This function will now only control the visible property of the pre-existing fire elements. Count Unique Active Fruit Types: Iterate through the fruits array. Keep track of unique fruit.type.ids for fruits that are active (not static, not merging). Store the uniqueFruitTypeCount. Calculate Target Visible Fires: targetVisibleCount = BASE_FIRES_VISIBLE + uniqueFruitTypeCount; Ensure targetVisibleCount is capped by MAX_POSSIBLE_FIRES (the total number of fires in activeFireElements). Update Visibility: Iterate through the activeFireElements array from index = 0 to MAX_POSSIBLE_FIRES - 1. The fires that should be visible are the last targetVisibleCount elements in the activeFireElements array, because these are the ones created to be at the bottom. Hint: For fire[i]: If i >= (MAX_POSSIBLE_FIRES - targetVisibleCount), then activeFireElements[i].visible = true;. Else, activeFireElements[i].visible = false;. This logic effectively "reveals" fires from the bottom of the pre-populated stack upwards. VI. game.update() Function: Call updateFireVisibility(): On every frame, call this function to adjust which fires are visible based on the current fruit types. Update Individual Fire Animations: Loop through all FireElement instances in activeFireElements and call their update() method. Even if not visible, their internal animation state (like sway position) can continue to update, so they look natural when they become visible. Alternatively, you could choose to only update() visible fires for a slight performance optimization, but it might lead to less smooth transitions when they reappear. The alpha flicker should ideally only run/loop for visible fires (the FireElement class has a check for self.visible in its tween loop). VII. Cleanup (initGame()): * When initGame() runs again, ensure all FireElement instances from the previous game in activeFireElements are properly destroy()-ed and removed from fireContainer before re-populating. Clearing activeFireElements and destroying/recreating fireContainer should handle this.
User prompt
change the fires flicker range to 10%-50%
User prompt
III. initGame() Function: Call updateFireBackground(): Ensure updateFireBackground() is called once at the end of initGame() (after fireContainer is created and activeFireElements is empty) to establish the initial BASE_FIRES. The corrected updateFireBackground will handle this based on zero initial fruits. IV. game.update() Function: Call updateFireBackground(): This is correctly called to adjust fires based on fruit count. Update Individual Fires: The loop for (var i = 0; i < activeFireElements.length; i++) { activeFireElements[i].update(); } is correct for driving the individual sway animations.
User prompt
Alpha Range: The FireElement class currently sets alphaMin and alphaMax but the flicker functions flickerToMax and flickerToMin use hardcoded values 0.6 and 0.2. Correction: Modify flickerToMax to use self.alphaMax (target 1.0 as per requirement) and flickerToMin to use self.alphaMin (target 0.7 as per requirement). Hint: In FireElement: // self.alphaMin = 0.2; // Current (incorrect based on new req) // self.alphaMax = 0.6; // Current (incorrect based on new req) self.alphaMin = 0.7; // As per new requirement self.alphaMax = 1.0; // As per new requirement Use code with caution. JavaScript And update the tween targets: // In self.flickerToMax // alpha: 0.6 -> alpha: self.alphaMax // In self.flickerToMin // alpha: 0.2 -> alpha: self.alphaMin โช๐ก Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the fires alpha between 10%-50% and also make them actualyl flicker instead of smoothly transition between their alpha. I want them to randomly flicker their alpha between the range of 10-50 โช๐ก Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fire Removal: When targetFireCount is less than activeFireElements.length, fires should be removed from the end of the activeFireElements array (these are the "highest" or "last added" ones). Ensure destroy() is called on the FireElement and it's removed from fireContainer.
User prompt
Consistent Fire Creation and Placement (createFireElement helper function): Single Creation Point: All fires (initial and subsequent) should be created through a single, consistent helper function (like the existing createFireElement). This ensures all fires follow the same rules. Y-Positioning: The yPos calculation needs to be strictly incremental. The first fire (index 0) should be at gameHeight + FIRE_Y_START_OFFSET. Each subsequent fire (index 1, 2, 3, ...) should be FIRE_Y_STACK_OFFSET (which is 100 in your requirement, but your constant FIRE_Y_STACK_OFFSET is 80 - ensure this is the desired 100) higher than the previous fire's base y-position. Hint: The yPos for fire[index] should be (gameHeight + FIRE_Y_START_OFFSET) - (index * 100). The FIRE_Y_STACK_OFFSET in the global constants should be set to 100 if that's the desired vertical separation. The + 100 in the current yPos calculation (gameHeight + (FIRE_Y_START_OFFSET + 100)) is likely causing the initial fires to be placed differently. It should just be gameHeight + FIRE_Y_START_OFFSET as the starting baseline. X-Axis Flipping: The if (index % 2 === 1) condition correctly flips every other fire. This means fire at index 0 (first) is normal, index 1 (second) is flipped, index 2 (third) is normal, index 3 (fourth) is flipped, and so on. This seems to match the "alternate their x axis" requirement. Layering: Continue adding new fires to fireContainer at index 0 using addChildAt(newFire, 0) to ensure new fires appear behind existing ones.
User prompt
Unified Fire Count Calculation: Remove Old Logic: The current updateFireBackground seems to base the fire count on typeCount (number of unique fruit types). This needs to change. New Logic: The target number of fires should be BASE_FIRES (which is 3) plus one additional fire for every FRUITS_PER_ADDITIONAL_FIRE total active fruits on the board (not unique types). Hint: Calculate activeFruitCount = fruits.filter(f => f && !f.isStatic && !f.merging).length; Hint: targetFireCount = BASE_FIRES + Math.floor(Math.max(0, activeFruitCount - 1) / FRUITS_PER_ADDITIONAL_FIRE); (The -1 in activeFruitCount - 1 ensures the first 1-10 fruits correctly map to BASE_FIRES, and then each subsequent block of 10 adds one more). Ensure targetFireCount is capped by MAX_FIRES.
User prompt
apply the alternation flipping logic for the first 3 created fires as well, so that the second fire is flipped
User prompt
centralize the fire creation logic as the 3 initial fires don't have the same values or logic as the next added ones.
User prompt
make the game over bar visible please. also the game came out EXCEPTIONAL! amazing job Ava!
User prompt
make the fires flicker between 20%-60% alpha
Code edit (2 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var ChargedBallUI = Container.expand(function () { var self = Container.call(this); self.chargeNeededForRelease = 15; self.currentCharge = 0; self.isReadyToRelease = false; self.countdownText = null; self.pulseAnimationActive = false; self.initialize = function () { self.portalAsset = self.attachAsset('portal', { anchorX: 0.5, anchorY: 0.5 }); self.portalAsset.x = gameWidth / 2 + 870; self.portalAsset.alpha = 0; // Start invisible self.portalAsset.scaleX = 0; self.portalAsset.scaleY = 0; self.y = 120; }; self.updateChargeDisplay = function (chargeCount) { self.currentCharge = chargeCount; var remainingCount = Math.max(0, self.chargeNeededForRelease - self.currentCharge); // No hue/tint changes for portal - keep original appearance // Calculate size based on charge progress var progressPercent = (self.chargeNeededForRelease - remainingCount) / self.chargeNeededForRelease; var targetScale = progressPercent; // Make portal visible once charging starts if (progressPercent > 0 && self.portalAsset.alpha === 0) { self.portalAsset.alpha = 1; } tween(self.portalAsset, { scaleX: targetScale, scaleY: targetScale, alpha: progressPercent // Gradually increase visibility }, { duration: 300, easing: tween.easeOut }); if (remainingCount === 0 && !self.isReadyToRelease) { self.setReadyState(true); } }; self.setReadyState = function (isReady) { self.isReadyToRelease = isReady; if (isReady) { tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0, rotation: Math.PI * 2 // Make portal spin clockwise 360 degrees }, { duration: 300, easing: tween.easeOut }); self.startPulseAnimation(); } }; self.startPulseAnimation = function () { if (self.pulseAnimationActive) { return; } self.pulseAnimationActive = true; self._pulseText(); }; self._pulseText = function () { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.3, scaleY: 1.3 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut, onFinish: self._pulseText }); } }); }; self.reset = function () { self.isReadyToRelease = false; self.currentCharge = 0; self.pulseAnimationActive = false; tween(self.portalAsset, { alpha: 0 }, { duration: 200, easing: tween.easeOut }); tween(self.portalAsset, { scaleX: 0, scaleY: 0 }, { duration: 200, easing: tween.easeOut }); }; return self; }); var CollisionComponent = Container.expand(function () { var self = Container.call(this); self.wallContactFrames = 0; self.checkBoundaryCollisions = function (fruit, walls, floor) { if (!walls || !walls.left || !walls.right || !floor) { return; } var fruitHalfWidth = fruit.width / 2; var fruitHalfHeight = fruit.height / 2; var fruitLevel = getFruitLevel(fruit); var sizeReduction = 0; if (fruitLevel > 5) { sizeReduction = (fruitLevel - 5) * 3; } fruitHalfWidth = Math.max(10, fruitHalfWidth - sizeReduction / 2); fruitHalfHeight = Math.max(10, fruitHalfHeight - sizeReduction / 2); var cosAngle = Math.abs(Math.cos(fruit.rotation)); var sinAngle = Math.abs(Math.sin(fruit.rotation)); var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle; var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle; var wasInContact = fruit.wallContactFrames > 0; fruit._boundaryContacts = fruit._boundaryContacts || { left: false, right: false, floor: false }; fruit._boundaryContacts.left = false; fruit._boundaryContacts.right = false; fruit._boundaryContacts.floor = false; self.checkLeftWallCollision(fruit, walls.left, effectiveWidth); self.checkRightWallCollision(fruit, walls.right, effectiveWidth); self.checkFloorCollision(fruit, floor, effectiveHeight); var isInContact = fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor; if (isInContact) { fruit.wallContactFrames++; var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01); fruit.angularVelocity *= progressiveFriction; if (fruit.wallContactFrames > 10) { fruit.vx *= 0.9; fruit.vy *= 0.9; } } else { fruit.wallContactFrames = Math.max(0, fruit.wallContactFrames - 1); } }; self.checkLeftWallCollision = function (fruit, leftWall, effectiveWidth) { var leftBoundary = leftWall.x + leftWall.width / 2 + effectiveWidth; if (fruit.x < leftBoundary) { fruit.x = leftBoundary; fruit.vx = -fruit.vx * fruit.elasticity * 0.7; 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; fruit.vx = -fruit.vx * fruit.elasticity * 0.7; var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; fruit._boundaryContacts.right = true; if (fruit.wallContactFrames > 3 && Math.abs(fruit.vx) < 0.8) { fruit.vx = 0; } } }; self.checkFloorCollision = function (fruit, floor, effectiveHeight) { var floorCollisionY = floor.y - floor.height / 2 - effectiveHeight; if (fruit.y > floorCollisionY) { fruit.y = floorCollisionY; var oldVy = fruit.vy; fruit.vy = -fruit.vy * fruit.elasticity * 0.5; 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.15); 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.bounceCount = physics.bounceCount; self.maxBounces = physics.maxBounces; self.lastBounceTime = physics.lastBounceTime; self.bounceThreshold = physics.bounceThreshold; 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.maxBounces = 6; // Reduced maximum number of bounces before enforced damping self.bounceCount = 0; // Current bounce count self.lastBounceTime = 0; // Track time of last bounce self.bounceThreshold = 0.5; // Velocity threshold to count as a bounce self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) { var shouldStabilize = false; var stabilizationLevel = 0; var fruitLevel = getFruitLevel(fruit); if (!fruit._stabilizeMetrics) { fruit._stabilizeMetrics = { consecutiveSmallMovements: 0, wallContactDuration: 0, surroundedDuration: 0, restingDuration: 0 }; } var massFactor = Math.pow(fruitLevel, 1.8) / 10; // Increased power to amplify mass difference // Lower thresholds for large fruits (harder to move) and higher for small fruits (easier to move) var movementThreshold = 0.7 - (fruitLevel - 1) * 0.08; // Make large fruits stabilize their rotation more quickly var angularThreshold = 0.07 - (fruitLevel - 1) * 0.006; // Specific oranges fix - add periodic small impulse for mid-air stuck fruit if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE' && Math.abs(fruit.vx) < 0.1 && Math.abs(fruit.vy) < 0.1 && !fruit._boundaryContacts && fruit.neighborContacts && fruit.neighborContacts.length < 2) { // Apply a small random impulse to oranges that appear stuck fruit.vx += Math.random() * 0.4 - 0.2; fruit.vy += Math.random() * 0.3 - 0.05; // Bias slightly downward } if (movementMagnitude < movementThreshold && Math.abs(fruit.angularVelocity) < angularThreshold) { // Small fruits take longer to stabilize, while large fruits stabilize faster var smallFruitBonus = fruitLevel <= 3 ? 0.3 : 0; var largeFruitBonus = fruitLevel >= 6 ? (fruitLevel - 5) * 0.4 : 0; fruit._stabilizeMetrics.consecutiveSmallMovements += 1.5 + massFactor * 0.3 + largeFruitBonus - smallFruitBonus; fruit._stabilizeMetrics.restingDuration += 1.5 + massFactor * 0.3 + largeFruitBonus - smallFruitBonus; } else { fruit._stabilizeMetrics.consecutiveSmallMovements = 0; fruit._stabilizeMetrics.restingDuration = Math.max(0, fruit._stabilizeMetrics.restingDuration - 2); } if (fruit.wallContactFrames > 0) { // Increase wall contact stabilization for smaller fruits var wallContactBonus = fruitLevel <= 3 ? 0.4 : 0; fruit._stabilizeMetrics.wallContactDuration += 1.2 + massFactor * 0.15 + wallContactBonus; } else { fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1); } if (fruit.surroundedFrames > 0) { // Increase surrounded stabilization for smaller fruits var surroundedBonus = fruitLevel <= 3 ? 0.6 : 0; fruit._stabilizeMetrics.surroundedDuration += 1.5 + massFactor * 0.25 + surroundedBonus; } else { fruit._stabilizeMetrics.surroundedDuration = Math.max(0, fruit._stabilizeMetrics.surroundedDuration - 1); } var levelFactor = (fruitLevel - 1) * 0.12; var totalStabilizationScore = fruit._stabilizeMetrics.consecutiveSmallMovements * (1.0 + levelFactor) + fruit._stabilizeMetrics.wallContactDuration * (0.8 + levelFactor) + fruit._stabilizeMetrics.surroundedDuration * (1.2 + levelFactor) + fruit._stabilizeMetrics.restingDuration * (0.5 + levelFactor); // Lower stabilization thresholds for small fruits to make them stabilize more quickly var sizeAdjustment = fruitLevel <= 3 ? 3 : 0; var fullStabilizationThreshold = 15 - (fruitLevel - 1) * 1.0 - sizeAdjustment; var partialStabilizationThreshold = 8 - (fruitLevel - 1) * 0.6 - sizeAdjustment * 0.5; if (totalStabilizationScore > fullStabilizationThreshold) { stabilizationLevel = 2; } else if (totalStabilizationScore > partialStabilizationThreshold) { stabilizationLevel = 1; } if (stabilizationLevel > 0) { // Larger fruits stabilize more quickly (harder to move) var baseDampFactor = stabilizationLevel === 2 ? 0.6 : 0.85; // Larger fruits have stronger mass-based damping var massDampFactor = baseDampFactor - massFactor * 0.04; // Make it harder for larger fruits to be displaced var dampFactor = Math.max(0.4, massDampFactor - (fruitLevel - 1) * 0.03); // Apply stabilization differently based on size if (fruitLevel <= 3) { // Small fruits stabilize faster to reduce vibration dampFactor = Math.min(dampFactor * 0.85, 0.85); } else if (fruitLevel >= 7) { // Very large fruits (levels 7+) are more stable, harder to move dampFactor = Math.max(0.3, dampFactor - 0.1); } fruit.vx *= dampFactor; fruit.vy *= dampFactor; fruit.angularVelocity *= dampFactor * 0.9; // Reduce angular velocity more aggressively // More aggressive stop threshold for smaller fruits var sizeStopBonus = fruitLevel <= 3 ? 0.05 : 0; var stopThreshold = 0.15 - (fruitLevel - 1) * 0.01 + sizeStopBonus; if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) { fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; return true; } } return false; }; self.apply = function (fruit) { if (fruit.isStatic || fruit.merging) { return; } fruit.lastVx = fruit.vx; fruit.lastVy = fruit.vy; var fruitLevel = getFruitLevel(fruit); // Significantly stronger gravity multiplier for larger fruits var gravityMultiplier = 1 + (fruitLevel - 1) * 0.8; fruit.vy += fruit.gravity * gravityMultiplier * 5; var fruitLevel = getFruitLevel(fruit); // More aggressive max velocity reduction for larger fruits to make them feel heavier var maxVelocity = 65 - Math.min(25, (fruitLevel - 1) * 2.0); if (Math.abs(fruit.vx) > maxVelocity) { fruit.vx = fruit.vx > 0 ? maxVelocity : -maxVelocity; } if (Math.abs(fruit.vy) > maxVelocity) { fruit.vy = fruit.vy > 0 ? maxVelocity : -maxVelocity; } // Substantially enhanced downward force for larger fruits if (fruitLevel > 3) { var bottomTendencyForce = (fruitLevel - 2) * 0.08; fruit.vy += bottomTendencyForce; } else if (fruitLevel <= 2) { // Slight upward force for very small fruits to help them stay on top fruit.vy -= 0.02; } fruit.x += fruit.vx; fruit.y += fruit.vy; var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); var velocityChange = Math.abs(fruit.vx - fruit.lastVx) + Math.abs(fruit.vy - fruit.lastVy); var isFullyStabilized = self.stabilizeFruit(fruit, movementMagnitude, velocityChange); if (!isFullyStabilized) { if (movementMagnitude > 0.5 || velocityChange > 0.3) { // Larger fruits rotate less easily var rotationFactor = 0.015 / (1 + (fruitLevel - 1) * 0.18); var targetAngularVelocity = fruit.vx * rotationFactor; fruit.angularVelocity = fruit.angularVelocity * 0.8 + targetAngularVelocity * 0.2; } else { fruit.angularVelocity *= 0.8; } fruit.rotation += fruit.angularVelocity; // Heavier fruits have more momentum (less friction) var frictionModifier = 0.99 + (fruitLevel - 1) * 0.005; fruit.vx *= fruit.friction * frictionModifier; fruit.vy *= fruit.friction * frictionModifier * 0.99; // Larger fruits come to rest more decisively var horizontalStopThreshold = Math.max(0.05, 0.15 - (fruitLevel - 1) * 0.01); var verticalStopThreshold = Math.max(0.05, 0.15 - (fruitLevel - 1) * 0.01); if (Math.abs(fruit.vx) < horizontalStopThreshold) { fruit.vx = 0; } if (Math.abs(fruit.vy) < verticalStopThreshold && fruit.y > gameHeight - 300) { fruit.vy = 0; } self.handleRotationDamping(fruit); } }; self.handleRotationDamping = function (fruit) { var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); var fruitLevel = getFruitLevel(fruit); var isSmallFruit = fruitLevel <= 3; var isLargeFruit = fruitLevel >= 6; // Less damping for smaller fruits (stay in motion longer) and more damping for larger fruits (settle faster) var sizeBasedDamp = isSmallFruit ? -0.05 : isLargeFruit ? 0.12 : 0; 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; // Additional damping for fruits that have bounced too much var excessBounces = fruit.bounceCount ? Math.max(0, fruit.bounceCount - fruit.maxBounces) : 0; if (excessBounces > 0 && isSmallFruit) { // Enhanced rotation damping for small fruits that have bounced too much var bounceDampFactor = Math.max(0.4, 0.9 - excessBounces * 0.05); fruit.angularVelocity *= bounceDampFactor; // Hard stop for excessive bounces if (excessBounces > 3 && Math.abs(fruit.angularVelocity) < angularThreshold * 2) { fruit.angularVelocity = 0; } } if (Math.abs(fruit.angularVelocity) < angularThreshold) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > restFramesThreshold) { fruit.angularVelocity = 0; } } else { fruit.rotationRestCounter = 0; } // Limit max angular velocity slightly more for smaller fruits var maxAngularMultiplier = isSmallFruit ? 1.0 : 1.2; fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity * maxAngularMultiplier), fruit.maxAngularVelocity * maxAngularMultiplier); }; return self; }); var SpatialGrid = Container.expand(function (cellSize) { var self = Container.call(this); self.cellSize = cellSize || 200; self.grid = {}; self.lastRebuildTime = Date.now(); self.rebuildInterval = 60000; self.insertObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) { return; } obj._currentCells = obj._currentCells || []; var cells = self.getCellsForObject(obj); obj._currentCells = cells.slice(); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (!self.grid[cellKey]) { self.grid[cellKey] = []; } if (self.grid[cellKey].indexOf(obj) === -1) { self.grid[cellKey].push(obj); } } }; self.removeObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var cells = obj._currentCells || self.getCellsForObject(obj); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { var cellIndex = self.grid[cellKey].indexOf(obj); if (cellIndex !== -1) { self.grid[cellKey].splice(cellIndex, 1); } if (self.grid[cellKey].length === 0) { delete self.grid[cellKey]; } } } obj._currentCells = []; }; self.getCellsForObject = function (obj) { if (!obj || typeof obj.x !== 'number' || typeof obj.y !== 'number' || typeof obj.width !== 'number' || typeof obj.height !== 'number') { return []; } var cells = []; var halfWidth = obj.width / 2; var halfHeight = obj.height / 2; var minCellX = Math.floor((obj.x - halfWidth) / self.cellSize); var maxCellX = Math.floor((obj.x + halfWidth) / self.cellSize); var minCellY = Math.floor((obj.y - halfHeight) / self.cellSize); var maxCellY = Math.floor((obj.y + halfHeight) / self.cellSize); for (var cellX = minCellX; cellX <= maxCellX; cellX++) { for (var cellY = minCellY; cellY <= maxCellY; cellY++) { cells.push(cellX + "," + cellY); } } return cells; }; self.updateObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var newCells = self.getCellsForObject(obj); var oldCells = obj._currentCells || []; var cellsChanged = false; if (oldCells.length !== newCells.length) { cellsChanged = true; } else { for (var i = 0; i < newCells.length; i++) { if (oldCells.indexOf(newCells[i]) === -1) { cellsChanged = true; break; } } } if (cellsChanged) { self.removeObject(obj); self.insertObject(obj); } }; self.getPotentialCollisions = function (obj) { var candidates = []; var cells = self.getCellsForObject(obj); var addedObjects = {}; for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { for (var j = 0; j < self.grid[cellKey].length; j++) { var otherObj = self.grid[cellKey][j]; if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) { candidates.push(otherObj); addedObjects[otherObj.id] = true; } } } } return candidates; }; self.clear = function () { self.grid = {}; self.lastRebuildTime = Date.now(); }; self.rebuildGrid = function (allObjects) { self.grid = {}; self.lastRebuildTime = Date.now(); if (Array.isArray(allObjects)) { for (var i = 0; i < allObjects.length; i++) { if (allObjects[i] && !allObjects[i].merging && !allObjects[i].isStatic) { self.insertObject(allObjects[i]); } } } }; return self; }); var TrajectoryLine = Container.expand(function () { var self = Container.call(this); self.dotPool = new DotPool(100); self.activeDots = []; self.dots = []; self.dotSpacing = 10; self.dotSize = 15; self.maxDots = 100; self.createDots = function () { self.clearDots(); self.dotPool.initialize(self.maxDots); }; self.clearDots = function () { for (var i = 0; i < self.activeDots.length; i++) { if (self.activeDots[i]) { self.removeChild(self.activeDots[i]); self.dotPool.release(self.activeDots[i]); } } self.activeDots = []; }; self.updateTrajectory = function (startX, startY) { if (!activeFruit) { return; } self.clearDots(); var dotY = startY; var dotSpacing = 25; var dotCount = 0; var hitDetected = false; while (dotCount < self.maxDots && !hitDetected) { var dot = self.dotPool.get(); self.addChild(dot); self.activeDots.push(dot); dot.x = startX; dot.y = dotY; dot.visible = true; dot.alpha = 1.0; dotCount++; dotY += dotSpacing; var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; if (dotY > floorCollisionY) { if (dotCount > 0) { self.activeDots[dotCount - 1].y = floorCollisionY; } hitDetected = true; break; } var potentialHits = spatialGrid.getPotentialCollisions({ x: startX, y: dotY, width: activeFruit.width, height: activeFruit.height, id: 'trajectory_check' }); for (var j = 0; j < potentialHits.length; j++) { var fruit = potentialHits[j]; if (fruit && fruit !== activeFruit && !fruit.merging && fruit.width && fruit.height) { if (self.wouldIntersectFruit(fruit.x, fruit.y, startX, dotY, activeFruit, fruit)) { if (dotCount > 0) { var dx = fruit.x - startX; var dy = fruit.y - dotY; var dist = Math.sqrt(dx * dx + dy * dy); var overlap = activeFruit.width / 2 + fruit.width / 2 - dist; if (dist > 0) { self.activeDots[dotCount - 1].y = dotY - dy / dist * overlap; } } hitDetected = true; break; } } } } }; self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) { var dx = fruitX - dropX; var dy = fruitY - dropY; var distanceSquared = dx * dx + dy * dy; var activeFruitLevel = getFruitLevel(activeFruitObj); var targetFruitLevel = getFruitLevel(targetFruitObj); var activeFruitRadius = activeFruitObj.width / 2; var targetFruitRadius = targetFruitObj.width / 2; var hitboxReduction = 0; if (activeFruitLevel > 5) { hitboxReduction += (activeFruitLevel - 5) * 3; } if (targetFruitLevel > 5) { hitboxReduction += (targetFruitLevel - 5) * 3; } var combinedRadii = activeFruitRadius + targetFruitRadius - hitboxReduction; var minDistanceSquared = combinedRadii * combinedRadii; if (distanceSquared < minDistanceSquared) { return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf6e58d }); /**** * Game Code ****/ var gameOverLine; var pineapple; var pineappleActive = false; var pineapplePushCount = 0; var readyToReleaseCharged = false; var trajectoryLine; var chargedFruitIconScale = 0.3; var isClickable = true; var evolutionLine; var fireContainer; var activeFireElements = []; var BASE_FIRES = 3; var FRUITS_PER_ADDITIONAL_FIRE = 3; var FIRE_Y_START_OFFSET = 50; var FIRE_Y_STACK_OFFSET = 100; var FIRE_X_SPREAD = 500; var MAX_FIRES = 15; var fruitLevels = { 'CHERRY': 1, 'GRAPE': 2, 'APPLE': 3, 'ORANGE': 4, 'WATERMELON': 5, 'PINEAPPLE': 6, 'MELON': 7, 'PEACH': 8, 'COCONUT': 9, 'DURIAN': 10 }; function getFruitLevel(fruit) { if (!fruit || !fruit.type || !fruit.type.id) { return 10; } return fruitLevels[fruit.type.id.toUpperCase()] || 10; } function releasePineappleOnMerge() { mergeCounter++; pushPineapple(); if (mergeCounter >= 15 && !pineappleActive && pineapple) { pineappleActive = true; pineapple.isStatic = false; applyDropPhysics(pineapple, 2.5); fruits.push(pineapple); if (spatialGrid) { spatialGrid.insertObject(pineapple); } LK.setTimeout(function () { if (pineapple && fruits.includes(pineapple)) { pineapple.immuneToGameOver = false; } }, 2000); setupPineapple(); mergeCounter = 0; } } var FruitTypes = { CHERRY: { id: 'cherry', size: 150, points: 1, next: 'grape' }, GRAPE: { id: 'grape', size: 200, points: 2, next: 'apple' }, APPLE: { id: 'apple', size: 250, points: 3, next: 'orange' }, ORANGE: { id: 'orange', size: 200, points: 5, next: 'watermelon' }, WATERMELON: { id: 'watermelon', size: 350, points: 8, next: 'pineapple' }, PINEAPPLE: { id: 'pineapple', size: 400, points: 13, next: 'melon' }, MELON: { id: 'melon', size: 450, points: 21, next: 'peach' }, PEACH: { id: 'peach', size: 500, points: 34, next: 'coconut' }, COCONUT: { id: 'coconut', size: 550, points: 55, next: 'durian' }, DURIAN: { id: 'durian', size: 600, points: 89, next: null } }; var gameWidth = 2048; var gameHeight = 2732; var fruits = []; var nextFruitType = null; var activeFruit = null; var wallLeft, wallRight, gameFloor; var dropPointY = 200; var gameOver = false; var scoreText; var isDragging = false; var chargedBallUI = null; var chargeCounter = 0; var mergeCounter = 0; var lastDroppedFruit = null; var lastDroppedHasMerged = false; var spatialGrid = null; function setupBoundaries() { wallLeft = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallLeft.x = 0; wallLeft.y = gameHeight / 2; wallLeft.alpha = 0; wallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallRight.x = gameWidth; wallRight.y = gameHeight / 2; wallRight.alpha = 0; gameFloor = game.addChild(LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5 })); gameFloor.x = gameWidth / 2; gameFloor.y = gameHeight; gameFloor.alpha = 0; gameOverLine = game.addChild(new Line()); gameOverLine.x = gameWidth / 2; gameOverLine.y = 550; gameOverLine.scaleX = 1; gameOverLine.scaleY = 0.2; gameOverLine.alpha = 1; } function createNextFruit() { var fruitProbability = Math.random(); var fruitType = fruitProbability < 0.6 ? FruitTypes.CHERRY : FruitTypes.GRAPE; nextFruitType = fruitType; activeFruit = new Fruit(nextFruitType); activeFruit.x = lastDroppedFruit && lastDroppedFruit.x ? lastDroppedFruit.x : gameWidth / 2; activeFruit.y = dropPointY + 200; activeFruit.isStatic = true; // Initialize bounce tracking activeFruit.bounceCount = 0; activeFruit._lastBounceTime = 0; activeFruit._collisionHistory = {}; game.addChild(activeFruit); if (trajectoryLine) { trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y); } } function dropFruit() { if (gameOver || !activeFruit || !isClickable) { return; } isClickable = false; LK.setTimeout(function () { isClickable = true; }, 300); activeFruit.isStatic = false; applyDropPhysics(activeFruit, 3.5); fruits.push(activeFruit); spatialGrid.insertObject(activeFruit); lastDroppedFruit = activeFruit; lastDroppedHasMerged = false; chargeCounter++; updateChargedBallDisplay(); if (chargeCounter >= 15 && !readyToReleaseCharged) { releaseChargedBalls(); } activeFruit.mergeGracePeriodActive = true; LK.setTimeout(function () { if (activeFruit && fruits.includes(activeFruit)) { activeFruit.mergeGracePeriodActive = false; } }, 2000); if (trajectoryLine && trajectoryLine.dots && trajectoryLine.dots.length) { for (var i = 0; i < trajectoryLine.dots.length; i++) { trajectoryLine.dots[i].visible = false; } } for (var i = 0; i < fruits.length; i++) { if (fruits[i] && fruits[i].fromChargedRelease) { fruits[i].fromChargedRelease = false; } } LK.getSound('drop').play(); if (readyToReleaseCharged && chargeCounter >= 15) { LK.getSound('pickleRick').play(); var orange = new Fruit(FruitTypes.ORANGE); var minX = wallLeft.x + wallLeft.width / 2 + orange.width / 2 + 50; var maxX = wallRight.x - wallRight.width / 2 - orange.width / 2 - 50; orange.x = minX + Math.random() * (maxX - minX); orange.y = -orange.height; orange.isStatic = false; var forceMultiplier = 3.5 + (Math.random() * 1 - 0.5); applyDropPhysics(orange, forceMultiplier); orange.fromChargedRelease = true; game.addChild(orange); fruits.push(orange); spatialGrid.insertObject(orange); chargeCounter = 0; resetChargedBalls(); readyToReleaseCharged = false; } activeFruit = null; createNextFruit(); } function applyDropPhysics(fruit, forceMultiplier) { var fruitLevel = getFruitLevel(fruit); forceMultiplier *= 1.4; var levelAdjustedForce = forceMultiplier * (1 + (fruitLevel - 1) * 0.05); var mobilityReduction = Math.max(0, (fruitLevel - 2) * 0.03); levelAdjustedForce *= 1 - mobilityReduction; var angle = (Math.random() * 20 - 10) * (Math.PI / 180); fruit.vx = Math.sin(angle) * levelAdjustedForce; fruit.vy = Math.abs(Math.cos(angle) * levelAdjustedForce) * 1.5; fruit.gravity = 13.0 * (1 + (fruitLevel - 1) * 0.2); if (fruitLevel >= 5) { fruit.gravity *= 1 + (fruitLevel - 5) * 0.03; } 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, 2.0) * Math.pow(fruit1.type.size, 1.5); var mass2 = Math.pow(level2, 2.0) * Math.pow(fruit2.type.size, 1.5); var totalMass = mass1 + mass2; var impulseRatio1 = totalMass > 0 ? mass2 / totalMass : 0.5; var impulseRatio2 = totalMass > 0 ? mass1 / totalMass : 0.5; var levelDifference = Math.abs(level1 - level2); // Enhanced impact multiplier based on level difference var levelImpactMultiplier = 1 + levelDifference * 0.08; // Higher maximum impulse ratio allows more dramatic bounces var maxImpulseRatio = 2.2; if (level1 > level2) { // Larger fruit pushes smaller fruit more strongly impulseRatio2 = Math.min(impulseRatio2 * levelImpactMultiplier * 1.6, maxImpulseRatio * impulseRatio2); } else if (level2 > level1) { // Larger fruit pushes smaller fruit more strongly impulseRatio1 = Math.min(impulseRatio1 * levelImpactMultiplier * 1.6, maxImpulseRatio * impulseRatio1); } var velocityMagnitude = Math.sqrt(rvX * rvX + rvY * rvY); var velocityDampening = 1.0; if (velocityMagnitude > 5) { velocityDampening = 0.5 + 0.5 * (5 / velocityMagnitude); } var impulse1 = impulse * impulseRatio1 * velocityDampening; var impulse2 = impulse * impulseRatio2 * velocityDampening; var sizeDifference = Math.abs(fruit1.type.size - fruit2.type.size) / Math.max(fruit1.type.size, fruit2.type.size, 1); // Track bounce counts for both fruits fruit1.bounceCount = fruit1.bounceCount || 0; fruit2.bounceCount = fruit2.bounceCount || 0; var currentTime = Date.now(); // Count this as a bounce if velocity is significant if (velocityMagnitude > fruit1.bounceThreshold || velocityMagnitude > fruit2.bounceThreshold) { // Increment bounce counters based on elapsed time (avoid counting continuous contact) if (!fruit1._lastBounceTime || currentTime - fruit1._lastBounceTime > 200) { fruit1.bounceCount++; } if (!fruit2._lastBounceTime || currentTime - fruit2._lastBounceTime > 200) { fruit2.bounceCount++; } } // Apply progressive exponential bounce damping based on bounce count var bounce1Damping = Math.max(0.15, Math.pow(0.75, fruit1.bounceCount / (fruit1.maxBounces * 0.7))); var bounce2Damping = Math.max(0.15, Math.pow(0.75, fruit2.bounceCount / (fruit2.maxBounces * 0.7))); // Dramatically enhanced size-based bouncing effect if (fruit1.type.size < fruit2.type.size) { // Smaller fruit bounces much more aggressively against larger fruit var levelDiffBonus = Math.pow(level2 - level1, 1.2); impulse1 *= (1 + sizeDifference * 6.5 + levelDiffBonus * 0.3) * bounce1Damping; fruit1._lastBounceTime = currentTime; // Apply stronger upward bounce for small fruits to prevent them from sinking if (level1 < level2) { var upwardForce = Math.min(7.0, (level2 - level1) * 1.4) * bounce1Damping; // Extra upward force when small fruit is below larger fruit if (fruit1.y > fruit2.y) { upwardForce *= 1.5; } fruit1.vy -= upwardForce; } } else if (fruit2.type.size < fruit1.type.size) { // Smaller fruit bounces much more aggressively against larger fruit var levelDiffBonus = Math.pow(level1 - level2, 1.2); impulse2 *= (1 + sizeDifference * 6.5 + levelDiffBonus * 0.3) * bounce2Damping; fruit2._lastBounceTime = currentTime; // Apply stronger upward bounce for small fruits to prevent them from sinking if (level2 < level1) { var upwardForce = Math.min(7.0, (level1 - level2) * 1.4) * bounce2Damping; // Extra upward force when small fruit is below larger fruit if (fruit2.y > fruit1.y) { upwardForce *= 1.5; } fruit2.vy -= upwardForce; } } fruit1.vx -= impulse1 * normalizeX; fruit1.vy -= impulse1 * normalizeY; 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; 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 progressive bounce damping based on bounce count var fruit1ExcessBounces = Math.max(0, (fruit1.bounceCount || 0) - (fruit1.maxBounces || 8)); var fruit2ExcessBounces = Math.max(0, (fruit2.bounceCount || 0) - (fruit2.maxBounces || 8)); // Apply extra damping for fruits that have exceeded bounce limit - more aggressive curve var extraDamp1 = fruit1ExcessBounces > 0 ? Math.min(0.7, 0.3 + Math.pow(fruit1ExcessBounces, 1.5) * 0.05) : 0; var extraDamp2 = fruit2ExcessBounces > 0 ? Math.min(0.7, 0.3 + Math.pow(fruit2ExcessBounces, 1.5) * 0.05) : 0; // Also consider high bounce count even if not exceeding max - gradual slowdown if (fruit1.bounceCount > fruit1.maxBounces * 0.6) { extraDamp1 = Math.max(extraDamp1, (fruit1.bounceCount - fruit1.maxBounces * 0.6) / fruit1.maxBounces * 0.5); } if (fruit2.bounceCount > fruit2.maxBounces * 0.6) { extraDamp2 = Math.max(extraDamp2, (fruit2.bounceCount - fruit2.maxBounces * 0.6) / fruit2.maxBounces * 0.5); } 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 bounced excessively if (level1 <= 3 && (fruit1ExcessBounces > 3 || fruit1.bounceCount > fruit1.maxBounces * 0.8) && 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 && (fruit2ExcessBounces > 3 || fruit2.bounceCount > fruit2.maxBounces * 0.8) && Math.abs(fruit2.vx) < 1.0 && Math.abs(fruit2.vy) < 1.0) { fruit2.vx *= 0.2; fruit2.vy *= 0.2; fruit2.angularVelocity *= 0.2; } } if (fruit1.neighborContacts.length >= 3 || fruit2.neighborContacts.length >= 3) { var stabilizeFactor = 0.6; fruit1.vx *= stabilizeFactor; fruit1.vy *= stabilizeFactor; fruit2.vx *= stabilizeFactor; fruit2.vy *= stabilizeFactor; fruit1.angularVelocity *= 0.75; fruit2.angularVelocity *= 0.75; } // Detect vibration pattern - fruits that bounce back and forth rapidly var isVibrating1 = fruit1.bounceCount > 4 && currentTime - fruit1._lastBounceTime < 300; var isVibrating2 = fruit2.bounceCount > 4 && currentTime - fruit2._lastBounceTime < 300; // Special case for small fruits that are vibrating too much if (isVibrating1 && level1 <= 3 || isVibrating2 && level2 <= 3) { // Apply strong stabilization to immediately reduce vibration var hardStopFactor = 0.1; if (isVibrating1 && level1 <= 3) { fruit1.vx *= hardStopFactor; fruit1.vy *= hardStopFactor; fruit1.angularVelocity *= hardStopFactor; // If velocity is already small, completely stop the fruit if (Math.abs(fruit1.vx) < 0.7 && Math.abs(fruit1.vy) < 0.7) { fruit1.vx = 0; fruit1.vy = 0; fruit1.angularVelocity = 0; } } if (isVibrating2 && level2 <= 3) { fruit2.vx *= hardStopFactor; fruit2.vy *= hardStopFactor; fruit2.angularVelocity *= hardStopFactor; // If velocity is already small, completely stop the fruit if (Math.abs(fruit2.vx) < 0.7 && Math.abs(fruit2.vy) < 0.7) { fruit2.vx = 0; fruit2.vy = 0; fruit2.angularVelocity = 0; } } } } } } } } } function checkGameOver() { if (gameOver) { return; } for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit || fruit === activeFruit || fruit.merging || fruit.isStatic) { continue; } var fruitHalfHeight = fruit.height / 2; var fruitHalfWidth = fruit.width / 2; var fruitLevel = getFruitLevel(fruit); var sizeReduction = 0; if (fruitLevel > 5) { sizeReduction = (fruitLevel - 5) * 3; } fruitHalfHeight = Math.max(10, fruitHalfHeight - sizeReduction / 2); fruitHalfWidth = Math.max(10, fruitHalfWidth - sizeReduction / 2); var cosAngle = Math.abs(Math.cos(fruit.rotation)); var sinAngle = Math.abs(Math.sin(fruit.rotation)); var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle; var fruitTopY = fruit.y - effectiveHeight; var lineBottomY = gameOverLine.y + gameOverLine.height / 2; if (fruitTopY <= lineBottomY) { var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle; var fruitLeftX = fruit.x - effectiveWidth; var fruitRightX = fruit.x + effectiveWidth; var lineLeftX = gameOverLine.x - gameOverLine.width * gameOverLine.scaleX / 2; var lineRightX = gameOverLine.x + gameOverLine.width * gameOverLine.scaleX / 2; var horizontalOverlap = !(fruitRightX < lineLeftX || fruitLeftX > lineRightX); if (horizontalOverlap) { if (fruit.immuneToGameOver) { continue; } var stableOrSlowing = Math.abs(fruit.vy) < 1.0 || fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor); if (stableOrSlowing) { gameOver = true; LK.showGameOver(); return; } } } else { fruit.safetyPeriod = undefined; } } } function setupPineapple() { pineapple = new Fruit(FruitTypes.PINEAPPLE); pineapple.x = -pineapple.width / 2; pineapple.y = 200; pineapple.isStatic = true; pineappleActive = false; pineapplePushCount = 0; game.addChild(pineapple); } function pushPineapple() { if (!pineappleActive && pineapple) { var step = mergeCounter; var totalSteps = 15; var percentage = Math.min(step / totalSteps, 1.0); var startPos = -pineapple.width / 2; var endPos = gameWidth * 0.16; var newX = startPos + percentage * (endPos - startPos); tween(pineapple, { x: newX }, { duration: 300, easing: tween.bounceOut }); } } function initGame() { LK.setScore(0); gameOver = false; fruits = []; chargeCounter = 0; if (chargedBallUI) { chargedBallUI.destroy(); } chargedBallUI = null; readyToReleaseCharged = false; lastScoreCheckForCoconut = 0; lastDroppedFruit = null; lastDroppedHasMerged = false; mergeCounter = 0; isClickable = true; // Initialize fire background if (fireContainer) { // Clean up existing fire elements for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) { activeFireElements[i].destroy(); } } fireContainer.destroy(); } fireContainer = new Container(); game.addChildAt(fireContainer, 0); // Add at bottom layer activeFireElements = []; if (spatialGrid) { spatialGrid.clear(); } else { var avgFruitSize = 0; var fruitCount = 0; for (var fruitType in FruitTypes) { if (FruitTypes.hasOwnProperty(fruitType)) { avgFruitSize += FruitTypes[fruitType].size; fruitCount++; } } avgFruitSize = fruitCount > 0 ? avgFruitSize / fruitCount : 300; var optimalCellSize = Math.ceil(avgFruitSize * 1.1); spatialGrid = new SpatialGrid(optimalCellSize); } LK.playMusic('bgmusic'); if (wallLeft) { wallLeft.destroy(); } if (wallRight) { wallRight.destroy(); } if (gameFloor) { gameFloor.destroy(); } if (gameOverLine) { gameOverLine.destroy(); } if (pineapple) { pineapple.destroy(); } if (trajectoryLine) { trajectoryLine.destroy(); } if (evolutionLine) { evolutionLine.destroy(); } setupBoundaries(); setupUI(); setupPineapple(); updateFireBackground(); // Initial setup of fire elements trajectoryLine = game.addChild(new TrajectoryLine()); trajectoryLine.createDots(); evolutionLine = game.addChild(new EvolutionLine()); evolutionLine.initialize(); updateScoreDisplay(); activeFruit = null; createNextFruit(); resetChargedBalls(); updateFireBackground(); // Initial setup of fire elements } function spawnCoconut() { var coconut = new Fruit(FruitTypes.COCONUT); var minX = wallLeft.x + wallLeft.width / 2 + coconut.width / 2 + 50; var maxX = wallRight.x - wallRight.width / 2 - coconut.width / 2 - 50; coconut.x = minX + Math.random() * (maxX - minX); coconut.y = gameHeight + coconut.height / 2; coconut.isStatic = true; game.addChild(coconut); fruits.push(coconut); coconut.safetyPeriod = false; coconut.immuneToGameOver = true; var targetY = gameHeight - gameFloor.height / 2 - coconut.height / 2 - 10; tween(coconut, { y: targetY }, { duration: 1200, easing: tween.easeIn, onFinish: function onFinish() { if (!coconut || !fruits.includes(coconut)) { return; } coconut.isStatic = false; coconut.vy = -2; coconut.vx = (Math.random() * 2 - 1) * 1.5; spatialGrid.insertObject(coconut); LK.setTimeout(function () { if (coconut && fruits.includes(coconut)) { coconut.immuneToGameOver = false; } }, 1000); } }); } var lastScoreCheckForCoconut = 0; game.down = function (x, y) { if (activeFruit && !gameOver) { isDragging = true; game.move(x, y); } }; game.move = function (x, y) { if (isDragging && activeFruit && !gameOver) { var fruitRadius = activeFruit.width / 2; var minX = wallLeft.x + wallLeft.width / 2 + fruitRadius; var maxX = wallRight.x - wallRight.width / 2 - fruitRadius; activeFruit.x = Math.max(minX, Math.min(maxX, x)); activeFruit.y = dropPointY + 200; if (trajectoryLine) { trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y); } } }; game.up = function () { if (isDragging && activeFruit && isClickable && !gameOver) { dropFruit(); } isDragging = false; }; function updatePhysics() { if (spatialGrid && Date.now() - spatialGrid.lastRebuildTime > spatialGrid.rebuildInterval) { spatialGrid.rebuildGrid(fruits); } // Check for and fix stuck fruits checkAndFixStuckFruits(); for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (!fruit || fruit.isStatic || fruit.merging) { continue; } fruit.updatePhysics(); var walls = { left: wallLeft, right: wallRight }; fruit.checkBoundaries(walls, gameFloor); } checkFruitCollisions(); for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (fruit && !fruit.isStatic && !fruit.merging) { if (fruit.surroundedFrames === undefined) { fruit.surroundedFrames = 0; } var boundaryContact = fruit.wallContactFrames > 0; var floorProximity = fruit.y + fruit.height / 2 >= gameFloor.y - gameFloor.height / 2 - 10; var hasMultipleNeighbors = fruit.neighborContacts && fruit.neighborContacts.length >= 2; var isSlowMoving = Math.abs(fruit.vx) < 0.3 && Math.abs(fruit.vy) < 0.3; var hasGapBelow = false; if (floorProximity || hasMultipleNeighbors) { var potentialNeighbors = spatialGrid.getPotentialCollisions({ x: fruit.x, y: fruit.y + fruit.height / 2 + 20, width: fruit.width * 0.6, height: 20, id: 'gap_check_' + fruit.id }); hasGapBelow = potentialNeighbors.length === 0 && !floorProximity; if (hasGapBelow && isSlowMoving) { var leftCount = 0, rightCount = 0; for (var j = 0; j < fruit.neighborContacts.length; j++) { var neighborId = fruit.neighborContacts[j]; for (var k = 0; k < fruits.length; k++) { if (fruits[k] && fruits[k].id === neighborId) { if (fruits[k].x < fruit.x) { leftCount++; } else if (fruits[k].x > fruit.x) { rightCount++; } break; } } } if (leftCount < rightCount) { fruit.vx -= 0.05; } else if (rightCount < leftCount) { fruit.vx += 0.05; } else { fruit.vx += Math.random() * 0.1 - 0.05; } } } var stabilizationScenario = boundaryContact ? "boundary" : floorProximity ? "floor" : hasMultipleNeighbors ? "surrounded" : "free"; var fruitLevel = getFruitLevel(fruit); var isSmallFruit = fruitLevel <= 3; switch (stabilizationScenario) { case "boundary": fruit.surroundedFrames = 0; break; case "floor": fruit.surroundedFrames = 0; break; case "surrounded": fruit.surroundedFrames++; // Much stronger stabilization for smaller fruits to reduce vibration var sizeBonus = isSmallFruit ? 0.15 : fruitLevel >= 7 ? -0.05 : 0; var stabilizationStrength = Math.min(0.95, 0.75 + fruit.surroundedFrames * (0.022 + sizeBonus)); // Additional stabilization boost when a fruit has multiple neighbors if (fruit.neighborContacts.length >= 3) { var contactBonus = isSmallFruit ? 0.1 : 0.03; stabilizationStrength = Math.min(0.98, stabilizationStrength + contactBonus); } // Apply even stronger damping to angular velocity for small fruits var angularStabilization = isSmallFruit ? stabilizationStrength * 0.9 : stabilizationStrength; fruit.vx *= stabilizationStrength; fruit.vy *= stabilizationStrength; fruit.angularVelocity *= angularStabilization; // Small fruits settle faster when surrounded var thresholdFrames = isSmallFruit ? 8 : 10; var thresholdFramesForced = isSmallFruit ? 15 : 18; if (fruit.surroundedFrames > thresholdFrames && isSlowMoving || fruit.surroundedFrames > thresholdFramesForced) { // Use larger threshold for small fruits to help them settle faster var velocityThreshold = isSmallFruit ? 0.08 : 0.05; var angularThreshold = isSmallFruit ? 0.008 : 0.005; if (Math.abs(fruit.vx) < velocityThreshold) { fruit.vx = 0; } if (Math.abs(fruit.vy) < velocityThreshold) { fruit.vy = 0; } if (Math.abs(fruit.angularVelocity) < angularThreshold) { fruit.angularVelocity = 0; } } break; case "free": fruit.surroundedFrames = 0; break; } spatialGrid.updateObject(fruit); } } } game.update = function () { if (gameOver) { return; } var currentScore = LK.getScore(); if (currentScore >= lastScoreCheckForCoconut + 1000) { lastScoreCheckForCoconut = Math.floor(currentScore / 1000) * 1000; spawnCoconut(); } else { if (currentScore > lastScoreCheckForCoconut) { lastScoreCheckForCoconut = Math.floor(currentScore / 1000) * 1000; } } updatePhysics(); checkGameOver(); // Update fire background updateFireBackground(); // Animate individual fire elements for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) { activeFireElements[i].update(); } } }; initGame(); function updateFireBackground() { // Track unique fruit types on the board var uniqueFruitTypes = {}; for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (fruit && !fruit.isStatic && !fruit.merging && fruit.type && fruit.type.id) { uniqueFruitTypes[fruit.type.id] = true; } } // Count the number of unique fruit types var uniqueTypeCount = Object.keys(uniqueFruitTypes).length; // Calculate target number of fire elements: BASE_FIRES + one for each unique fruit type var targetFireCount = BASE_FIRES + uniqueTypeCount; targetFireCount = Math.min(targetFireCount, MAX_FIRES); // Create or remove fires to match target count if (targetFireCount > activeFireElements.length) { // Need to add more fires var newFiresCount = targetFireCount - activeFireElements.length; for (var i = 0; i < newFiresCount; i++) { // Create fire at appropriate position createFireElement(activeFireElements.length); } } else if (targetFireCount < activeFireElements.length) { // Need to remove excess fires var fireToRemoveCount = activeFireElements.length - targetFireCount; for (var i = 0; i < fireToRemoveCount; i++) { var fire = activeFireElements.pop(); // Remove from end (highest/last added fires) if (fire) { fire.destroy(); // Ensure destroy() is called to clean up resources fireContainer.removeChild(fire); } } } } // Helper function to create a single fire element at the specified index function createFireElement(index) { // Calculate y position (higher fires appear behind/further back) var yPos = gameHeight + FIRE_Y_START_OFFSET - index * FIRE_Y_STACK_OFFSET; // Randomize x position around center of screen var xPos = gameWidth / 2 + (Math.random() * FIRE_X_SPREAD - FIRE_X_SPREAD / 2); // Create new fire element var newFire = new FireElement(xPos, yPos); // Alternate x-axis flip for all fires including the first three if (index % 2 === 1) { newFire.fireAsset.scaleX = -1; // Flip every other fire } // Add at index 0 to render behind existing fires fireContainer.addChildAt(newFire, 0); activeFireElements.push(newFire); } function removeFruitFromGame(fruit) { var index = fruits.indexOf(fruit); if (index !== -1) { fruits.splice(index, 1); } // Make sure to clear bounce tracking to avoid memory leaks fruit.bounceCount = 0; fruit._lastBounceTime = 0; fruit._collisionHistory = {}; spatialGrid.removeObject(fruit); fruit.destroy(); } function checkAndFixStuckFruits() { // Current time to track stuck duration var currentTime = Date.now(); for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit || fruit.isStatic || fruit.merging) { continue; } // Create tracking object if it doesn't exist if (!fruit._stuckTracking) { fruit._stuckTracking = { lastPosition: { x: fruit.x, y: fruit.y }, stuckTime: 0, lastCheck: currentTime, lastForceApplied: 0 }; } // Distance moved since last check var dx = fruit.x - fruit._stuckTracking.lastPosition.x; var dy = fruit.y - fruit._stuckTracking.lastPosition.y; var distMoved = Math.sqrt(dx * dx + dy * dy); // Time since last check var timeDelta = currentTime - fruit._stuckTracking.lastCheck; // Update tracking fruit._stuckTracking.lastPosition.x = fruit.x; fruit._stuckTracking.lastPosition.y = fruit.y; fruit._stuckTracking.lastCheck = currentTime; var fruitLevel = getFruitLevel(fruit); var movementThreshold = 0.5; // If fruit hasn't moved significantly and is not touching boundaries var isOnFloor = fruit._boundaryContacts && fruit._boundaryContacts.floor; var isAtWall = fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right); // Check if fruit is stuck in mid-air if (distMoved < movementThreshold && !isOnFloor && !isAtWall) { fruit._stuckTracking.stuckTime += timeDelta; // If stuck for more than 2 seconds and not been nudged recently if (fruit._stuckTracking.stuckTime > 2000 && currentTime - fruit._stuckTracking.lastForceApplied > 1500) { // Apply a force based on fruit type - stronger for smaller fruits var forceMultiplier = 1.0; if (fruitLevel <= 4) { // Stronger nudge for smaller fruits, especially oranges forceMultiplier = 2.0; if (fruit.type && fruit.type.id && fruit.type.id.toUpperCase() === 'ORANGE') { forceMultiplier = 2.5; } } // Apply force with downward bias fruit.vx += (Math.random() * 2.0 - 1.0) * forceMultiplier; fruit.vy += (Math.random() * 1.0 + 0.5) * forceMultiplier; // Reset stuck tracking fruit._stuckTracking.stuckTime = 0; fruit._stuckTracking.lastForceApplied = currentTime; } } else { // Fruit is moving or touching boundaries, reset stuck counter fruit._stuckTracking.stuckTime = 0; } } }
===================================================================
--- original.js
+++ change.js
@@ -654,9 +654,9 @@
self.maxAngularVelocity = 0.08;
self.rotationRestCounter = 0;
self.lastVx = 0;
self.lastVy = 0;
- self.maxBounces = 8; // Maximum number of bounces before enforced damping
+ self.maxBounces = 6; // Reduced maximum number of bounces before enforced damping
self.bounceCount = 0; // Current bounce count
self.lastBounceTime = 0; // Track time of last bounce
self.bounceThreshold = 0.5; // Velocity threshold to count as a bounce
self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) {
@@ -1258,8 +1258,9 @@
activeFruit.isStatic = true;
// Initialize bounce tracking
activeFruit.bounceCount = 0;
activeFruit._lastBounceTime = 0;
+ activeFruit._collisionHistory = {};
game.addChild(activeFruit);
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
@@ -1483,11 +1484,11 @@
if (!fruit2._lastBounceTime || currentTime - fruit2._lastBounceTime > 200) {
fruit2.bounceCount++;
}
}
- // Apply progressive damping based on bounce count
- var bounce1Damping = Math.max(0.2, 1.0 - fruit1.bounceCount / fruit1.maxBounces * 0.8);
- var bounce2Damping = Math.max(0.2, 1.0 - fruit2.bounceCount / fruit2.maxBounces * 0.8);
+ // Apply progressive exponential bounce damping based on bounce count
+ var bounce1Damping = Math.max(0.15, Math.pow(0.75, fruit1.bounceCount / (fruit1.maxBounces * 0.7)));
+ var bounce2Damping = Math.max(0.15, Math.pow(0.75, fruit2.bounceCount / (fruit2.maxBounces * 0.7)));
// 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);
@@ -1561,12 +1562,19 @@
fruit2._collisionHistory[fruit1.id] = currentTime;
// Apply progressive bounce damping based on bounce count
var fruit1ExcessBounces = Math.max(0, (fruit1.bounceCount || 0) - (fruit1.maxBounces || 8));
var fruit2ExcessBounces = Math.max(0, (fruit2.bounceCount || 0) - (fruit2.maxBounces || 8));
- // Apply extra damping for fruits that have exceeded bounce limit
- var extraDamp1 = fruit1ExcessBounces > 0 ? Math.min(0.5, 0.2 + fruit1ExcessBounces * 0.05) : 0;
- var extraDamp2 = fruit2ExcessBounces > 0 ? Math.min(0.5, 0.2 + fruit2ExcessBounces * 0.05) : 0;
- if (recentCollision || fruit1ExcessBounces > 0 || fruit2ExcessBounces > 0) {
+ // Apply extra damping for fruits that have exceeded bounce limit - more aggressive curve
+ var extraDamp1 = fruit1ExcessBounces > 0 ? Math.min(0.7, 0.3 + Math.pow(fruit1ExcessBounces, 1.5) * 0.05) : 0;
+ var extraDamp2 = fruit2ExcessBounces > 0 ? Math.min(0.7, 0.3 + Math.pow(fruit2ExcessBounces, 1.5) * 0.05) : 0;
+ // Also consider high bounce count even if not exceeding max - gradual slowdown
+ if (fruit1.bounceCount > fruit1.maxBounces * 0.6) {
+ extraDamp1 = Math.max(extraDamp1, (fruit1.bounceCount - fruit1.maxBounces * 0.6) / fruit1.maxBounces * 0.5);
+ }
+ if (fruit2.bounceCount > fruit2.maxBounces * 0.6) {
+ extraDamp2 = Math.max(extraDamp2, (fruit2.bounceCount - fruit2.maxBounces * 0.6) / fruit2.maxBounces * 0.5);
+ }
+ 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
@@ -1587,17 +1595,17 @@
angular2Damp *= 1 - extraDamp2 * 0.5;
fruit1.angularVelocity *= angular1Damp;
fruit2.angularVelocity *= angular2Damp;
// Hard stop for small fruits that have bounced excessively
- if (level1 <= 3 && fruit1ExcessBounces > 3 && Math.abs(fruit1.vx) < 1.0 && Math.abs(fruit1.vy) < 1.0) {
- fruit1.vx *= 0.3;
- fruit1.vy *= 0.3;
- fruit1.angularVelocity *= 0.3;
+ if (level1 <= 3 && (fruit1ExcessBounces > 3 || fruit1.bounceCount > fruit1.maxBounces * 0.8) && 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 && fruit2ExcessBounces > 3 && Math.abs(fruit2.vx) < 1.0 && Math.abs(fruit2.vy) < 1.0) {
- fruit2.vx *= 0.3;
- fruit2.vy *= 0.3;
- fruit2.angularVelocity *= 0.3;
+ if (level2 <= 3 && (fruit2ExcessBounces > 3 || fruit2.bounceCount > fruit2.maxBounces * 0.8) && 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;
@@ -1607,8 +1615,38 @@
fruit2.vy *= stabilizeFactor;
fruit1.angularVelocity *= 0.75;
fruit2.angularVelocity *= 0.75;
}
+ // Detect vibration pattern - fruits that bounce back and forth rapidly
+ var isVibrating1 = fruit1.bounceCount > 4 && currentTime - fruit1._lastBounceTime < 300;
+ var isVibrating2 = fruit2.bounceCount > 4 && currentTime - fruit2._lastBounceTime < 300;
+ // Special case for small fruits that are vibrating too much
+ if (isVibrating1 && level1 <= 3 || isVibrating2 && level2 <= 3) {
+ // Apply strong stabilization to immediately reduce vibration
+ var hardStopFactor = 0.1;
+ if (isVibrating1 && level1 <= 3) {
+ fruit1.vx *= hardStopFactor;
+ fruit1.vy *= hardStopFactor;
+ fruit1.angularVelocity *= hardStopFactor;
+ // If velocity is already small, completely stop the fruit
+ if (Math.abs(fruit1.vx) < 0.7 && Math.abs(fruit1.vy) < 0.7) {
+ fruit1.vx = 0;
+ fruit1.vy = 0;
+ fruit1.angularVelocity = 0;
+ }
+ }
+ if (isVibrating2 && level2 <= 3) {
+ fruit2.vx *= hardStopFactor;
+ fruit2.vy *= hardStopFactor;
+ fruit2.angularVelocity *= hardStopFactor;
+ // If velocity is already small, completely stop the fruit
+ if (Math.abs(fruit2.vx) < 0.7 && Math.abs(fruit2.vy) < 0.7) {
+ fruit2.vx = 0;
+ fruit2.vy = 0;
+ fruit2.angularVelocity = 0;
+ }
+ }
+ }
}
}
}
}