User prompt
gravity no longer works now
User prompt
higher level fruits should be more affected by gravity. the larger the fruit, the bigger the gravity
User prompt
once every 500 points, a Coconut was supposed to be slided from under the screen and the sound Stonks would play, but that no longer happens, fix it.
User prompt
the stonks sound should not play when the pineapple is released
User prompt
- The merge detection and execution is spread across multiple components, making it harder to debug - Special fruit behaviors (like Durian effects) are embedded in the merge component rather than being handled by the fruit types
User prompt
(implement the following without addind comments) 2. **Physics Stabilization Logic** - The current fruit stabilization is complex with multiple overlapping systems (surroundedFrames, wallContactFrames, etc.) - The logic for deciding when to stop a fruit's movement could be consolidated into a single function with clear thresholds - There's redundant code in different places handling similar stabilization tasks
User prompt
**Spatial Grid Optimization** - The current spatial grid implementation has some inefficiencies in how objects are inserted, removed, and queried - The cell size (250) could be better tuned based on the average fruit size to reduce unnecessary collision checks - The grid should be cleared and rebuilt periodically to prevent memory leaks from orphaned references
User prompt
remove all comments from the code
User prompt
fruits in the middle that only have neighboring fruits but dont touch a wall or the floor, still keep spinning indefinitely, I think because they are affected by the other neighboring fruits. can you create a new logic that brings those fruits to a still too?
User prompt
fruits in the middle that only have neighboring fruits but dont touch a wall or the floor, still keep spinning indefinitely, I think because they are affected by the other neighboring fruits. can you create a new logic that brings those fruits to a still too? and also Ava, you did an AMAZING job with this game so far, it's turning out sooooo great!
User prompt
the fruits rotation must be directly correlated to how it moves. right now a fruit can rotate a lot, even though it's actually staying in place. and make gravity even stronger
User prompt
after the last update, fruits drop way too slow, increase the gravitational force
User prompt
the fruits keep spinning and bouncing causing infinite movement and rotation. can you bring them to a still?
User prompt
move the text charging counter more to the right by 500 pixels
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'for (var i = 0; i < trajectoryLine.dots.length; i++) {' Line Number: 866
User prompt
Optimize fruitLevels lookup by implementing a Function to get fruit level ✅ Optimize fruit elasticity calculation using the new getFruitLevel helper function ✅ Optimize fruit rotation size factor calculation to use getFruitLevel helper ✅ Optimize explosion force calculation to use getFruitLevel helper ✅ Create reusable object pool for trajectory dots to reduce memory churn ✅ Optimize TrajectoryLine to use object pool for dots ✅ Optimize TrajectoryLine clearDots to use object pool ✅ Improve collision detection in wouldIntersectFruit method ✅ Optimize fruit-fruit collision detection to use squared distances ✅ Consolidate duplicate check for charged balls display 🔄 Optimize game update loop for better performance
User prompt
remove all comments from the code
User prompt
remove the logic that pushes fruits away when other frutis merge
User prompt
**Improve UI Management**: Consolidate the charged ball UI code into a separate class with cleaner state management.
User prompt
**Refactor Fruit Class**: Split the Fruit class into smaller, more focused components or simplify its structure.
Code edit (3 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: gotoNextOuterLoop is not defined' in or related to this line: 'label: gotoNextOuterLoop;' Line Number: 879
Code edit (1 edits merged)
Please save this source code
User prompt
**Simplify Collision Detection**: Implement a more efficient collision detection system, possibly using spatial partitioning.
User prompt
centralize fruits info in a single place and call it from there, instead of hardcoding it multiple times "var fruitLevels = { 'CHERRY': 1, 'GRAPE': 2, 'APPLE': 3, 'ORANGE': 4, 'WATERMELON': 5, 'PINEAPPLE': 6, 'MELON': 7, 'PEACH': 8, 'COCONUT': 9, 'DURIAN': 10"
/**** * 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); // Properties self.chargeNeededForRelease = 9; self.currentCharge = 0; self.isReadyToRelease = false; self.countdownText = null; self.pulseAnimationActive = false; // Initialize UI elements 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 + 270; self.addChild(self.countdownText); self.y = 120; }; // Update the UI based on charge count self.updateChargeDisplay = function (chargeCount) { self.currentCharge = chargeCount; var remainingCount = Math.max(0, self.chargeNeededForRelease - self.currentCharge); self.countdownText.setText(remainingCount.toString()); // Update color based on remaining count var textColor = remainingCount <= 3 ? 0xFF0000 : remainingCount <= 6 ? 0xFFA500 : 0x000000; tween(self.countdownText, { tint: textColor }, { duration: 300, easing: tween.easeOut }); // Update size based on charge progress var baseSize = 1.0; var sizeMultiplier = baseSize + 0.2 * (self.chargeNeededForRelease - remainingCount); tween(self.countdownText, { scaleX: sizeMultiplier, scaleY: sizeMultiplier }, { duration: 300, easing: tween.easeOut }); // Check if fully charged if (remainingCount === 0 && !self.isReadyToRelease) { self.setReadyState(true); } }; // Set ready state for charged release 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(); } }; // Start the pulse animation for ready state self.startPulseAnimation = function () { if (self.pulseAnimationActive) { return; } self.pulseAnimationActive = true; self._pulseText(); }; // Private method for pulse animation 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 }); } }); }; // Reset the charged ball UI 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 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; self.checkLeftWallCollision(fruit, walls.left, effectiveWidth); self.checkRightWallCollision(fruit, walls.right, effectiveWidth); self.checkFloorCollision(fruit, floor, effectiveHeight); if (fruit.wallContactFrames > 0) { var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01); fruit.angularVelocity *= progressiveFriction; } }; 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; var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.5; fruit.angularVelocity *= fruit.groundAngularFriction; fruit.wallContactFrames++; } else if (fruit.x > leftWall.x + leftWall.width * 2) { fruit.wallContactFrames = 0; } }; 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; var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.5; fruit.angularVelocity *= fruit.groundAngularFriction; fruit.wallContactFrames++; } }; 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; if (Math.abs(fruit.vx) > 0.5) { var angularImpactMultiplier = 0.01 * (1 + (0.9 - fruit.elasticity) * 5); fruit.angularVelocity += fruit.vx * angularImpactMultiplier * 0.5; } fruit.angularVelocity *= fruit.groundAngularFriction; var restThreshold = 1 + (fruit.elasticity - 0.7) * 10; if (Math.abs(fruit.vy) < restThreshold) { fruit.vy = 0; } var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2); if (Math.abs(fruit.angularVelocity) < angularRestThreshold && fruit.vy === 0) { fruit.angularVelocity = 0; fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2); } } }; 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(); self.vx = physics.vx; self.vy = physics.vy; self.rotation = physics.rotation; self.angularVelocity = physics.angularVelocity; self.angularFriction = physics.angularFriction; self.groundAngularFriction = physics.groundAngularFriction; self.gravity = physics.gravity; self.friction = physics.friction; self.rotationRestCounter = physics.rotationRestCounter; self.maxAngularVelocity = physics.maxAngularVelocity; self.isStatic = physics.isStatic; var currentLevel = self.type ? fruitLevels[self.type.id.toUpperCase()] || 10 : 10; self.elasticity = 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; 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; } 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 } }; // Merge method (delegates to merge component) self.merge = function (otherFruit) { mergeHandler.beginMerge(self, otherFruit); }; 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.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.applyExplosionForce = function (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(); // Play special sound effects based on fruit types self.playSpecialSoundEffects(fruit1, fruit2); // Track merge for gameplay mechanics self.trackMerge(fruit1, fruit2); // Handle Durian special case or create next fruit if (fruit1.type.id.toUpperCase() === 'DURIAN') { LK.setScore(LK.getScore() + fruit1.type.points); updateScoreDisplay(); removeFruitFromGame(fruit1); removeFruitFromGame(fruit2); releasePineappleOnMerge(); } else { releasePineappleOnMerge(); self.createNextLevelFruit(fruit1, midX, midY); } }; self.playSpecialSoundEffects = function (fruit1, fruit2) { if (fruit1.type.id.toUpperCase() === 'COCONUT' && fruit2.type.id.toUpperCase() === 'COCONUT') { LK.getSound('ThisIsFine').play(); } if (fruit1.type.id.toUpperCase() === 'MELON' && fruit2.type.id.toUpperCase() === 'MELON') { LK.getSound('Smartz').play(); } if (fruit1.type.id.toUpperCase() === 'PEACH' && fruit2.type.id.toUpperCase() === 'PEACH') { LK.getSound('stonks').play(); } }; 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; } }; self.createNextLevelFruit = function (fruit, midX, midY) { var nextType = FruitTypes[fruit.type.next.toUpperCase()]; var newFruit = new Fruit(nextType); newFruit.x = midX; newFruit.y = midY; 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 self; }); var PhysicsComponent = Container.expand(function () { var self = Container.call(this); self.vx = 0; self.vy = 0; self.gravity = 1.8; self.friction = 0.98; self.elasticity = 0.7; self.isStatic = false; self.rotation = 0; self.angularVelocity = 0; self.angularFriction = 0.95; self.groundAngularFriction = 0.75; self.maxAngularVelocity = 0.15; self.rotationRestCounter = 0; self.apply = function (fruit) { if (fruit.isStatic || fruit.merging) { return; } fruit.vy += fruit.gravity; fruit.x += fruit.vx; fruit.y += fruit.vy; fruit.rotation += fruit.angularVelocity; fruit.vx *= fruit.friction; fruit.vy *= fruit.friction; fruit.angularVelocity *= fruit.angularFriction; self.handleRotationDamping(fruit); }; self.handleRotationDamping = function (fruit) { var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); if (Math.abs(fruit.vx) < 0.5 && Math.abs(fruit.vy) < 0.5) { fruit.angularVelocity *= 0.85; if (Math.abs(fruit.angularVelocity) < 0.02) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > 45) { fruit.angularVelocity = 0; fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2); } } else { fruit.rotationRestCounter = 0; } } else { fruit.rotationRestCounter = 0; } if (movementMagnitude < 0.05) { fruit.angularVelocity *= 0.80; } else if (movementMagnitude < 0.3) { fruit.angularVelocity *= 0.85; } else if (movementMagnitude < 0.8) { fruit.angularVelocity *= 0.90; } if (Math.abs(fruit.angularVelocity) < 0.005) { fruit.angularVelocity = 0; } fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity); }; return self; }); var SpatialGrid = Container.expand(function (cellSize) { var self = Container.call(this); self.cellSize = cellSize || 200; self.grid = {}; self.insertObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) { return; } var cells = self.getCellsForObject(obj); for (var i = 0; i < cells.length; i++) { var cellKey = cells[i]; if (!self.grid[cellKey]) { self.grid[cellKey] = []; } var alreadyInCell = false; for (var j = 0; j < self.grid[cellKey].length; j++) { if (self.grid[cellKey][j] === obj) { alreadyInCell = true; break; } } if (!alreadyInCell) { self.grid[cellKey].push(obj); } } }; self.removeObject = function (obj) { if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) { return; } var cells = 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]; } } } }; 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) { self.removeObject(obj); // Remove first based on old position self.insertObject(obj); // Insert based on new position }; 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]; // Additional check to ensure otherObj has an id if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) { candidates.push(otherObj); addedObjects[otherObj.id] = true; } } } } return candidates; }; self.clear = function () { self.grid = {}; }; return self; }); var TrajectoryLine = Container.expand(function () { var self = Container.call(this); self.dots = []; self.dotSpacing = 10; self.dotSize = 15; self.maxDots = 100; self.createDots = function () { self.clearDots(); for (var i = 0; i < self.maxDots; i++) { 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; self.addChild(dot); self.dots.push(dot); } }; self.clearDots = function () { for (var i = 0; i < self.dots.length; i++) { if (self.dots[i]) { self.dots[i].destroy(); } } self.dots = []; }; self.updateTrajectory = function (startX, startY) { if (!activeFruit) { return; } for (var i = 0; i < self.dots.length; i++) { self.dots[i].visible = false; } var simX = startX; var simY = startY; var simVX = 0; var simVY = 0; var gravity = 1.8; var dotCount = 0; var hitDetected = false; var dotY = startY; var dotSpacing = 25; while (dotCount < self.maxDots && !hitDetected) { if (dotCount < self.dots.length) { self.dots[dotCount].x = startX; self.dots[dotCount].y = dotY; self.dots[dotCount].visible = true; self.dots[dotCount].alpha = 1.0; dotCount++; } dotY += dotSpacing; var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; if (dotY > floorCollisionY) { if (dotCount > 0 && dotCount <= self.dots.length) { self.dots[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 && dotCount <= self.dots.length) { 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.dots[dotCount - 1].y = dotY - dy / dist * overlap; } } hitDetected = true; break; } } } } for (var k = dotCount; k < self.dots.length; k++) { self.dots[k].visible = false; } }; self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) { var dx = fruitX - dropX; var dy = fruitY - dropY; var distance = Math.sqrt(dx * dx + dy * dy); var combinedRadii = activeFruitObj.width / 2 + targetFruitObj.width / 2; if (distance < combinedRadii - 5) { 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 fruitLevels = { 'CHERRY': 1, 'GRAPE': 2, 'APPLE': 3, 'ORANGE': 4, 'WATERMELON': 5, 'PINEAPPLE': 6, 'MELON': 7, 'PEACH': 8, 'COCONUT': 9, 'DURIAN': 10 }; function releasePineappleOnMerge() { mergeCounter++; pushPineapple(); if (mergeCounter >= 10 && !pineappleActive && pineapple) { 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 () { 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; // Start slightly higher activeFruit.isStatic = true; // Keep static until dropped 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); // Add to grid immediately 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) { 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); // Add charged fruit to grid chargeCounter = 0; resetChargedBalls(); readyToReleaseCharged = false; } activeFruit = null; // Clear active fruit *before* creating next createNextFruit(); } function applyDropPhysics(fruit, forceMultiplier) { var angle = (Math.random() * 20 - 10) * (Math.PI / 180); fruit.vx = Math.sin(angle) * forceMultiplier; fruit.vy = Math.abs(Math.cos(angle) * forceMultiplier); fruit.safetyPeriod = false; // Start in safety period 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: 80, 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() { if (chargedBallUI) { chargedBallUI.updateChargeDisplay(chargeCounter); } } function releaseChargedBalls() { readyToReleaseCharged = true; if (chargedBallUI) { chargedBallUI.setReadyState(true); } } function resetChargedBalls() { if (chargedBallUI) { chargedBallUI.reset(); } } function checkFruitCollisions() { 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 distance = Math.sqrt(dx * dx + dy * dy); 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 combinedHalfWidths = fruit1HalfWidth + fruit2HalfWidth; var combinedHalfHeights = fruit1HalfHeight + fruit2HalfHeight; 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 mass1 = Math.pow(fruit1.type.size, 1.5); var mass2 = 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 impulse1 = impulse * impulseRatio1; var impulse2 = impulse * impulseRatio2; 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 * 0.5; } else if (fruit2.type.size < fruit1.type.size) { impulse2 *= 1 + sizeDifference * 0.5; } 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); } } } } } } 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 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; } if (fruit.safetyPeriod !== true) { if (fruit.vy > 0.1) { fruit.safetyPeriod = false; continue; } else { fruit.safetyPeriod = true; } } if (fruit.safetyPeriod === true) { 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); // Don't add to spatial grid until it's dropped } 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 = []; // 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 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 { spatialGrid = new SpatialGrid(250); // Adjusted cell size slightly } LK.playMusic('bgmusic'); // Destroy old boundaries if they exist before creating new ones if (wallLeft) { wallLeft.destroy(); } if (wallRight) { wallRight.destroy(); } if (gameFloor) { gameFloor.destroy(); } if (gameOverLine) { gameOverLine.destroy(); } if (pineapple) { pineapple.destroy(); } if (trajectoryLine) { trajectoryLine.destroy(); } setupBoundaries(); setupUI(); // Setup UI after clearing old elements setupPineapple(); trajectoryLine = game.addChild(new TrajectoryLine()); trajectoryLine.createDots(); 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 LK.getSound('stonks').play(); game.addChild(coconut); fruits.push(coconut); // Don't add to grid yet, wait for animation finish coconut.safetyPeriod = false; // Initial state 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; } // Check if coconut still exists coconut.isStatic = false; coconut.vy = -2; // Small bounce coconut.vx = (Math.random() * 2 - 1) * 1.5; spatialGrid.insertObject(coconut); // Add to grid *after* becoming dynamic LK.setTimeout(function () { if (coconut && fruits.includes(coconut)) { coconut.immuneToGameOver = false; } }, 1000); } }); } 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 = 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); } } }; game.up = function () { if (isDragging && activeFruit && isClickable && !gameOver) { dropFruit(); } isDragging = false; // Always reset dragging on up }; function updatePhysics() { 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++) { if (fruits[i] && !fruits[i].isStatic && !fruits[i].merging) { spatialGrid.updateObject(fruits[i]); } } } game.update = function () { if (gameOver) { return; } var currentScore = LK.getScore(); if (Math.floor(currentScore / 500) > Math.floor(lastScoreCheckForCoconut / 500)) { spawnCoconut(); } lastScoreCheckForCoconut = currentScore; updatePhysics(); checkGameOver(); }; // Initialize the game 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
@@ -138,15 +138,11 @@
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;
- // Check left wall collision
self.checkLeftWallCollision(fruit, walls.left, effectiveWidth);
- // Check right wall collision
self.checkRightWallCollision(fruit, walls.right, effectiveWidth);
- // Check floor collision
self.checkFloorCollision(fruit, floor, effectiveHeight);
- // Apply progressive wall friction
if (fruit.wallContactFrames > 0) {
var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01);
fruit.angularVelocity *= progressiveFriction;
}
@@ -155,68 +151,58 @@
var leftBoundary = leftWall.x + leftWall.width / 2 + effectiveWidth;
if (fruit.x < leftBoundary) {
fruit.x = leftBoundary;
fruit.vx = -fruit.vx * fruit.elasticity;
- // Angular impulse from wall hit
var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5);
- fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.5; // Adjusted impact direction
- fruit.angularVelocity *= fruit.groundAngularFriction; // Wall friction on rotation
- fruit.wallContactFrames++; // Increment wall contact
+ fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.5;
+ fruit.angularVelocity *= fruit.groundAngularFriction;
+ fruit.wallContactFrames++;
} else if (fruit.x > leftWall.x + leftWall.width * 2) {
- // Not contacting left wall anymore
fruit.wallContactFrames = 0;
}
};
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;
- // Angular impulse from wall hit
var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5);
- fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.5; // Adjusted impact direction
- fruit.angularVelocity *= fruit.groundAngularFriction; // Wall friction on rotation
- fruit.wallContactFrames++; // Increment wall contact
+ fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.5;
+ fruit.angularVelocity *= fruit.groundAngularFriction;
+ fruit.wallContactFrames++;
}
};
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; // Store velocity before bounce for sound check
+ var oldVy = fruit.vy;
fruit.vy = -fruit.vy * fruit.elasticity;
- // Angular impulse from floor hit (based on horizontal movement)
if (Math.abs(fruit.vx) > 0.5) {
var angularImpactMultiplier = 0.01 * (1 + (0.9 - fruit.elasticity) * 5);
- fruit.angularVelocity += fruit.vx * angularImpactMultiplier * 0.5; // Adjusted impact factor
+ fruit.angularVelocity += fruit.vx * angularImpactMultiplier * 0.5;
}
- // Apply stronger ground angular friction
fruit.angularVelocity *= fruit.groundAngularFriction;
- // Resting conditions on floor
- var restThreshold = 1 + (fruit.elasticity - 0.7) * 10; // Scale rest threshold
+ var restThreshold = 1 + (fruit.elasticity - 0.7) * 10;
if (Math.abs(fruit.vy) < restThreshold) {
- fruit.vy = 0; // Stop vertical bounce if below threshold
+ fruit.vy = 0;
}
- var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2); // Scale angular rest
+ var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2);
if (Math.abs(fruit.angularVelocity) < angularRestThreshold && fruit.vy === 0) {
- fruit.angularVelocity = 0; // Stop rotation completely if slow enough and on floor
- // Snap rotation when fully rested on floor
+ fruit.angularVelocity = 0;
fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2);
}
}
};
return self;
});
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
- // Unique identifier
self.id = 'fruit_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
self.type = type;
- // Initialize components
var physics = new PhysicsComponent();
var collision = new CollisionComponent();
var mergeHandler = new MergeComponent();
- // Apply physics component properties
self.vx = physics.vx;
self.vy = physics.vy;
self.rotation = physics.rotation;
self.angularVelocity = physics.angularVelocity;
@@ -226,21 +212,16 @@
self.friction = physics.friction;
self.rotationRestCounter = physics.rotationRestCounter;
self.maxAngularVelocity = physics.maxAngularVelocity;
self.isStatic = physics.isStatic;
- // Apply level specific properties
var currentLevel = self.type ? fruitLevels[self.type.id.toUpperCase()] || 10 : 10;
self.elasticity = 0.9 - (currentLevel - 1) * (0.2 / 9);
- // Apply collision component properties
self.wallContactFrames = collision.wallContactFrames;
- // Apply merge component properties
self.merging = mergeHandler.merging;
self.mergeGracePeriodActive = mergeHandler.mergeGracePeriodActive;
self.fromChargedRelease = mergeHandler.fromChargedRelease;
- // Game mechanics state properties
self.safetyPeriod = false;
self.immuneToGameOver = false;
- // Set up graphics
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
@@ -290,15 +271,11 @@
fruit1.merging = true;
fruit2.merging = true;
var midX = (fruit1.x + fruit2.x) / 2;
var midY = (fruit1.y + fruit2.y) / 2;
- // Animate merge effect
self.animateMerge(fruit1, fruit2, midX, midY);
};
- // Empty function to maintain API but without explosion effect
- self.applyExplosionForce = function (fruit1, fruit2, midX, midY) {
- // No explosion force applied
- };
+ self.applyExplosionForce = function (fruit1, fruit2, midX, midY) {};
self.animateMerge = function (fruit1, fruit2, midX, midY) {
tween(fruit1, {
alpha: 0,
scaleX: 0.5,
@@ -377,9 +354,8 @@
});
};
return self;
});
-// --- END OF FILE game code.txt ---
var PhysicsComponent = Container.expand(function () {
var self = Container.call(this);
self.vx = 0;
self.vy = 0;
@@ -393,58 +369,46 @@
self.groundAngularFriction = 0.75;
self.maxAngularVelocity = 0.15;
self.rotationRestCounter = 0;
self.apply = function (fruit) {
- // Don't apply physics to static or merging fruits
if (fruit.isStatic || fruit.merging) {
return;
}
- // Apply gravity and update position
fruit.vy += fruit.gravity;
fruit.x += fruit.vx;
fruit.y += fruit.vy;
- // Apply rotation
fruit.rotation += fruit.angularVelocity;
- // Apply friction
fruit.vx *= fruit.friction;
fruit.vy *= fruit.friction;
fruit.angularVelocity *= fruit.angularFriction;
- // Handle rotation damping and resting
self.handleRotationDamping(fruit);
};
self.handleRotationDamping = function (fruit) {
- // Progressive angular damping based on overall movement speed
var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy);
if (Math.abs(fruit.vx) < 0.5 && Math.abs(fruit.vy) < 0.5) {
- fruit.angularVelocity *= 0.85; // Increased damping
+ fruit.angularVelocity *= 0.85;
if (Math.abs(fruit.angularVelocity) < 0.02) {
fruit.rotationRestCounter++;
if (fruit.rotationRestCounter > 45) {
fruit.angularVelocity = 0;
- fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2); // Snap rotation
+ fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2);
}
} else {
- fruit.rotationRestCounter = 0; // Reset counter if spinning up again
+ fruit.rotationRestCounter = 0;
}
} else {
- fruit.rotationRestCounter = 0; // Reset if moving significantly
+ fruit.rotationRestCounter = 0;
}
- // Additional damping based on movement speed
if (movementMagnitude < 0.05) {
- // Very slow
fruit.angularVelocity *= 0.80;
} else if (movementMagnitude < 0.3) {
- // Moderately slow
fruit.angularVelocity *= 0.85;
} else if (movementMagnitude < 0.8) {
- // Moderate speed
fruit.angularVelocity *= 0.90;
}
- // Force rotation stop if very slow angular velocity
if (Math.abs(fruit.angularVelocity) < 0.005) {
fruit.angularVelocity = 0;
}
- // Clamp final angular velocity
fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity);
};
return self;
});
@@ -453,9 +417,8 @@
self.cellSize = cellSize || 200;
self.grid = {};
self.insertObject = function (obj) {
if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) {
- // Added checks
return;
}
var cells = self.getCellsForObject(obj);
for (var i = 0; i < cells.length; i++) {
@@ -476,9 +439,8 @@
}
};
self.removeObject = function (obj) {
if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) {
- // Added checks
return;
}
var cells = self.getCellsForObject(obj);
for (var i = 0; i < cells.length; i++) {
@@ -494,11 +456,9 @@
}
}
};
self.getCellsForObject = function (obj) {
- // Ensure object properties exist before calculating cells
if (!obj || typeof obj.x !== 'number' || typeof obj.y !== 'number' || typeof obj.width !== 'number' || typeof obj.height !== 'number') {
- // console.warn("Invalid object passed to getCellsForObject:", obj);
return [];
}
var cells = [];
var halfWidth = obj.width / 2;
@@ -584,9 +544,9 @@
var simVX = 0;
var simVY = 0;
var gravity = 1.8;
var dotCount = 0;
- var hitDetected = false; // Renamed from hitFruit for clarity
+ var hitDetected = false;
var dotY = startY;
var dotSpacing = 25;
while (dotCount < self.maxDots && !hitDetected) {
if (dotCount < self.dots.length) {
@@ -596,38 +556,32 @@
self.dots[dotCount].alpha = 1.0;
dotCount++;
}
dotY += dotSpacing;
- // Check floor collision first
- var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; // Use activeFruit height
+ var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2;
if (dotY > floorCollisionY) {
- // Place the last dot exactly on the floor projection
if (dotCount > 0 && dotCount <= self.dots.length) {
self.dots[dotCount - 1].y = floorCollisionY;
}
- hitDetected = true; // Stop trajectory at floor
+ hitDetected = true;
break;
}
- // Check fruit collision using spatial grid for efficiency
var potentialHits = spatialGrid.getPotentialCollisions({
x: startX,
y: dotY,
width: activeFruit.width,
height: activeFruit.height,
id: 'trajectory_check'
- }); // Pass a dummy object
+ });
for (var j = 0; j < potentialHits.length; j++) {
var fruit = potentialHits[j];
- // Ensure fruit is valid and not merging before checking intersection
if (fruit && fruit !== activeFruit && !fruit.merging && fruit.width && fruit.height) {
if (self.wouldIntersectFruit(fruit.x, fruit.y, startX, dotY, activeFruit, fruit)) {
- // Place the last dot exactly at the predicted collision point
if (dotCount > 0 && dotCount <= self.dots.length) {
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;
- // Adjust slightly back along the line
if (dist > 0) {
self.dots[dotCount - 1].y = dotY - dy / dist * overlap;
}
}
@@ -636,43 +590,21 @@
}
}
}
}
- // Hide remaining dots if trajectory was cut short
for (var k = dotCount; k < self.dots.length; k++) {
self.dots[k].visible = false;
}
};
self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) {
- // Simplified Circle Collision Check for Trajectory (more performant)
var dx = fruitX - dropX;
var dy = fruitY - dropY;
var distance = Math.sqrt(dx * dx + dy * dy);
- var combinedRadii = activeFruitObj.width / 2 + targetFruitObj.width / 2; // Use width as approx radius
- // Add a small buffer (-5 pixels) for visual satisfaction
+ var combinedRadii = activeFruitObj.width / 2 + targetFruitObj.width / 2;
if (distance < combinedRadii - 5) {
return true;
}
return false;
- /* // Original OBB check (more complex, potentially less performant for trajectory)
- var activeFruitHalfWidth = activeFruitObj.width / 2;
- var activeFruitHalfHeight = activeFruitObj.height / 2;
- var fruitHalfWidth = targetFruitObj.width / 2;
- var fruitHalfHeight = targetFruitObj.height / 2;
- var activeCosAngle = Math.abs(Math.cos(activeFruitObj.rotation));
- var activeSinAngle = Math.abs(Math.sin(activeFruitObj.rotation));
- var fruitCosAngle = Math.abs(Math.cos(targetFruitObj.rotation));
- var fruitSinAngle = Math.abs(Math.sin(targetFruitObj.rotation));
- var activeEffectiveRadiusX = activeFruitHalfWidth * activeCosAngle + activeFruitHalfHeight * activeSinAngle;
- var activeEffectiveRadiusY = activeFruitHalfHeight * activeCosAngle + activeFruitHalfWidth * activeSinAngle;
- var fruitEffectiveRadiusX = fruitHalfWidth * fruitCosAngle + fruitHalfHeight * fruitSinAngle;
- var fruitEffectiveRadiusY = fruitHalfHeight * fruitCosAngle + fruitHalfWidth * fruitSinAngle;
- var dx = fruitX - dropX;
- var dy = fruitY - dropY;
- if (Math.abs(dx) < activeEffectiveRadiusX + fruitEffectiveRadiusX - 5 && Math.abs(dy) < activeEffectiveRadiusY + fruitEffectiveRadiusY - 5) {
- return true;
- }
- return false; */
};
return self;
});
@@ -685,9 +617,8 @@
/****
* Game Code
****/
-// --- START OF FILE game code.txt ---
var gameOverLine;
var pineapple;
var pineappleActive = false;
var pineapplePushCount = 0;
@@ -947,31 +878,26 @@
if (chargedBallUI) {
chargedBallUI.reset();
}
}
-// Centralized collision check and resolution
function checkFruitCollisions() {
- // Iterate backwards for safe removal during merge
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];
- // Need to check if fruit2 still exists and meets criteria, especially after potential merges
if (!fruit2 || fruit2 === activeFruit || fruit2.merging || fruit2.isStatic || fruit1 === fruit2) {
continue;
}
- // Check if fruit2 is still in the main fruits array (could have been removed by a previous merge in this loop)
if (fruits.indexOf(fruit2) === -1) {
continue;
}
var dx = fruit2.x - fruit1.x;
var dy = fruit2.y - fruit1.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // AABB Check (using width/height directly)
var fruit1HalfWidth = fruit1.width / 2;
var fruit1HalfHeight = fruit1.height / 2;
var fruit2HalfWidth = fruit2.width / 2;
var fruit2HalfHeight = fruit2.height / 2;
@@ -980,56 +906,44 @@
var combinedHalfWidths = fruit1HalfWidth + fruit2HalfWidth;
var combinedHalfHeights = fruit1HalfHeight + fruit2HalfHeight;
var colliding = absDistanceX < combinedHalfWidths && absDistanceY < combinedHalfHeights;
if (colliding) {
- // Merge Check
if (fruit1.type === fruit2.type) {
fruit1.merge(fruit2);
- // Important: Since fruit1 merged, break inner loop and outer loop will continue from next index (i--)
- continue outerLoop; // Use labeled continue to break inner loop and continue outer correctly after merge
- }
- // Collision Resolution (Different Types)
- else {
+ continue outerLoop;
+ } else {
if (distance === 0) {
- // Prevent division by zero if perfectly overlapped
distance = 0.1;
- dx = distance; // Give a slight horizontal separation
+ dx = distance;
dy = 0;
}
- var overlap = combinedHalfWidths - absDistanceX; // Simplified overlap estimation
+ var overlap = combinedHalfWidths - absDistanceX;
if (absDistanceY < combinedHalfHeights && absDistanceX < combinedHalfWidths) {
- // Check both axes for overlap
overlap = Math.min(combinedHalfWidths - absDistanceX, combinedHalfHeights - absDistanceY);
}
var normalizeX = dx / distance;
var normalizeY = dy / distance;
- // Separation based on overlap
var moveX = overlap / 2 * normalizeX;
var moveY = overlap / 2 * normalizeY;
- // Slightly adjust separation to prevent sticking
var separationFactor = 1.05;
fruit1.x -= moveX * separationFactor;
fruit1.y -= moveY * separationFactor;
fruit2.x += moveX * separationFactor;
fruit2.y += moveY * separationFactor;
- // Velocity Resolution
var rvX = fruit2.vx - fruit1.vx;
var rvY = fruit2.vy - fruit1.vy;
var contactVelocity = rvX * normalizeX + rvY * normalizeY;
if (contactVelocity < 0) {
- // Only resolve if moving towards each other
var collisionElasticity = Math.max(fruit1.elasticity, fruit2.elasticity);
var impulse = -(1 + collisionElasticity) * contactVelocity;
- // Approximate mass based on size (volume might be better: size^3)
- var mass1 = Math.pow(fruit1.type.size, 1.5); // Using power 1.5 as proxy
+ var mass1 = Math.pow(fruit1.type.size, 1.5);
var mass2 = Math.pow(fruit2.type.size, 1.5);
var totalMass = mass1 + mass2;
- var impulseRatio1 = totalMass > 0 ? mass2 / totalMass : 0.5; // Handle zero mass case
+ var impulseRatio1 = totalMass > 0 ? mass2 / totalMass : 0.5;
var impulseRatio2 = totalMass > 0 ? mass1 / totalMass : 0.5;
var impulse1 = impulse * impulseRatio1;
var impulse2 = impulse * impulseRatio2;
- // Apply impact scaling for smaller fruits
- var sizeDifference = Math.abs(fruit1.type.size - fruit2.type.size) / Math.max(fruit1.type.size, fruit2.type.size, 1); // Avoid div by zero
+ 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 * 0.5;
} else if (fruit2.type.size < fruit1.type.size) {
impulse2 *= 1 + sizeDifference * 0.5;
@@ -1037,31 +951,25 @@
fruit1.vx -= impulse1 * normalizeX;
fruit1.vy -= impulse1 * normalizeY;
fruit2.vx += impulse2 * normalizeX;
fruit2.vy += impulse2 * normalizeY;
- // Friction between fruits
var tangentX = -normalizeY;
var tangentY = normalizeX;
var tangentVelocity = rvX * tangentX + rvY * tangentY;
- var frictionImpulse = -tangentVelocity * 0.1; // Reduced friction factor slightly
- fruit1.vx -= frictionImpulse * tangentX * impulseRatio1; // Scale friction by mass ratio
+ 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;
- // Rotation transfer
- var rotationTransferFactor = 0.01; // Adjusted factor
- var tangentialComponent = rvX * tangentX + rvY * tangentY; // Use tangent velocity
- // Moment of inertia approximation (higher for larger fruits)
+ 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;
- // Apply angular impulse inversely proportional to inertia (approx)
fruit1.angularVelocity += inertia2 > 0 ? angularImpulse * (inertia1 / (inertia1 + inertia2)) : angularImpulse * 0.5;
fruit2.angularVelocity -= inertia1 > 0 ? angularImpulse * (inertia2 / (inertia1 + inertia2)) : angularImpulse * 0.5;
- // Damp angular velocity during collision
fruit1.angularVelocity *= 0.95;
fruit2.angularVelocity *= 0.95;
- // Clamp angular velocity after applying impulse
fruit1.angularVelocity = Math.min(Math.max(fruit1.angularVelocity, -fruit1.maxAngularVelocity), fruit1.maxAngularVelocity);
fruit2.angularVelocity = Math.min(Math.max(fruit2.angularVelocity, -fruit2.maxAngularVelocity), fruit2.maxAngularVelocity);
}
}
@@ -1083,42 +991,36 @@
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; // Use line's actual bottom
+ var lineBottomY = gameOverLine.y + gameOverLine.height / 2;
if (fruitTopY <= lineBottomY) {
- // Check horizontal overlap with the line itself
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; // Use scaled width
+ 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; // Skip check if immune
+ continue;
}
- // If safetyPeriod hasn't been explicitly set to true (meaning it bounced/settled), check velocity
if (fruit.safetyPeriod !== true) {
if (fruit.vy > 0.1) {
- // Still falling significantly?
- fruit.safetyPeriod = false; // Mark as unsafe (falling into line) but don't end game yet
+ fruit.safetyPeriod = false;
continue;
} else {
- // Fruit is near/above the line and has stopped falling or is moving up slightly
- fruit.safetyPeriod = true; // It has settled or bounced into the line
+ fruit.safetyPeriod = true;
}
}
- // Only trigger game over if safety period is definitively true (settled/bounced into line)
if (fruit.safetyPeriod === true) {
gameOver = true;
LK.showGameOver();
- return; // Exit check immediately on game over
+ return;
}
}
} else {
- // If fruit moves below the line, reset its safety period check state
- fruit.safetyPeriod = undefined; // Reset so it needs to bounce/settle again if it goes back up
+ fruit.safetyPeriod = undefined;
}
}
}
function setupPineapple() {
@@ -1264,52 +1166,39 @@
dropFruit();
}
isDragging = false; // Always reset dragging on up
};
-// NEW: Centralized Physics Update Function
function updatePhysics() {
- // 1. Apply forces, update velocity and position for all fruits
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
- // Skip static, merging, or destroyed fruits
if (!fruit || fruit.isStatic || fruit.merging) {
continue;
}
- // Use component methods to update physics
fruit.updatePhysics();
- // Check boundary collisions using component method
var walls = {
left: wallLeft,
right: wallRight
};
fruit.checkBoundaries(walls, gameFloor);
- // --- End Physics Application ---
- } // End of fruit loop
- // 2. Check and Resolve Inter-Fruit Collisions (using the dedicated function)
+ }
checkFruitCollisions();
- // 3. Update Spatial Grid for all dynamic fruits based on their final positions for this frame
for (var i = 0; i < fruits.length; i++) {
if (fruits[i] && !fruits[i].isStatic && !fruits[i].merging) {
spatialGrid.updateObject(fruits[i]);
}
}
}
-// Game update loop
game.update = function () {
if (gameOver) {
- // Optional: Add any game over specific logic here, like freezing animations
- return; // Stop updates if game is over
+ return;
}
- // Check score for coconut spawn
var currentScore = LK.getScore();
if (Math.floor(currentScore / 500) > Math.floor(lastScoreCheckForCoconut / 500)) {
spawnCoconut();
}
lastScoreCheckForCoconut = currentScore;
- // Call the centralized physics update function
updatePhysics();
- // Check game over conditions (after physics and collisions)
- checkGameOver(); // checkGameOver now uses the fruit.safetyPeriod state updated in updatePhysics
+ checkGameOver();
};
// Initialize the game
initGame();
function removeFruitFromGame(fruit) {