User prompt
add a 200 padding to both sides of the container for the charged fruits, so that the first and last fruits aren't so close to the edges of the screen
Code edit (1 edits merged)
Please save this source code
User prompt
and now ensure the charged fruits array is perfectly centered in the middle, as right now it's offset to the right
User prompt
make the array of charging fruits shorter, so the first and last fruits dont touch the edge of the screen, instead bring them closer to the middle of the screen
User prompt
change the charged fruits level from 1 to level 3, and instead of 5 have only 3 of them
User prompt
make the charge mechanic only charge on merges done by player dropping a fruit. only th first merge done by the fruit dropped by the player with a fruit on the board count towards a charge
User prompt
remove any other game over logic from the code, other than the one where fruits on the board touch the red line. a fruit on the board is considered any fruit that has interacted with the gameplay area, either by touching the ground, a wall or another fruit
User prompt
sometimes the game goes to game over when too many merges happen very fast, een though no fruit on the board has touched the game over line, fix this, only the red line should trigger a game over when touched by a fruit from the board
User prompt
change the charging fruits number from 6 to 5
User prompt
change the charging fruits level from level 3 to level 1
User prompt
the 6 released fruits spawn from a different position than their UI counterparts, which is a bug. WHen a fruit turns it's alpha from 50% to 100%, that 100% alpha fruit needs to be the same fruit that drops, so when dropping the fruit, keep the same coordinates as the fruit that turned 100% alpha
User prompt
the charging fruits seem to charge when the player manually drops a fruit, but that logic should be removed. a fruit should only charge when a merge occurs on the board, that's the only way to charge up a charging fruit. and also, on resetting the charges, the assets still remain small, I think it has something to do with the tween naiamtion that doesnt properly trigger thus the assets remain stuck at their small size. remove the tween effect completely
User prompt
consolidate the 6 charging fruits logic in a single place, to avoid current recharging UI issues like we have right now. after releasing the fruits and refilling, the next batch of apples is of a much smaller size than the actual apple icons size, so that shouldnt be the case. and also, when they drop, they seem to drop from a higher point then the apples that turn 100% visible, so that position needs to be singular and use for both showing the icons but also used as the release point when they get released
User prompt
✅ Remove redundant charge ball update code that causes performance issues ✅ Fix bug in chargeCounter logic and simplify condition ✅ Fix duplicate check in DURIAN fruits merge condition ✅ Optimize fruit collisions by improved loop and early breaking for performance 🔄 Optimize update loop by caching values and removing redundant calculations
User prompt
Please fix the bug: 'ReferenceError: effectiveHeight is not defined' in or related to this line: 'if (fruit.y > gameFloor.y - gameFloor.height / 2 - effectiveHeight) {' Line Number: 975
User prompt
✅ Fix duplicate merge sounds playing on each successful merge ✅ Remove duplicate play of merge sound in merge function ✅ Optimize fruit update method with better variable tracking and physics calculations ✅ Optimize floor collision detection by caching calculated values and removing duplicate code
User prompt
when durian frutis touch each other, they need to simulate a merge, but instead of leveling up they simply disappear from the board. they do increase the score
User prompt
Please fix the bug: 'ReferenceError: combinedRadius is not defined' in or related to this line: 'var overlap = combinedRadius - distance;' Line Number: 598
User prompt
improve the merging collision detection mechanism to be the same as the actual assets size hitbow. right now, they seem to have a regular circle collision box even for fruits that are rectangles
Code edit (1 edits merged)
Please save this source code
User prompt
move the red game over line 200 pixels higher
User prompt
level 10 fruits should merge with each other as well, but instead of evolving to level 11, they simply disappear from the board and award their points
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
when the 6 apples get released, they spawn a bit higher then the actual location they're intended, fix this, so the spawn location coincides with the same y position they start from. spawn them 100 pixels lower
/**** * 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); // FruitTypes is being used before it's defined, so we need to handle this case self.type = type; self.vx = 0; self.vy = 0; self.rotation = 0; self.angularVelocity = 0; self.angularFriction = 0.92; // Increased friction for faster angular velocity reduction self.groundAngularFriction = 0.75; // Stronger ground friction to stop spinning faster self.gravity = 1.8; // Doubled gravity to make fruits drop faster self.friction = 0.98; // 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 var fruitLevels = { 'CHERRY': 1, 'GRAPE': 2, 'APPLE': 3, 'ORANGE': 4, 'WATERMELON': 5, 'PINEAPPLE': 6, 'MELON': 7, 'PEACH': 8, 'COCONUT': 9, 'DURIAN': 10 }; 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 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.9; // Apply damping when fruit is almost at rest } // 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; } }; 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 merge animation for both fruits 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() { // Play merge sound once for all fruit types LK.getSound('merge').play(); // Only increment charge counter if one of the fruits is the player's recently dropped fruit // and it hasn't had its first merge yet if ((self === lastDroppedFruit || otherFruit === lastDroppedFruit) && !lastDroppedHasMerged) { chargeCounter++; updateChargedBallDisplay(); lastDroppedHasMerged = true; // Mark that this fruit has had its first merge } // Special case for DURIAN - they simply disappear instead of merging if (self.type.id.toUpperCase() === 'DURIAN') { // Add points based on the durian's value LK.setScore(LK.getScore() + self.type.points); updateScoreDisplay(); // Remove both fruits removeFruitFromGame(self); removeFruitFromGame(otherFruit); // Check if we've reached 3 charged balls if (chargeCounter >= 3) { // Changed condition from 5 to 3 releaseChargedBalls(); } } else { // 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 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); // Check if we've reached 3 charged balls if (chargeCounter >= 3) { // Changed condition from 5 to 3 releaseChargedBalls(); } } } }); // Helper function to remove a fruit from the game function removeFruitFromGame(fruit) { var index = fruits.indexOf(fruit); if (index !== -1) { fruits.splice(index, 1); } 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 }); // 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 return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf6e58d }); /**** * 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 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: 300, 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; // The fruit currently controlled by the player var wallLeft, wallRight, gameFloor; var dropPointY = 200; // Y coordinate where new fruits appear 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 lastDroppedFruit = null; // Track last fruit dropped by player var lastDroppedHasMerged = false; // Track if last dropped fruit has already had its first merge // Setup game boundaries 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 = 200; // Position 200 pixels higher than before gameOverLine.scaleX = 1; // Make it stretch across the entire width of the screen gameOverLine.scaleY = 0.2; // Make it thinner } // Create new next fruit function createNextFruit() { // Determine which fruit to spawn (for now just start with smaller fruits) var fruitProbability = Math.random(); var fruitType; if (fruitProbability < 0.6) { fruitType = FruitTypes.CHERRY; } else if (fruitProbability < 0.85) { fruitType = FruitTypes.GRAPE; } else { fruitType = FruitTypes.APPLE; } 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 top center initially, but 100px lower than before activeFruit.x = gameWidth / 2; activeFruit.y = dropPointY; // Make it static while the player controls it activeFruit.isStatic = true; game.addChild(activeFruit); } // Drop fruit at specified position function dropFruit() { if (gameOver || !activeFruit) { return; } // 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 fruits.push(activeFruit); // Mark this as the player's dropped fruit to track its first merge lastDroppedFruit = activeFruit; lastDroppedHasMerged = false; // Play drop sound LK.getSound('drop').play(); // Charge counter is now only incremented on merges // Handle pineapple logic if (pineappleActive) { // Pineapple is ready to drop after pushes 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 LK.setTimeout(function () { pineapple.immuneToGameOver = false; }, 2000); // Setup a new pineapple for next cycle setupPineapple(); } else { // Push the pineapple further in pushPineapple(); } // Clear active fruit activeFruit = null; // Create the next fruit immediately 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 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.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("SCORE: " + LK.getScore()); } // Setup UI function setupUI() { // Score display scoreText = new Text2("SCORE: 0", { size: 80, fill: 0xFFFFFF }); 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 chargedBallContainer.y = 700; // Use consistent values for UI balls matching the actual game fruit var ballType = FruitTypes.APPLE; // Level 3 ball type // Calculate spacing to evenly distribute 3 balls with 200px padding on both sides var padding = 200; // 200px padding on both sides var spacing = (gameWidth - padding * 2) / 2; // Evenly distribute 3 balls with padding var startX = padding; // Starting position with padding from left edge // Store the reference size for later use in release animation var uiAppleScale = 0.75; // Use scaled size for apple UI elements // Create 3 balls in a single row for (var i = 0; i < 3; i++) { // Changed loop to 3 var ball = new Container(); // Use apple as charged balls with consistent scale var ballGraphics = ball.attachAsset('apple', { anchorX: 0.5, anchorY: 0.5 }); // Apply consistent scale for UI elements ball.scaleX = uiAppleScale; ball.scaleY = uiAppleScale; // Position in single row with equal spacing ball.x = startX + i * spacing; // Set to semi-transparent initially ball.alpha = 0.5; // Add to container and array chargedBallContainer.addChild(ball); chargedBalls.push(ball); } // Center the container horizontally chargedBallContainer.x = 0; } // Function to update charged ball display function updateChargedBallDisplay() { // Update the visibility of balls based on chargeCounter for (var i = 0; i < chargedBalls.length; i++) { if (i < chargeCounter) { // Balls that are charged are fully visible if (chargedBalls[i].alpha !== 1) { tween(chargedBalls[i], { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } } else { // Balls that are not yet charged are semi-transparent if (chargedBalls[i].alpha !== 0.5) { chargedBalls[i].alpha = 0.5; } } } } // Function to release all charged balls function releaseChargedBalls() { // Play drop sound LK.getSound('drop').play(); // Create and drop 3 level 3 (apple) balls immediately for (var i = 0; i < 3; i++) { // Changed loop to 3 // No disappearance animation for icons // Create the actual fruit from the same position as the UI ball var apple = new Fruit(FruitTypes.APPLE); // Create Apple instead of Cherry // Get the global position of the charged ball var chargedBallPosition = chargedBalls[i].parent.toGlobal(chargedBalls[i].position); var gamePosition = game.toLocal(chargedBallPosition); // Position the apple at the exact same position as the UI ball apple.x = gamePosition.x; apple.y = gamePosition.y; // Use the calculated Y position from the UI ball // Make it dynamic so it drops apple.isStatic = false; // Apply standard drop physics applyDropPhysics(apple, 3.5); // Add to game and fruits array game.addChild(apple); fruits.push(apple); } // Reset charge counter immediately chargeCounter = 0; // Reset the charged ball display immediately for (var j = 0; j < chargedBalls.length; j++) { // Fully reset all properties chargedBalls[j].alpha = 0.5; chargedBalls[j].scaleX = 0.75; // Reset to apple scale chargedBalls[j].scaleY = 0.75; // Reset to apple scale } // No need to call updateChargedBallDisplay() as we set the state directly here } // Check for fruit collisions function checkFruitCollisions() { for (var i = 0; i < fruits.length; i++) { var fruit1 = fruits[i]; // Skip collision for the active fruit if (fruit1 === activeFruit || fruit1.merging) { continue; } for (var j = i + 1; j < fruits.length; j++) { var fruit2 = fruits[j]; // Skip collision for the active fruit if (fruit2 === activeFruit || fruit2.merging) { continue; } // Calculate distance between centers 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 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; // If distance on either axis is less than combined halves, we have an overlap 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 } 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; // Apply angular velocity change based on collision with proportional damping var fruit1AngularImpulse = impulse1 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005; var fruit2AngularImpulse = impulse2 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005; fruit1.angularVelocity += fruit1AngularImpulse; fruit2.angularVelocity -= fruit2AngularImpulse; // 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 } } // Only proceed with merge if fruits are the same type if (fruit1.type === fruit2.type) { // Merge fruits if they are close enough and of the same type fruit1.merge(fruit2); break; } } } } } // 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) { 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; if (!fruit.merging && fruitTopY <= lineBottomY) { // 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 } // 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 } } // 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; } } } } // 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 game.addChild(pineapple); } // Function to push the pineapple function pushPineapple() { // Only push if not already active in gameplay if (!pineappleActive) { pineapplePushCount++; // Calculate new x position (100px per push instead of 200px) var newX = -pineapple.width / 2 + pineapplePushCount * 70; // Animate the push tween(pineapple, { x: newX }, { duration: 300, easing: tween.bounceOut }); // Check if we've pushed the pineapple 7 times if (pineapplePushCount >= 7) { pineappleActive = true; // Mark as ready to drop on next fruit release } } } // Initialize game function initGame() { LK.setScore(0); gameOver = false; fruits = []; chargeCounter = 0; chargedBalls = []; lastScoreCheckForCoconut = 0; lastDroppedFruit = null; lastDroppedHasMerged = false; // Start background music LK.playMusic('bgmusic'); // Setup game elements setupBoundaries(); setupUI(); setupPineapple(); // Setup the pineapple updateScoreDisplay(); createNextFruit(); } // 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 LK.getSound('stonks').play(); // Add to game and fruits array game.addChild(coconut); fruits.push(coconut); // Mark as in safety period coconut.safetyPeriod = false; // Make it immune to game over for a second 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 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.vx = (Math.random() * 2 - 1) * 1.5; // Start 1-second timer to enable game over contact 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) { isDragging = true; // Update active fruit position immediately game.move(x, y); } }; // 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 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)); } }; // Mouse or touch up on game object game.up = function () { if (isDragging && activeFruit) { dropFruit(); } isDragging = false; }; // 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 for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (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; } // Apply gravity fruit.vy += fruit.gravity; // Apply velocity fruit.x += fruit.vx; fruit.y += fruit.vy; // Apply rotation fruit.rotation += fruit.angularVelocity; // Apply friction fruit.vx *= fruit.friction; fruit.vy *= fruit.friction; // Apply angular friction fruit.angularVelocity *= fruit.angularFriction; // Force fruits to stop rotating when they're barely moving if (Math.abs(fruit.vx) < 0.1 && Math.abs(fruit.vy) < 0.1 && Math.abs(fruit.angularVelocity) < 0.03) { fruit.angularVelocity = 0; } // Apply stronger angular friction when moving slowly if (Math.abs(fruit.vx) < 0.8 && Math.abs(fruit.vy) < 0.8) { fruit.angularVelocity *= 0.9; } // 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 // For non-circular fruits, adjust collision bounds based on asset orientation // Use the larger dimension to ensure no part of the fruit goes through walls var collisionRadius = Math.max(fruitHalfWidth, fruitHalfHeight); if (fruit.x < wallLeft.x + wallLeft.width / 2 + collisionRadius) { fruit.x = wallLeft.x + wallLeft.width / 2 + collisionRadius; fruit.vx = -fruit.vx * fruit.elasticity; // Smaller fruits get more angular velocity from impacts 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 - collisionRadius) { fruit.x = wallRight.x - wallRight.width / 2 - collisionRadius; fruit.vx = -fruit.vx * fruit.elasticity; // Smaller fruits get more angular velocity from impacts 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 } } // Floor collision - use cached values for better performance // 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; 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.vy = -fruit.vy * fruit.elasticity; 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 } // 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; if (Math.abs(fruit.vy) < restThreshold) { fruit.vy = 0; } // 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 (Math.abs(fruit.vy) > 1) { // Bounce sound removed } } // Update last positions fruit.lastX = fruit.x; fruit.lastY = fruit.y; } // Check for fruit collisions checkFruitCollisions(); // Check game over conditions checkGameOver(); }; // Initialize the game initGame();
===================================================================
--- original.js
+++ change.js
@@ -453,36 +453,36 @@
// Position the container at the top of the screen
chargedBallContainer.y = 700;
// Use consistent values for UI balls matching the actual game fruit
var ballType = FruitTypes.APPLE; // Level 3 ball type
- // Calculate spacing to create a more centered row of 3 balls
- var totalWidth = gameWidth * 0.5; // Use half of game width for the entire row
- var spacing = totalWidth / 2; // Divide by (n-1) where n is number of balls
- var startX = gameWidth / 2 - totalWidth / 2 + spacing / 2; // Center the row horizontally
+ // Calculate spacing to evenly distribute 3 balls with 200px padding on both sides
+ var padding = 200; // 200px padding on both sides
+ var spacing = (gameWidth - padding * 2) / 2; // Evenly distribute 3 balls with padding
+ var startX = padding; // Starting position with padding from left edge
// Store the reference size for later use in release animation
var uiAppleScale = 0.75; // Use scaled size for apple UI elements
// Create 3 balls in a single row
for (var i = 0; i < 3; i++) {
// Changed loop to 3
var ball = new Container();
// Use apple as charged balls with consistent scale
var ballGraphics = ball.attachAsset('apple', {
- anchorX: 0.0,
+ anchorX: 0.5,
anchorY: 0.5
});
// Apply consistent scale for UI elements
ball.scaleX = uiAppleScale;
ball.scaleY = uiAppleScale;
- // Position in single row with centered spacing
+ // Position in single row with equal spacing
ball.x = startX + i * spacing;
// Set to semi-transparent initially
ball.alpha = 0.5;
// Add to container and array
chargedBallContainer.addChild(ball);
chargedBalls.push(ball);
}
// Center the container horizontally
- chargedBallContainer.x = (gameWidth - totalWidth) / 2;
+ chargedBallContainer.x = 0;
}
// Function to update charged ball display
function updateChargedBallDisplay() {
// Update the visibility of balls based on chargeCounter