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"
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
/****
* 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 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
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
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 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
}
}
// 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();
// 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
}
// 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);
// 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
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;
}
} else {
// 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
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;
}
// 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);
// 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);
}
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;
});
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.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.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 dotCount = 0;
var hitFruit = false;
// Create dots in a straight line directly downward
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
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;
if (dotY > floorCollisionY) {
// Stop at the 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
if (self.wouldIntersectFruit(fruit.x, fruit.y, startX, dotY, activeFruit, fruit)) {
hitFruit = true;
break;
}
}
}
if (hitFruit) {
break;
}
}
};
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 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
var FruitTypes = {
CHERRY: {
id: 'cherry',
size: 150,
points: 1,
next: 'grape'
},
GRAPE: {
id: 'grape',
size: 200,
points: 2,
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
},
ORANGE: {
id: 'orange',
size: 200,
// Match the actual orange asset width
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 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
// 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 = 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
}
// 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;
}
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;
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
fruits.push(activeFruit);
// Mark this as the player's dropped fruit to track its first merge
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.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
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
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(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
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
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
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,
scaleY: sizeMultiplier
}, {
duration: 300,
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 _pulseText = function pulseText() {
tween(countdownText, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
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();
}
}
});
}
});
}; // 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
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;
// 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
fruit1.merge(fruit2);
// Break the inner loop since fruit1/fruit2 are merging
break; //{4A} // Ensure the break remains if needed
}
// 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
}
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)
// Access fruitLevels from Fruit class definition
var fruitLevels = {
'CHERRY': 1,
'GRAPE': 2,
'APPLE': 3,
'ORANGE': 4,
'WATERMELON': 5,
'PINEAPPLE': 6,
'MELON': 7,
'PEACH': 8,
'COCONUT': 9,
'DURIAN': 10
};
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
}
}
}
}
}
}
// 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;
// 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
}
// 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 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 percentage = Math.min(step / totalSteps, 1.0);
var startPos = -pineapple.width / 2;
var endPos = gameWidth * 0.16; // Final position before release
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 = [];
chargeCounter = 0;
chargedBalls = [];
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
// 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
if (trajectoryLine) {
trajectoryLine.destroy();
}
trajectoryLine = game.addChild(new TrajectoryLine());
trajectoryLine.createDots();
updateScoreDisplay();
createNextFruit();
// Ensure all UI balls are properly initialized to inactive state
LK.setTimeout(function () {
if (chargedBalls.length === 3) {
resetChargedBalls();
}
}, 100);
}
// 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));
// Update trajectory line
if (trajectoryLine) {
trajectoryLine.updateTrajectory(activeFruit.x, activeFruit.y);
}
}
};
// Mouse or touch up on game object
game.up = function () {
if (isDragging && activeFruit && isClickable) {
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
// Calculate effective width based on rotation angle for more accurate wall collision
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;
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 - effectiveWidth) {
fruit.x = wallRight.x - wallRight.width / 2 - effectiveWidth;
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
@@ -949,8 +949,21 @@
// 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)
+ // Access fruitLevels from Fruit class definition
+ var fruitLevels = {
+ 'CHERRY': 1,
+ 'GRAPE': 2,
+ 'APPLE': 3,
+ 'ORANGE': 4,
+ 'WATERMELON': 5,
+ 'PINEAPPLE': 6,
+ 'MELON': 7,
+ 'PEACH': 8,
+ 'COCONUT': 9,
+ 'DURIAN': 10
+ };
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;