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 (2 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
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
User prompt
when 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
Code edit (2 edits merged)
Please save this source code
User prompt
consolidate the y position of the apples. right now their starting position and their spawning position differs, so they charge in a position but spawn a bit higher, ensure this position is used for both loading and spawning
User prompt
all new generated fruits should have one second delay before interacting with the game over trigger so that if I release fruits super fast they don't trigger a game over
User prompt
after releasing the pineapple from the left side onto the bboard, disabble its contact with the game over for 2 seconds after release. this delay is meant to not cause a gameover immediately after releasing it, allowing it to fall inside the board first. after the 2 seconds delay, then it becomes active βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
move the pineapple that slides from the left, 200 pixels higher
User prompt
mak the coconut appear once every 500 points instead of every 200 please.
User prompt
make the coconut sldie in a bit faster on the board
User prompt
create a sound named Stonks and play it when a new coconut appears every 200 points
User prompt
make a coconut only appear after very 200 points instead of 100
User prompt
the coconut should slide from below the screen, much slower, like increase it's speed upwards as it slides. right now it snaps into place, it doesn't slide, make sure the speed is much slower so it takes a while for it to actully fully enter the screen, giving players a chance to see it coming from below βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
the coconut should slide in slower in the board βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
/****
* 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();
// Increment charge counter once for any fruit merge
chargeCounter++;
updateChargedBallDisplay();
// 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 6 charged balls
if (chargeCounter >= 6) {
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 6 charged balls
if (chargeCounter >= 6) {
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
// 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);
// Play drop sound
LK.getSound('drop').play();
// Increment charge counter for each fruit dropped
chargeCounter++;
// Update charged ball display
updateChargedBallDisplay();
// Check if we've reached 6 charged balls
if (chargeCounter >= 6) {
// Release all charged balls
releaseChargedBalls();
}
// 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 6 balls
var spacing = (gameWidth - 200) / 5; // Evenly distribute 6 balls
var startX = 100; // Starting position from left edge
// Store the reference size for later use in release animation
var uiAppleScale = 0.8; // Scale down UI apples by 20% for better appearance
// Create 6 balls in a single row
for (var i = 0; i < 6; i++) {
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 6 level 3 (apple) balls
for (var i = 0; i < 6; i++) {
// First animate the charged balls before creating the actual fruits
tween(chargedBalls[i], {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function (index) {
return function () {
// Create the actual fruit from the same position as the UI ball
var apple = new Fruit(FruitTypes.APPLE);
// Get the global position of the charged ball
var chargedBallPosition = chargedBalls[index].parent.toGlobal(chargedBalls[index].position);
var gamePosition = game.toLocal(chargedBallPosition);
// Position the apple at the exact same position as the UI ball
apple.x = gamePosition.x;
apple.y = dropPointY; // Use consistent drop point Y
// Make it dynamic so it drops
apple.isStatic = false;
// Add random horizontal velocity and downward vertical velocity
var angle = (Math.random() * 20 - 10) * (Math.PI / 180);
var force = 3.5;
apple.vx = Math.sin(angle) * force;
apple.vy = Math.abs(Math.cos(angle) * force);
// Mark this fruit as in safety period since it's newly dropped
apple.safetyPeriod = false;
// Make it immune to game over for a second
apple.immuneToGameOver = true;
// Start 1-second timer to enable game over contact
LK.setTimeout(function () {
if (apple && fruits.includes(apple)) {
apple.immuneToGameOver = false;
}
}, 1000);
// Add to game and fruits array
game.addChild(apple);
fruits.push(apple);
};
}(i)
});
}
// Reset charge counter
chargeCounter = 0;
// Update the charged ball display after a delay - ensure complete reset
LK.setTimeout(function () {
for (var i = 0; i < chargedBalls.length; i++) {
// Fully reset all properties
chargedBalls[i].alpha = 0.5;
// Remove any scaling to ensure original size
chargedBalls[i].scaleX = 1;
chargedBalls[i].scaleY = 1;
}
updateChargedBallDisplay();
}, 300);
}
// 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 (too many fruits on screen or stacked too high)
function checkGameOver() {
if (gameOver) {
return;
}
if (fruits.length > 30) {
gameOver = true;
LK.showGameOver();
return;
}
// Check if any fruits are too high
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;
}
}
// Only check for game over if fruit is high AND has had time to settle
// Use the fruit's height to determine proper position check
var fruitRadius = fruits[i].height / 2;
if (fruits[i].y < 300 + fruitRadius && !fruits[i].merging) {
var isFruitMoving = Math.abs(fruits[i].vx) > 0.5 || Math.abs(fruits[i].vy) > 0.5;
// Add a countdown to ensure the fruit has truly stopped moving
if (!isFruitMoving) {
// Initialize the stable timer if not already set
if (fruits[i].stableTimer === undefined) {
fruits[i].stableTimer = 60; // Give 1 second (60 frames) to confirm stability
} else {
fruits[i].stableTimer--;
if (fruits[i].stableTimer <= 0) {
gameOver = true;
LK.showGameOver();
return;
}
}
} else {
// Reset timer if the fruit starts moving again
fruits[i].stableTimer = undefined;
}
}
}
}
// 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;
// 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
@@ -54,11 +54,19 @@
// 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
@@ -68,18 +76,15 @@
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
- var fruitRadius = self.width / 2;
- var isContactingLeftWall = self.x <= wallLeft.x + wallLeft.width / 2 + fruitRadius + 2;
- var isContactingRightWall = self.x >= wallRight.x - wallRight.width / 2 - fruitRadius - 2;
+ // 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
- if (!self.wallContactFrames) {
- self.wallContactFrames = 1;
- } else {
- self.wallContactFrames++;
- }
+ 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 {
@@ -89,13 +94,13 @@
// 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 !== this && !otherFruit.merging && !otherFruit.isStatic) {
- var dx = otherFruit.x - this.x;
- var dy = otherFruit.y - this.y;
+ 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 = (this.width + otherFruit.width) / 2;
+ var combinedHalfWidths = (self.width + otherFruit.width) / 2;
if (distance < combinedHalfWidths + 2) {
// Small buffer for contact detection
isContactingOtherFruit = true;
break;
@@ -111,17 +116,19 @@
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
+ // Create merge animation for both fruits
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
@@ -136,80 +143,66 @@
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
+ // Play merge sound once for all fruit types
+ LK.getSound('merge').play();
+ // Increment charge counter once for any fruit merge
+ chargeCounter++;
+ updateChargedBallDisplay();
// 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();
- // Play merge sound
- LK.getSound('merge').play();
- // Remove old fruits
- var indexSelf = fruits.indexOf(self);
- if (indexSelf !== -1) {
- fruits.splice(indexSelf, 1);
- }
- self.destroy();
- var indexOther = fruits.indexOf(otherFruit);
- if (indexOther !== -1) {
- fruits.splice(indexOther, 1);
- }
- otherFruit.destroy();
- // Charge one ball icon per merge
- chargeCounter++;
- updateChargedBallDisplay();
+ // Remove both fruits
+ removeFruitFromGame(self);
+ removeFruitFromGame(otherFruit);
// Check if we've reached 6 charged balls
if (chargeCounter >= 6) {
- // Release all charged balls on the next merge
releaseChargedBalls();
}
} else {
// Normal merge behavior for all other fruits
- // Create new merged fruit
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();
- // Play merge sound
- LK.getSound('merge').play();
// Animate new fruit growing
tween(newFruit, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
- // Remove old fruits
- var indexSelf = fruits.indexOf(self);
- if (indexSelf !== -1) {
- fruits.splice(indexSelf, 1);
- }
- self.destroy();
- var indexOther = fruits.indexOf(otherFruit);
- if (indexOther !== -1) {
- fruits.splice(indexOther, 1);
- }
- otherFruit.destroy();
- // Charge one ball icon per merge
- chargeCounter++;
- updateChargedBallDisplay();
+ // Remove both original fruits
+ removeFruitFromGame(self);
+ removeFruitFromGame(otherFruit);
// Check if we've reached 6 charged balls
if (chargeCounter >= 6) {
- // Release all charged balls on the next merge
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 () {
@@ -375,45 +368,29 @@
return;
}
// Make the active fruit dynamic so it drops
activeFruit.isStatic = false;
- // Add angle variation - random angle between -10 and +10 degrees
- var angle = (Math.random() * 20 - 10) * (Math.PI / 180); // Convert to radians
- var force = 3.5; // Force magnitude
- // Apply velocity based on angle
- activeFruit.vx = Math.sin(angle) * force;
- activeFruit.vy = Math.abs(Math.cos(angle) * force); // Make sure initial Y velocity is downward
- // Mark this fruit as in safety period since it's newly dropped
- activeFruit.safetyPeriod = false;
- // Make it immune to game over for a second
- activeFruit.immuneToGameOver = true;
- // Start 1-second timer to enable game over contact
- LK.setTimeout(function () {
- if (activeFruit && fruits.includes(activeFruit)) {
- activeFruit.immuneToGameOver = false;
- }
- }, 1000);
+ // Use standardized drop mechanics
+ applyDropPhysics(activeFruit, 3.5); // Standard force for normal fruits
// Add the fruit to the main fruits array
fruits.push(activeFruit);
// Play drop sound
LK.getSound('drop').play();
+ // Increment charge counter for each fruit dropped
+ chargeCounter++;
// Update charged ball display
updateChargedBallDisplay();
- // Removed charge counter increment from here
+ // Check if we've reached 6 charged balls
+ if (chargeCounter >= 6) {
+ // Release all charged balls
+ releaseChargedBalls();
+ }
// Handle pineapple logic
if (pineappleActive) {
- // Pineapple is ready to drop after 3 pushes
+ // Pineapple is ready to drop after pushes
pineapple.isStatic = false;
- // Add angle variation
- var pineappleAngle = (Math.random() * 10 - 5) * (Math.PI / 180);
- var pineappleForce = 2.5; // Less force for bigger fruit
- // Apply velocity
- pineapple.vx = Math.sin(pineappleAngle) * pineappleForce;
- pineapple.vy = Math.abs(Math.cos(pineappleAngle) * pineappleForce);
- // Mark as in safety period and disable game over contact for 2 seconds
- pineapple.safetyPeriod = false;
- // Make a property to track if pineapple is immune to game over
- pineapple.immuneToGameOver = true;
+ // 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 () {
@@ -429,8 +406,26 @@
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());
}
@@ -451,28 +446,33 @@
function setupChargedBallDisplay() {
// Create container for charged balls
chargedBallContainer = new Container();
game.addChild(chargedBallContainer);
- // Position the container at the top of the screen, 100 pixels higher than before
+ // Position the container at the top of the screen
chargedBallContainer.y = 700;
- // Calculate total width needed for all balls
+ // Use consistent values for UI balls matching the actual game fruit
var ballType = FruitTypes.APPLE; // Level 3 ball type
- var ballSize = ballType.size; // Get actual size of apple
+ // Calculate spacing to evenly distribute 6 balls
var spacing = (gameWidth - 200) / 5; // Evenly distribute 6 balls
- // Horizontal position to start the row
var startX = 100; // Starting position from left edge
+ // Store the reference size for later use in release animation
+ var uiAppleScale = 0.8; // Scale down UI apples by 20% for better appearance
// Create 6 balls in a single row
for (var i = 0; i < 6; i++) {
var ball = new Container();
- // Use apple as charged balls (level 3) with actual size
+ // 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
@@ -501,47 +501,54 @@
}
}
// Function to release all charged balls
function releaseChargedBalls() {
+ // Play drop sound
+ LK.getSound('drop').play();
// Create and drop 6 level 3 (apple) balls
for (var i = 0; i < 6; i++) {
- var apple = new Fruit(FruitTypes.APPLE);
- // Distribute the balls across the width of the game
- var offset = i * (gameWidth - 300) / 5 + 150;
- apple.x = offset;
- apple.y = dropPointY; // Position 100 pixels lower
- // Make it dynamic so it drops
- apple.isStatic = false;
- // Add random horizontal velocity and downward vertical velocity
- var angle = (Math.random() * 20 - 10) * (Math.PI / 180);
- var force = 3.5;
- apple.vx = Math.sin(angle) * force;
- apple.vy = Math.abs(Math.cos(angle) * force);
- // Mark this fruit as in safety period since it's newly dropped
- apple.safetyPeriod = false;
- // Make it immune to game over for a second
- apple.immuneToGameOver = true;
- // Start 1-second timer to enable game over contact
- LK.setTimeout(function () {
- if (apple && fruits.includes(apple)) {
- apple.immuneToGameOver = false;
- }
- }, 1000);
- // Add to game and fruits array
- game.addChild(apple);
- fruits.push(apple);
- // Add animation to make it look like the charged balls are dropping
+ // First animate the charged balls before creating the actual fruits
tween(chargedBalls[i], {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
- easing: tween.easeIn
+ easing: tween.easeIn,
+ onFinish: function (index) {
+ return function () {
+ // Create the actual fruit from the same position as the UI ball
+ var apple = new Fruit(FruitTypes.APPLE);
+ // Get the global position of the charged ball
+ var chargedBallPosition = chargedBalls[index].parent.toGlobal(chargedBalls[index].position);
+ var gamePosition = game.toLocal(chargedBallPosition);
+ // Position the apple at the exact same position as the UI ball
+ apple.x = gamePosition.x;
+ apple.y = dropPointY; // Use consistent drop point Y
+ // Make it dynamic so it drops
+ apple.isStatic = false;
+ // Add random horizontal velocity and downward vertical velocity
+ var angle = (Math.random() * 20 - 10) * (Math.PI / 180);
+ var force = 3.5;
+ apple.vx = Math.sin(angle) * force;
+ apple.vy = Math.abs(Math.cos(angle) * force);
+ // Mark this fruit as in safety period since it's newly dropped
+ apple.safetyPeriod = false;
+ // Make it immune to game over for a second
+ apple.immuneToGameOver = true;
+ // Start 1-second timer to enable game over contact
+ LK.setTimeout(function () {
+ if (apple && fruits.includes(apple)) {
+ apple.immuneToGameOver = false;
+ }
+ }, 1000);
+ // Add to game and fruits array
+ game.addChild(apple);
+ fruits.push(apple);
+ };
+ }(i)
});
}
- // Play drop sound
- LK.getSound('drop').play();
// Reset charge counter
chargeCounter = 0;
// Update the charged ball display after a delay - ensure complete reset
LK.setTimeout(function () {