User prompt
yukardaki tabukları hepsini 150 150 yap ama yere atıldığında assets deki boyu olsun
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'points')' in or related to this line: 'LK.setScore(LK.getScore() + nextType.points);' Line Number: 580
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'toUpperCase')' in or related to this line: 'var nextType = FruitTypes[fruit1.type.next.toUpperCase()];' Line Number: 561
User prompt
Oyundaki kareleri atiyozya assetsdeki renkleri oyundaki renklere cevir
Remix started
Copy Memerge
/**** * 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 = GAME_CONSTANTS.CHARGE_NEEDED_FOR_RELEASE; self.currentCharge = 0; self.isReadyToRelease = false; self.pulseAnimationActive = false; self.initialize = function () { self.portalAsset = self.attachAsset('portal', { anchorX: 0.5, anchorY: 0.5 }); self.portalAsset.x = GAME_CONSTANTS.GAME_WIDTH / 2 + GAME_CONSTANTS.PORTAL_UI_X_OFFSET; self.portalAsset.alpha = 0; self.portalAsset.scaleX = 0; self.portalAsset.scaleY = 0; self.y = GAME_CONSTANTS.PORTAL_UI_Y; }; self.updateChargeDisplay = function (chargeCount) { self.currentCharge = chargeCount; var remainingCount = Math.max(0, self.chargeNeededForRelease - self.currentCharge); var progressPercent = (self.chargeNeededForRelease - remainingCount) / self.chargeNeededForRelease; var targetScale = progressPercent; if (progressPercent > 0 && self.portalAsset.alpha === 0) { self.portalAsset.alpha = 1; } tween(self.portalAsset, { scaleX: targetScale, scaleY: targetScale, alpha: progressPercent }, { duration: GAME_CONSTANTS.PORTAL_TWEEN_DURATION, easing: tween.easeOut }); if (remainingCount === 0 && !self.isReadyToRelease) { self.setReadyState(true); } }; self.setReadyState = function (isReady) { self.isReadyToRelease = isReady; if (isReady) { tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0, rotation: Math.PI * 2 }, { duration: GAME_CONSTANTS.PORTAL_TWEEN_DURATION, easing: tween.easeOut }); self.startPulseAnimation(); } }; self.startPulseAnimation = function () { if (self.pulseAnimationActive) return; self.pulseAnimationActive = true; self._pulseText(); }; self._pulseText = function () { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.3, scaleY: 1.3 }, { duration: GAME_CONSTANTS.PORTAL_PULSE_DURATION, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.isReadyToRelease) { self.pulseAnimationActive = false; return; } tween(self.portalAsset, { scaleX: 1.0, scaleY: 1.0 }, { duration: GAME_CONSTANTS.PORTAL_PULSE_DURATION, easing: tween.easeInOut, onFinish: self._pulseText }); } }); }; self.reset = function () { self.isReadyToRelease = false; self.currentCharge = 0; self.pulseAnimationActive = false; tween(self.portalAsset, { alpha: 0 }, { duration: 200, easing: tween.easeOut }); tween(self.portalAsset, { scaleX: 0, scaleY: 0 }, { duration: 200, easing: tween.easeOut }); }; self.initialize(); return self; }); var CollisionComponent = Container.expand(function () { var self = Container.call(this); self.wallContactFrames = 0; self.checkBoundaryCollisions = function (fruit, walls, floor) { if (!walls || !walls.left || !walls.right || !floor) return; var fruitHalfWidth = fruit.width / 2; var fruitHalfHeight = fruit.height / 2; var fruitLevel = getFruitLevel(fruit); var sizeReduction = Math.max(0, fruitLevel - 4) * 2; fruitHalfWidth = Math.max(10, fruitHalfWidth - sizeReduction / 2); fruitHalfHeight = Math.max(10, fruitHalfHeight - sizeReduction / 2); var cosAngle = Math.abs(Math.cos(fruit.rotation)); var sinAngle = Math.abs(Math.sin(fruit.rotation)); var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle; var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle; fruit._boundaryContacts = fruit._boundaryContacts || { left: false, right: false, floor: false }; fruit._boundaryContacts.left = false; fruit._boundaryContacts.right = false; fruit._boundaryContacts.floor = false; self.checkLeftWallCollision(fruit, walls.left, effectiveWidth); self.checkRightWallCollision(fruit, walls.right, effectiveWidth); self.checkFloorCollision(fruit, floor, effectiveHeight); var isInContact = fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor; if (isInContact) { fruit.wallContactFrames++; var progressiveFriction = Math.min(0.75, 0.55 + fruit.wallContactFrames * 0.015); fruit.angularVelocity *= progressiveFriction; if (fruit.wallContactFrames > 15) { fruit.vx *= 0.75; fruit.vy *= 0.75; fruit.angularVelocity *= 0.5; } else if (fruit.wallContactFrames > 10) { fruit.vx *= 0.8; fruit.vy *= 0.85; fruit.angularVelocity *= 0.7; } else if (fruit.wallContactFrames > 5) { fruit.vx *= 0.9; fruit.vy *= 0.9; } } else { fruit.wallContactFrames = Math.max(0, fruit.wallContactFrames - 1); } }; self.checkLeftWallCollision = function (fruit, leftWall, effectiveWidth) { var leftBoundary = leftWall.x + leftWall.width / 2 + effectiveWidth; if (fruit.x < leftBoundary) { var incomingVx = fruit.vx; fruit.x = leftBoundary; fruit.vx = -incomingVx * fruit.elasticity * 0.7; if (Math.abs(incomingVx) > GAME_CONSTANTS.BOUNCE_SOUND_VELOCITY_THRESHOLD) { LK.getSound('bounce').play(); } var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= GAME_CONSTANTS.GROUND_ANGULAR_FRICTION * 0.4; fruit._boundaryContacts.left = true; } else if (fruit.x > leftWall.x + leftWall.width * 2) { if (fruit._boundaryContacts && !fruit._boundaryContacts.right && !fruit._boundaryContacts.floor) { fruit.wallContactFrames = Math.max(0, fruit.wallContactFrames - 1); } } }; self.checkRightWallCollision = function (fruit, rightWall, effectiveWidth) { var rightBoundary = rightWall.x - rightWall.width / 2 - effectiveWidth; if (fruit.x > rightBoundary) { var incomingVx = fruit.vx; fruit.x = rightBoundary; fruit.vx = -incomingVx * fruit.elasticity * 0.7; if (Math.abs(incomingVx) > GAME_CONSTANTS.BOUNCE_SOUND_VELOCITY_THRESHOLD) { LK.getSound('bounce').play(); } var angularImpactMultiplier = 0.0025 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.25; fruit.angularVelocity *= GAME_CONSTANTS.GROUND_ANGULAR_FRICTION * 0.4; fruit._boundaryContacts.right = true; } }; self.checkFloorCollision = function (fruit, floor, effectiveHeight) { var floorCollisionY = floor.y - floor.height / 2 - effectiveHeight; if (fruit.y > floorCollisionY) { var incomingVy = fruit.vy; fruit.y = floorCollisionY; fruit.vy = -incomingVy * fruit.elasticity * 0.5; if (Math.abs(incomingVy) > GAME_CONSTANTS.BOUNCE_SOUND_VELOCITY_THRESHOLD) { LK.getSound('bounce').play(); } fruit._boundaryContacts.floor = true; if (Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity = fruit.vx * 0.008; } fruit.angularVelocity *= GAME_CONSTANTS.GROUND_ANGULAR_FRICTION * 0.4; var restThreshold = fruit.wallContactFrames > 5 ? 1.5 : 2.5; if (Math.abs(fruit.vy) < restThreshold) { fruit.vy = 0; fruit.vx *= 0.6; if (fruit.wallContactFrames > 8) { fruit.vx *= 0.75; } } var angularRestThreshold = fruit.wallContactFrames > 5 ? 0.012 : 0.018; 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 = GAME_CONSTANTS.EVOLUTION_LINE_Y; self.x = GAME_CONSTANTS.GAME_WIDTH / 2; // Remove the X_OFFSET to center properly 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(GAME_CONSTANTS.EVOLUTION_ICON_MAX_SIZE / fruitIcon.width, GAME_CONSTANTS.EVOLUTION_ICON_MAX_SIZE / fruitIcon.height); fruitIcon.scaleX = scale; fruitIcon.scaleY = scale; totalWidth += fruitIcon.width * scale; if (i < fruitTypes.length - 1) { totalWidth += GAME_CONSTANTS.EVOLUTION_ICON_SPACING; } fruitIcons.push(fruitIcon); } var currentX = -totalWidth / 2; for (var i = 0; i < fruitIcons.length; i++) { var icon = fruitIcons[i]; icon.x = currentX + icon.width * icon.scaleX / 2; icon.y = 0; self.addChild(icon); currentX += icon.width * icon.scaleX + GAME_CONSTANTS.EVOLUTION_ICON_SPACING; } }; return self; }); var FireElement = Container.expand(function (initX, initY, zIndex) { var self = Container.call(this); self.baseX = initX || 0; self.baseY = initY || 0; self.zIndex = zIndex || 0; self.movementRange = 30 + Math.random() * 20; self.movementSpeed = 0.3 + Math.random() * 0.4; self.direction = Math.random() > 0.5 ? 1 : -1; self.alphaMin = GAME_CONSTANTS.FIRE_ALPHA_MIN; self.alphaMax = GAME_CONSTANTS.FIRE_ALPHA_MAX; self.flickerSpeed = GAME_CONSTANTS.FIRE_FLICKER_SPEED_BASE + Math.random() * GAME_CONSTANTS.FIRE_FLICKER_SPEED_RANDOM; self.frameIndex = 0; self.frameTimer = null; self.frameDuration = GAME_CONSTANTS.FIRE_FRAME_DURATION; self.initialize = function () { self.fireAsset = self.attachAsset('fire', { anchorX: 0.5, anchorY: 1.0 }); self.fireAsset2 = self.attachAsset('fire_2', { anchorX: 0.5, anchorY: 1.0 }); self.fireAsset2.visible = false; self.x = self.baseX; self.y = self.baseY; self.startAlphaFlicker(); self.startFrameAnimation(); }; self.update = function () { self.x += self.movementSpeed * self.direction; if (Math.abs(self.x - self.baseX) > self.movementRange) { self.direction *= -1; } }; self.startFrameAnimation = function () { if (self.frameTimer) LK.clearInterval(self.frameTimer); self.frameTimer = LK.setInterval(function () { self.toggleFrame(); }, self.frameDuration); }; self.toggleFrame = function () { self.frameIndex = (self.frameIndex + 1) % 2; self.fireAsset.visible = self.frameIndex === 0; self.fireAsset2.visible = self.frameIndex === 1; }; self.startAlphaFlicker = function () { if (self.flickerTween) self.flickerTween.stop(); var startDelay = Math.random() * 500; LK.setTimeout(function () { self.flickerToMax(); }, startDelay); }; self.flickerToMax = function () { self.flickerTween = tween(self, { alpha: self.alphaMax }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMin }); }; self.flickerToMin = function () { self.flickerTween = tween(self, { alpha: self.alphaMin }, { duration: self.flickerSpeed, easing: tween.easeInOut, onFinish: self.flickerToMax }); }; self.destroy = function () { if (self.flickerTween) self.flickerTween.stop(); if (self.frameTimer) { LK.clearInterval(self.frameTimer); self.frameTimer = null; } Container.prototype.destroy.call(this); }; self.initialize(); return self; }); var Fruit = Container.expand(function (type) { var self = Container.call(this); self.id = 'fruit_' + Date.now() + '_' + Math.floor(Math.random() * 10000); self.type = type; var physics = new PhysicsComponent(); var collision = new CollisionComponent(); var mergeHandler = new MergeComponent(); var behaviorSystem = new FruitBehavior(); self.vx = physics.vx; self.vy = physics.vy; self.rotation = physics.rotation; self.angularVelocity = physics.angularVelocity; self.angularFriction = GAME_CONSTANTS.ANGULAR_FRICTION; self.groundAngularFriction = GAME_CONSTANTS.GROUND_ANGULAR_FRICTION; var currentLevel = getFruitLevel(self); self.gravity = GAME_CONSTANTS.BASE_GRAVITY * (1 + (currentLevel - 1) * GAME_CONSTANTS.GRAVITY_LEVEL_MULTIPLIER); self.friction = GAME_CONSTANTS.FRICTION; self.rotationRestCounter = physics.rotationRestCounter; self.maxAngularVelocity = GAME_CONSTANTS.MAX_ANGULAR_VELOCITY; self.isStatic = physics.isStatic; self.elasticity = currentLevel < GAME_CONSTANTS.ELASTICITY_LOW_START_LEVEL ? GAME_CONSTANTS.ELASTICITY_HIGH : GAME_CONSTANTS.ELASTICITY_LOW_BASE - (currentLevel - (GAME_CONSTANTS.ELASTICITY_LOW_START_LEVEL - 1)) * GAME_CONSTANTS.ELASTICITY_DECREASE_FACTOR; self.elasticity = Math.max(0.1, self.elasticity); // Ensure minimum elasticity self.wallContactFrames = collision.wallContactFrames; self.merging = mergeHandler.merging; self.mergeGracePeriodActive = mergeHandler.mergeGracePeriodActive; self.fromChargedRelease = mergeHandler.fromChargedRelease; self.safetyPeriod = false; self.immuneToGameOver = false; self.isFullyStabilized = false; self.isSleeping = false; self._sleepCounter = 0; 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 }); // Always start with 150x150 for all tabuk fruits fruitGraphics.width = 150; fruitGraphics.height = 150; self.width = fruitGraphics.width; self.height = fruitGraphics.height; self._assetOriginalWidth = LK.getAsset(self.type.id, { anchorX: 0.5, anchorY: 0.5 }).width; self._assetOriginalHeight = LK.getAsset(self.type.id, { anchorX: 0.5, anchorY: 0.5 }).height; self._fruitGraphics = fruitGraphics; 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(f1, f2, x, y) { return self.standardMerge(f1, f2, x, y); } }, GRAPE: { onMerge: function onMerge(f1, f2, x, y) { return self.standardMerge(f1, f2, x, y); } }, APPLE: { onMerge: function onMerge(f1, f2, x, y) { return self.standardMerge(f1, f2, x, y); } }, ORANGE: { onMerge: function onMerge(f1, f2, x, y) { return self.standardMerge(f1, f2, x, y); } }, WATERMELON: { onMerge: function onMerge(f1, f2, x, y) { return self.standardMerge(f1, f2, x, y); } }, PINEAPPLE: { onMerge: function onMerge(f1, f2, x, y) { return self.standardMerge(f1, f2, x, y); }, onSpawn: function onSpawn() {} }, MELON: { onMerge: function onMerge(f1, f2, x, y) { LK.getSound('Smartz').play(); return self.standardMerge(f1, f2, x, y); } }, PEACH: { onMerge: function onMerge(f1, f2, x, y) { LK.getSound('stonks').play(); return self.standardMerge(f1, f2, x, y); } }, COCONUT: { onMerge: function onMerge(f1, f2, x, y) { LK.getSound('ThisIsFine').play(); return self.standardMerge(f1, f2, x, y); }, onSpawn: function onSpawn() { LK.getSound('stonks').play(); } }, DURIAN: { onMerge: function onMerge(f1, f2, x, y) { LK.setScore(LK.getScore() + f1.type.points); updateScoreDisplay(); removeFruitFromGame(f1); removeFruitFromGame(f2); 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 = null; if (fruit1 && fruit1.type && fruit1.type.next) { var nextKey = fruit1.type.next; if (typeof nextKey === "string" && FruitTypes[nextKey.toUpperCase()]) { nextType = FruitTypes[nextKey.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); if (nextType && typeof nextType.points === "number") { 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 || fruit2.merging) return; // Added check for fruit2 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); if (behaviorHandler && behaviorHandler.onMerge) { // Added check behaviorHandler.onMerge(fruit1, fruit2, midX, midY); } // Ensure cleanup happens correctly, check if fruits still exist before removal if (fruit1 && fruit1.parent && fruit1.type.id.toUpperCase() !== 'DURIAN') removeFruitFromGame(fruit1); if (fruit2 && fruit2.parent) 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 = GAME_CONSTANTS.BASE_GRAVITY; self.friction = GAME_CONSTANTS.FRICTION; self.isStatic = false; self.rotation = 0; self.angularVelocity = 0; self.maxAngularVelocity = GAME_CONSTANTS.MAX_ANGULAR_VELOCITY; self.rotationRestCounter = 0; self.lastVx = 0; self.lastVy = 0; self.isFullyStabilized = false; // Moved here self.stabilizeFruit = function (fruit, movementMagnitude, velocityChange) { var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; if (isMidAir) { fruit.isFullyStabilized = false; // Ensure mid-air fruits are never marked stabilized return false; } var stabilizationLevel = 0; var fruitLevel = getFruitLevel(fruit); if (!fruit._stabilizeMetrics) { fruit._stabilizeMetrics = { consecutiveSmallMovements: 0, wallContactDuration: 0, surroundedDuration: 0, restingDuration: 0 }; } // Dynamic thresholds based only on fruitLevel - lower for higher-level fruits var movementThreshold = GAME_CONSTANTS.STABILIZATION_MOVEMENT_THRESHOLD_BASE - fruitLevel * GAME_CONSTANTS.STABILIZATION_MOVEMENT_THRESHOLD_LEVEL_FACTOR * 1.2; var angularThreshold = GAME_CONSTANTS.STABILIZATION_ANGULAR_THRESHOLD_BASE - fruitLevel * GAME_CONSTANTS.STABILIZATION_ANGULAR_THRESHOLD_LEVEL_FACTOR * 1.2; if (movementMagnitude < movementThreshold && Math.abs(fruit.angularVelocity) < angularThreshold) { var stabilizationRate = GAME_CONSTANTS.STABILIZATION_SCORE_RATE_BASE + fruitLevel * GAME_CONSTANTS.STABILIZATION_SCORE_LEVEL_FACTOR * 1.4; fruit._stabilizeMetrics.consecutiveSmallMovements += stabilizationRate; fruit._stabilizeMetrics.restingDuration += stabilizationRate; } else { fruit._stabilizeMetrics.consecutiveSmallMovements = 0; fruit._stabilizeMetrics.restingDuration = Math.max(0, fruit._stabilizeMetrics.restingDuration - 2); } if (fruit.wallContactFrames > 0) fruit._stabilizeMetrics.wallContactDuration += GAME_CONSTANTS.STABILIZATION_WALL_SCORE_RATE * 1.4 + fruitLevel * 0.15;else fruit._stabilizeMetrics.wallContactDuration = Math.max(0, fruit._stabilizeMetrics.wallContactDuration - 1); if (fruit.surroundedFrames > 0) fruit._stabilizeMetrics.surroundedDuration += GAME_CONSTANTS.STABILIZATION_SURROUNDED_SCORE_RATE * 1.4 + fruitLevel * 0.22;else fruit._stabilizeMetrics.surroundedDuration = Math.max(0, fruit._stabilizeMetrics.surroundedDuration - 1); var levelFactor = fruitLevel * GAME_CONSTANTS.STABILIZATION_SCORE_LEVEL_INFLUENCE; 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); // Dynamic thresholds for stabilization based only on fruitLevel - lower for higher-level fruits var fullStabilizationThreshold = GAME_CONSTANTS.STABILIZATION_THRESHOLD_FULL_BASE - fruitLevel * GAME_CONSTANTS.STABILIZATION_THRESHOLD_FULL_LEVEL_FACTOR * 1.25; var partialStabilizationThreshold = GAME_CONSTANTS.STABILIZATION_THRESHOLD_PARTIAL_BASE - fruitLevel * GAME_CONSTANTS.STABILIZATION_THRESHOLD_PARTIAL_LEVEL_FACTOR * 1.25; if (totalStabilizationScore > fullStabilizationThreshold) stabilizationLevel = 2;else if (totalStabilizationScore > partialStabilizationThreshold) stabilizationLevel = 1; if (stabilizationLevel > 0) { // Make damping stronger for higher-level fruits var baseDamp = stabilizationLevel === 2 ? GAME_CONSTANTS.STABILIZATION_DAMP_FACTOR_FULL * 0.9 : GAME_CONSTANTS.STABILIZATION_DAMP_FACTOR_PARTIAL * 0.95; var dampFactor = Math.max(0.35, baseDamp - fruitLevel * GAME_CONSTANTS.STABILIZATION_DAMP_LEVEL_FACTOR * 1.1); fruit.vx *= dampFactor; fruit.vy *= dampFactor; fruit.angularVelocity *= dampFactor * 0.85; var stopThreshold = GAME_CONSTANTS.STABILIZATION_STOP_THRESHOLD_BASE - fruitLevel * GAME_CONSTANTS.STABILIZATION_STOP_THRESHOLD_LEVEL_FACTOR; // Unconditionally stop when fully stabilized (level 2) and below movement threshold if (stabilizationLevel === 2 && movementMagnitude < stopThreshold) { fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; fruit.isFullyStabilized = true; // Track frames of being fully stabilized if (!fruit._sleepCounter) fruit._sleepCounter = 0; // Only increment sleep counter if both linear and angular velocities are near zero if (movementMagnitude < stopThreshold * 0.8 && Math.abs(fruit.angularVelocity) < angularThreshold * 0.7) { fruit._sleepCounter++; // After being fully stabilized for SLEEP_DELAY_FRAMES consecutive frames, put to sleep if (fruit._sleepCounter >= GAME_CONSTANTS.SLEEP_DELAY_FRAMES) { fruit.isSleeping = true; fruit.isFullyStabilized = true; // Ensure fully stabilized state when sleeping } } else { // Reset sleep counter if either velocity isn't near zero fruit._sleepCounter = 0; fruit.isSleeping = false; // Ensure fruit is awake if conditions aren't met } return true; } } fruit.isFullyStabilized = false; return false; }; self.apply = function (fruit) { if (fruit.isStatic || fruit.merging) return; // Skip physics calculations for sleeping fruits, but keep collision detection if (fruit.isSleeping) { // Keep fruit in the exact same position - no physics updates fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; return; } // Reset stabilization flag if significant external force applied (e.g., explosion) // Wake fruit up from potential stabilization if there's significant velocity change var velocityDeltaX = Math.abs(fruit.vx - fruit.lastVx); var velocityDeltaY = Math.abs(fruit.vy - fruit.lastVy); if ((fruit.isFullyStabilized || fruit._sleepCounter > 0) && (velocityDeltaX > 0.7 || velocityDeltaY > 0.7)) { fruit.isFullyStabilized = false; fruit._sleepCounter = 0; // Reset sleep counter on significant velocity change } // More aggressively ensure stability when at rest - use a tighter threshold if (fruit.isFullyStabilized && Math.abs(fruit.vx) < 0.3 && Math.abs(fruit.vy) < 0.3) { fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; // Ensure zero angular velocity return; } fruit.lastVx = fruit.vx; fruit.lastVy = fruit.vy; var fruitLevel = getFruitLevel(fruit); var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; // Use exponential scaling for gravity to make higher-level fruits dramatically heavier var gravityMultiplier = 1 + Math.pow(fruitLevel, 1.5) * 0.25; // Stronger influence fruit.vy += fruit.gravity * gravityMultiplier; var maxVelocity = GAME_CONSTANTS.MAX_VELOCITY_BASE - fruitLevel * GAME_CONSTANTS.MAX_VELOCITY_LEVEL_FACTOR; fruit.vx = Math.max(-maxVelocity, Math.min(maxVelocity, fruit.vx)); fruit.vy = Math.max(-maxVelocity, Math.min(maxVelocity, fruit.vy)); // Removed separate LEVEL_DOWNWARD_FORCE_FACTOR as it's now incorporated in the gravity calculation if (fruit.lastVx !== undefined && fruit.lastVy !== undefined) { var dvx = fruit.vx - fruit.lastVx; var dvy = fruit.vy - fruit.lastVy; // Make inertia scale dramatically with fruitLevel using power function var inertiaResistance = Math.min(0.95, Math.pow(fruitLevel, 1.8) * 0.01); fruit.vx = fruit.lastVx + dvx * (1 - inertiaResistance); fruit.vy = fruit.lastVy + dvy * (1 - inertiaResistance); } var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); var velocityChange = Math.abs(fruit.vx - fruit.lastVx) + Math.abs(fruit.vy - fruit.lastVy); // Apply strong damping when velocity is very low to help fruits stabilize faster if (movementMagnitude < 0.5) { fruit.vx *= 0.80; fruit.vy *= 0.80; fruit.angularVelocity *= 0.70; } var isFullyStabilizedByFunc = self.stabilizeFruit(fruit, movementMagnitude, velocityChange); fruit.isFullyStabilized = isFullyStabilizedByFunc; // Update fruit's state if (!fruit.isFullyStabilized) { // Only apply rolling rotation when in contact with a surface var isRolling = fruit._boundaryContacts && (fruit._boundaryContacts.floor || fruit._boundaryContacts.left || fruit._boundaryContacts.right); if (isRolling && (movementMagnitude > 0.5 || velocityChange > 0.3)) { var rotationFactor = 0.035 / (1 + fruitLevel * 0.08); var targetAngularVelocity = fruit.vx * rotationFactor; fruit.angularVelocity = fruit.angularVelocity * 0.4 + targetAngularVelocity * 0.6; } else { // Apply stronger damping when not rolling or in air fruit.angularVelocity *= isRolling ? 0.75 : 0.65; // More damping when not rolling } fruit.rotation += fruit.angularVelocity; // Make friction increase slightly with level (heavier objects have more surface interaction) var frictionModifier = 0.98 + fruitLevel * 0.005; // Apply more friction when nearly stopped to push toward stabilization var nearStopMultiplier = movementMagnitude < 1.0 ? 0.95 : 0.98; // More aggressive fruit.vx *= fruit.friction * frictionModifier * nearStopMultiplier; fruit.vy *= fruit.friction * frictionModifier * nearStopMultiplier * 0.98; var stopThreshold = GAME_CONSTANTS.STABILIZATION_STOP_THRESHOLD_BASE - fruitLevel * GAME_CONSTANTS.STABILIZATION_STOP_THRESHOLD_LEVEL_FACTOR; stopThreshold = Math.max(0.05, stopThreshold); if (Math.abs(fruit.vx) < stopThreshold) fruit.vx = 0; // Only stop vertical velocity if NOT in mid-air AND not fully stabilized by function if (Math.abs(fruit.vy) < stopThreshold && !isMidAir) fruit.vy = 0; self.handleRotationDamping(fruit); } fruit.x += fruit.vx; fruit.y += fruit.vy; if (isMidAir) { var currentMinFallSpeed = GAME_CONSTANTS.MIN_FALL_SPEED + fruitLevel * 0.02; if (fruit.vy < currentMinFallSpeed) { fruit.vy = currentMinFallSpeed; } fruit.isFullyStabilized = false; // Explicitly ensure mid-air is not stabilized } }; self.handleRotationDamping = function (fruit) { var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); var fruitLevel = getFruitLevel(fruit); var isMidAir = !fruit._boundaryContacts || !fruit._boundaryContacts.floor && !fruit._boundaryContacts.left && !fruit._boundaryContacts.right; // Scale rotation factor inversely with fruitLevel - higher level = less rotation from rolling var rotationFactor = 0.04 / (1 + fruitLevel * 0.15); // Increased inverse scaling with level var targetAngularVelocity = fruit.vx * rotationFactor; var hasNeighbors = fruit.neighborContacts && fruit.neighborContacts.length > 0; var neighborCount = hasNeighbors ? fruit.neighborContacts.length : 0; // Only apply rotation forces if moving with sufficient velocity if (movementMagnitude > 0.3) { // Less influence when more neighbors present - scaled by neighbor count var blendRatio = Math.max(0.2, 0.6 - neighborCount * 0.08); fruit.angularVelocity = fruit.angularVelocity * (1 - blendRatio) + targetAngularVelocity * blendRatio; // Correct rotation direction if it's opposite of movement var targetDirection = fruit.vx > 0 ? 1 : -1; var currentDirection = fruit.angularVelocity > 0 ? 1 : -1; if (targetDirection !== currentDirection && Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity *= 0.25; // Stronger correction when rotation direction opposite of movement } } // Base damping primarily on movement magnitude and neighbor count var movementFactor = Math.min(1, movementMagnitude * 2); var neighborFactor = Math.min(1, neighborCount * 0.2); var levelFactor = fruitLevel * 0.06; // Increased level factor influence // Calculate rotation damp factor based on these conditions var rotationDampFactor; // Apply much stronger damping when nearly stopped if (movementMagnitude < 0.05) { // Near-zero movement gets extreme damping rotationDampFactor = 0.12 - levelFactor * 0.6; // Increased extreme damping near zero } else if (isMidAir) { // Mid-air damping - moderately strong rotationDampFactor = 0.6 - levelFactor * 0.8; } else if (movementMagnitude < 0.3) { // Very slow movement - strong damping rotationDampFactor = 0.25 - levelFactor * 0.7 - neighborFactor * 0.15; // Stronger damping for slow movement } else if (neighborCount >= 3) { // Many neighbors - increased resistance rotationDampFactor = 0.35 - levelFactor * 0.6 - neighborFactor * 0.15; } else if (hasNeighbors) { // Some neighbors - moderate resistance rotationDampFactor = 0.45 - levelFactor * 0.5 - neighborFactor * 0.1; } else { // Default case - moving freely with no neighbors rotationDampFactor = 0.6 - levelFactor * 0.4; } // Check if fruit is rolling (in contact with surface with meaningful velocity) var isRolling = !isMidAir && movementMagnitude > 0.5 && fruit._boundaryContacts && (fruit._boundaryContacts.floor || fruit._boundaryContacts.left || fruit._boundaryContacts.right); if (isRolling) { // Fine-tune damping when rolling based on speed if (movementMagnitude < 0.5) { rotationDampFactor = 0.7 - levelFactor * 0.4; } else if (movementMagnitude < 1.0) { rotationDampFactor = 0.8 - levelFactor * 0.3; } else { // Allow more natural rotation when moving quickly rotationDampFactor = 0.85 - levelFactor * 0.2; } } // Apply the calculated damping factor fruit.angularVelocity *= rotationDampFactor; // Apply additional damping when velocity changes suddenly if (fruit.lastVx !== undefined && Math.abs(fruit.vx - fruit.lastVx) > 0.5) { fruit.angularVelocity -= (fruit.vx - fruit.lastVx) * 0.02; } // Dramatically increase damping when velocity is very low if (movementMagnitude < 0.2) { // Apply extreme angular damping when linear movement is minimal var linearDampingFactor = movementMagnitude < 0.08 ? 0.03 : 0.15; // More aggressive damping fruit.angularVelocity *= linearDampingFactor; // Directly zero out tiny angular velocities when there's almost no movement if (Math.abs(fruit.angularVelocity) < 0.002 || movementMagnitude < 0.02) { // Lower thresholds fruit.angularVelocity = 0; } } // Handling rolling physics when in contact with surfaces if (fruit._boundaryContacts) { // Apply rolling physics when in contact with floor or walls var isRolling = fruit._boundaryContacts.floor || fruit._boundaryContacts.left || fruit._boundaryContacts.right; if (isRolling && Math.abs(fruit.vx) > 0.2) { // Scale roll factor inversely with level - higher level fruits roll less var rollFactor = 0.025 / (1 + fruitLevel * 0.2); // Increased level scaling for rolling var idealRollingVelocity = fruit.vx * rollFactor; fruit.angularVelocity = fruit.angularVelocity * 0.7 + idealRollingVelocity * 0.3; } else if (isMidAir) { // Apply stronger damping when airborne with no contacts fruit.angularVelocity *= 0.85; } } // Increase wall damping if (fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right) && Math.abs(fruit.angularVelocity) > 0.01) { fruit.angularVelocity *= 0.6; // Even stronger wall damping } // Lowered level-based thresholds for stopping rotation - significantly lowered var angularThreshold = GAME_CONSTANTS.ANGULAR_STOP_THRESHOLD - fruitLevel * 0.0005; // Lower threshold for higher level fruits var restFramesThreshold = Math.max(1, 2 - Math.floor(fruitLevel * 0.35)); // More aggressive frame requirement reduction if (Math.abs(fruit.angularVelocity) < angularThreshold) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > restFramesThreshold) { fruit.angularVelocity = 0; } } else { fruit.rotationRestCounter = 0; } // Scale max angular velocity based on level var maxAngularMultiplier = 1.2 - fruitLevel * 0.06; // Higher level fruits rotate even less fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -self.maxAngularVelocity * maxAngularMultiplier), self.maxAngularVelocity * maxAngularMultiplier); }; return self; }); var SpatialGrid = Container.expand(function (cellSize) { var self = Container.call(this); self.cellSize = cellSize || 200; self.grid = {}; self.lastRebuildTime = Date.now(); self.rebuildInterval = GAME_CONSTANTS.SPATIAL_GRID_REBUILD_INTERVAL_MS; self.insertObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) return; obj._currentCells = obj._currentCells || []; var cells = self.getCellsForObject(obj); obj._currentCells = cells.slice(); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (!self.grid[cellKey]) self.grid[cellKey] = []; if (self.grid[cellKey].indexOf(obj) === -1) self.grid[cellKey].push(obj); } }; self.removeObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) return; var cells = obj._currentCells || self.getCellsForObject(obj); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { var cellIndex = self.grid[cellKey].indexOf(obj); if (cellIndex !== -1) self.grid[cellKey].splice(cellIndex, 1); if (self.grid[cellKey].length === 0) delete self.grid[cellKey]; } } obj._currentCells = []; }; self.getCellsForObject = function (obj) { if (!obj || typeof obj.x !== 'number' || typeof obj.y !== 'number' || typeof obj.width !== 'number' || typeof obj.height !== 'number') return []; var cells = []; var halfWidth = obj.width / 2; var halfHeight = obj.height / 2; var minCellX = Math.floor((obj.x - halfWidth) / self.cellSize); var maxCellX = Math.floor((obj.x + halfWidth) / self.cellSize); var minCellY = Math.floor((obj.y - halfHeight) / self.cellSize); var maxCellY = Math.floor((obj.y + halfHeight) / self.cellSize); for (var cellX = minCellX; cellX <= maxCellX; cellX++) { for (var cellY = minCellY; cellY <= maxCellY; cellY++) { cells.push(cellX + "," + cellY); } } return cells; }; self.updateObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) return; var newCells = self.getCellsForObject(obj); var oldCells = obj._currentCells || []; var cellsChanged = false; if (oldCells.length !== newCells.length) cellsChanged = true;else { for (var i = 0; i < newCells.length; i++) { if (oldCells.indexOf(newCells[i]) === -1) { cellsChanged = true; break; } } } if (cellsChanged) { self.removeObject(obj); self.insertObject(obj); } }; self.getPotentialCollisions = function (obj) { var candidates = []; var cells = self.getCellsForObject(obj); var addedObjects = {}; for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (self.grid[cellKey]) { for (var j = 0; j < self.grid[cellKey].length; j++) { var otherObj = self.grid[cellKey][j]; if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) { candidates.push(otherObj); addedObjects[otherObj.id] = true; } } } } return candidates; }; self.clear = function () { self.grid = {}; self.lastRebuildTime = Date.now(); }; self.rebuildGrid = function (allObjects) { self.grid = {}; self.lastRebuildTime = Date.now(); if (Array.isArray(allObjects)) { for (var i = 0; i < allObjects.length; i++) { if (allObjects[i] && !allObjects[i].merging && !allObjects[i].isStatic) { self.insertObject(allObjects[i]); } } } }; return self; }); var TrajectoryLine = Container.expand(function () { var self = Container.call(this); self.dotPool = new DotPool(100); self.activeDots = []; self.dots = []; self.dotSpacing = 10; self.dotSize = 15; self.maxDots = 100; self.createDots = function () { self.clearDots(); self.dotPool.initialize(self.maxDots); }; self.clearDots = function () { for (var i = 0; i < self.activeDots.length; i++) { if (self.activeDots[i]) { self.removeChild(self.activeDots[i]); self.dotPool.release(self.activeDots[i]); } } self.activeDots = []; }; self.updateTrajectory = function (startX, startY) { if (!activeFruit) return; self.clearDots(); var dotY = startY; var dotSpacing = 25; var dotCount = 0; var hitDetected = false; while (dotCount < self.maxDots && !hitDetected) { var dot = self.dotPool.get(); self.addChild(dot); self.activeDots.push(dot); dot.x = startX; dot.y = dotY; dot.visible = true; dot.alpha = 1.0; dotCount++; dotY += dotSpacing; var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; if (dotY > floorCollisionY) { if (dotCount > 0) self.activeDots[dotCount - 1].y = floorCollisionY; hitDetected = true; break; } var potentialHits = spatialGrid.getPotentialCollisions({ x: startX, y: dotY, width: activeFruit.width, height: activeFruit.height, id: 'trajectory_check' }); for (var j = 0; j < potentialHits.length; j++) { var fruit = potentialHits[j]; if (fruit && fruit !== activeFruit && !fruit.merging && fruit.width && fruit.height) { if (self.wouldIntersectFruit(fruit.x, fruit.y, startX, dotY, activeFruit, fruit)) { if (dotCount > 0) { var dx = fruit.x - startX; var dy = fruit.y - dotY; var dist = Math.sqrt(dx * dx + dy * dy); var overlap = activeFruit.width / 2 + fruit.width / 2 - dist; if (dist > 0) self.activeDots[dotCount - 1].y = dotY - dy / dist * overlap; } hitDetected = true; break; } } } } }; self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) { var dx = fruitX - dropX; var dy = fruitY - dropY; var distanceSquared = dx * dx + dy * dy; var activeFruitLevel = getFruitLevel(activeFruitObj); var targetFruitLevel = getFruitLevel(targetFruitObj); var activeFruitRadius = activeFruitObj.width / 2; var targetFruitRadius = targetFruitObj.width / 2; var hitboxReduction = (Math.max(0, activeFruitLevel - 4) + Math.max(0, targetFruitLevel - 4)) * 2; var combinedRadii = activeFruitRadius + targetFruitRadius - hitboxReduction; var minDistanceSquared = combinedRadii * combinedRadii; return distanceSquared < minDistanceSquared; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xffe122 }); /**** * Game Code ****/ // --- Constants --- var _GAME_CONSTANTS; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } var GAME_CONSTANTS = (_GAME_CONSTANTS = { GAME_WIDTH: 2048, GAME_HEIGHT: 2732, DROP_POINT_Y: 200, DROP_START_Y_OFFSET: 200, CLICK_DELAY_MS: 300, FRUIT_IMMUNITY_MS: 1000, MERGE_GRACE_MS: 2000, GAME_OVER_LINE_Y: 550, GAME_OVER_COUNTDOWN_MS: 3000, // Physics BASE_GRAVITY: 5.0, GRAVITY_LEVEL_MULTIPLIER: 0.4, // Adjusted gravity scaling FRICTION: 0.90, // Increased base friction ANGULAR_FRICTION: 0.80, GROUND_ANGULAR_FRICTION: 0.60, // Further increased ground friction MAX_ANGULAR_VELOCITY: 0.15, ELASTICITY_HIGH: 0.85, // Sleep state constants SLEEP_DELAY_FRAMES: 15, WAKE_UP_IMPULSE_THRESHOLD: 1.5, ANGULAR_STOP_THRESHOLD: 0.003 }, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_GAME_CONSTANTS, "SLEEP_DELAY_FRAMES", 15), "WAKE_UP_IMPULSE_THRESHOLD", 1.5), "ELASTICITY_LOW_START_LEVEL", 4), "ELASTICITY_LOW_BASE", 0.8), "ELASTICITY_DECREASE_FACTOR", 0.2 / 9), "MIN_FALL_SPEED", 0.1), "MAX_VELOCITY_BASE", 65), "MAX_VELOCITY_LEVEL_FACTOR", 5), "LEVEL_INERTIA_FACTOR", 0.05), "LEVEL_DOWNWARD_FORCE_FACTOR", 0.1), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_GAME_CONSTANTS, "BOUNCE_SOUND_VELOCITY_THRESHOLD", 0.5), "COLLISION_SEPARATION_FACTOR", 1.01), "INTER_FRUIT_FRICTION", 0.1), "ROTATION_TRANSFER_FACTOR", 0.01), "STABILIZATION_MOVEMENT_THRESHOLD_BASE", 0.7), "STABILIZATION_MOVEMENT_THRESHOLD_LEVEL_FACTOR", 0.08), "STABILIZATION_ANGULAR_THRESHOLD_BASE", 0.07), "STABILIZATION_ANGULAR_THRESHOLD_LEVEL_FACTOR", 0.006), "STABILIZATION_SCORE_RATE_BASE", 1.5), "STABILIZATION_SCORE_LEVEL_FACTOR", 0.2), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_GAME_CONSTANTS, "STABILIZATION_WALL_SCORE_RATE", 1.2), "STABILIZATION_SURROUNDED_SCORE_RATE", 1.5), "STABILIZATION_SCORE_LEVEL_INFLUENCE", 0.1), "STABILIZATION_THRESHOLD_FULL_BASE", 15), "STABILIZATION_THRESHOLD_FULL_LEVEL_FACTOR", 0.8), "STABILIZATION_THRESHOLD_PARTIAL_BASE", 8), "STABILIZATION_THRESHOLD_PARTIAL_LEVEL_FACTOR", 0.4), "STABILIZATION_DAMP_FACTOR_FULL", 0.6), "STABILIZATION_DAMP_FACTOR_PARTIAL", 0.85), "STABILIZATION_DAMP_LEVEL_FACTOR", 0.03), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_GAME_CONSTANTS, "STABILIZATION_STOP_THRESHOLD_BASE", 0.15), "STABILIZATION_STOP_THRESHOLD_LEVEL_FACTOR", 0.01), "SPATIAL_GRID_REBUILD_INTERVAL_MS", 60000), "SPATIAL_GRID_CELL_SIZE_FACTOR", 1.1), "CHARGE_NEEDED_FOR_RELEASE", 15), "PORTAL_UI_Y", 120), "PORTAL_UI_X_OFFSET", 870), "PORTAL_TWEEN_DURATION", 300), "PORTAL_PULSE_DURATION", 500), "PINEAPPLE_MERGES_NEEDED", 15), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_GAME_CONSTANTS, "PINEAPPLE_START_Y", 200), "PINEAPPLE_END_POS_FACTOR", 0.16), "PINEAPPLE_TWEEN_DURATION", 300), "PINEAPPLE_IMMUNITY_MS", 3000), "FIRE_BASE_COUNT", 3), "FIRE_FRUIT_TYPE_THRESHOLD", 1), "FIRE_MAX_COUNT", 15), "FIRE_START_Y_OFFSET", 50), "FIRE_STACK_Y_OFFSET", 100), "FIRE_X_SPREAD", 500), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_GAME_CONSTANTS, "FIRE_FLICKER_SPEED_BASE", 500), "FIRE_FLICKER_SPEED_RANDOM", 300), "FIRE_ALPHA_MIN", 0.1), "FIRE_ALPHA_MAX", 0.5), "FIRE_FRAME_DURATION", 200), "COCONUT_SPAWN_SCORE_INTERVAL", 1000), "EVOLUTION_LINE_Y", 120), "EVOLUTION_LINE_X_OFFSET", 350), "EVOLUTION_ICON_MAX_SIZE", 150), "EVOLUTION_ICON_SPACING", 20), _defineProperty(_defineProperty(_GAME_CONSTANTS, "SCORE_TEXT_Y", 400), "SCORE_TEXT_SIZE", 120)); var gameOverLine; var pineapple; var pineappleActive = false; var pineapplePushCount = 0; var readyToReleaseCharged = false; var trajectoryLine; var isClickable = true; var evolutionLine; var fireContainer; var activeFireElements = []; var fruitLevels = { 'CHERRY': 1, 'GRAPE': 2, 'APPLE': 3, 'ORANGE': 4, 'WATERMELON': 5, 'PINEAPPLE': 6, 'MELON': 7, 'PEACH': 8, 'COCONUT': 9, 'DURIAN': 10 }; var FruitTypes = { CHERRY: { id: 'Taaaaaaaaaavuk', // pinkish red size: 150, points: 1, next: 'grape' }, GRAPE: { id: 'Taaaaaaaaavuk', // blue size: 200, points: 2, next: 'apple' }, APPLE: { id: 'Taaaaaaaavuk', // brown size: 250, points: 3, next: 'orange' }, ORANGE: { id: 'Taaaaaaavuk', // yellow-brown size: 200, points: 5, next: 'watermelon' }, WATERMELON: { id: 'Taaaaaavuk', // dark blue size: 350, points: 8, next: 'pineapple' }, PINEAPPLE: { id: 'Taaaaavuk', // pink size: 400, points: 13, next: 'melon' }, MELON: { id: 'Taaaavuk', // purple size: 450, points: 21, next: 'peach' }, PEACH: { id: 'Taaavuk', // light pink size: 500, points: 34, next: 'coconut' }, COCONUT: { id: 'Taavuk', // dark magenta size: 550, points: 55, next: 'durian' }, DURIAN: { id: 'Tavuk', // violet size: 600, points: 89, next: null } }; var gameWidth = GAME_CONSTANTS.GAME_WIDTH; var gameHeight = GAME_CONSTANTS.GAME_HEIGHT; var fruits = []; var nextFruitType = null; var activeFruit = null; var wallLeft, wallRight, gameFloor; var dropPointY = GAME_CONSTANTS.DROP_POINT_Y; 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; var lastScoreCheckForCoconut = 0; 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 >= GAME_CONSTANTS.PINEAPPLE_MERGES_NEEDED && !pineappleActive && pineapple) { pineappleActive = true; pineapple.isStatic = false; pineapple.immuneToGameOver = true; applyDropPhysics(pineapple, 2.5); fruits.push(pineapple); if (spatialGrid) spatialGrid.insertObject(pineapple); LK.setTimeout(function () { if (pineapple && fruits.includes(pineapple)) { pineapple.immuneToGameOver = false; } }, GAME_CONSTANTS.PINEAPPLE_IMMUNITY_MS); setupPineapple(); mergeCounter = 0; } } function setupBoundaries() { wallLeft = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallLeft.x = 0; wallLeft.y = gameHeight / 2; wallLeft.alpha = 0; wallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallRight.x = gameWidth; wallRight.y = gameHeight / 2; wallRight.alpha = 0; gameFloor = game.addChild(LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5 })); gameFloor.x = gameWidth / 2; gameFloor.y = gameHeight; gameFloor.alpha = 0; gameOverLine = game.addChild(new Line()); gameOverLine.x = gameWidth / 2; gameOverLine.y = GAME_CONSTANTS.GAME_OVER_LINE_Y; gameOverLine.scaleX = gameWidth / 100; // Scale to full screen width (Line is 100px wide by default) 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 + GAME_CONSTANTS.DROP_START_Y_OFFSET; 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; }, GAME_CONSTANTS.CLICK_DELAY_MS); activeFruit.isStatic = false; applyDropPhysics(activeFruit, 3.5); fruits.push(activeFruit); spatialGrid.insertObject(activeFruit); lastDroppedFruit = activeFruit; lastDroppedHasMerged = false; chargeCounter++; updateChargedBallDisplay(); if (chargeCounter >= GAME_CONSTANTS.CHARGE_NEEDED_FOR_RELEASE && !readyToReleaseCharged) { releaseChargedBalls(); } activeFruit.mergeGracePeriodActive = true; LK.setTimeout(function () { if (activeFruit && fruits.includes(activeFruit)) { activeFruit.mergeGracePeriodActive = false; } }, GAME_CONSTANTS.MERGE_GRACE_MS); 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 >= GAME_CONSTANTS.CHARGE_NEEDED_FOR_RELEASE) { LK.getSound('pickleRick').play(); var orange = new Fruit(FruitTypes.ORANGE); orange.hasBounced = false; var minX = wallLeft.x + wallLeft.width / 2 + orange.width / 2 + 50; var maxX = wallRight.x - wallRight.width / 2 - orange.width / 2 - 50; orange.x = minX + Math.random() * (maxX - minX); orange.y = -orange.height; orange.isStatic = false; var forceMultiplier = 3.5 + (Math.random() * 1 - 0.5); applyDropPhysics(orange, forceMultiplier); orange.fromChargedRelease = true; game.addChild(orange); fruits.push(orange); spatialGrid.insertObject(orange); chargeCounter = 0; resetChargedBalls(); readyToReleaseCharged = false; } activeFruit = null; createNextFruit(); } function applyDropPhysics(fruit, forceMultiplier) { var fruitLevel = getFruitLevel(fruit); forceMultiplier *= 1.4; var levelAdjustedForce = forceMultiplier * (1 - fruitLevel * 0.05); var angle = (Math.random() * 20 - 10) * (Math.PI / 180); fruit.vx = Math.sin(angle) * levelAdjustedForce; fruit.vy = Math.abs(Math.cos(angle) * levelAdjustedForce) * 1.5; fruit.angularVelocity = 0; // Explicitly reset angular velocity on drop fruit.gravity = GAME_CONSTANTS.BASE_GRAVITY * (1 + fruitLevel * GAME_CONSTANTS.GRAVITY_LEVEL_MULTIPLIER); fruit.safetyPeriod = false; fruit.immuneToGameOver = true; fruit.isFullyStabilized = false; // Reset stabilization state fruit.isSleeping = false; // Wake up the fruit when dropping fruit._sleepCounter = 0; // Reset sleep counter - ensure even if undefined // Restore asset to original asset size when dropped if (fruit._fruitGraphics && fruit._assetOriginalWidth && fruit._assetOriginalHeight) { fruit._fruitGraphics.width = fruit._assetOriginalWidth; fruit._fruitGraphics.height = fruit._assetOriginalHeight; fruit.width = fruit._assetOriginalWidth; fruit.height = fruit._assetOriginalHeight; } LK.setTimeout(function () { if (fruit && fruits.includes(fruit)) { fruit.immuneToGameOver = false; } }, GAME_CONSTANTS.FRUIT_IMMUNITY_MS); } function updateScoreDisplay() { scoreText.setText(LK.getScore()); } function setupUI() { scoreText = new Text2("0", { size: GAME_CONSTANTS.SCORE_TEXT_SIZE, fill: 0x000000 }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = GAME_CONSTANTS.SCORE_TEXT_Y; setupChargedBallDisplay(); } function setupChargedBallDisplay() { chargedBallUI = new ChargedBallUI(); // Initialization moved to class constructor 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 = []; // Reset surroundedFrames here as it's primarily determined by collisions fruits[k].surroundedFrames = 0; } } 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 = (Math.max(0, level1 - 4) + Math.max(0, level2 - 4)) * 2; // Use simplified reduction 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; } // Calculate precise overlap along the collision normal var overlap = combinedHalfWidths - absDistanceX; if (absDistanceY < combinedHalfHeights && overlap > 0) { // Use most accurate overlap calculation var verticalOverlap = combinedHalfHeights - absDistanceY; if (verticalOverlap < overlap) { overlap = verticalOverlap; } // Use exact normal vector from centers var normalizeX = dx / distance; var normalizeY = dy / distance; // Calculate proportional separation based on fruit mass var level1 = getFruitLevel(fruit1); var level2 = getFruitLevel(fruit2); // Use a strongly exponential formula for mass based on level - using power of 3.0 var mass1 = Math.pow(level1, 3.0); var mass2 = Math.pow(level2, 3.0); var totalMass = mass1 + mass2; var moveRatio1 = totalMass > 0 ? mass2 / totalMass : 0.5; var moveRatio2 = totalMass > 0 ? mass1 / totalMass : 0.5; // Apply separation proportional to mass var moveX = overlap * normalizeX; var moveY = overlap * normalizeY; // Apply smaller separation factor for more stable stacking var separationFactor = GAME_CONSTANTS.COLLISION_SEPARATION_FACTOR; fruit1.x -= moveX * moveRatio1 * separationFactor; fruit1.y -= moveY * moveRatio1 * separationFactor; fruit2.x += moveX * moveRatio2 * separationFactor; fruit2.y += moveY * moveRatio2 * 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; // Use strongly exponential mass formula to exaggerate weight differences var mass1 = Math.pow(level1, 3.0); // Removed size dependency, using pure level exponent var mass2 = Math.pow(level2, 3.0); var totalMass = mass1 + mass2; // Let impulse ratios be calculated directly from the exaggerated masses var impulseRatio1 = totalMass > 0 ? mass2 / totalMass : 0.5; var impulseRatio2 = totalMass > 0 ? mass1 / totalMass : 0.5; var velocityMagnitude = Math.sqrt(rvX * rvX + rvY * rvY); var velocityDampening = velocityMagnitude > 5 ? 0.5 + 2.5 / velocityMagnitude : 1.0; var impulse1 = impulse * impulseRatio1 * velocityDampening; var impulse2 = impulse * impulseRatio2 * velocityDampening; // Apply impulses with sleeping state awareness but no special level-based adjustments var impactThreshold = 1.2; // Threshold to determine significant impacts // For first fruit: apply impulse and wake up if needed // Wake up sleeping fruits on significant impacts based on WAKE_UP_IMPULSE_THRESHOLD if (fruit1.isSleeping) { if (Math.abs(impulse1) > GAME_CONSTANTS.WAKE_UP_IMPULSE_THRESHOLD) { // Wake up if impact is very significant fruit1.isSleeping = false; fruit1.isFullyStabilized = false; fruit1._sleepCounter = 0; fruit1.vx -= impulse1 * normalizeX; fruit1.vy -= impulse1 * normalizeY; } else { // Keep sleeping but ensure proper state tracking fruit1.vx = 0; fruit1.vy = 0; fruit1.angularVelocity = 0; } } else if (fruit1.isFullyStabilized) { // Apply minimal impulse to stabilized fruits - just enough to handle edge cases if (Math.abs(impulse1) > impactThreshold) { // If impact is large, unstabilize and apply full impulse fruit1.isFullyStabilized = false; fruit1._sleepCounter = 0; // Reset sleep counter on destabilization fruit1.vx -= impulse1 * normalizeX; fruit1.vy -= impulse1 * normalizeY; } else { // For small impacts, apply minimal response that won't disturb stability fruit1.vx -= impulse1 * normalizeX * 0.1; fruit1.vy -= impulse1 * normalizeY * 0.1; } } else { fruit1.vx -= impulse1 * normalizeX; fruit1.vy -= impulse1 * normalizeY; } // For second fruit: apply impulse with the same sleep/stabilization logic if (fruit2.isSleeping) { if (Math.abs(impulse2) > GAME_CONSTANTS.WAKE_UP_IMPULSE_THRESHOLD) { // Wake up if impact is very significant fruit2.isSleeping = false; fruit2.isFullyStabilized = false; fruit2._sleepCounter = 0; fruit2.vx += impulse2 * normalizeX; fruit2.vy += impulse2 * normalizeY; } else { // Keep sleeping but ensure proper state tracking fruit2.vx = 0; fruit2.vy = 0; fruit2.angularVelocity = 0; } } else if (fruit2.isFullyStabilized) { // Apply minimal impulse to stabilized fruits - just enough to handle edge cases if (Math.abs(impulse2) > impactThreshold) { // If impact is large, unstabilize and apply full impulse fruit2.isFullyStabilized = false; fruit2._sleepCounter = 0; // Reset sleep counter on destabilization fruit2.vx += impulse2 * normalizeX; fruit2.vy += impulse2 * normalizeY; } else { // For small impacts, apply minimal response that won't disturb stability fruit2.vx += impulse2 * normalizeX * 0.1; fruit2.vy += impulse2 * normalizeY * 0.1; } } else { fruit2.vx += impulse2 * normalizeX; fruit2.vy += impulse2 * normalizeY; } // Apply additional post-collision damping to dissipate energy more rapidly fruit1.vx *= 0.95; // Increased damping factor to reduce velocities after collision fruit1.vy *= 0.95; fruit2.vx *= 0.95; fruit2.vy *= 0.95; // Apply stronger damping for low-velocity collisions to promote settling if (velocityMagnitude < 3.0) { fruit1.vx *= 0.92; fruit1.vy *= 0.92; fruit2.vx *= 0.92; fruit2.vy *= 0.92; } var tangentX = -normalizeY; var tangentY = normalizeX; var tangentVelocity = rvX * tangentX + rvY * tangentY; var frictionImpulse = -tangentVelocity * GAME_CONSTANTS.INTER_FRUIT_FRICTION; // Apply tangential friction with same stabilization logic if (!fruit1.isFullyStabilized) { fruit1.vx -= frictionImpulse * tangentX * impulseRatio1; fruit1.vy -= frictionImpulse * tangentY * impulseRatio1; } if (!fruit2.isFullyStabilized) { fruit2.vx += frictionImpulse * tangentX * impulseRatio2; fruit2.vy += frictionImpulse * tangentY * impulseRatio2; } var tangentialComponent = rvX * tangentX + rvY * tangentY; var inertia1 = mass1 * Math.pow(fruit1.width / 2, 2); var inertia2 = mass2 * Math.pow(fruit2.width / 2, 2); // Only apply angular impulse if the tangential component or contact velocity is significant var tangentialMagnitude = Math.abs(tangentialComponent); var contactMagnitude = Math.abs(contactVelocity); var angularThreshold = 1.2; // Increased threshold for generating rotational force // Calculate angular impulse with adjustment for impact magnitude var angularImpulse = 0; if (tangentialMagnitude > angularThreshold || contactMagnitude > angularThreshold) { // Apply a curve that increases rotation for more significant impacts var impactFactor = Math.min(1.0, (Math.max(tangentialMagnitude, contactMagnitude) - angularThreshold) / 2.0); angularImpulse = tangentialComponent * GAME_CONSTANTS.ROTATION_TRANSFER_FACTOR * impactFactor; } // Apply angular velocity with same stabilization logic if (!fruit1.isFullyStabilized && angularImpulse !== 0) { fruit1.angularVelocity += inertia2 > 0 ? angularImpulse * (inertia1 / (inertia1 + inertia2)) : angularImpulse * 0.5; fruit1.angularVelocity *= 0.95; } if (!fruit2.isFullyStabilized && angularImpulse !== 0) { fruit2.angularVelocity -= inertia1 > 0 ? angularImpulse * (inertia2 / (inertia1 + inertia2)) : angularImpulse * 0.5; fruit2.angularVelocity *= 0.95; } // Only unstabilize on significantly large angular impacts if (Math.abs(angularImpulse) > 0.015) { // Unstabilize on significant angular impact if (fruit1.isFullyStabilized) fruit1.isFullyStabilized = false; if (fruit2.isFullyStabilized) fruit2.isFullyStabilized = false; } fruit1.angularVelocity = Math.min(Math.max(fruit1.angularVelocity, -fruit1.maxAngularVelocity), fruit1.maxAngularVelocity); fruit2.angularVelocity = Math.min(Math.max(fruit2.angularVelocity, -fruit2.maxAngularVelocity), fruit2.maxAngularVelocity); fruit1.neighboringFruits = (fruit1.neighboringFruits || 0) + 1; fruit2.neighboringFruits = (fruit2.neighboringFruits || 0) + 1; fruit1.surroundedFrames++; // Increment surrounded counter on collision fruit2.surroundedFrames++; 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); // Removed bounce flag dependent damping } } } } } } function checkGameOver() { if (gameOver) return; for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit || fruit === activeFruit || fruit.merging || fruit.isStatic) continue; var fruitHalfHeight = fruit.height / 2; var fruitHalfWidth = fruit.width / 2; var fruitLevel = getFruitLevel(fruit); var sizeReduction = Math.max(0, fruitLevel - 4) * 2; fruitHalfHeight = Math.max(10, fruitHalfHeight - sizeReduction / 2); fruitHalfWidth = Math.max(10, fruitHalfWidth - sizeReduction / 2); var cosAngle = Math.abs(Math.cos(fruit.rotation)); var sinAngle = Math.abs(Math.sin(fruit.rotation)); var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle; var fruitTopY = fruit.y - effectiveHeight; var lineBottomY = gameOverLine.y + gameOverLine.height / 2; if (fruitTopY <= lineBottomY) { var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle; var fruitLeftX = fruit.x - effectiveWidth; var fruitRightX = fruit.x + effectiveWidth; var lineLeftX = gameOverLine.x - gameOverLine.width * gameOverLine.scaleX / 2; var lineRightX = gameOverLine.x + gameOverLine.width * gameOverLine.scaleX / 2; var horizontalOverlap = !(fruitRightX < lineLeftX || fruitLeftX > lineRightX); if (horizontalOverlap) { if (fruit.immuneToGameOver) continue; // Game over if fruit is overlapping line AND not moving significantly OR fully stabilized var stableOrSlowing = Math.abs(fruit.vy) < 1.0 || fruit.isFullyStabilized || fruit._boundaryContacts && (fruit._boundaryContacts.left || fruit._boundaryContacts.right || fruit._boundaryContacts.floor); if (stableOrSlowing) { if (!fruit.gameOverTimer) { fruit.gameOverTimer = Date.now(); tween(fruit, { alpha: 0.5 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(fruit, { alpha: 1 }, { duration: 500, easing: tween.easeInOut, repeat: 2 }); } }); continue; } var currentTime = Date.now(); if (currentTime - fruit.gameOverTimer >= GAME_CONSTANTS.GAME_OVER_COUNTDOWN_MS) { gameOver = true; LK.showGameOver(); return; } } else { // Reset timer if fruit starts moving significantly while overlapping fruit.gameOverTimer = null; fruit.alpha = 1; } } else { // Reset timer if no longer overlapping horizontally if (fruit.gameOverTimer) { fruit.gameOverTimer = null; fruit.alpha = 1; } } } else { fruit.safetyPeriod = undefined; if (fruit.gameOverTimer) { fruit.gameOverTimer = null; fruit.alpha = 1; } } } } function setupPineapple() { pineapple = new Fruit(FruitTypes.PINEAPPLE); pineapple.x = -pineapple.width / 2; pineapple.y = GAME_CONSTANTS.PINEAPPLE_START_Y; pineapple.isStatic = true; pineappleActive = false; pineapplePushCount = 0; game.addChild(pineapple); } function pushPineapple() { if (!pineappleActive && pineapple) { var step = mergeCounter; var totalSteps = GAME_CONSTANTS.PINEAPPLE_MERGES_NEEDED; var percentage = Math.min(step / totalSteps, 1.0); var startPos = -pineapple.width / 2; var endPos = gameWidth * GAME_CONSTANTS.PINEAPPLE_END_POS_FACTOR; var newX = startPos + percentage * (endPos - startPos); tween(pineapple, { x: newX }, { duration: GAME_CONSTANTS.PINEAPPLE_TWEEN_DURATION, easing: tween.bounceOut }); } } function initGame() { LK.setScore(0); gameOver = false; if (fruits && fruits.length > 0) { // Ensure cleanup only happens if needed for (var i = fruits.length - 1; i >= 0; i--) { if (fruits[i]) removeFruitFromGame(fruits[i]); } } fruits = []; chargeCounter = 0; if (chargedBallUI) chargedBallUI.destroy(); chargedBallUI = null; readyToReleaseCharged = false; lastScoreCheckForCoconut = 0; lastDroppedFruit = null; lastDroppedHasMerged = false; mergeCounter = 0; isClickable = true; if (fireContainer) { for (var i = activeFireElements.length - 1; i >= 0; i--) { if (activeFireElements[i]) activeFireElements[i].destroy(); } fireContainer.destroy(); } fireContainer = new Container(); game.addChildAt(fireContainer, 0); activeFireElements = []; if (spatialGrid) { spatialGrid.clear(); } else { var avgFruitSize = 0; var fruitCount = 0; for (var fruitType in FruitTypes) { if (FruitTypes.hasOwnProperty(fruitType)) { avgFruitSize += FruitTypes[fruitType].size; fruitCount++; } } avgFruitSize = fruitCount > 0 ? avgFruitSize / fruitCount : 300; var optimalCellSize = Math.ceil(avgFruitSize * GAME_CONSTANTS.SPATIAL_GRID_CELL_SIZE_FACTOR); spatialGrid = new SpatialGrid(optimalCellSize); } // Destroy existing boundaries before setup 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(); LK.playMusic('bgmusic'); // Start music after potential cleanup setupBoundaries(); setupUI(); setupPineapple(); updateFireBackground(); 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); } }); } 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 + GAME_CONSTANTS.DROP_START_Y_OFFSET; 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); } // First phase: Apply gravity and update velocities for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (!fruit || fruit.isStatic || fruit.merging) continue; // Apply physics without position update (updatePhysics handles this) fruit.updatePhysics(); } // Second phase: Check collisions and resolve overlaps checkFruitCollisions(); // Third phase: Apply boundary collisions and finalize positions for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (!fruit || fruit.isStatic || fruit.merging) continue; var walls = { left: wallLeft, right: wallRight }; fruit.checkBoundaries(walls, gameFloor); // Final stabilization check - aggressively zero out extremely small velocity values if (fruit.isFullyStabilized) { // If fully stabilized, ensure complete zero velocity to prevent micro-movements fruit.vx = 0; fruit.vy = 0; fruit.angularVelocity = 0; } else if (!fruit.isSleeping && fruit._boundaryContacts && (fruit._boundaryContacts.floor || fruit._boundaryContacts.left || fruit._boundaryContacts.right)) { // For fruits touching boundaries but not fully stabilized, apply additional damping // to encourage them to come to a full stop more quickly if (Math.abs(fruit.vx) < 0.2) fruit.vx = 0; if (Math.abs(fruit.vy) < 0.2) fruit.vy = 0; if (Math.abs(fruit.angularVelocity) < 0.01) fruit.angularVelocity = 0; } } // Fourth phase: Handle environmental interactions and position adjustments 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; } } // Final position update in the spatial grid spatialGrid.updateObject(fruit); } } } game.update = function () { if (gameOver) return; var currentScore = LK.getScore(); if (currentScore >= lastScoreCheckForCoconut + GAME_CONSTANTS.COCONUT_SPAWN_SCORE_INTERVAL) { lastScoreCheckForCoconut = Math.floor(currentScore / GAME_CONSTANTS.COCONUT_SPAWN_SCORE_INTERVAL) * GAME_CONSTANTS.COCONUT_SPAWN_SCORE_INTERVAL; spawnCoconut(); } else { if (currentScore > lastScoreCheckForCoconut) { lastScoreCheckForCoconut = Math.floor(currentScore / GAME_CONSTANTS.COCONUT_SPAWN_SCORE_INTERVAL) * GAME_CONSTANTS.COCONUT_SPAWN_SCORE_INTERVAL; } } updatePhysics(); checkGameOver(); updateFireBackground(); for (var i = 0; i < activeFireElements.length; i++) { if (activeFireElements[i]) activeFireElements[i].update(); } }; initGame(); function updateFireBackground() { var uniqueFruitTypes = {}; for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (fruit && !fruit.isStatic && !fruit.merging && fruit.type && fruit.type.id) { uniqueFruitTypes[fruit.type.id] = true; } } var uniqueTypeCount = Object.keys(uniqueFruitTypes).length; // Updated fire count logic based on unique types present var targetFireCount = GAME_CONSTANTS.FIRE_BASE_COUNT + Math.floor(uniqueTypeCount / GAME_CONSTANTS.FIRE_FRUIT_TYPE_THRESHOLD); targetFireCount = Math.min(targetFireCount, GAME_CONSTANTS.FIRE_MAX_COUNT); if (targetFireCount > activeFireElements.length) { var newFiresCount = targetFireCount - activeFireElements.length; for (var i = 0; i < newFiresCount; i++) { createFireElement(activeFireElements.length); } } else if (targetFireCount < activeFireElements.length) { var fireToRemoveCount = activeFireElements.length - targetFireCount; for (var i = 0; i < fireToRemoveCount; i++) { var fire = activeFireElements.pop(); if (fire) { fire.destroy(); fireContainer.removeChild(fire); } } } } function createFireElement(index) { var yPos = gameHeight + GAME_CONSTANTS.FIRE_START_Y_OFFSET - index * GAME_CONSTANTS.FIRE_STACK_Y_OFFSET; var xPos = gameWidth / 2 + (Math.random() * GAME_CONSTANTS.FIRE_X_SPREAD - GAME_CONSTANTS.FIRE_X_SPREAD / 2); var newFire = new FireElement(xPos, yPos); if (index % 2 === 1) { newFire.fireAsset.scaleX = -1; } fireContainer.addChildAt(newFire, 0); activeFireElements.push(newFire); } function removeFruitFromGame(fruit) { // Before removing, wake up any sleeping neighbors that were resting on this fruit if (fruit.neighborContacts && fruit.neighborContacts.length > 0) { for (var i = 0; i < fruit.neighborContacts.length; i++) { var neighborId = fruit.neighborContacts[i]; for (var j = 0; j < fruits.length; j++) { if (fruits[j] && fruits[j].id === neighborId) { // Wake up any sleeping neighbors if (fruits[j].isSleeping || fruits[j].isFullyStabilized) { fruits[j].isSleeping = false; fruits[j].isFullyStabilized = false; fruits[j]._sleepCounter = 0; // Always reset sleep counter (don't check if exists) // Apply a small impulse to ensure physics recalculation fruits[j].vx += Math.random() * 0.6 - 0.3; fruits[j].vy -= 0.5; // Small upward force } } } } } var index = fruits.indexOf(fruit); if (index !== -1) fruits.splice(index, 1); spatialGrid.removeObject(fruit); fruit.destroy(); }
===================================================================
--- original.js
+++ change.js
@@ -422,10 +422,22 @@
var fruitGraphics = self.attachAsset(self.type.id, {
anchorX: 0.5,
anchorY: 0.5
});
+ // Always start with 150x150 for all tabuk fruits
+ fruitGraphics.width = 150;
+ fruitGraphics.height = 150;
self.width = fruitGraphics.width;
self.height = fruitGraphics.height;
+ self._assetOriginalWidth = LK.getAsset(self.type.id, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }).width;
+ self._assetOriginalHeight = LK.getAsset(self.type.id, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }).height;
+ self._fruitGraphics = fruitGraphics;
if (self.behavior && self.behavior.onSpawn) {
self.behavior.onSpawn(self);
}
} else {
@@ -1403,8 +1415,15 @@
fruit.immuneToGameOver = true;
fruit.isFullyStabilized = false; // Reset stabilization state
fruit.isSleeping = false; // Wake up the fruit when dropping
fruit._sleepCounter = 0; // Reset sleep counter - ensure even if undefined
+ // Restore asset to original asset size when dropped
+ if (fruit._fruitGraphics && fruit._assetOriginalWidth && fruit._assetOriginalHeight) {
+ fruit._fruitGraphics.width = fruit._assetOriginalWidth;
+ fruit._fruitGraphics.height = fruit._assetOriginalHeight;
+ fruit.width = fruit._assetOriginalWidth;
+ fruit.height = fruit._assetOriginalHeight;
+ }
LK.setTimeout(function () {
if (fruit && fruits.includes(fruit)) {
fruit.immuneToGameOver = false;
}
green round portal made of green portal fuid. In-Game asset. 2d. High contrast. No shadows
Tavuk beyaz renkte ve küçük . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
chicken ball and pink. In-Game asset. 2d. High contrast. No shadows
ball chicken and black. In-Game asset. 2d. High contrast. No shadows
ball chicken and red. In-Game asset. 2d. High contrast. No shadows
ball chicken and green. In-Game asset. 2d. High contrast. No shadows
ball chicken and yellow. In-Game asset. 2d. High contrast. No shadows
ball chicken blue. In-Game asset. 2d. High contrast. No shadows
purple chicken ball. In-Game asset. 2d. High contrast. No shadows
cyan chicken ball. In-Game asset. 2d. High contrast. No shadows
gray chicken ball. In-Game asset. 2d. High contrast. No shadows