Code edit (1 edits merged)
Please save this source code
User prompt
increase the fruits falling speed
Code edit (1 edits merged)
Please save this source code
User prompt
make smaller fruits vibrate even more off larger fruits, but remove the reverberation that happens when they keep bouncing between multiple fruits ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make smaller fruits bounce a bit more off larger fruits
User prompt
ok, now, can you help me add an evolution line at the top of the screen? SHow all 10 fruits on a grid, a single row containing all 10 fruits, from level 1 on the left to level 10 on the right. this is an UI element, that will remain on the screen to show players the evolution of the fruits. all icons should maintain their aspect raio, but the width and height can be up to 100 pixels
User prompt
fix the game over line as it no longer records fruits that should trigger a game over state. only fruits that have already touches the floor, walls or other fruits count
Code edit (1 edits merged)
Please save this source code
User prompt
increase the size of the score
User prompt
move the charging fruits text UI to the right by 100 pixels
User prompt
make fruits fall even faster
User prompt
make fruits fall even faster
User prompt
larger fruits hitbox is still weird sometimes, pushing away fruits before they actually hit the asset sie, as if the hitbox is larger than the asset size
User prompt
now fruits drop too slow when dropped, like the air is water, the density is too large, make them drop faster
User prompt
something still makes small fruits jumps like popcorn. i think it ahs soemthing to do with when their force amplifies when beeeing bounced between multiple fruits. can you fix this, so they dont jump like popcorn?
Code edit (1 edits merged)
Please save this source code
User prompt
it seems that niow larger fruits tend to bounce around smaller fruits way too much, decrease that force so it doesn't push them so violently far away
User prompt
I'm notsure using gravity is necesarely the best approach for this problem, bin the sense gravity should be the same for all fruits, BUT, larger fruits should tend to go more to the bottom of the screen, since they are heavier
User prompt
increase the effect gravity has on larger fruits. just that effect, nothing else
User prompt
Add gravity increase to larger fruits for more realistic physics. increase the gravity effect even more
User prompt
the higher the fruit's level is, the larger the gravity impact it, so larger fruits tend to go more to the bottom and also move less, or be heavier to be moved
User prompt
fruits behave a bit weird, they tend to snap back to their upward position so they never rest diagonally... and they also leave large gaps between the fruits, when fruits should be rolling into those spaces. idk, the physicsl still feel a bit off and kind of rigid
User prompt
gravity still doesnt work, released fruits just remain stuck in place
User prompt
gravity still doesnt work, released fruits just remain stuck in place
User prompt
gravity still doesnt work, released fruits just remain there
/**** * 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 = 9; self.currentCharge = 0; self.isReadyToRelease = false; self.countdownText = null; self.pulseAnimationActive = false; self.initialize = function () { self.countdownText = new Text2(self.chargeNeededForRelease.toString(), { size: 100, fill: 0x000000 }); self.countdownText.anchor.set(0.5, 0.5); self.countdownText.x = gameWidth / 2 + 870; self.addChild(self.countdownText); self.y = 120; }; self.updateChargeDisplay = function (chargeCount) { self.currentCharge = chargeCount; var remainingCount = Math.max(0, self.chargeNeededForRelease - self.currentCharge); self.countdownText.setText(remainingCount.toString()); var textColor = remainingCount <= 3 ? 0xFF0000 : remainingCount <= 6 ? 0xFFA500 : 0x000000; tween(self.countdownText, { tint: textColor }, { duration: 300, easing: tween.easeOut }); var baseSize = 1.0; var sizeMultiplier = baseSize + 0.2 * (self.chargeNeededForRelease - remainingCount); tween(self.countdownText, { scaleX: sizeMultiplier, scaleY: sizeMultiplier }, { duration: 300, easing: tween.easeOut }); if (remainingCount === 0 && !self.isReadyToRelease) { self.setReadyState(true); } }; self.setReadyState = function (isReady) { self.isReadyToRelease = isReady; if (isReady) { self.countdownText.setText("0"); tween(self.countdownText, { tint: 0xFF0000 }, { 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.countdownText, { scaleX: 1.3, scaleY: 1.3 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.countdownText, { 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; self.countdownText.setText(self.chargeNeededForRelease.toString()); tween(self.countdownText, { tint: 0x000000 }, { duration: 200, easing: tween.easeOut }); tween(self.countdownText, { scaleX: 1.0, scaleY: 1.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; 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(100 / fruitIcon.width, 100 / fruitIcon.height); fruitIcon.scaleX = scale; fruitIcon.scaleY = scale; totalWidth += fruitIcon.width * scale; if (i < fruitTypes.length - 1) { totalWidth += 10; } 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 + 10; } }; 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.93 : 0.9 - (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 * 3.0; var fruitLevel = getFruitLevel(fruit); var maxVelocity = 40 - 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 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 >= 10 && !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; wallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallRight.x = gameWidth; wallRight.y = gameHeight / 2; gameFloor = game.addChild(LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5 })); gameFloor.x = gameWidth / 2; gameFloor.y = gameHeight; 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 >= 9 && !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 >= 9) { 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 = 30; 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 = 10; var percentage = Math.min(step / totalSteps, 1.0); var startPos = -pineapple.width / 2; var endPos = gameWidth * 0.16; var newX = startPos + percentage * (endPos - startPos); tween(pineapple, { x: newX }, { duration: 300, easing: tween.bounceOut }); } } function initGame() { LK.setScore(0); gameOver = false; fruits = []; chargeCounter = 0; if (chargedBallUI) { chargedBallUI.destroy(); } chargedBallUI = null; readyToReleaseCharged = false; lastScoreCheckForCoconut = 0; lastDroppedFruit = null; lastDroppedHasMerged = false; mergeCounter = 0; isClickable = true; if (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(); 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 + 500) { lastScoreCheckForCoconut = Math.floor(currentScore / 500) * 500; spawnCoconut(); } else { if (currentScore > lastScoreCheckForCoconut) { lastScoreCheckForCoconut = Math.floor(currentScore / 500) * 500; } } updatePhysics(); checkGameOver(); }; initGame(); function removeFruitFromGame(fruit) { var index = fruits.indexOf(fruit); if (index !== -1) { fruits.splice(index, 1); } spatialGrid.removeObject(fruit); fruit.destroy(); }
===================================================================
--- original.js
+++ change.js
@@ -124,9 +124,8 @@
return;
}
var fruitHalfWidth = fruit.width / 2;
var fruitHalfHeight = fruit.height / 2;
- // Adjust effective size for larger fruits to match visual asset size
var fruitLevel = getFruitLevel(fruit);
var sizeReduction = 0;
if (fruitLevel > 5) {
sizeReduction = (fruitLevel - 5) * 3;
@@ -136,11 +135,9 @@
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;
- // Track wall contact state before collision checks
var wasInContact = fruit.wallContactFrames > 0;
- // Track which boundaries the fruit is touching
fruit._boundaryContacts = fruit._boundaryContacts || {
left: false,
right: false,
floor: false
@@ -150,41 +147,34 @@
fruit._boundaryContacts.floor = false;
self.checkLeftWallCollision(fruit, walls.left, effectiveWidth);
self.checkRightWallCollision(fruit, walls.right, effectiveWidth);
self.checkFloorCollision(fruit, floor, effectiveHeight);
- // Update wall contact frames counter based on contact with any boundary
var isInContact = fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor;
if (isInContact) {
fruit.wallContactFrames++;
- // Apply progressive friction based on contact duration
var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01);
fruit.angularVelocity *= progressiveFriction;
- // Apply more aggressive stabilization for prolonged contact
if (fruit.wallContactFrames > 10) {
fruit.vx *= 0.9;
fruit.vy *= 0.9;
}
} else {
- // Gradually decrease contact counter when not touching walls
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; // More elasticity to promote movement
- var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); // Increased to improve rotation
- fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.25; // More rotational force
- fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; // Less aggressive damping
- // Track that fruit is touching left wall
+ 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;
- // Progressive stabilization based on contact duration
if (fruit.wallContactFrames > 3 && Math.abs(fruit.vx) < 0.8) {
fruit.vx = 0;
}
} else if (fruit.x > leftWall.x + leftWall.width * 2) {
- // Only reset here if we're far from the wall
if (fruit._boundaryContacts && !fruit._boundaryContacts.right && !fruit._boundaryContacts.floor) {
fruit.wallContactFrames = Math.max(0, fruit.wallContactFrames - 1);
}
}
@@ -192,15 +182,13 @@
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; // More elasticity to promote movement
- var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); // Increased to improve rotation
- fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.25; // More rotational force
- fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; // Less aggressive damping
- // Track that fruit is touching right wall
+ 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;
- // Progressive stabilization based on contact duration
if (fruit.wallContactFrames > 3 && Math.abs(fruit.vx) < 0.8) {
fruit.vx = 0;
}
}
@@ -210,30 +198,24 @@
if (fruit.y > floorCollisionY) {
fruit.y = floorCollisionY;
var oldVy = fruit.vy;
fruit.vy = -fruit.vy * fruit.elasticity * 0.5;
- // Track that fruit is touching floor
fruit._boundaryContacts.floor = true;
- // Update angular velocity based on horizontal movement
if (Math.abs(fruit.vx) > 0.5) {
fruit.angularVelocity = fruit.vx * 0.01;
}
- fruit.angularVelocity *= fruit.groundAngularFriction * 0.8; // Less aggressive friction to allow more natural movement
- // Progressive stabilization based on contact duration and velocity
+ 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; // Less aggressive horizontal friction to allow better rolling into gaps
- // More adaptive stabilization for fruits that have been on floor for a while
+ fruit.vx *= 0.7;
if (fruit.wallContactFrames > 8) {
- fruit.vx *= 0.85; // Less aggressive to allow fruits to roll into gaps
+ fruit.vx *= 0.85;
}
}
- // Angular rest handling with adaptive threshold - more permissive
var angularRestThreshold = fruit.wallContactFrames > 5 ? 0.015 : 0.025;
if (Math.abs(fruit.angularVelocity) < angularRestThreshold) {
fruit.angularVelocity = 0;
- // Don't force rotation to 90-degree increments to allow diagonal resting
}
}
};
return self;
@@ -287,40 +269,35 @@
});
var EvolutionLine = Container.expand(function () {
var self = Container.call(this);
self.initialize = function () {
- // Position at the top of the screen
self.y = 120;
self.x = gameWidth / 2;
- // Create icons for all fruit types in evolution order
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 = [];
- // First pass to calculate total width needed
for (var i = 0; i < fruitTypes.length; i++) {
var fruitType = fruitTypes[i];
var fruitIcon = LK.getAsset(fruitType.id, {
anchorX: 0.5,
anchorY: 0.5
});
- // Scale to maintain aspect ratio with max width/height of 100px
var scale = Math.min(100 / fruitIcon.width, 100 / fruitIcon.height);
fruitIcon.scaleX = scale;
fruitIcon.scaleY = scale;
totalWidth += fruitIcon.width * scale;
if (i < fruitTypes.length - 1) {
- totalWidth += 10; // 10px spacing between icons
+ totalWidth += 10;
}
fruitIcons.push(fruitIcon);
}
- // Position icons in a row
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 + 10; // Add 10px spacing
+ currentX += icon.width * icon.scaleX + 10;
}
};
return self;
});
@@ -338,15 +315,13 @@
self.angularVelocity = physics.angularVelocity;
self.angularFriction = physics.angularFriction;
self.groundAngularFriction = physics.groundAngularFriction;
var currentLevel = getFruitLevel(self);
- // Increase gravity based on fruit level - larger fruits fall faster and are heavier
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;
- // Adjust elasticity to give smaller fruits more bounce
self.elasticity = currentLevel <= 3 ? 0.93 : 0.9 - (currentLevel - 1) * (0.2 / 9);
self.wallContactFrames = collision.wallContactFrames;
self.merging = mergeHandler.merging;
self.mergeGracePeriodActive = mergeHandler.mergeGracePeriodActive;
@@ -360,28 +335,23 @@
anchorY: 0.5
});
self.width = fruitGraphics.width;
self.height = fruitGraphics.height;
- // Execute spawn behavior if defined
if (self.behavior && self.behavior.onSpawn) {
self.behavior.onSpawn(self);
}
} else {
console.log("Warning: Fruit type not available yet or missing required properties");
}
- // Method to handle physics update
self.updatePhysics = function () {
physics.apply(self);
};
- // Method to handle boundary collisions
self.checkBoundaries = function (walls, floor) {
collision.checkBoundaryCollisions(self, walls, floor);
- // Safety period check for game over conditions
if (self.safetyPeriod === false && self.vy <= 0.1) {
- self.safetyPeriod = undefined; // Reset safety check state
+ self.safetyPeriod = undefined;
}
};
- // Merge method (delegates to merge component)
self.merge = function (otherFruit) {
mergeHandler.beginMerge(self, otherFruit);
};
return self;
@@ -442,15 +412,14 @@
}
},
DURIAN: {
onMerge: function onMerge(fruit1, fruit2, posX, posY) {
- // Special durian behavior
LK.setScore(LK.getScore() + fruit1.type.points);
updateScoreDisplay();
removeFruitFromGame(fruit1);
removeFruitFromGame(fruit2);
releasePineappleOnMerge();
- return null; // No new fruit
+ return null;
}
}
};
self.getMergeHandler = function (fruitTypeId) {
@@ -484,9 +453,8 @@
easing: tween.bounceOut
});
return newFruit;
};
- // Centralized sound effect manager
self.playSoundEffect = function (soundId) {
if (soundId) {
LK.getSound(soundId).play();
}
@@ -541,14 +509,11 @@
});
};
self.completeMerge = function (fruit1, fruit2, midX, midY) {
LK.getSound('merge').play();
- // Track merge for gameplay mechanics
self.trackMerge(fruit1, fruit2);
- // Get the appropriate behavior handler for this fruit type
var behaviorHandler = self.fruitBehavior.getMergeHandler(fruit1.type.id);
behaviorHandler.onMerge(fruit1, fruit2, midX, midY);
- // Cleanup the original fruits
if (fruit1 && fruits.includes(fruit1) && fruit1.type.id.toUpperCase() !== 'DURIAN') {
removeFruitFromGame(fruit1);
}
if (fruit2 && fruits.includes(fruit2)) {
@@ -583,73 +548,58 @@
self.lastVy = 0;
self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) {
var shouldStabilize = false;
var stabilizationLevel = 0;
- // Get fruit level for mass-based stabilization
var fruitLevel = getFruitLevel(fruit);
- // Track stabilization metrics in a single place
if (!fruit._stabilizeMetrics) {
fruit._stabilizeMetrics = {
consecutiveSmallMovements: 0,
wallContactDuration: 0,
surroundedDuration: 0,
restingDuration: 0
};
}
- // Calculate mass factor for more pronounced level effects
var massFactor = Math.pow(fruitLevel, 1.5) / 10;
- // Adjust movement threshold based on fruit level - larger fruits stabilize more quickly
- var movementThreshold = 0.4 - (fruitLevel - 1) * 0.03; // Lower threshold for larger fruits
- var angularThreshold = 0.04 - (fruitLevel - 1) * 0.003; // Lower threshold for larger fruits
- // Combine all stabilization factors with thresholds that account for fruit mass
+ 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 {
- // Reset more aggressively to prevent oscillation
fruit._stabilizeMetrics.consecutiveSmallMovements = 0;
fruit._stabilizeMetrics.restingDuration = Math.max(0, fruit._stabilizeMetrics.restingDuration - 2);
}
- // Update wall contact tracking with mass impact
if (fruit.wallContactFrames > 0) {
fruit._stabilizeMetrics.wallContactDuration += 1 + massFactor * 0.15;
} else {
fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1);
}
- // Update surrounded tracking with mass impact
if (fruit.surroundedFrames > 0) {
fruit._stabilizeMetrics.surroundedDuration += 1 + massFactor * 0.25;
} else {
fruit._stabilizeMetrics.surroundedDuration = Math.max(0, fruit._stabilizeMetrics.surroundedDuration - 1);
}
- // Determine stabilization level based on combined factors, with level influence
- var levelFactor = (fruitLevel - 1) * 0.12; // Larger fruits stabilize faster (increased from 0.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);
- // Larger fruits need less time to stabilize (increased effect)
var fullStabilizationThreshold = 15 - (fruitLevel - 1) * 1.0;
var partialStabilizationThreshold = 8 - (fruitLevel - 1) * 0.6;
if (totalStabilizationScore > fullStabilizationThreshold) {
- stabilizationLevel = 2; // Full stabilization
+ stabilizationLevel = 2;
} else if (totalStabilizationScore > partialStabilizationThreshold) {
- stabilizationLevel = 1; // Partial stabilization
+ stabilizationLevel = 1;
}
- // Apply stabilization based on level
if (stabilizationLevel > 0) {
- // Dampen movement increasingly with stabilization level and mass
- var baseDampFactor = stabilizationLevel === 2 ? 0.6 : 0.85; // Base damping factor
- var massDampFactor = baseDampFactor - massFactor * 0.02; // Heavier fruits dampen more
- var dampFactor = Math.max(0.4, massDampFactor); // Ensure minimum damping
+ 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;
- // For full stabilization, completely stop if movement is minimal
- // Threshold reduces with fruit level - larger fruits stop more easily
var stopThreshold = 0.1 - (fruitLevel - 1) * 0.005;
if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) {
fruit.vx = 0;
fruit.vy = 0;
fruit.angularVelocity = 0;
- // Don't auto-align rotation to 90-degree increments to allow diagonal resting
return true;
}
}
return false;
@@ -659,53 +609,42 @@
return;
}
fruit.lastVx = fruit.vx;
fruit.lastVy = fruit.vy;
- // Get fruit level for level-based physics
var fruitLevel = getFruitLevel(fruit);
- // Apply gravity with level-based impact - heavier fruits accelerate faster
var gravityMultiplier = 1 + (fruitLevel - 1) * 0.25;
- fruit.vy += fruit.gravity * gravityMultiplier * 3.0; // Further increased gravity multiplier for faster dropping
- // Limit maximum velocity based on fruit level to prevent popcorn-like bouncing
+ fruit.vy += fruit.gravity * gravityMultiplier * 3.0;
var fruitLevel = getFruitLevel(fruit);
- var maxVelocity = 40 - Math.min(8, (fruitLevel - 1) * 0.8); // Increased max velocity from 30 to 40
- // Apply velocity limiting for extremely fast movements
+ var maxVelocity = 40 - 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;
}
- // Apply a subtle downward force for larger fruits to make them tend to move to bottom
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);
- // Unified stabilization check
var isFullyStabilized = self.stabilizeFruit(fruit, movementMagnitude, velocityChange);
- // Skip further processing if fruit is fully stabilized
if (!isFullyStabilized) {
- // Angular velocity adjustment based on movement, with less rotation for heavier fruits
if (movementMagnitude > 0.5 || velocityChange > 0.3) {
- var rotationFactor = 0.015 / (1 + (fruitLevel - 1) * 0.12); // Higher level fruits rotate less
+ 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;
- // Apply friction with level-based modifications - heavier fruits have more momentum
- var frictionModifier = 0.99 + (fruitLevel - 1) * 0.003; // Further increased for even less friction
+ var frictionModifier = 0.99 + (fruitLevel - 1) * 0.003;
fruit.vx *= fruit.friction * frictionModifier;
- fruit.vy *= fruit.friction * frictionModifier * 0.99; // Further reduce vertical friction (more retention)
- // Adjust velocity thresholds based on level - larger fruits stop at lower velocities
+ 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);
- // Velocity thresholds for stopping
if (Math.abs(fruit.vx) < horizontalStopThreshold) {
fruit.vx = 0;
}
if (Math.abs(fruit.vy) < verticalStopThreshold && fruit.y > gameHeight - 300) {
@@ -715,43 +654,34 @@
}
};
self.handleRotationDamping = function (fruit) {
var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy);
- // Progressive rotation damping based on movement - less aggressive damping
- var rotationDampFactor = 0.9; // Base damping factor is less aggressive
+ var rotationDampFactor = 0.9;
if (movementMagnitude < 0.05) {
- rotationDampFactor = 0.6; // Less aggressive for very slow movements
+ rotationDampFactor = 0.6;
} else if (movementMagnitude < 0.3) {
- rotationDampFactor = 0.7; // Less aggressive for slow movements
+ rotationDampFactor = 0.7;
} else if (movementMagnitude < 0.5) {
- rotationDampFactor = 0.8; // Less aggressive for medium movements
+ rotationDampFactor = 0.8;
} else if (movementMagnitude < 0.8) {
- rotationDampFactor = 0.85; // Less aggressive for faster movements
+ rotationDampFactor = 0.85;
}
fruit.angularVelocity *= rotationDampFactor;
- // Direction correction for rotation - more subtle corrections
if (movementMagnitude > 0.3) {
- // Only correct at higher speeds
var targetDirection = fruit.vx > 0 ? 1 : -1;
var currentDirection = fruit.angularVelocity > 0 ? 1 : -1;
if (targetDirection !== currentDirection && Math.abs(fruit.vx) > 1.0) {
- // Higher threshold
- fruit.angularVelocity *= 0.7; // Less aggressive correction
+ fruit.angularVelocity *= 0.7;
}
}
- // Rotation rest handling - more permissive to allow diagonal resting
if (Math.abs(fruit.angularVelocity) < 0.008) {
- // Lower threshold to stop rotation
fruit.rotationRestCounter++;
if (fruit.rotationRestCounter > 6) {
- // Requires more frames of stability
fruit.angularVelocity = 0;
- // Don't force rotation to 90-degree increments to allow diagonal resting
}
} else {
fruit.rotationRestCounter = 0;
}
- // Enforce angular velocity limits - allow slightly more rotation
fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity * 1.2), fruit.maxAngularVelocity * 1.2);
};
return self;
});
@@ -759,24 +689,21 @@
var self = Container.call(this);
self.cellSize = cellSize || 200;
self.grid = {};
self.lastRebuildTime = Date.now();
- self.rebuildInterval = 60000; // Rebuild grid every minute to prevent memory leaks
+ self.rebuildInterval = 60000;
self.insertObject = function (obj) {
if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) {
return;
}
- // Store the current cells the object belongs to for efficient updates
obj._currentCells = obj._currentCells || [];
var cells = self.getCellsForObject(obj);
- // Store new cells for faster future updates
obj._currentCells = cells.slice();
for (var i = 0; i < cells.length; i++) {
var cellKey = cells[i];
if (!self.grid[cellKey]) {
self.grid[cellKey] = [];
}
- // Using a faster direct check instead of iterating the entire cell array
if (self.grid[cellKey].indexOf(obj) === -1) {
self.grid[cellKey].push(obj);
}
}
@@ -784,24 +711,21 @@
self.removeObject = function (obj) {
if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) {
return;
}
- // Use cached cells if available, otherwise calculate them
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);
}
- // Clean up empty cells to prevent memory leaks
if (self.grid[cellKey].length === 0) {
delete self.grid[cellKey];
}
}
}
- // Clear the cached cells
obj._currentCells = [];
};
self.getCellsForObject = function (obj) {
if (!obj || typeof obj.x !== 'number' || typeof obj.y !== 'number' || typeof obj.width !== 'number' || typeof obj.height !== 'number') {
@@ -821,33 +745,27 @@
}
return cells;
};
self.updateObject = function (obj) {
- // Optimize by only updating if the object has actually moved between cells
if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) {
return;
}
- // Get new cells
var newCells = self.getCellsForObject(obj);
- // Get old cells
var oldCells = obj._currentCells || [];
- // Check if cells have changed
var cellsChanged = false;
if (oldCells.length !== newCells.length) {
cellsChanged = true;
} else {
- // Check if any cell is different
for (var i = 0; i < newCells.length; i++) {
if (oldCells.indexOf(newCells[i]) === -1) {
cellsChanged = true;
break;
}
}
}
- // Only update if cells have changed to avoid unnecessary operations
if (cellsChanged) {
- self.removeObject(obj); // Remove from old cells
- self.insertObject(obj); // Insert to new cells
+ self.removeObject(obj);
+ self.insertObject(obj);
}
};
self.getPotentialCollisions = function (obj) {
var candidates = [];
@@ -857,9 +775,8 @@
var cellKey = cells[i];
if (self.grid[cellKey]) {
for (var j = 0; j < self.grid[cellKey].length; j++) {
var otherObj = self.grid[cellKey][j];
- // Additional check to ensure otherObj has an id
if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) {
candidates.push(otherObj);
addedObjects[otherObj.id] = true;
}
@@ -872,12 +789,10 @@
self.grid = {};
self.lastRebuildTime = Date.now();
};
self.rebuildGrid = function (allObjects) {
- // Clear the grid
self.grid = {};
self.lastRebuildTime = Date.now();
- // Re-insert all active objects
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]);
@@ -890,9 +805,9 @@
var TrajectoryLine = Container.expand(function () {
var self = Container.call(this);
self.dotPool = new DotPool(100);
self.activeDots = [];
- self.dots = []; // Initialize dots array
+ self.dots = [];
self.dotSpacing = 10;
self.dotSize = 15;
self.maxDots = 100;
self.createDots = function () {
@@ -965,14 +880,12 @@
self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) {
var dx = fruitX - dropX;
var dy = fruitY - dropY;
var distanceSquared = dx * dx + dy * dy;
- // Adjust hitbox calculation to better match visual assets
var activeFruitLevel = getFruitLevel(activeFruitObj);
var targetFruitLevel = getFruitLevel(targetFruitObj);
var activeFruitRadius = activeFruitObj.width / 2;
var targetFruitRadius = targetFruitObj.width / 2;
- // Apply slight reduction to hitbox for larger fruits to match visual size
var hitboxReduction = 0;
if (activeFruitLevel > 5) {
hitboxReduction += (activeFruitLevel - 5) * 3;
}
@@ -1033,9 +946,8 @@
pineappleActive = true;
pineapple.isStatic = false;
applyDropPhysics(pineapple, 2.5);
fruits.push(pineapple);
- // Add to spatial grid immediately after adding to fruits
if (spatialGrid) {
spatialGrid.insertObject(pineapple);
}
LK.setTimeout(function () {
@@ -1156,10 +1068,10 @@
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; // Start slightly higher
- activeFruit.isStatic = true; // Keep static until dropped
+ activeFruit.y = dropPointY + 200;
+ activeFruit.isStatic = true;
game.addChild(activeFruit);
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
@@ -1174,9 +1086,9 @@
}, 300);
activeFruit.isStatic = false;
applyDropPhysics(activeFruit, 3.5);
fruits.push(activeFruit);
- spatialGrid.insertObject(activeFruit); // Add to grid immediately
+ spatialGrid.insertObject(activeFruit);
lastDroppedFruit = activeFruit;
lastDroppedHasMerged = false;
chargeCounter++;
updateChargedBallDisplay();
@@ -1212,35 +1124,30 @@
applyDropPhysics(orange, forceMultiplier);
orange.fromChargedRelease = true;
game.addChild(orange);
fruits.push(orange);
- spatialGrid.insertObject(orange); // Add charged fruit to grid
+ spatialGrid.insertObject(orange);
chargeCounter = 0;
resetChargedBalls();
readyToReleaseCharged = false;
}
- activeFruit = null; // Clear active fruit *before* creating next
+ activeFruit = null;
createNextFruit();
}
function applyDropPhysics(fruit, forceMultiplier) {
var fruitLevel = getFruitLevel(fruit);
- // Increase force multiplier for faster dropping
forceMultiplier *= 1.4;
- // Level-adjusted force multiplier - heavier fruits need more initial force
var levelAdjustedForce = forceMultiplier * (1 + (fruitLevel - 1) * 0.05);
- // Reduce force multiplier slightly for heavier fruits to make them less mobile
- var mobilityReduction = Math.max(0, (fruitLevel - 2) * 0.03); // Reduced from 0.04
+ 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; // Increased vertical velocity
- // Adjust gravity based on fruit level - higher level = heavier and falls faster
- fruit.gravity = 13.0 * (1 + (fruitLevel - 1) * 0.2); // Further increased base gravity for faster dropping
- // For very heavy fruits, add even more gravity impact
+ 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; // Start in safety period
+ fruit.safetyPeriod = false;
fruit.immuneToGameOver = true;
LK.setTimeout(function () {
if (fruit && fruits.includes(fruit)) {
fruit.immuneToGameOver = false;
@@ -1275,14 +1182,13 @@
function resetChargedBalls() {
chargedBallUI && chargedBallUI.reset();
}
function checkFruitCollisions() {
- // Reset surrounded status for all fruits before checking
for (var k = 0; k < fruits.length; k++) {
if (fruits[k]) {
fruits[k].neighboringFruits = 0;
fruits[k].neighborContacts = fruits[k].neighborContacts || [];
- fruits[k].neighborContacts = []; // Reset neighbor contacts list
+ fruits[k].neighborContacts = [];
}
}
outerLoop: for (var i = fruits.length - 1; i >= 0; i--) {
var fruit1 = fruits[i];
@@ -1307,9 +1213,8 @@
var fruit2HalfWidth = fruit2.width / 2;
var fruit2HalfHeight = fruit2.height / 2;
var absDistanceX = Math.abs(dx);
var absDistanceY = Math.abs(dy);
- // Adjust hitbox for larger fruits to match visual assets
var level1 = getFruitLevel(fruit1);
var level2 = getFruitLevel(fruit2);
var hitboxReduction = 0;
if (level1 > 5) {
@@ -1349,51 +1254,36 @@
var contactVelocity = rvX * normalizeX + rvY * normalizeY;
if (contactVelocity < 0) {
var collisionElasticity = Math.max(fruit1.elasticity, fruit2.elasticity);
var impulse = -(1 + collisionElasticity) * contactVelocity;
- // More pronounced mass calculation using fruit level for more realistic physics
var level1 = getFruitLevel(fruit1);
var level2 = getFruitLevel(fruit2);
- // Enhanced exponential mass increase with level
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;
- // Apply anti-popcorn damping for collisions between fruits of different levels
var levelDifference = Math.abs(level1 - level2);
- // Increased multiplier to create more bounce for smaller fruits
- var levelImpactMultiplier = 1 + levelDifference * 0.03; // Increased from 0.0005 to 0.03
- // Adjust impulse force cap for smaller fruits
- var maxImpulseRatio = 1.7; // Increased from 1.2 to allow more bounce
- // Enhanced bounce effect when smaller fruits hit larger ones
+ var levelImpactMultiplier = 1 + levelDifference * 0.03;
+ var maxImpulseRatio = 1.7;
if (level1 > level2) {
- // Larger fruit (1) hitting smaller fruit (2) - give smaller fruit more bounce
impulseRatio2 = Math.min(impulseRatio2 * levelImpactMultiplier * 1.3, maxImpulseRatio * impulseRatio2);
} else if (level2 > level1) {
- // Larger fruit (2) hitting smaller fruit (1) - give smaller fruit more bounce
impulseRatio1 = Math.min(impulseRatio1 * levelImpactMultiplier * 1.3, maxImpulseRatio * impulseRatio1);
}
- // Calculate impulses with additional velocity dampening to prevent popcorn effect
var velocityMagnitude = Math.sqrt(rvX * rvX + rvY * rvY);
var velocityDampening = 1.0;
- // Progressive dampening for higher velocities to prevent popcorn effect
if (velocityMagnitude > 5) {
velocityDampening = 0.5 + 0.5 * (5 / velocityMagnitude);
}
var impulse1 = impulse * impulseRatio1 * velocityDampening;
var impulse2 = impulse * impulseRatio2 * velocityDampening;
- // Enhanced size difference effect for more pronounced bouncing of smaller fruits
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) {
- // Smaller fruit (1) hitting larger fruit (2) - give smaller fruit more bounce
- impulse1 *= 1 + sizeDifference * 2.5; // Increased from 1.4 to 2.5 for more vibrant bouncing
- // Apply one-time bounce rather than continuous vibrations
+ impulse1 *= 1 + sizeDifference * 2.5;
fruit1._lastBounceTime = Date.now();
} else if (fruit2.type.size < fruit1.type.size) {
- // Smaller fruit (2) hitting larger fruit (1) - give smaller fruit more bounce
- impulse2 *= 1 + sizeDifference * 2.5; // Increased from 1.4 to 2.5 for more vibrant bouncing
- // Apply one-time bounce rather than continuous vibrations
+ impulse2 *= 1 + sizeDifference * 2.5;
fruit2._lastBounceTime = Date.now();
}
fruit1.vx -= impulse1 * normalizeX;
fruit1.vy -= impulse1 * normalizeY;
@@ -1417,45 +1307,36 @@
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);
- // Track that these fruits are neighbors for stabilization detection
fruit1.neighboringFruits = (fruit1.neighboringFruits || 0) + 1;
fruit2.neighboringFruits = (fruit2.neighboringFruits || 0) + 1;
- // Track detailed neighbor information for improved stability detection
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);
}
- // Anti-reverberation logic - prevent rapid back-and-forth bouncing
var currentTime = Date.now();
fruit1._collisionHistory = fruit1._collisionHistory || {};
fruit2._collisionHistory = fruit2._collisionHistory || {};
- // Check if these fruits have collided recently
var recentCollision = false;
if (fruit1._collisionHistory[fruit2.id] && currentTime - fruit1._collisionHistory[fruit2.id] < 300) {
recentCollision = true;
}
- // Record current collision
fruit1._collisionHistory[fruit2.id] = currentTime;
fruit2._collisionHistory[fruit1.id] = currentTime;
- // Apply dampening for repeated collisions to prevent reverberation
if (recentCollision) {
- // Apply stronger dampening to prevent ping-pong effect
fruit1.vx *= 0.7;
fruit1.vy *= 0.7;
fruit2.vx *= 0.7;
fruit2.vy *= 0.7;
fruit1.angularVelocity *= 0.6;
fruit2.angularVelocity *= 0.6;
}
- // Additional stability check for network of fruits
if (fruit1.neighborContacts.length >= 3 || fruit2.neighborContacts.length >= 3) {
- // Apply additional stabilizing forces to complex fruit networks
var stabilizeFactor = 0.6;
fruit1.vx *= stabilizeFactor;
fruit1.vy *= stabilizeFactor;
fruit2.vx *= stabilizeFactor;
@@ -1479,9 +1360,8 @@
continue;
}
var fruitHalfHeight = fruit.height / 2;
var fruitHalfWidth = fruit.width / 2;
- // Adjust effective size for larger fruits to match visual asset
var fruitLevel = getFruitLevel(fruit);
var sizeReduction = 0;
if (fruitLevel > 5) {
sizeReduction = (fruitLevel - 5) * 3;
@@ -1503,12 +1383,8 @@
if (horizontalOverlap) {
if (fruit.immuneToGameOver) {
continue;
}
- // Check if the fruit is already stable or coming to rest
- // For game over, we care about:
- // 1. Fruits that are stable and overlapping the line
- // 2. Fruits that are slowing down and overlapping the line
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();
@@ -1527,9 +1403,8 @@
pineapple.isStatic = true;
pineappleActive = false;
pineapplePushCount = 0;
game.addChild(pineapple);
- // Don't add to spatial grid until it's dropped
}
function pushPineapple() {
if (!pineappleActive && pineapple) {
var step = mergeCounter;
@@ -1548,12 +1423,9 @@
}
function initGame() {
LK.setScore(0);
gameOver = false;
- fruits = []; // Clear fruits array first
- // Clear existing children from the game except UI elements if needed
- // (Assuming LK.Game handles cleanup or we manage it here)
- // Example: game.removeChildren(); // Be careful with this, might remove UI
+ fruits = [];
chargeCounter = 0;
if (chargedBallUI) {
chargedBallUI.destroy();
}
@@ -1566,9 +1438,8 @@
isClickable = true;
if (spatialGrid) {
spatialGrid.clear();
} else {
- // Calculate the optimal cell size based on average fruit size
var avgFruitSize = 0;
var fruitCount = 0;
for (var fruitType in FruitTypes) {
if (FruitTypes.hasOwnProperty(fruitType)) {
@@ -1576,14 +1447,12 @@
fruitCount++;
}
}
avgFruitSize = fruitCount > 0 ? avgFruitSize / fruitCount : 300;
- // Set cell size to approximately 110% of average fruit size for optimal collision checks
var optimalCellSize = Math.ceil(avgFruitSize * 1.1);
spatialGrid = new SpatialGrid(optimalCellSize);
}
LK.playMusic('bgmusic');
- // Destroy old boundaries if they exist before creating new ones
if (wallLeft) {
wallLeft.destroy();
}
if (wallRight) {
@@ -1604,33 +1473,29 @@
if (evolutionLine) {
evolutionLine.destroy();
}
setupBoundaries();
- setupUI(); // Setup UI after clearing old elements
+ setupUI();
setupPineapple();
trajectoryLine = game.addChild(new TrajectoryLine());
trajectoryLine.createDots();
- // Add evolution line to the game
evolutionLine = game.addChild(new EvolutionLine());
evolutionLine.initialize();
updateScoreDisplay();
- // Clear active fruit *before* creating the first one
activeFruit = null;
createNextFruit();
- // Reset charged ball display explicitly after setup
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; // Start static for animation
+ coconut.isStatic = true;
game.addChild(coconut);
fruits.push(coconut);
- // Don't add to grid yet, wait for animation finish
- coconut.safetyPeriod = false; // Initial state
+ coconut.safetyPeriod = false;
coconut.immuneToGameOver = true;
var targetY = gameHeight - gameFloor.height / 2 - coconut.height / 2 - 10;
tween(coconut, {
y: targetY
@@ -1639,13 +1504,13 @@
easing: tween.easeIn,
onFinish: function onFinish() {
if (!coconut || !fruits.includes(coconut)) {
return;
- } // Check if coconut still exists
+ }
coconut.isStatic = false;
- coconut.vy = -2; // Small bounce
+ coconut.vy = -2;
coconut.vx = (Math.random() * 2 - 1) * 1.5;
- spatialGrid.insertObject(coconut); // Add to grid *after* becoming dynamic
+ spatialGrid.insertObject(coconut);
LK.setTimeout(function () {
if (coconut && fruits.includes(coconut)) {
coconut.immuneToGameOver = false;
}
@@ -1653,23 +1518,20 @@
}
});
}
var lastScoreCheckForCoconut = 0;
-// Event handlers
game.down = function (x, y) {
if (activeFruit && !gameOver) {
- // Prevent interaction if game over
isDragging = true;
- game.move(x, y); // Initial move to cursor
+ 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));
- // Keep Y fixed at the top while dragging
activeFruit.y = dropPointY + 200;
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
@@ -1678,16 +1540,14 @@
game.up = function () {
if (isDragging && activeFruit && isClickable && !gameOver) {
dropFruit();
}
- isDragging = false; // Always reset dragging on up
+ isDragging = false;
};
function updatePhysics() {
- // Periodically rebuild the spatial grid to prevent memory leaks
if (spatialGrid && Date.now() - spatialGrid.lastRebuildTime > spatialGrid.rebuildInterval) {
spatialGrid.rebuildGrid(fruits);
}
- // First pass: Update physics and boundary collisions
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
if (!fruit || fruit.isStatic || fruit.merging) {
continue;
@@ -1698,41 +1558,30 @@
right: wallRight
};
fruit.checkBoundaries(walls, gameFloor);
}
- // Process fruit-to-fruit collisions
checkFruitCollisions();
- // Second pass: Stabilize fruits based on surroundings
for (var i = 0; i < fruits.length; i++) {
var fruit = fruits[i];
if (fruit && !fruit.isStatic && !fruit.merging) {
- // Initialize stabilization tracking variables if needed
if (fruit.surroundedFrames === undefined) {
fruit.surroundedFrames = 0;
}
- // Combined check for all stabilization scenarios in one place
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;
- // Check for potential gaps beneath the fruit
var hasGapBelow = false;
if (floorProximity || hasMultipleNeighbors) {
- // Only check for gaps if near floor or surrounded by other fruits
var potentialNeighbors = spatialGrid.getPotentialCollisions({
x: fruit.x,
y: fruit.y + fruit.height / 2 + 20,
- // Check below the fruit
width: fruit.width * 0.6,
- // Smaller check area to detect gaps
height: 20,
id: 'gap_check_' + fruit.id
});
- // If no neighbors below, there might be a gap
hasGapBelow = potentialNeighbors.length === 0 && !floorProximity;
- // If there's a gap, encourage rolling by adding a small horizontal nudge
if (hasGapBelow && isSlowMoving) {
- // Find which direction might have the gap
var leftCount = 0,
rightCount = 0;
for (var j = 0; j < fruit.neighborContacts.length; j++) {
var neighborId = fruit.neighborContacts[j];
@@ -1746,48 +1595,35 @@
break;
}
}
}
- // Apply a gentle nudge in the direction with fewer neighbors
if (leftCount < rightCount) {
fruit.vx -= 0.05;
} else if (rightCount < leftCount) {
fruit.vx += 0.05;
} else {
- // Random direction if balanced
fruit.vx += Math.random() * 0.1 - 0.05;
}
}
}
- // Determine stabilization scenario
var stabilizationScenario = boundaryContact ? "boundary" : floorProximity ? "floor" : hasMultipleNeighbors ? "surrounded" : "free";
- // Apply appropriate stabilization based on scenario
switch (stabilizationScenario) {
case "boundary":
- // Already handled by boundary collisions
fruit.surroundedFrames = 0;
break;
case "floor":
- // Already handled by floor collision
fruit.surroundedFrames = 0;
break;
case "surrounded":
- // Floating fruit surrounded by other fruits
fruit.surroundedFrames++;
- // Stabilization strength increases with duration but is more permissive
var stabilizationStrength = Math.min(0.92, 0.75 + fruit.surroundedFrames * 0.015);
- // Apply stronger stabilization for fruits in a complex network
if (fruit.neighborContacts.length >= 3) {
stabilizationStrength = Math.min(0.95, stabilizationStrength + 0.03);
}
- // Apply stabilization
fruit.vx *= stabilizationStrength;
fruit.vy *= stabilizationStrength;
fruit.angularVelocity *= stabilizationStrength;
- // Stop fruit completely if it's been surrounded and nearly stationary for a while
- // More tolerant threshold and no forced rotation to 90 degrees
if (fruit.surroundedFrames > 10 && isSlowMoving || fruit.surroundedFrames > 18) {
- // Allow very slight movement to permit settling into gaps
if (Math.abs(fruit.vx) < 0.05) {
fruit.vx = 0;
}
if (Math.abs(fruit.vy) < 0.05) {
@@ -1795,17 +1631,14 @@
}
if (Math.abs(fruit.angularVelocity) < 0.005) {
fruit.angularVelocity = 0;
}
- // Don't force rotation to 90-degree increments to allow diagonal resting
}
break;
case "free":
- // Reset surrounded counter for free-moving fruits
fruit.surroundedFrames = 0;
break;
}
- // Update spatial grid with new position
spatialGrid.updateObject(fruit);
}
}
}
@@ -1817,17 +1650,15 @@
if (currentScore >= lastScoreCheckForCoconut + 500) {
lastScoreCheckForCoconut = Math.floor(currentScore / 500) * 500;
spawnCoconut();
} else {
- // Keep track of the current score threshold even when not spawning
if (currentScore > lastScoreCheckForCoconut) {
lastScoreCheckForCoconut = Math.floor(currentScore / 500) * 500;
}
}
updatePhysics();
checkGameOver();
};
-// Initialize the game
initGame();
function removeFruitFromGame(fruit) {
var index = fruits.indexOf(fruit);
if (index !== -1) {