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 (3 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
User prompt
let's change how fires are created. always start with 3 fires, that's the base value, but then add new fires bsed on which fruits are on the board. for every different fruit type existing, add another fire, so that means there can be a total of up to 13 fires, the starting fires, then another 10 for every different fruit that's on the board. if there's grapes on the board, only create a fire, counting the existence of a grape on the board. if all grapes are merged, remove one fire, until a new grape appears.
Code edit (1 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.7-1.0 self.alphaMin = 0.7; self.alphaMax = 1.0; self.flickerSpeed = 500 + Math.random() * 300; // Setup self.initialize = function () { self.fireAsset = self.attachAsset('fire', { anchorX: 0.5, anchorY: 1.0 // Anchor at bottom-center }); self.x = self.baseX; self.y = self.baseY; // Start animations self.startAlphaFlicker(); }; // 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; } }; // 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 () { self.flickerTween = tween(self.fireAsset, { alpha: self.alphaMax // Use class variable instead of hardcoded value }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMin }); }; self.flickerToMin = function () { self.flickerTween = tween(self.fireAsset, { 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(); } 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.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.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.5) / 10; var movementThreshold = 0.4 - (fruitLevel - 1) * 0.03; var angularThreshold = 0.04 - (fruitLevel - 1) * 0.003; if (movementMagnitude < movementThreshold && Math.abs(fruit.angularVelocity) < angularThreshold) { fruit._stabilizeMetrics.consecutiveSmallMovements += 1 + massFactor * 0.2; fruit._stabilizeMetrics.restingDuration += 1 + massFactor * 0.2; } else { fruit._stabilizeMetrics.consecutiveSmallMovements = 0; fruit._stabilizeMetrics.restingDuration = Math.max(0, fruit._stabilizeMetrics.restingDuration - 2); } if (fruit.wallContactFrames > 0) { fruit._stabilizeMetrics.wallContactDuration += 1 + massFactor * 0.15; } else { fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1); } if (fruit.surroundedFrames > 0) { fruit._stabilizeMetrics.surroundedDuration += 1 + massFactor * 0.25; } 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); var fullStabilizationThreshold = 15 - (fruitLevel - 1) * 1.0; var partialStabilizationThreshold = 8 - (fruitLevel - 1) * 0.6; if (totalStabilizationScore > fullStabilizationThreshold) { stabilizationLevel = 2; } else if (totalStabilizationScore > partialStabilizationThreshold) { stabilizationLevel = 1; } if (stabilizationLevel > 0) { var baseDampFactor = stabilizationLevel === 2 ? 0.6 : 0.85; var massDampFactor = baseDampFactor - massFactor * 0.02; var dampFactor = Math.max(0.4, massDampFactor); fruit.vx *= dampFactor; fruit.vy *= dampFactor; fruit.angularVelocity *= dampFactor; var stopThreshold = 0.1 - (fruitLevel - 1) * 0.005; 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); var gravityMultiplier = 1 + (fruitLevel - 1) * 0.25; fruit.vy += fruit.gravity * gravityMultiplier * 5; var fruitLevel = getFruitLevel(fruit); var maxVelocity = 60 - Math.min(8, (fruitLevel - 1) * 0.8); 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; } if (fruitLevel > 3) { var bottomTendencyForce = (fruitLevel - 3) * 0.018; fruit.vy += bottomTendencyForce; } 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) { var rotationFactor = 0.015 / (1 + (fruitLevel - 1) * 0.12); var targetAngularVelocity = fruit.vx * rotationFactor; fruit.angularVelocity = fruit.angularVelocity * 0.8 + targetAngularVelocity * 0.2; } else { fruit.angularVelocity *= 0.8; } fruit.rotation += fruit.angularVelocity; var frictionModifier = 0.99 + (fruitLevel - 1) * 0.003; fruit.vx *= fruit.friction * frictionModifier; fruit.vy *= fruit.friction * frictionModifier * 0.99; var horizontalStopThreshold = Math.max(0.05, 0.1 - (fruitLevel - 1) * 0.005); var verticalStopThreshold = Math.max(0.05, 0.1 - (fruitLevel - 1) * 0.005); 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 rotationDampFactor = 0.9; if (movementMagnitude < 0.05) { rotationDampFactor = 0.6; } else if (movementMagnitude < 0.3) { rotationDampFactor = 0.7; } else if (movementMagnitude < 0.5) { rotationDampFactor = 0.8; } else if (movementMagnitude < 0.8) { rotationDampFactor = 0.85; } fruit.angularVelocity *= rotationDampFactor; if (movementMagnitude > 0.3) { var targetDirection = fruit.vx > 0 ? 1 : -1; var currentDirection = fruit.angularVelocity > 0 ? 1 : -1; if (targetDirection !== currentDirection && Math.abs(fruit.vx) > 1.0) { fruit.angularVelocity *= 0.7; } } if (Math.abs(fruit.angularVelocity) < 0.008) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > 6) { fruit.angularVelocity = 0; } } else { fruit.rotationRestCounter = 0; } fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity * 1.2), fruit.maxAngularVelocity * 1.2); }; 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; 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); var levelImpactMultiplier = 1 + levelDifference * 0.03; var maxImpulseRatio = 1.7; if (level1 > level2) { impulseRatio2 = Math.min(impulseRatio2 * levelImpactMultiplier * 1.3, maxImpulseRatio * impulseRatio2); } else if (level2 > level1) { impulseRatio1 = Math.min(impulseRatio1 * levelImpactMultiplier * 1.3, 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); if (fruit1.type.size < fruit2.type.size) { impulse1 *= 1 + sizeDifference * 2.5; fruit1._lastBounceTime = Date.now(); } else if (fruit2.type.size < fruit1.type.size) { impulse2 *= 1 + sizeDifference * 2.5; fruit2._lastBounceTime = Date.now(); } 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; if (recentCollision) { fruit1.vx *= 0.7; fruit1.vy *= 0.7; fruit2.vx *= 0.7; fruit2.vy *= 0.7; fruit1.angularVelocity *= 0.6; fruit2.angularVelocity *= 0.6; } 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 = 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(); } 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"; switch (stabilizationScenario) { case "boundary": fruit.surroundedFrames = 0; break; case "floor": fruit.surroundedFrames = 0; break; case "surrounded": fruit.surroundedFrames++; var stabilizationStrength = Math.min(0.92, 0.75 + fruit.surroundedFrames * 0.015); if (fruit.neighborContacts.length >= 3) { stabilizationStrength = Math.min(0.95, stabilizationStrength + 0.03); } fruit.vx *= stabilizationStrength; fruit.vy *= stabilizationStrength; fruit.angularVelocity *= stabilizationStrength; if (fruit.surroundedFrames > 10 && isSlowMoving || fruit.surroundedFrames > 18) { if (Math.abs(fruit.vx) < 0.05) { fruit.vx = 0; } if (Math.abs(fruit.vy) < 0.05) { fruit.vy = 0; } if (Math.abs(fruit.angularVelocity) < 0.005) { 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() { // Count active fruits on the board (not static, not merging) var activeFruitCount = fruits.filter(function (f) { return f && !f.isStatic && !f.merging; }).length; // Calculate target number of fire elements: BASE_FIRES + one for each FRUITS_PER_ADDITIONAL_FIRE total fruits var targetFireCount = BASE_FIRES + Math.floor(Math.max(0, activeFruitCount - 1) / FRUITS_PER_ADDITIONAL_FIRE); targetFireCount = Math.min(targetFireCount, MAX_FIRES); // Create or remove fires to match target count if (targetFireCount > activeFireElements.length) { // Need to add more fires var newFiresCount = targetFireCount - activeFireElements.length; for (var i = 0; i < newFiresCount; i++) { // Create fire at appropriate position createFireElement(activeFireElements.length); } } else if (targetFireCount < activeFireElements.length) { // Need to remove excess fires var fireToRemoveCount = activeFireElements.length - targetFireCount; for (var i = 0; i < fireToRemoveCount; i++) { var fire = activeFireElements.pop(); // Remove from end (highest/last added fires) if (fire) { fire.destroy(); // Ensure destroy() is called to clean up resources fireContainer.removeChild(fire); } } } } // Helper function to create a single fire element at the specified index function createFireElement(index) { // Calculate y position (higher fires appear behind/further back) var yPos = gameHeight + FIRE_Y_START_OFFSET - index * FIRE_Y_STACK_OFFSET; // Randomize x position around center of screen var xPos = gameWidth / 2 + (Math.random() * FIRE_X_SPREAD - FIRE_X_SPREAD / 2); // Create new fire element var newFire = new FireElement(xPos, yPos); // Alternate x-axis flip for all fires including the first three if (index % 2 === 1) { newFire.fireAsset.scaleX = -1; // Flip every other fire } // Add at index 0 to render behind existing fires fireContainer.addChildAt(newFire, 0); activeFireElements.push(newFire); } function removeFruitFromGame(fruit) { var index = fruits.indexOf(fruit); if (index !== -1) { fruits.splice(index, 1); } spatialGrid.removeObject(fruit); fruit.destroy(); }
===================================================================
--- original.js
+++ change.js
@@ -310,9 +310,11 @@
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.2-0.6
+ // Alpha range changed to 0.7-1.0
+ self.alphaMin = 0.7;
+ self.alphaMax = 1.0;
self.flickerSpeed = 500 + Math.random() * 300;
// Setup
self.initialize = function () {
self.fireAsset = self.attachAsset('fire', {
@@ -337,44 +339,36 @@
self.startAlphaFlicker = function () {
if (self.flickerTween) {
self.flickerTween.stop();
}
- // Set initial random alpha between 10%-50%
- self.fireAsset.alpha = 0.1 + Math.random() * 0.4;
// Randomize starting delay to avoid synchronized flickers
- var startDelay = Math.random() * 300;
+ var startDelay = Math.random() * 500;
LK.setTimeout(function () {
self.flickerToMax();
}, startDelay);
};
self.flickerToMax = function () {
- // Set a random alpha between 0.1 (10%) and 0.5 (50%)
- var randomAlpha = 0.1 + Math.random() * 0.4;
- self.fireAsset.alpha = randomAlpha;
- // Schedule next flicker with random timing
- LK.setTimeout(function () {
- if (!self.isDestroyed) {
- self.flickerToMin();
- }
- }, 100 + Math.random() * 300);
+ self.flickerTween = tween(self.fireAsset, {
+ alpha: self.alphaMax // Use class variable instead of hardcoded value
+ }, {
+ duration: self.flickerSpeed,
+ easing: tween.easeInOut,
+ onFinish: self.flickerToMin
+ });
};
self.flickerToMin = function () {
- // Set a random alpha between 0.1 (10%) and 0.5 (50%)
- var randomAlpha = 0.1 + Math.random() * 0.4;
- self.fireAsset.alpha = randomAlpha;
- // Schedule next flicker with random timing
- LK.setTimeout(function () {
- if (!self.isDestroyed) {
- self.flickerToMax();
- }
- }, 100 + Math.random() * 300);
+ self.flickerTween = tween(self.fireAsset, {
+ 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();
}
- // We'll use a flag to prevent any pending setTimeouts from running
- self.isDestroyed = true;
Container.prototype.destroy.call(this);
};
self.initialize();
return self;