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"
User prompt
Please fix the bug: 'ReferenceError: fruitLevels is not defined' in or related to this line: 'var sizeFactor1 = 1 + (10 - fruitLevels[fruit1.type.id.toUpperCase()] || 1) * 0.1;' Line Number: 976
User prompt
identify and remove any duplicate code
User prompt
fruits keep rotating infinitely, you need to find a system that brings them to a rest
User prompt
Please fix the bug: 'ReferenceError: fruitLevels is not defined' in or related to this line: 'var sizeFactor1 = 1 + (10 - fruitLevels[fruit1.type.id.toUpperCase()] || 1) * 0.1;' Line Number: 979
User prompt
it seems fruits only rotate when in contact with the floor or the walls, but they should also rotate when touching each other. basically, they need to rotate off of other fruits, thus imprinting rotation on them as well
User prompt
let's change the charging mechanism UI. instead of showing 9 different icons, remove that, and replace it with showing a number that countsdown from 9 to 0. at 0 that means the element has charged and on the next fruit drop, the timer resets to 9 and the fruti is released ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
the explosion either doesnt happen or the force is too small, make it really push ther fruits when a merge occurs
User prompt
when 2 fruits merge, create a mini explosion around them, pushing adjacent fruits away. the larger the fruit, the more impactful the explosion. no need to include a visual explosion, just the force. keep it simple, dont rewrite the items levels, create a simple formula that scales based on level
User prompt
ensure the released fruit is a orange not an apple, when you release the fruit from the top
User prompt
there's a bug with orange fruits as they are rectangular not squre, and because of this they often dont merge when intersecting. they need for their centers to be pushed really hard against each other for this to happen. pls fix this bug so they merge as soon as ANY part of their hit boxes touch
User prompt
there's a bug with orangefruits as they are rectangular not squre, and because of this they often dont merge when intersecting. they need for their centers to be pushed really hard against each other for this to happen. pls fix this bug so they merge as soon as ANY part of their hit boxes touch
User prompt
improve the collision detection between fruits of the same level, so they merge instantly as soon as any part of them touches the other. right now they have to hit their actual centers, which makes it difficult to merge them
User prompt
improve the collision detection between fruits of the same level, so they merge instantly as soon as any part of them touches the other. right now they have to hit their actual centers, which makes it difficult to merge them
User prompt
improve the collision detection between fruits of the same level, so they merge instantly as soon as any part of them touches the other. right now they have to hit their actual centers, which makes it difficult to merge them
User prompt
the orange colision box is all messed up now ,ensure it's the same as it's size, ensure all fruits have their hitboxes the same as their asset sizes
User prompt
when releasing a fruit, drop an orange instead of an apple. also update the UI icons
User prompt
when two peaches merge, play the stonks sound effect
User prompt
make the score color black
User prompt
when two melons merge, play the sound "Smartz"
User prompt
the "this isfine" sound should only play when the coconuts merge, not when two durians merge
/**** * 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 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--) gotoNextOuterLoop; // Use label to break inner 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); } } } } // Label for skipping to next outer loop iteration after a merge label: gotoNextOuterLoop; } } 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();
===================================================================
--- original.js
+++ change.js
@@ -732,9 +732,9 @@
}
// Centralized collision check and resolution
function checkFruitCollisions() {
// Iterate backwards for safe removal during merge
- outerLoop: for (var i = fruits.length - 1; i >= 0; i--) {
+ for (var i = fruits.length - 1; i >= 0; i--) {
var fruit1 = fruits[i];
if (!fruit1 || fruit1 === activeFruit || fruit1.merging || fruit1.isStatic) {
continue;
}
@@ -766,9 +766,9 @@
// 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
+ gotoNextOuterLoop; // Use label to break inner and continue outer correctly after merge
}
// Collision Resolution (Different Types)
else {
if (distance === 0) {
@@ -848,8 +848,10 @@
}
}
}
}
+ // Label for skipping to next outer loop iteration after a merge
+ label: gotoNextOuterLoop;
}
}
function checkGameOver() {
if (gameOver) {
@@ -1199,6 +1201,5 @@
// 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 ---
\ No newline at end of file
+initGame();
\ No newline at end of file