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 Fruit = Container.expand(function (type) { var self = Container.call(this); self.id = 'fruit_' + Date.now() + '_' + Math.floor(Math.random() * 10000); self.type = type; self.vx = 0; self.vy = 0; self.rotation = 0; self.angularVelocity = 0; self.angularFriction = 0.95; self.groundAngularFriction = 0.75; self.gravity = 1.8; self.friction = 0.98; self.rotationRestCounter = 0; var currentLevel = self.type ? fruitLevels[self.type.id.toUpperCase()] || 10 : 10; self.elasticity = 0.9 - (currentLevel - 1) * (0.2 / 9); self.merging = false; self.isStatic = false; self.maxAngularVelocity = 0.15; self.safetyPeriod = false; // Initialize here self.wallContactFrames = 0; // Initialize here self.immuneToGameOver = false; // Initialize here self.mergeGracePeriodActive = false; // Initialize here self.fromChargedRelease = false; // Initialize here 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"); } // Removed the Fruit.update() method as its logic is moved to updatePhysics self.merge = function (otherFruit) { if (self.merging) { return; } self.merging = true; otherFruit.merging = true; var midX = (self.x + otherFruit.x) / 2; var midY = (self.y + otherFruit.y) / 2; var fruitLevel = fruitLevels[self.type.id.toUpperCase()] || 1; var explosionRadius = 350 + fruitLevel * 50; var explosionForce = 5 + fruitLevel * 1.5; for (var i = 0; i < fruits.length; i++) { var nearbyFruit = fruits[i]; if (nearbyFruit === self || nearbyFruit === otherFruit || nearbyFruit.isStatic || nearbyFruit.merging) { continue; } var dx = nearbyFruit.x - midX; var dy = nearbyFruit.y - midY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < explosionRadius && distance > 0) { // Added distance > 0 check var dirX = dx / distance; var dirY = dy / distance; var forceFactor = Math.pow(1 - distance / explosionRadius, 1.5); var appliedForce = explosionForce * forceFactor; nearbyFruit.vx += dirX * appliedForce; nearbyFruit.vy += dirY * appliedForce; nearbyFruit.angularVelocity += (Math.random() * 0.08 - 0.04) * appliedForce; } } tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut }); tween(otherFruit, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { LK.getSound('merge').play(); if (self.type.id.toUpperCase() === 'COCONUT' && otherFruit.type.id.toUpperCase() === 'COCONUT') { LK.getSound('ThisIsFine').play(); } if (self.type.id.toUpperCase() === 'MELON' && otherFruit.type.id.toUpperCase() === 'MELON') { LK.getSound('Smartz').play(); } if (self.type.id.toUpperCase() === 'PEACH' && otherFruit.type.id.toUpperCase() === 'PEACH') { LK.getSound('stonks').play(); } var fromReleasedFruits = self.fromChargedRelease || otherFruit.fromChargedRelease; var isPlayerDroppedFruitMerge = !fromReleasedFruits && (self === lastDroppedFruit || otherFruit === lastDroppedFruit) && !lastDroppedHasMerged; var fruitHasMergeGracePeriod = self.mergeGracePeriodActive || otherFruit.mergeGracePeriodActive; if (isPlayerDroppedFruitMerge || fruitHasMergeGracePeriod) { lastDroppedHasMerged = true; } if (self.type.id.toUpperCase() === 'DURIAN') { LK.setScore(LK.getScore() + self.type.points); updateScoreDisplay(); removeFruitFromGame(self); removeFruitFromGame(otherFruit); releasePineappleOnMerge(); } else { releasePineappleOnMerge(); var nextType = FruitTypes[self.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 }); removeFruitFromGame(self); removeFruitFromGame(otherFruit); } } }); function removeFruitFromGame(fruit) { var index = fruits.indexOf(fruit); if (index !== -1) { fruits.splice(index, 1); } spatialGrid.removeObject(fruit); fruit.destroy(); } }; 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 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) { // Added checks 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) { // Added checks 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) { // 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; 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; // Renamed from hitFruit for clarity 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; // Check floor collision first var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; // Use activeFruit height 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 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; } } hitDetected = true; break; } } } } // 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 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; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf6e58d }); /**** * Game Code ****/ // --- START OF FILE game code.txt --- 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 chargedBalls = []; var chargedBallContainer = 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() { chargedBallContainer = new Container(); game.addChild(chargedBallContainer); chargedBallContainer.y = 120; var countdownText = new Text2("9", { size: 100, fill: 0x000000 }); countdownText.anchor.set(0.5, 0.5); countdownText.x = gameWidth / 2 + 270; chargedBallContainer.addChild(countdownText); chargedBalls.push(countdownText); chargedBallContainer.x = 0; } function updateChargedBallDisplay() { if (chargedBalls.length > 0) { var countdownText = chargedBalls[0]; var remainingCount = Math.max(0, 9 - chargeCounter); // Ensure count doesn't go below 0 countdownText.setText(remainingCount.toString()); var textColor = remainingCount <= 3 ? 0xFF0000 : remainingCount <= 6 ? 0xFFA500 : 0x000000; tween(countdownText, { tint: textColor }, { duration: 300, easing: tween.easeOut }); var baseSize = 1.0; var sizeMultiplier = baseSize + 0.2 * (9 - remainingCount); tween(countdownText, { scaleX: sizeMultiplier, scaleY: sizeMultiplier }, { duration: 300, easing: tween.easeOut }); } } function releaseChargedBalls() { readyToReleaseCharged = true; if (chargedBalls.length > 0) { var countdownText = chargedBalls[0]; countdownText.setText("0"); tween(countdownText, { tint: 0xFF0000 }, { duration: 300, easing: tween.easeOut }); var _pulseText = function pulseText() { if (!readyToReleaseCharged) { return; } // Stop pulsing if released tween(countdownText, { scaleX: 1.3, scaleY: 1.3 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (!readyToReleaseCharged) { return; } tween(countdownText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut, onFinish: _pulseText }); } }); }; _pulseText(); } } function resetChargedBalls() { if (chargedBalls.length > 0) { var countdownText = chargedBalls[0]; countdownText.setText("9"); tween(countdownText, { tint: 0x000000 }, { duration: 200, easing: tween.easeOut }); tween(countdownText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeOut }); } } // 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; 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) { // 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 { if (distance === 0) { // Prevent division by zero if perfectly overlapped distance = 0.1; dx = distance; // Give a slight horizontal separation dy = 0; } var overlap = combinedHalfWidths - absDistanceX; // Simplified overlap estimation 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 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 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 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; // 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 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 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); } } } } } } 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; // Use line's actual bottom 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 lineRightX = gameOverLine.x + gameOverLine.width * gameOverLine.scaleX / 2; var horizontalOverlap = !(fruitRightX < lineLeftX || fruitLeftX > lineRightX); if (horizontalOverlap) { if (fruit.immuneToGameOver) { continue; // Skip check if immune } // 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 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 } } // 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 } } } 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 } } } 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; chargedBalls = []; // Clear charged balls array if (chargedBallContainer) { chargedBallContainer.destroy(); } // Remove old UI container chargedBallContainer = 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 }; // 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; } // --- Start Physics Application --- // Apply gravity fruit.vy += fruit.gravity; // Apply velocity to position fruit.x += fruit.vx; fruit.y += fruit.vy; // Apply angular velocity to rotation fruit.rotation += fruit.angularVelocity; // Apply linear friction fruit.vx *= fruit.friction; fruit.vy *= fruit.friction; // Apply basic angular friction fruit.angularVelocity *= fruit.angularFriction; // --- Advanced Damping and Resting Logic --- // Check if fruit is nearly at rest (low linear velocity) if (Math.abs(fruit.vx) < 0.5 && Math.abs(fruit.vy) < 0.5) { fruit.angularVelocity *= 0.85; // Increased damping if (Math.abs(fruit.angularVelocity) < 0.02) { fruit.rotationRestCounter++; if (fruit.rotationRestCounter > 45) { // ~0.75 seconds fruit.angularVelocity = 0; fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2); // Snap rotation } } else { fruit.rotationRestCounter = 0; // Reset counter if spinning up again } } else { fruit.rotationRestCounter = 0; // Reset if moving significantly } // Progressive angular damping based on overall movement speed var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy); 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; } // --- Boundary Collisions (Walls and Floor) --- 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; // Left Wall var leftBoundary = wallLeft.x + wallLeft.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 } // Right Wall else if (fruit.x > wallRight.x - wallRight.width / 2 - effectiveWidth) { var rightBoundary = wallRight.x - wallRight.width / 2 - effectiveWidth; 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 } else { fruit.wallContactFrames = 0; // Reset wall contact if not touching } // Apply progressive wall friction based on contact frames if (fruit.wallContactFrames > 0) { var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01); fruit.angularVelocity *= progressiveFriction; } // Floor Collision var floorCollisionY = gameFloor.y - gameFloor.height / 2 - effectiveHeight; if (fruit.y > floorCollisionY) { fruit.y = floorCollisionY; var oldVy = fruit.vy; // Store velocity before bounce for sound check 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 } // Apply stronger ground angular friction fruit.angularVelocity *= fruit.groundAngularFriction; // Resting conditions on floor var restThreshold = 1 + (fruit.elasticity - 0.7) * 10; // Scale rest threshold if (Math.abs(fruit.vy) < restThreshold) { fruit.vy = 0; // Stop vertical bounce if below threshold } var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2); // Scale angular rest 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.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2); } } // Clamp final angular velocity fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity); // --- Safety Period Update --- // If it was freshly dropped (false) and stopped moving down or moved up, it's no longer in initial drop phase if (fruit.safetyPeriod === false && fruit.vy <= 0.1) { fruit.safetyPeriod = undefined; // Reset safety check state (needs to settle/bounce for game over) } // --- 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 } // 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 }; // Initialize the game initGame(); // --- END OF FILE game code.txt ---
===================================================================
--- original.js
+++ change.js
@@ -14,155 +14,64 @@
self.vx = 0;
self.vy = 0;
self.rotation = 0;
self.angularVelocity = 0;
- self.angularFriction = 0.95; // Slightly reduced friction for initial movement
- self.groundAngularFriction = 0.75; // Stronger ground friction to stop spinning faster
- self.gravity = 1.8; // Doubled gravity to make fruits drop faster
+ self.angularFriction = 0.95;
+ self.groundAngularFriction = 0.75;
+ self.gravity = 1.8;
self.friction = 0.98;
- self.rotationRestCounter = 0; // Counter to track how long the fruit has been nearly at rest
- // Calculate elasticity based on fruit level
- // The biggest fruit (DURIAN) has elasticity of 0.7
- // Smaller fruits are more bouncy with elasticity closer to 1.0
- // Using global fruitLevels definition
+ self.rotationRestCounter = 0;
var currentLevel = self.type ? fruitLevels[self.type.id.toUpperCase()] || 10 : 10;
- // Scale elasticity from 0.9 (most bouncy) for level 1 to 0.7 (least bouncy) for level 10
self.elasticity = 0.9 - (currentLevel - 1) * (0.2 / 9);
self.merging = false;
self.isStatic = false;
- self.maxAngularVelocity = 0.15; // Add maximum angular velocity cap
- // Only attempt to attach the asset if self.type exists and has necessary properties
+ self.maxAngularVelocity = 0.15;
+ self.safetyPeriod = false; // Initialize here
+ self.wallContactFrames = 0; // Initialize here
+ self.immuneToGameOver = false; // Initialize here
+ self.mergeGracePeriodActive = false; // Initialize here
+ self.fromChargedRelease = false; // Initialize here
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
});
- // Set width and height directly from the actual asset for accurate hitbox
self.width = fruitGraphics.width;
self.height = fruitGraphics.height;
- // No point value shown on fruit
} else {
- // This will be initialized properly when the game is fully loaded
console.log("Warning: Fruit type not available yet or missing required properties");
}
- self.update = function () {
- // Skip updates for static or merging fruits
- if (self.isStatic || self.merging) {
- return;
- }
- // Initialize necessary tracking properties if undefined
- if (self.safetyPeriod === undefined) {
- self.safetyPeriod = false;
- }
- if (self.wallContactFrames === undefined) {
- self.wallContactFrames = 0;
- }
- // Track safety period state changes
- if (self.safetyPeriod === false && self.vy <= 0) {
- // When a fruit that was in safety period starts moving upward or stops,
- // it means it has hit something, so it's no longer in safety period
- self.safetyPeriod = true;
- }
- // Add damping when velocity is low
- if (Math.abs(self.vx) < 0.5 && Math.abs(self.vy) < 0.5) {
- self.angularVelocity *= 0.85; // Increased damping when fruit is almost at rest
- // Track how long the fruit has been nearly at rest
- if (Math.abs(self.angularVelocity) < 0.02) {
- self.rotationRestCounter++;
- // After being nearly at rest for some time, force rotation to stop completely
- if (self.rotationRestCounter > 45) {
- // ~0.75 seconds at 60fps
- self.angularVelocity = 0;
- self.rotation = Math.round(self.rotation / (Math.PI / 2)) * (Math.PI / 2); // Snap to nearest 90 degrees
- }
- } else {
- // Reset counter if angular velocity increases
- self.rotationRestCounter = 0;
- }
- }
- // Check for contact with walls to apply wall friction
- // Use width instead of assuming a radius - better for non-circular fruits
- var fruitHalfWidth = self.width / 2;
- var isContactingLeftWall = self.x <= wallLeft.x + wallLeft.width / 2 + fruitHalfWidth + 2;
- var isContactingRightWall = self.x >= wallRight.x - wallRight.width / 2 - fruitHalfWidth - 2;
- if (isContactingLeftWall || isContactingRightWall) {
- // Apply progressive wall friction based on how long the fruit has been in contact
- self.wallContactFrames++;
- // Increase wall friction the longer the fruit stays in contact
- var progressiveFriction = Math.min(0.85, 0.65 + self.wallContactFrames * 0.01);
- self.angularVelocity *= progressiveFriction;
- } else {
- // Reset wall contact frames when not touching walls
- self.wallContactFrames = 0;
- }
- // Check for contact with other fruits to apply additional friction
- var isContactingOtherFruit = false;
- for (var i = 0; i < fruits.length; i++) {
- var otherFruit = fruits[i];
- if (otherFruit !== self && !otherFruit.merging && !otherFruit.isStatic) {
- var dx = otherFruit.x - self.x;
- var dy = otherFruit.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var combinedHalfWidths = (self.width + otherFruit.width) / 2;
- if (distance < combinedHalfWidths + 2) {
- // Small buffer for contact detection
- isContactingOtherFruit = true;
- break;
- }
- }
- }
- // Apply stronger friction when in contact with other fruits
- if (isContactingOtherFruit) {
- self.angularVelocity *= 0.8; // Stronger friction when touching other fruits
- }
- // Apply extreme damping when almost stopped rotating
- if (Math.abs(self.angularVelocity) < 0.01) {
- self.angularVelocity = 0;
- }
- };
+ // Removed the Fruit.update() method as its logic is moved to updatePhysics
self.merge = function (otherFruit) {
- // Prevent already merging fruits from merging again
if (self.merging) {
return;
}
- // Mark both fruits as merging to prevent further interactions
self.merging = true;
otherFruit.merging = true;
- // Calculate midpoint between fruits for new fruit position
var midX = (self.x + otherFruit.x) / 2;
var midY = (self.y + otherFruit.y) / 2;
- // Create explosion effect pushing adjacent fruits away
- // Calculate explosion force based on fruit level (higher level = stronger explosion)
var fruitLevel = fruitLevels[self.type.id.toUpperCase()] || 1;
- var explosionRadius = 350 + fruitLevel * 50; // Significantly increased radius for wider effect
- var explosionForce = 5 + fruitLevel * 1.5; // Substantially increased base force for stronger push
- // Affect all nearby fruits
+ var explosionRadius = 350 + fruitLevel * 50;
+ var explosionForce = 5 + fruitLevel * 1.5;
for (var i = 0; i < fruits.length; i++) {
var nearbyFruit = fruits[i];
- // Skip the merging fruits and static fruits
if (nearbyFruit === self || nearbyFruit === otherFruit || nearbyFruit.isStatic || nearbyFruit.merging) {
continue;
}
- // Calculate distance from explosion center to nearby fruit
var dx = nearbyFruit.x - midX;
var dy = nearbyFruit.y - midY;
var distance = Math.sqrt(dx * dx + dy * dy);
- // Only affect fruits within explosion radius
- if (distance < explosionRadius) {
- // Calculate normalized direction vector
+ if (distance < explosionRadius && distance > 0) {
+ // Added distance > 0 check
var dirX = dx / distance;
var dirY = dy / distance;
- // Force decreases with distance (improved curve for more natural explosion)
- var forceFactor = Math.pow(1 - distance / explosionRadius, 1.5); // Added exponential curve for stronger close effect
+ var forceFactor = Math.pow(1 - distance / explosionRadius, 1.5);
var appliedForce = explosionForce * forceFactor;
- // Apply force as velocity change with additional impact for closer fruits
nearbyFruit.vx += dirX * appliedForce;
nearbyFruit.vy += dirY * appliedForce;
- // Add more significant random spin based on explosion force
- nearbyFruit.angularVelocity += (Math.random() * 0.08 - 0.04) * appliedForce; // Doubled spin effect
+ nearbyFruit.angularVelocity += (Math.random() * 0.08 - 0.04) * appliedForce;
}
}
- // Create merge animation for both fruits
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
@@ -177,85 +86,60 @@
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
- // Play merge sound once for all fruit types
LK.getSound('merge').play();
- // Play ThisIsFine sound when two coconuts merge to form a durian
if (self.type.id.toUpperCase() === 'COCONUT' && otherFruit.type.id.toUpperCase() === 'COCONUT') {
LK.getSound('ThisIsFine').play();
}
- // Play Smartz sound when two melons merge
if (self.type.id.toUpperCase() === 'MELON' && otherFruit.type.id.toUpperCase() === 'MELON') {
LK.getSound('Smartz').play();
}
- // Play stonks sound when two peaches merge
if (self.type.id.toUpperCase() === 'PEACH' && otherFruit.type.id.toUpperCase() === 'PEACH') {
LK.getSound('stonks').play();
}
- // Track if this is a merge involving the player's most recently dropped fruit
var fromReleasedFruits = self.fromChargedRelease || otherFruit.fromChargedRelease;
var isPlayerDroppedFruitMerge = !fromReleasedFruits && (self === lastDroppedFruit || otherFruit === lastDroppedFruit) && !lastDroppedHasMerged;
- // Allow for a 2-second grace period after dropping the fruit
var fruitHasMergeGracePeriod = self.mergeGracePeriodActive || otherFruit.mergeGracePeriodActive;
- // Only mark as merged if it's the player's dropped fruit or in grace period
if (isPlayerDroppedFruitMerge || fruitHasMergeGracePeriod) {
- lastDroppedHasMerged = true; // Mark that this fruit has had its first merge
+ lastDroppedHasMerged = true;
}
- // Reset fromChargedRelease flag after this merge completes
- // so these fruits can participate in future chain reactions with next player drop
- // Special case for DURIAN - they simply disappear instead of merging
if (self.type.id.toUpperCase() === 'DURIAN') {
- // No longer playing ThisIsFine sound when durian is created
- // Add points based on the durian's value
LK.setScore(LK.getScore() + self.type.points);
updateScoreDisplay();
- // Remove both fruits
removeFruitFromGame(self);
removeFruitFromGame(otherFruit);
- // Handle pineapple release (shared code moved to a function)
releasePineappleOnMerge();
} else {
- // Handle pineapple release (shared code moved to a function)
releasePineappleOnMerge();
- // Normal merge behavior for all other fruits
var nextType = FruitTypes[self.type.next.toUpperCase()];
var newFruit = new Fruit(nextType);
- // Position at midpoint with initial small scale
newFruit.x = midX;
newFruit.y = midY;
newFruit.scaleX = 0.5;
newFruit.scaleY = 0.5;
- // Add to game and array
game.addChild(newFruit);
fruits.push(newFruit);
- // Add new fruit to spatial grid
spatialGrid.insertObject(newFruit);
- // Add merge points based on the new fruit's level
LK.setScore(LK.getScore() + nextType.points);
updateScoreDisplay();
- // Animate new fruit growing
tween(newFruit, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
- // Remove both original fruits
removeFruitFromGame(self);
removeFruitFromGame(otherFruit);
- // Removed charged balls check here since it's now handled at the beginning of merge function
}
}
});
- // Helper function to remove a fruit from the game
function removeFruitFromGame(fruit) {
var index = fruits.indexOf(fruit);
if (index !== -1) {
fruits.splice(index, 1);
}
- // Remove from spatial grid before destroying
spatialGrid.removeObject(fruit);
fruit.destroy();
}
};
@@ -266,32 +150,27 @@
var lineGraphics = self.attachAsset('floor', {
anchorX: 0.5,
anchorY: 0.5
});
- // Make it visually distinct from the floor
lineGraphics.tint = 0xff0000;
- // Ensure full width is used for collision but visual is thin
- lineGraphics.height = 20; // Make the visual thinner
+ lineGraphics.height = 20;
return self;
});
var SpatialGrid = Container.expand(function (cellSize) {
var self = Container.call(this);
- self.cellSize = cellSize || 200; // Default cell size (can be adjusted based on average fruit size)
- self.grid = {}; // Hash map for grid cells
- // Add an object to the grid
+ self.cellSize = cellSize || 200;
+ self.grid = {};
self.insertObject = function (obj) {
- if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) {
- return; // Skip invalid objects
+ if (!obj || !obj.x || !obj.y || !obj.width || !obj.height || obj.merging || obj.isStatic) {
+ // Added checks
+ return;
}
- // Calculate the grid cells this object occupies
var cells = self.getCellsForObject(obj);
- // Add the object to each cell
for (var i = 0; i < cells.length; i++) {
var cellKey = cells[i];
if (!self.grid[cellKey]) {
self.grid[cellKey] = [];
}
- // Only add if not already in this cell
var alreadyInCell = false;
for (var j = 0; j < self.grid[cellKey].length; j++) {
if (self.grid[cellKey][j] === obj) {
alreadyInCell = true;
@@ -302,201 +181,206 @@
self.grid[cellKey].push(obj);
}
}
};
- // Remove an object from the grid
self.removeObject = function (obj) {
- if (!obj) {
+ if (!obj || !obj.x || !obj.y || !obj.width || !obj.height) {
+ // Added checks
return;
}
- // Calculate the grid cells this object occupied
var cells = self.getCellsForObject(obj);
- // Remove the object from each cell
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);
}
- // Remove empty cells to save memory
if (self.grid[cellKey].length === 0) {
delete self.grid[cellKey];
}
}
}
};
- // Get all cells that an object occupies
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;
var halfHeight = obj.height / 2;
- // Calculate grid coordinates for the four corners of the object's bounding box
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);
- // Add all cells in the object's bounding area
for (var cellX = minCellX; cellX <= maxCellX; cellX++) {
for (var cellY = minCellY; cellY <= maxCellY; cellY++) {
cells.push(cellX + "," + cellY);
}
}
return cells;
};
- // Update object's position in the grid
self.updateObject = function (obj) {
- self.removeObject(obj);
- self.insertObject(obj);
+ self.removeObject(obj); // Remove first based on old position
+ self.insertObject(obj); // Insert based on new position
};
- // Get potential collision candidates for an object
self.getPotentialCollisions = function (obj) {
var candidates = [];
var cells = self.getCellsForObject(obj);
- var addedObjects = {}; // Track which objects we've already added
- // Gather all objects from the cells this object occupies
+ 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];
- // Skip if it's the same object or already added
- if (otherObj !== obj && !addedObjects[otherObj.id]) {
+ // Additional check to ensure otherObj has an id
+ if (otherObj && otherObj !== obj && !addedObjects[otherObj.id]) {
candidates.push(otherObj);
addedObjects[otherObj.id] = true;
}
}
}
}
return candidates;
};
- // Clear the entire grid
self.clear = function () {
self.grid = {};
};
return self;
});
var TrajectoryLine = Container.expand(function () {
var self = Container.call(this);
self.dots = [];
- self.dotSpacing = 10; // Space between dots in the trajectory (closer dots)
- self.dotSize = 15; // Size of trajectory dots (larger dots)
- self.maxDots = 100; // Maximum number of dots to prevent excessive calculations (increased for longer line)
- // Create dots
+ self.dotSpacing = 10;
+ self.dotSize = 15;
+ self.maxDots = 100;
self.createDots = function () {
- // Clear existing dots first
self.clearDots();
- // Create new dots
for (var i = 0; i < self.maxDots; i++) {
var dot = new Container();
- // Create a small white circle using the trajectory dot asset
var dotGraphic = dot.attachAsset('trajectoryDot', {
anchorX: 0.5,
anchorY: 0.5
});
- // Make sure the dot is white and appropriately sized
dotGraphic.tint = 0xFFFFFF;
- dot.scaleX = 0.8; // Make dots more visible
+ dot.scaleX = 0.8;
dot.scaleY = 0.8;
- // Initially hide the dot
dot.visible = false;
- // Add to container and array
self.addChild(dot);
self.dots.push(dot);
}
};
- // Clear all dots
self.clearDots = function () {
for (var i = 0; i < self.dots.length; i++) {
if (self.dots[i]) {
self.dots[i].destroy();
}
}
self.dots = [];
};
- // Update trajectory based on current active fruit position
self.updateTrajectory = function (startX, startY) {
if (!activeFruit) {
return;
}
- // Hide all dots first
for (var i = 0; i < self.dots.length; i++) {
self.dots[i].visible = false;
}
- // Physics simulation variables
var simX = startX;
var simY = startY;
- var simVX = 0; // Starting with no horizontal velocity
- var simVY = 0; // Starting with no vertical velocity
- var gravity = 1.8; // Same gravity as in the game
- // Show dots along predicted path
+ var simVX = 0;
+ var simVY = 0;
+ var gravity = 1.8;
var dotCount = 0;
- var hitFruit = false;
- // Create dots in a straight line directly downward
+ var hitDetected = false; // Renamed from hitFruit for clarity
var dotY = startY;
- var dotSpacing = 25; // Smaller spacing between dots for a more continuous line
- while (dotCount < self.maxDots && !hitFruit) {
- // Place dot at current position
+ var dotSpacing = 25;
+ while (dotCount < self.maxDots && !hitDetected) {
if (dotCount < self.dots.length) {
- // Place dot directly below the fruit in a straight line
self.dots[dotCount].x = startX;
self.dots[dotCount].y = dotY;
self.dots[dotCount].visible = true;
self.dots[dotCount].alpha = 1.0;
dotCount++;
}
- // Move to next dot position
dotY += dotSpacing;
- // Check if we've hit the floor
- var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.width / 2;
+ // Check floor collision first
+ var floorCollisionY = gameFloor.y - gameFloor.height / 2 - activeFruit.height / 2; // Use activeFruit height
if (dotY > floorCollisionY) {
- // Stop at the floor
+ // 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
break;
}
- // Check if we've hit any fruits
- var hitFruit = false;
- // Helper function to predict if the trajectory would intersect with a fruit
- self.wouldIntersectFruit = function (fruitX, fruitY, dropX, dropY, activeFruitObj, targetFruitObj) {
- // Get dimensions for both fruits
- var activeFruitHalfWidth = activeFruitObj.width / 2;
- var activeFruitHalfHeight = activeFruitObj.height / 2;
- var fruitHalfWidth = targetFruitObj.width / 2;
- var fruitHalfHeight = targetFruitObj.height / 2;
- // Calculate effective dimensions based on rotation
- 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));
- // Calculate effective radii for both fruits
- var activeEffectiveRadiusX = activeFruitHalfWidth * activeCosAngle + activeFruitHalfHeight * activeSinAngle;
- var activeEffectiveRadiusY = activeFruitHalfHeight * activeCosAngle + activeFruitHalfWidth * activeSinAngle;
- var fruitEffectiveRadiusX = fruitHalfWidth * fruitCosAngle + fruitHalfHeight * fruitSinAngle;
- var fruitEffectiveRadiusY = fruitHalfHeight * fruitCosAngle + fruitHalfWidth * fruitSinAngle;
- // Calculate distance between centers
- var dx = fruitX - dropX;
- var dy = fruitY - dropY;
- // Use oriented bounding box approximation for more accurate collision detection
- // Add a small buffer (-5 pixels) to ensure the trajectory line extends all the way to the fruit
- if (Math.abs(dx) < activeEffectiveRadiusX + fruitEffectiveRadiusX - 5 && Math.abs(dy) < activeEffectiveRadiusY + fruitEffectiveRadiusY - 5) {
- return true;
- }
- return false;
- };
- for (var j = 0; j < fruits.length; j++) {
- var fruit = fruits[j];
- if (fruit !== activeFruit && !fruit.merging) {
- // Use the new intersection prediction method
+ // 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)) {
- hitFruit = true;
+ // 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;
+ }
+ }
+ hitDetected = true;
break;
}
}
}
- if (hitFruit) {
- break;
- }
}
+ // 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
+ 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;
});
/****
@@ -508,18 +392,17 @@
/****
* Game Code
****/
-// Game variables
-var gameOverLine; // Game over line
-var pineapple; // The pineapple that pushes in from left
-var pineappleActive = false; // Track if pineapple is active in gameplay
-var pineapplePushCount = 0; // Count how many times pineapple has been pushed
-var readyToReleaseCharged = false; // Flag to indicate if charged fruits are ready to be released
-var trajectoryLine; // Line showing trajectory of active fruit
-var chargedFruitIconScale = 0.3; // Global scale for charged fruit icons
-var isClickable = true; // Flag to track if the game accepts clicks
-// Centralized fruit levels definition
+// --- START OF FILE game code.txt ---
+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,
@@ -530,33 +413,26 @@
'PEACH': 8,
'COCONUT': 9,
'DURIAN': 10
};
-// Helper function to handle pineapple release on merge
function releasePineappleOnMerge() {
- // No longer charging on merges, only when dropping fruits
- // Increment merge counter for pineapple release on any merge
mergeCounter++;
- // Update pineapple position based on merge count
pushPineapple();
- // Check if we've reached 10 merges to release pineapple
if (mergeCounter >= 10 && !pineappleActive && pineapple) {
pineappleActive = true;
- // Pineapple is ready to drop after 10 merges
pineapple.isStatic = false;
- // Use standardized drop mechanics with less force for bigger fruit
applyDropPhysics(pineapple, 2.5);
- // Add to fruits array
fruits.push(pineapple);
- // Start 2-second timer to enable game over contact
+ // 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);
- // Setup a new pineapple for next cycle
setupPineapple();
- // Reset merge counter for next pineapple
mergeCounter = 0;
}
}
var FruitTypes = {
@@ -573,18 +449,15 @@
next: 'apple'
},
APPLE: {
id: 'apple',
- // Use the correct apple asset ID
size: 250,
- // Match apple asset width
points: 3,
- next: 'orange' // Apple merges into Orange
+ next: 'orange'
},
ORANGE: {
id: 'orange',
size: 200,
- // Match the actual orange asset width
points: 5,
next: 'watermelon'
},
WATERMELON: {
@@ -627,253 +500,168 @@
var gameWidth = 2048;
var gameHeight = 2732;
var fruits = [];
var nextFruitType = null;
-var activeFruit = null; // The fruit currently controlled by the player
+var activeFruit = null;
var wallLeft, wallRight, gameFloor;
-var dropPointY = 200; // Y coordinate where new fruits appear
+var dropPointY = 200;
var gameOver = false;
var scoreText;
-var isDragging = false; // Flag to check if the player is currently dragging
-var chargedBalls = []; // Array to hold charged ball icons
-var chargedBallContainer = null; // Container for charged ball icons
-var chargeCounter = 0; // Counter to track dropped balls for charging
-var mergeCounter = 0; // Counter to track merges for pineapple release
-var lastDroppedFruit = null; // Track last fruit dropped by player
-var lastDroppedHasMerged = false; // Track if last dropped fruit has already had its first merge
-var spatialGrid = null; // Spatial grid for optimized collision detection
-// Setup game boundaries
+var isDragging = false;
+var chargedBalls = [];
+var chargedBallContainer = null;
+var chargeCounter = 0;
+var mergeCounter = 0;
+var lastDroppedFruit = null;
+var lastDroppedHasMerged = false;
+var spatialGrid = null;
function setupBoundaries() {
- // Left wall
wallLeft = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
wallLeft.x = 0;
wallLeft.y = gameHeight / 2;
- // Right wall
wallRight = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
wallRight.x = gameWidth;
wallRight.y = gameHeight / 2;
- // Floor
gameFloor = game.addChild(LK.getAsset('floor', {
anchorX: 0.5,
anchorY: 0.5
}));
gameFloor.x = gameWidth / 2;
gameFloor.y = gameHeight;
- // Game over line
gameOverLine = game.addChild(new Line());
gameOverLine.x = gameWidth / 2;
- gameOverLine.y = 550; // Position 500 pixels lower than before
- gameOverLine.scaleX = 1; // Make it stretch across the entire width of the screen
- gameOverLine.scaleY = 0.2; // Make it thinner
- gameOverLine.alpha = 1; // Make the line visible again
+ gameOverLine.y = 550;
+ gameOverLine.scaleX = 1;
+ gameOverLine.scaleY = 0.2;
+ gameOverLine.alpha = 1;
}
-// Create new next fruit
function createNextFruit() {
- // Determine which fruit to spawn - only level 1 (CHERRY) or level 2 (GRAPE), never level 3+
var fruitProbability = Math.random();
- var fruitType;
- if (fruitProbability < 0.6) {
- fruitType = FruitTypes.CHERRY;
- } else {
- fruitType = FruitTypes.GRAPE;
- }
+ var fruitType = fruitProbability < 0.6 ? FruitTypes.CHERRY : FruitTypes.GRAPE;
nextFruitType = fruitType;
- // Update display
- // No explicit preview display needed, the next fruit will be the one the player controls
- // Create the active fruit
activeFruit = new Fruit(nextFruitType);
- // Position at the location of the last dropped fruit if available, otherwise at the top center
- if (lastDroppedFruit) {
- activeFruit.x = lastDroppedFruit.x;
- activeFruit.y = dropPointY + 200;
- } else {
- activeFruit.x = gameWidth / 2;
- activeFruit.y = dropPointY + 200;
- }
- // Make it static while the player controls it
- activeFruit.isStatic = true;
+ 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);
- // Update trajectory line for the new fruit
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
}
-// Drop fruit at specified position
function dropFruit() {
if (gameOver || !activeFruit || !isClickable) {
return;
}
- // Disable clicking for 300ms
isClickable = false;
LK.setTimeout(function () {
isClickable = true;
}, 300);
- // Make the active fruit dynamic so it drops
activeFruit.isStatic = false;
- // Use standardized drop mechanics
- applyDropPhysics(activeFruit, 3.5); // Standard force for normal fruits
- // Add the fruit to the main fruits array
+ applyDropPhysics(activeFruit, 3.5);
fruits.push(activeFruit);
- // Add the fruit to the spatial grid
- spatialGrid.insertObject(activeFruit);
- // Mark this as the player's dropped fruit to track its first merge
+ spatialGrid.insertObject(activeFruit); // Add to grid immediately
lastDroppedFruit = activeFruit;
lastDroppedHasMerged = false;
- // Increment charge counter when dropping a fruit
chargeCounter++;
- // Update the charged ball display
updateChargedBallDisplay();
- // Check if we've reached 9 charged balls
if (chargeCounter >= 9 && !readyToReleaseCharged) {
releaseChargedBalls();
}
- // Set merge grace period flag on the dropped fruit
activeFruit.mergeGracePeriodActive = true;
- // Start 2-second timer after which the grace period expires
LK.setTimeout(function () {
if (activeFruit && fruits.includes(activeFruit)) {
activeFruit.mergeGracePeriodActive = false;
}
}, 2000);
- // Hide all trajectory dots when fruit is dropped
if (trajectoryLine) {
for (var i = 0; i < trajectoryLine.dots.length; i++) {
trajectoryLine.dots[i].visible = false;
}
}
- // Reset fromChargedRelease flags on all fruits when a new player fruit is dropped
- // This allows previously released charged fruits to participate in new chain reactions
for (var i = 0; i < fruits.length; i++) {
if (fruits[i] && fruits[i].fromChargedRelease) {
fruits[i].fromChargedRelease = false;
}
}
- // Play drop sound
LK.getSound('drop').play();
- // Check if we have charged balls ready to be released
if (readyToReleaseCharged && chargeCounter >= 9) {
- // Play the PickleRick sound when releasing the charged fruits
LK.getSound('pickleRick').play();
- // Create and drop 1 level 3 (orange) ball from above the screen
- // Create the actual fruit
var orange = new Fruit(FruitTypes.ORANGE);
- // Position fruit at random horizontal position
var minX = wallLeft.x + wallLeft.width / 2 + orange.width / 2 + 50;
var maxX = wallRight.x - wallRight.width / 2 - orange.width / 2 - 50;
- var randomX = minX + Math.random() * (maxX - minX);
- // Place fruit above the screen
- orange.x = randomX;
+ orange.x = minX + Math.random() * (maxX - minX);
orange.y = -orange.height;
- // Make it dynamic so it drops
orange.isStatic = false;
- // Apply standard drop physics - slightly randomize forces for natural effect
var forceMultiplier = 3.5 + (Math.random() * 1 - 0.5);
applyDropPhysics(orange, forceMultiplier);
- // Mark this fruit as coming from charged release
orange.fromChargedRelease = true;
- // Add to game and fruits array
game.addChild(orange);
fruits.push(orange);
- // Reset charge counter
+ spatialGrid.insertObject(orange); // Add charged fruit to grid
chargeCounter = 0;
- // Reset charged balls UI
resetChargedBalls();
- // Reset the release flag
readyToReleaseCharged = false;
}
- // Charge counter is now only incremented on merges
- // We don't handle pineapple in dropFruit anymore - it's now managed in the merge function
- // Clear active fruit
- activeFruit = null;
- // Create the next fruit immediately
+ activeFruit = null; // Clear active fruit *before* creating next
createNextFruit();
}
-// Helper function to standardize drop physics for all fruits
function applyDropPhysics(fruit, forceMultiplier) {
- // Add angle variation - random angle between -10 and +10 degrees
- var angle = (Math.random() * 20 - 10) * (Math.PI / 180); // Convert to radians
- // Apply velocity based on angle
+ var angle = (Math.random() * 20 - 10) * (Math.PI / 180);
fruit.vx = Math.sin(angle) * forceMultiplier;
- fruit.vy = Math.abs(Math.cos(angle) * forceMultiplier); // Make sure initial Y velocity is downward
- // Mark this fruit as in safety period since it's newly dropped
- fruit.safetyPeriod = false;
- // Make it immune to game over for a second
+ fruit.vy = Math.abs(Math.cos(angle) * forceMultiplier);
+ fruit.safetyPeriod = false; // Start in safety period
fruit.immuneToGameOver = true;
- // Start 1-second timer to enable game over contact
LK.setTimeout(function () {
if (fruit && fruits.includes(fruit)) {
fruit.immuneToGameOver = false;
}
}, 1000);
}
-// Update score display
function updateScoreDisplay() {
scoreText.setText(LK.getScore());
}
-// Setup UI
function setupUI() {
- // Score display
scoreText = new Text2("0", {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 30;
- // Create charged ball grid
setupChargedBallDisplay();
}
-// Create charged ball grid
function setupChargedBallDisplay() {
- // Create container for charged balls
chargedBallContainer = new Container();
game.addChild(chargedBallContainer);
- // Position the container at the top of the screen - move down slightly
chargedBallContainer.y = 120;
- // Create a text element for the countdown display
var countdownText = new Text2("9", {
size: 100,
fill: 0x000000
});
countdownText.anchor.set(0.5, 0.5);
- countdownText.x = gameWidth / 2 + 270; // Position where the icons used to be
- // Add to container and store as the only item in chargedBalls array
+ countdownText.x = gameWidth / 2 + 270;
chargedBallContainer.addChild(countdownText);
chargedBalls.push(countdownText);
- // Center the container horizontally
chargedBallContainer.x = 0;
}
-// Function to update charged ball display
function updateChargedBallDisplay() {
- // Update the countdown number display
if (chargedBalls.length > 0) {
var countdownText = chargedBalls[0];
- var remainingCount = 9 - chargeCounter;
- // Update the text to show remaining charges needed
+ var remainingCount = Math.max(0, 9 - chargeCounter); // Ensure count doesn't go below 0
countdownText.setText(remainingCount.toString());
- // Change color based on remaining count
- var textColor;
- if (remainingCount <= 3) {
- textColor = 0xFF0000; // Red for nearly charged
- } else if (remainingCount <= 6) {
- textColor = 0xFFA500; // Orange for halfway charged
- } else {
- textColor = 0x000000; // Black for starting
- }
- // Apply color change with tween for smooth transition
+ var textColor = remainingCount <= 3 ? 0xFF0000 : remainingCount <= 6 ? 0xFFA500 : 0x000000;
tween(countdownText, {
tint: textColor
}, {
duration: 300,
easing: tween.easeOut
});
- // Make text larger as countdown gets closer to 0
var baseSize = 1.0;
var sizeMultiplier = baseSize + 0.2 * (9 - remainingCount);
tween(countdownText, {
scaleX: sizeMultiplier,
@@ -883,567 +671,534 @@
easing: tween.easeOut
});
}
}
-// Function to prepare charged balls for release (just sets a flag, doesn't release them yet)
function releaseChargedBalls() {
- // Don't play drop sound here anymore as we're not dropping yet
- // Just set a flag to indicate we have charged balls ready to be released
readyToReleaseCharged = true;
- // Update the countdown text to show "0" and make it pulse
if (chargedBalls.length > 0) {
+ var countdownText = chargedBalls[0];
+ countdownText.setText("0");
+ tween(countdownText, {
+ tint: 0xFF0000
+ }, {
+ duration: 300,
+ easing: tween.easeOut
+ });
var _pulseText = function pulseText() {
+ if (!readyToReleaseCharged) {
+ return;
+ } // Stop pulsing if released
tween(countdownText, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
+ if (!readyToReleaseCharged) {
+ return;
+ }
tween(countdownText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
- onFinish: function onFinish() {
- // Only continue pulsing if still ready to release
- if (readyToReleaseCharged) {
- _pulseText();
- }
- }
+ onFinish: _pulseText
});
}
});
- }; // Start the pulsing animation
- var countdownText = chargedBalls[0];
- // Set text to "0" since we're fully charged
- countdownText.setText("0");
- // Make text bright red to indicate fully charged
- tween(countdownText, {
- tint: 0xFF0000
- }, {
- duration: 300,
- easing: tween.easeOut
- });
- // Make text pulse by animating size
+ };
_pulseText();
}
- // We don't reset the charge counter or UI here - we'll do that when the charged fruits are actually released
}
-// Separate function to reset charged balls UI
function resetChargedBalls() {
- // Reset the countdown text display
if (chargedBalls.length > 0) {
var countdownText = chargedBalls[0];
- // Reset text to "9" since we're starting fresh
countdownText.setText("9");
- // Reset color with tween
tween(countdownText, {
tint: 0x000000
}, {
duration: 200,
easing: tween.easeOut
});
- // Reset size to original
tween(countdownText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
}
- // No need to call updateChargedBallDisplay() as we set the state directly here
}
-// Check for fruit collisions using spatial partitioning
+// Centralized collision check and resolution
function checkFruitCollisions() {
- for (var i = 0; i < fruits.length; i++) {
+ // Iterate backwards for safe removal during merge
+ outerLoop: for (var i = fruits.length - 1; i >= 0; i--) {
var fruit1 = fruits[i];
- // Skip collision for the active fruit
- if (fruit1 === activeFruit || fruit1.merging) {
+ if (!fruit1 || fruit1 === activeFruit || fruit1.merging || fruit1.isStatic) {
continue;
}
- // Get potential collision candidates from spatial grid instead of checking all fruits
var candidates = spatialGrid.getPotentialCollisions(fruit1);
for (var j = 0; j < candidates.length; j++) {
var fruit2 = candidates[j];
- // Skip collision for the active fruit
- if (fruit2 === activeFruit || fruit2.merging) {
+ // 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;
}
- // Calculate distance between centers
+ // 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);
- // Check if they are overlapping - use actual asset dimensions for more accurate hitboxes
- // Calculate half dimensions for both fruits
+ // 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;
- // Check for collision using Rectangle Intersection algorithm
- // First, calculate the distance between centers on each axis
var absDistanceX = Math.abs(dx);
var absDistanceY = Math.abs(dy);
- // Then calculate the sum of half-widths and half-heights
var combinedHalfWidths = fruit1HalfWidth + fruit2HalfWidth;
var combinedHalfHeights = fruit1HalfHeight + fruit2HalfHeight;
- // Check for same type fruits - merge immediately if their AABBs touch or overlap
- if (fruit1.type === fruit2.type) {
- // Use AABB intersection check with '<=' to merge on exact contact or overlap
- if (absDistanceX <= combinedHalfWidths && absDistanceY <= combinedHalfHeights) {
- // Use <= instead of <
- // Trigger merge
+ var colliding = absDistanceX < combinedHalfWidths && absDistanceY < combinedHalfHeights;
+ if (colliding) {
+ // Merge Check
+ if (fruit1.type === fruit2.type) {
fruit1.merge(fruit2);
- // Break the inner loop since fruit1/fruit2 are merging
- break; //{4A} // Ensure the break remains if needed
+ // 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
}
- // If they are the same type but don't merge based on AABB check, skip normal physics resolution for this pair
- continue; // Skip normal collision handling for same-type fruits that aren't overlapping enough to merge yet
- } //{4C} // Keep original line identifier if structure remains similar
- // If fruits are of different types, check for collision using AABB (this block remains for different types)
- if (absDistanceX < combinedHalfWidths && absDistanceY < combinedHalfHeights) {
- // Resolve collision (simple separation and velocity adjustment)
- var combinedRadius = Math.min(combinedHalfWidths, combinedHalfHeights);
- var overlap = combinedRadius - distance;
- var normalizeX = dx / distance;
- var normalizeY = dy / distance;
- var moveX = overlap / 2 * normalizeX;
- var moveY = overlap / 2 * normalizeY;
- fruit1.x -= moveX;
- fruit1.y -= moveY;
- fruit2.x += moveX;
- fruit2.y += moveY;
- // Calculate relative velocity
- var rvX = fruit2.vx - fruit1.vx;
- var rvY = fruit2.vy - fruit1.vy;
- var contactVelocity = rvX * normalizeX + rvY * normalizeY;
- // Only resolve if velocities are separating
- if (contactVelocity < 0) {
- // Use the higher elasticity for the collision (smaller fruits bounce more)
- var collisionElasticity = Math.max(fruit1.elasticity, fruit2.elasticity);
- var impulse = -(1 + collisionElasticity) * contactVelocity;
- var totalMass = fruit1.type.size + fruit2.type.size; // Using size as a proxy for mass
- var impulse1 = impulse * (fruit2.type.size / totalMass);
- var impulse2 = impulse * (fruit1.type.size / totalMass);
- // Apply impact scaling for smaller fruits against bigger ones
- // Smaller fruits should bounce away more from larger fruits
- var sizeDifference = Math.abs(fruit1.type.size - fruit2.type.size) / Math.max(fruit1.type.size, fruit2.type.size);
- if (fruit1.type.size < fruit2.type.size) {
- impulse1 *= 1 + sizeDifference * 0.5; // Smaller fruit gets extra bounce
- } else if (fruit2.type.size < fruit1.type.size) {
- impulse2 *= 1 + sizeDifference * 0.5; // Smaller fruit gets extra bounce
+ // Collision Resolution (Different Types)
+ else {
+ if (distance === 0) {
+ // Prevent division by zero if perfectly overlapped
+ distance = 0.1;
+ dx = distance; // Give a slight horizontal separation
+ dy = 0;
}
- fruit1.vx -= impulse1 * normalizeX;
- fruit1.vy -= impulse1 * normalizeY;
- fruit2.vx += impulse2 * normalizeX;
- fruit2.vy += impulse2 * normalizeY;
- // Apply friction between colliding fruits
- var tangentX = -normalizeY;
- var tangentY = normalizeX;
- var tangentVelocity = rvX * tangentX + rvY * tangentY;
- var frictionImpulse = -tangentVelocity * 0.2; // Increased friction factor
- fruit1.vx -= frictionImpulse * tangentX;
- fruit1.vy -= frictionImpulse * tangentY;
- fruit2.vx += frictionImpulse * tangentX;
- fruit2.vy += frictionImpulse * tangentY;
- // Enhanced rotation physics for fruit-to-fruit contact
- // Calculate impact point and angular momentum transfer
- var relativeImpactX = (fruit2.x - fruit1.x) / distance;
- var relativeImpactY = (fruit2.y - fruit1.y) / distance;
- // Calculate tangential component of relative velocity for rotational transfer
- var tangentialComponent = rvX * tangentX + rvY * tangentY;
- // Calculate rotation transfer factor based on contact point and relative velocity
- var rotationTransferFactor = 0.03; // Strength of rotation transfer
- // Transfer rotation between fruits based on their relative positions and velocities
- var fruit1RotationImpulse = tangentialComponent * rotationTransferFactor;
- var fruit2RotationImpulse = -tangentialComponent * rotationTransferFactor;
- // Apply size-based scaling to rotation transfer (smaller fruits rotate more)
- // Using global fruitLevels definition
- var sizeFactor1 = 1 + (10 - fruitLevels[fruit1.type.id.toUpperCase()] || 1) * 0.1;
- var sizeFactor2 = 1 + (10 - fruitLevels[fruit2.type.id.toUpperCase()] || 1) * 0.1;
- // Apply rotation impulses with size adjustments
- fruit1.angularVelocity += fruit1RotationImpulse * sizeFactor1;
- fruit2.angularVelocity += fruit2RotationImpulse * sizeFactor2;
- // Also transfer some existing rotation between fruits
- var rotationExchangeFactor = 0.15; // How much of existing rotation transfers between fruits
- var rotationDifference = fruit2.angularVelocity - fruit1.angularVelocity;
- fruit1.angularVelocity += rotationDifference * rotationExchangeFactor;
- fruit2.angularVelocity -= rotationDifference * rotationExchangeFactor;
- // Apply additional angular damping during collisions
- fruit1.angularVelocity *= 0.9;
- fruit2.angularVelocity *= 0.9;
- // Cap angular velocity
- fruit1.angularVelocity = Math.min(Math.max(fruit1.angularVelocity, -fruit1.maxAngularVelocity), fruit1.maxAngularVelocity);
- fruit2.angularVelocity = Math.min(Math.max(fruit2.angularVelocity, -fruit2.maxAngularVelocity), fruit2.maxAngularVelocity);
- if (Math.abs(contactVelocity) > 1) {
- // Bounce sound removed
+ var overlap = combinedHalfWidths - absDistanceX; // Simplified overlap estimation
+ 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 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 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
+ 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;
+ // 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
+ 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 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);
+ }
}
}
}
}
}
-// Check if game is over (fruits touching the red line)
function checkGameOver() {
if (gameOver) {
return;
}
- // Remove "too many fruits" game over condition
- // Check if any fruits are touching the red line
for (var i = 0; i < fruits.length; i++) {
- // Don't check game over for the active fruit
- if (fruits[i] === activeFruit) {
+ var fruit = fruits[i];
+ if (!fruit || fruit === activeFruit || fruit.merging || fruit.isStatic) {
continue;
}
- // Check if fruit touches the game over line
- // For more accurate collision, calculate if any part of the fruit is above the line
- var fruit = fruits[i];
var fruitHalfHeight = fruit.height / 2;
var fruitHalfWidth = fruit.width / 2;
- // Calculate the effective height based on fruit's rotation
var cosAngle = Math.abs(Math.cos(fruit.rotation));
var sinAngle = Math.abs(Math.sin(fruit.rotation));
var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle;
- // Check if the top of the fruit is above/at the game over line
var fruitTopY = fruit.y - effectiveHeight;
- var lineBottomY = gameOverLine.y + gameOverLine.height / 2;
- // For wider fruits, check if any part of the fruit is above the line
- var effectiveWidth = fruitHalfWidth * cosAngle + fruitHalfHeight * sinAngle;
- var fruitLeftX = fruit.x - effectiveWidth;
- var fruitRightX = fruit.x + effectiveWidth;
- var lineLeftX = gameOverLine.x - gameOverLine.width / 2;
- var lineRightX = gameOverLine.x + gameOverLine.width / 2;
- // Check for horizontal overlap to determine if the fruit is actually over the line
- var horizontalOverlap = !(fruitRightX < lineLeftX || fruitLeftX > lineRightX);
- if (!fruit.merging && fruitTopY <= lineBottomY && horizontalOverlap) {
- // Skip game over if the fruit is immune (any fruit in grace period)
- if (fruit.immuneToGameOver) {
- continue;
- }
- // Initialize the safetyPeriod property if not already set
- if (fruits[i].safetyPeriod === undefined) {
- // If the fruit is still moving downward, it's probably just spawned
- if (fruits[i].vy > 0) {
- // Mark this fruit as in safety period - it has just been dropped
- fruits[i].safetyPeriod = false;
- continue; // Skip game over check for freshly dropped fruits
+ var lineBottomY = gameOverLine.y + gameOverLine.height / 2; // Use line's actual bottom
+ 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 lineRightX = gameOverLine.x + gameOverLine.width * gameOverLine.scaleX / 2;
+ var horizontalOverlap = !(fruitRightX < lineLeftX || fruitLeftX > lineRightX);
+ if (horizontalOverlap) {
+ if (fruit.immuneToGameOver) {
+ continue; // Skip check if immune
}
- // If the fruit has hit something and bounced back or is stable, it's no longer in safety period
- if (fruits[i].vy <= 0) {
- fruits[i].safetyPeriod = true; // Mark that we've checked and it's now unsafe
+ // 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
+ 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
+ }
}
+ // 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
+ }
}
- // Only trigger game over if the fruit is not in safety period
- if (fruits[i].safetyPeriod) {
- // Trigger game over when a fruit touches the line after having bounced/settled
- gameOver = true;
- LK.showGameOver();
- 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
}
}
}
-// Create and setup the pineapple
function setupPineapple() {
pineapple = new Fruit(FruitTypes.PINEAPPLE);
- pineapple.x = -pineapple.width / 2; // Start completely off screen
- pineapple.y = 200; // Position 200 pixels higher than before
- pineapple.isStatic = true; // Make it static until it's dropped
- pineappleActive = false; // Not active in gameplay yet
- pineapplePushCount = 0; // Reset push count
+ 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 to push the pineapple based on merge counter
function pushPineapple() {
- // Only push if not already active in gameplay
if (!pineappleActive && pineapple) {
- // Calculate new x position based on merge counter (10 steps total)
- var step = mergeCounter; // Use merge counter directly
- var totalSteps = 10; // Need 10 merges for full pineapple entry
+ var step = mergeCounter;
+ var totalSteps = 10;
var percentage = Math.min(step / totalSteps, 1.0);
var startPos = -pineapple.width / 2;
- var endPos = gameWidth * 0.16; // Final position before release
+ var endPos = gameWidth * 0.16;
var newX = startPos + percentage * (endPos - startPos);
- // Animate the push
tween(pineapple, {
x: newX
}, {
duration: 300,
easing: tween.bounceOut
});
}
}
-// Initialize game
function initGame() {
LK.setScore(0);
gameOver = false;
- fruits = [];
+ 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;
- chargedBalls = [];
+ chargedBalls = []; // Clear charged balls array
+ if (chargedBallContainer) {
+ chargedBallContainer.destroy();
+ } // Remove old UI container
+ chargedBallContainer = null;
readyToReleaseCharged = false;
lastScoreCheckForCoconut = 0;
lastDroppedFruit = null;
lastDroppedHasMerged = false;
- mergeCounter = 0; // Add counter to track merges for pineapple release
- isClickable = true; // Reset click state when game initializes
- // Initialize spatial grid for collision detection
+ mergeCounter = 0;
+ isClickable = true;
if (spatialGrid) {
spatialGrid.clear();
} else {
- spatialGrid = new SpatialGrid(200); // Cell size of 200px
+ spatialGrid = new SpatialGrid(250); // Adjusted cell size slightly
}
- // We no longer reset fromChargedRelease flag here
- // as we want it to persist only for the current chain reaction
- // fromChargedRelease is now reset in the merge function when needed
- // Start background music
LK.playMusic('bgmusic');
- // Setup game elements
- setupBoundaries();
- setupUI();
- setupPineapple(); // Setup the pineapple
- // Create trajectory line
+ // 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();
- // Ensure all UI balls are properly initialized to inactive state
- LK.setTimeout(function () {
- if (chargedBalls.length === 3) {
- resetChargedBalls();
- }
- }, 100);
+ // Reset charged ball display explicitly after setup
+ resetChargedBalls();
}
-// Function to spawn coconut from bottom of screen
function spawnCoconut() {
var coconut = new Fruit(FruitTypes.COCONUT);
- // Randomly position coconut horizontally within game area
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);
- // Position below the screen
coconut.y = gameHeight + coconut.height / 2;
- // Make it static while animating into place
- coconut.isStatic = true;
- // Play the Stonks sound when coconut appears
+ coconut.isStatic = true; // Start static for animation
LK.getSound('stonks').play();
- // Add to game and fruits array
game.addChild(coconut);
fruits.push(coconut);
- // Add to spatial grid for collision detection
- spatialGrid.insertObject(coconut);
- // Mark as in safety period
- coconut.safetyPeriod = false;
- // Make it immune to game over for a second
+ // Don't add to grid yet, wait for animation finish
+ coconut.safetyPeriod = false; // Initial state
coconut.immuneToGameOver = true;
- // Use tween to smoothly animate the coconut entering the screen from below
- // Calculate target Y position where the coconut is fully in the board
var targetY = gameHeight - gameFloor.height / 2 - coconut.height / 2 - 10;
- // Animate entry with an easeOut effect and gradually increasing speed
tween(coconut, {
y: targetY
}, {
duration: 1200,
- // 1.2 seconds for a faster entry
easing: tween.easeIn,
- // Start slow and speed up
onFinish: function onFinish() {
- // Once fully entered, make it dynamic so it can interact with other fruits
+ if (!coconut || !fruits.includes(coconut)) {
+ return;
+ } // Check if coconut still exists
coconut.isStatic = false;
- // Give it a small upward push to make it bounce slightly when it enters
- coconut.vy = -2;
- // Add random horizontal velocity for natural movement
+ coconut.vy = -2; // Small bounce
coconut.vx = (Math.random() * 2 - 1) * 1.5;
- // Start 1-second timer to enable game over contact
+ spatialGrid.insertObject(coconut); // Add to grid *after* becoming dynamic
LK.setTimeout(function () {
if (coconut && fruits.includes(coconut)) {
coconut.immuneToGameOver = false;
}
}, 1000);
}
});
}
-// Track last score checked for coconut spawn
var lastScoreCheckForCoconut = 0;
// Event handlers
game.down = function (x, y) {
- // We don't need to check specific boundaries to start dragging.
- // As long as there's an active fruit, we can start dragging.
- if (activeFruit) {
+ if (activeFruit && !gameOver) {
+ // Prevent interaction if game over
isDragging = true;
- // Update active fruit position immediately
- game.move(x, y);
+ game.move(x, y); // Initial move to cursor
}
};
-// Mouse or touch move on game object
game.move = function (x, y) {
- if (isDragging && activeFruit) {
- // Only move the active fruit on the X axis - use actual fruit width
+ 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));
- // Update trajectory line
+ // Keep Y fixed at the top while dragging
+ activeFruit.y = dropPointY + 200;
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
}
};
-// Mouse or touch up on game object
game.up = function () {
- if (isDragging && activeFruit && isClickable) {
+ if (isDragging && activeFruit && isClickable && !gameOver) {
dropFruit();
}
- isDragging = false;
+ isDragging = false; // Always reset dragging on up
};
-// Game update loop
-game.update = function () {
- // Check if we've reached a new 500-point threshold
- var currentScore = LK.getScore();
- if (Math.floor(currentScore / 500) > Math.floor(lastScoreCheckForCoconut / 500)) {
- // Spawn a coconut for every 500 points
- spawnCoconut();
- }
- lastScoreCheckForCoconut = currentScore;
- // We no longer need to check if pineapple is in the board
- // as we now use push count to determine when it's ready
- // Apply physics and check collisions for each fruit
+// 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];
- if (fruit.isStatic || fruit.merging) {
+ // Skip static, merging, or destroyed fruits
+ if (!fruit || fruit.isStatic || fruit.merging) {
continue;
}
- // Store last position for boundary checks
- if (fruit.lastY === undefined) {
- fruit.lastY = fruit.y;
- }
- if (fruit.lastX === undefined) {
- fruit.lastX = fruit.x;
- }
+ // --- Start Physics Application ---
// Apply gravity
fruit.vy += fruit.gravity;
- // Apply velocity
+ // Apply velocity to position
fruit.x += fruit.vx;
fruit.y += fruit.vy;
- // Apply rotation
+ // Apply angular velocity to rotation
fruit.rotation += fruit.angularVelocity;
- // Apply friction
+ // Apply linear friction
fruit.vx *= fruit.friction;
fruit.vy *= fruit.friction;
- // Apply angular friction
+ // Apply basic angular friction
fruit.angularVelocity *= fruit.angularFriction;
- // Apply progressive angular damping based on linear movement
- // The slower the fruit is moving, the more angular friction is applied
- var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy);
- var restThreshold = 0.05; // Very slow movement threshold
- if (movementMagnitude < restThreshold) {
- // When barely moving, apply very strong angular damping to bring rotation to complete stop
- fruit.angularVelocity *= 0.80; // Strong damping when almost at rest
- // Force fruits to stop rotating completely when angular velocity is very small
- if (Math.abs(fruit.angularVelocity) < 0.005) {
- fruit.angularVelocity = 0;
+ // --- Advanced Damping and Resting Logic ---
+ // Check if fruit is nearly at rest (low linear velocity)
+ if (Math.abs(fruit.vx) < 0.5 && Math.abs(fruit.vy) < 0.5) {
+ fruit.angularVelocity *= 0.85; // Increased damping
+ if (Math.abs(fruit.angularVelocity) < 0.02) {
+ fruit.rotationRestCounter++;
+ if (fruit.rotationRestCounter > 45) {
+ // ~0.75 seconds
+ fruit.angularVelocity = 0;
+ fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2); // Snap rotation
+ }
+ } else {
+ fruit.rotationRestCounter = 0; // Reset counter if spinning up again
}
+ } else {
+ fruit.rotationRestCounter = 0; // Reset if moving significantly
+ }
+ // Progressive angular damping based on overall movement speed
+ var movementMagnitude = Math.sqrt(fruit.vx * fruit.vx + fruit.vy * fruit.vy);
+ if (movementMagnitude < 0.05) {
+ // Very slow
+ fruit.angularVelocity *= 0.80;
} else if (movementMagnitude < 0.3) {
- // Moderate movement, stronger damping
+ // Moderately slow
fruit.angularVelocity *= 0.85;
} else if (movementMagnitude < 0.8) {
- // More movement, moderate damping
- fruit.angularVelocity *= 0.9;
+ // Moderate speed
+ fruit.angularVelocity *= 0.90;
}
- // Gradually reduce angular velocity when fruit is in contact with floor
- var floorProximity = Math.abs(fruit.y - (gameFloor.y - gameFloor.height / 2 - fruit.height / 2));
- if (floorProximity < 5) {
- // Apply extra floor friction to rotation when touching or very close to floor
- fruit.angularVelocity *= 0.85;
+ // Force rotation stop if very slow angular velocity
+ if (Math.abs(fruit.angularVelocity) < 0.005) {
+ fruit.angularVelocity = 0;
}
- // Clamp angular velocity
- fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity);
- // Wall collision - use actual fruit width for accurate collision
- var fruitHalfWidth = fruit.width / 2; // Use half width of the asset
- var fruitHalfHeight = fruit.height / 2; // Use half height of the asset
- // Calculate effective width based on rotation angle for more accurate wall collision
+ // --- Boundary Collisions (Walls and Floor) ---
+ 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;
- // Left wall collision with rotation-aware bounds
- if (fruit.x < wallLeft.x + wallLeft.width / 2 + effectiveWidth) {
- fruit.x = wallLeft.x + wallLeft.width / 2 + effectiveWidth;
+ var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle;
+ // Left Wall
+ var leftBoundary = wallLeft.x + wallLeft.width / 2 + effectiveWidth;
+ if (fruit.x < leftBoundary) {
+ fruit.x = leftBoundary;
fruit.vx = -fruit.vx * fruit.elasticity;
- // Smaller fruits get more angular velocity from impacts
+ // Angular impulse from wall hit
var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5);
- fruit.angularVelocity += fruit.vy * angularImpactMultiplier * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact
- // Apply wall friction - stronger when in wall contact and proportional to fruit size
- var wallFriction = 0.65 + (fruit.elasticity - 0.7) * 0.5; // More elastic (smaller) fruits get less wall friction
- fruit.angularVelocity *= wallFriction;
- fruit.angularVelocity *= fruit.groundAngularFriction;
- if (Math.abs(fruit.vx) > 1) {
- // Bounce sound removed
- }
- } else if (fruit.x > wallRight.x - wallRight.width / 2 - effectiveWidth) {
- fruit.x = wallRight.x - wallRight.width / 2 - effectiveWidth;
+ fruit.angularVelocity += fruit.vy * angularImpactMultiplier * 0.5; // Adjusted impact direction
+ fruit.angularVelocity *= fruit.groundAngularFriction; // Wall friction on rotation
+ fruit.wallContactFrames++; // Increment wall contact
+ }
+ // Right Wall
+ else if (fruit.x > wallRight.x - wallRight.width / 2 - effectiveWidth) {
+ var rightBoundary = wallRight.x - wallRight.width / 2 - effectiveWidth;
+ fruit.x = rightBoundary;
fruit.vx = -fruit.vx * fruit.elasticity;
- // Smaller fruits get more angular velocity from impacts
+ // Angular impulse from wall hit
var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5);
- fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact
- // Apply wall friction - stronger when in wall contact and proportional to fruit size
- var wallFriction = 0.65 + (fruit.elasticity - 0.7) * 0.5; // More elastic (smaller) fruits get less wall friction
- fruit.angularVelocity *= wallFriction;
- fruit.angularVelocity *= fruit.groundAngularFriction;
- if (Math.abs(fruit.vx) > 1) {
- // Bounce sound removed
- }
+ fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * 0.5; // Adjusted impact direction
+ fruit.angularVelocity *= fruit.groundAngularFriction; // Wall friction on rotation
+ fruit.wallContactFrames++; // Increment wall contact
+ } else {
+ fruit.wallContactFrames = 0; // Reset wall contact if not touching
}
- // Floor collision - use cached values for better performance
- // Use already calculated cosAngle and sinAngle values from earlier wall collision check
- var effectiveHeight = fruitHalfHeight * cosAngle + fruitHalfWidth * sinAngle;
+ // Apply progressive wall friction based on contact frames
+ if (fruit.wallContactFrames > 0) {
+ var progressiveFriction = Math.min(0.85, 0.65 + fruit.wallContactFrames * 0.01);
+ fruit.angularVelocity *= progressiveFriction;
+ }
+ // Floor Collision
var floorCollisionY = gameFloor.y - gameFloor.height / 2 - effectiveHeight;
- // Use the values we already calculated above
if (fruit.y > floorCollisionY) {
- fruit.y = gameFloor.y - gameFloor.height / 2 - effectiveHeight;
+ fruit.y = floorCollisionY;
+ var oldVy = fruit.vy; // Store velocity before bounce for sound check
fruit.vy = -fruit.vy * fruit.elasticity;
+ // Angular impulse from floor hit (based on horizontal movement)
if (Math.abs(fruit.vx) > 0.5) {
- // Smaller fruits get more angular velocity from impacts
var angularImpactMultiplier = 0.01 * (1 + (0.9 - fruit.elasticity) * 5);
- fruit.angularVelocity += fruit.vx * angularImpactMultiplier * (fruit.vy / Math.abs(fruit.vy || 1)); // Apply angular velocity based on horizontal velocity and direction of impact
+ fruit.angularVelocity += fruit.vx * angularImpactMultiplier * 0.5; // Adjusted impact factor
}
- // Smaller fruits should spin longer after impact
- var angularDamping = fruit.elasticity > 0.85 ? 0.85 : fruit.groundAngularFriction;
- fruit.angularVelocity *= angularDamping;
- // Smaller fruits take more time to come to rest
- var restThreshold = 1 + (fruit.elasticity - 0.7) * 10;
+ // Apply stronger ground angular friction
+ fruit.angularVelocity *= fruit.groundAngularFriction;
+ // Resting conditions on floor
+ var restThreshold = 1 + (fruit.elasticity - 0.7) * 10; // Scale rest threshold
if (Math.abs(fruit.vy) < restThreshold) {
- fruit.vy = 0;
+ fruit.vy = 0; // Stop vertical bounce if below threshold
}
- // Angular rest threshold should also scale with elasticity
- var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2);
- if (Math.abs(fruit.angularVelocity) < angularRestThreshold) {
- fruit.angularVelocity = 0;
- // If on floor and almost stopped rotating, snap to nearest quarter rotation for visual alignment
- if (Math.abs(fruit.vy) < 0.1) {
- fruit.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2);
- }
+ var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2); // Scale angular rest
+ 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.rotation = Math.round(fruit.rotation / (Math.PI / 2)) * (Math.PI / 2);
}
- if (Math.abs(fruit.vy) > 1) {
- // Bounce sound removed
- }
}
- // Update last positions
- fruit.lastX = fruit.x;
- fruit.lastY = fruit.y;
- }
- // Update positions in spatial grid for all fruits
+ // Clamp final angular velocity
+ fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity);
+ // --- Safety Period Update ---
+ // If it was freshly dropped (false) and stopped moving down or moved up, it's no longer in initial drop phase
+ if (fruit.safetyPeriod === false && fruit.vy <= 0.1) {
+ fruit.safetyPeriod = undefined; // Reset safety check state (needs to settle/bounce for game over)
+ }
+ // --- 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].isStatic && !fruits[i].merging) {
+ if (fruits[i] && !fruits[i].isStatic && !fruits[i].merging) {
spatialGrid.updateObject(fruits[i]);
}
}
- // Check for fruit collisions using spatial partitioning
- checkFruitCollisions();
- // Check game over conditions
- checkGameOver();
+}
+// 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
+ }
+ // 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
};
// Initialize the game
-initGame();
\ No newline at end of file
+initGame();
+// --- END OF FILE game code.txt ---
\ No newline at end of file