User prompt
Yatay ve dikey 1 piksel yap
User prompt
Toplar birbirine daha yakın olsun
User prompt
Yukardan düşün top statik topa carptıgında statik toplar saga sola kaysın ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fırlayan top oyun alanından cıkmasın
User prompt
Yukarıdan düşen top oyun alanındaki topa çarptıgın alan etkisi yaratsın çerptıgı toplar saga sola fırlasın ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Yukarıdan gelen topa agırlık fizigi ekle çarpıştıgında bölgedeki toplar hareket etsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Yukardan düşen top oyın alanındaki toplarla çarpiştiginda alttaki toplar hareket etsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Yukardan düşen top düştügü alandaki topları hareket ettirsin ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Farklı top boyutlarını hesaba katan daha gelişmiş paketleme kullan
User prompt
Aralıklar hala çok fazla
User prompt
Yan ve üstten toplar birbirine temas etsin
User prompt
Hala temas yok
User prompt
Oyun alanındaki Toplar birbirine sıfıra sıfır temas etsin
User prompt
Topların birbirine tam temasını sagla
User prompt
Hala fazla
User prompt
Top aralıkları hala cok fazla
User prompt
Topların büyüklügüme göre top aralıklarını ayarla olabildigince temas
User prompt
Birlelme sesi arttır
User prompt
Topları biraz daha büyğlt
User prompt
Toplar sürekli zıplıyor düzelt
User prompt
Please fix the bug: 'TypeError: tween.stopAll is not a function' in or related to this line: 'tween.stopAll(balls[i]);' Line Number: 669 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
1. 2. 4. 5. 6. Gerçekleştir
User prompt
Birleşme efecti ekle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
30 yap
User prompt
Daha sıkı top yerleşimi için toplar arası yatay dikey çapraz aralıgı 40 piksel
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function (value) {
var self = Container.call(this);
self.value = value || 2;
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.8;
self.bounce = 0.4;
self.friction = 0.98;
// Set radius based on ball value for proper physics
var radiusMap = {
2: 140,
4: 145,
8: 155,
16: 165,
32: 175,
64: 185,
128: 195,
256: 205,
512: 215,
1024: 220,
2048: 230
};
self.radius = radiusMap[self.value] || 60;
self.isStatic = false;
self.mergeTimer = 0;
self.hasBeenMerged = false;
var ballAsset;
try {
ballAsset = self.attachAsset('ball' + self.value, {
anchorX: 0.5,
anchorY: 0.5
});
} catch (e) {
// Fallback for 2048 ball if image asset fails
if (self.value === 2048) {
ballAsset = self.attachAsset('ball2048top', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
throw e;
}
}
var valueText = new Text2(self.value.toString(), {
size: 56,
fill: 0xFFFFFF
});
valueText.anchor.set(0.5, 0.5);
self.addChild(valueText);
self.update = function () {
if (self.hasBeenMerged) return;
if (self.mergeTimer > 0) {
self.mergeTimer--;
return;
}
// Apply physics only if not static
if (!self.isStatic) {
// Store previous position for continuous collision detection
var prevX = self.x;
var prevY = self.y;
// Apply gravity
self.velocityY += self.gravity;
// Calculate intended new position
var newX = self.x + self.velocityX;
var newY = self.y + self.velocityY;
// Continuous collision detection - check path from current to intended position
var stepCount = Math.max(1, Math.ceil(Math.abs(self.velocityX) + Math.abs(self.velocityY)) / (self.radius * 0.5));
var stepX = (newX - self.x) / stepCount;
var stepY = (newY - self.y) / stepCount;
var collisionOccurred = false;
// Check each step along the movement path
for (var step = 1; step <= stepCount && !collisionOccurred; step++) {
var testX = self.x + stepX * step;
var testY = self.y + stepY * step;
// Test collision with other balls
for (var i = 0; i < balls.length; i++) {
var otherBall = balls[i];
if (otherBall === self || otherBall.hasBeenMerged || otherBall.mergeTimer > 0) continue;
var dx = otherBall.x - testX;
var dy = otherBall.y - testY;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = self.radius + otherBall.radius;
if (distance < minDistance) {
// Collision detected - stop at safe position
var safeDistance = minDistance + 2; // Add small buffer
if (distance > 0) {
var normalX = dx / distance;
var normalY = dy / distance;
self.x = otherBall.x - normalX * safeDistance;
self.y = otherBall.y - normalY * safeDistance;
// Check for merge first - increased sensitivity for same-numbered balls
var mergeDistance = self.value === otherBall.value ? minDistance * 1.4 : minDistance;
if (self.value === otherBall.value && self.mergeTimer === 0 && otherBall.mergeTimer === 0 && distance < mergeDistance) {
// Special case: when two 2048 balls merge, they explode and disappear
if (self.value === 2048) {
// Create explosion effect for both 2048 balls
tween(self, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut
});
tween(otherBall, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut
});
// Mark both balls for removal
self.hasBeenMerged = true;
otherBall.hasBeenMerged = true;
LK.getSound('merge').play();
if (scoringActive) {
LK.setScore(LK.getScore() + 1);
scoreText.setText(LK.getScore());
}
return;
}
var newValue = self.value * 2;
if (newValue <= 2048) {
var newBall = new Ball(newValue);
newBall.x = (self.x + otherBall.x) / 2;
newBall.y = (self.y + otherBall.y) / 2;
newBall.velocityX = (self.velocityX + otherBall.velocityX) / 2;
newBall.velocityY = (self.velocityY + otherBall.velocityY) / 2;
newBall.mergeTimer = 10;
// Add merge animation - scale and pulse effect
newBall.scaleX = 0.2; // Start very small
newBall.scaleY = 0.2; // Start very small
newBall.alpha = 0.7; // Start semi-transparent
tween(newBall, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Pulse back to normal size
tween(newBall, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
// Animate merging balls with shrink and fade
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: tween.easeIn
});
tween(otherBall, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: tween.easeIn
});
balls.push(newBall);
gameArea.addChild(newBall);
// Mark for removal
self.hasBeenMerged = true;
otherBall.hasBeenMerged = true;
LK.getSound('merge').play();
if (scoringActive) {
LK.setScore(LK.getScore() + 1);
scoreText.setText(LK.getScore());
}
// No win condition - game continues even after reaching 2048
return;
}
}
// Enhanced collision response for better stacking
var relativeVelX = self.velocityX - otherBall.velocityX;
var relativeVelY = self.velocityY - otherBall.velocityY;
var relativeSpeed = relativeVelX * normalX + relativeVelY * normalY;
if (relativeSpeed > 0) {
// Check if this is a falling ball hitting a static ball
var isFallingBallHittingStatic = !self.isStatic && otherBall.isStatic && self.velocityY > 2;
var isStaticBallHitByFalling = self.isStatic && !otherBall.isStatic && otherBall.velocityY > 2;
// Calculate mass-based collision response (assume equal mass)
var restitution = 0.3; // Lower restitution for more stable stacking
var impulse = (1 + restitution) * relativeSpeed * 0.5;
self.velocityX -= impulse * normalX;
self.velocityY -= impulse * normalY;
otherBall.velocityX += impulse * normalX;
otherBall.velocityY += impulse * normalY;
// Add sliding effect for static balls when hit by falling balls
if (isFallingBallHittingStatic) {
// Make the static ball slide horizontally
var slideForce = Math.abs(self.velocityY) * 0.3; // Base slide force on falling speed
var slideDirection = normalX > 0 ? 1 : -1; // Slide away from collision
var slideDistance = slideForce * 15; // Convert force to distance
// Ensure slide stays within bounds
var localWidth = gameAreaRight - gameAreaLeft;
var targetX = otherBall.x + slideDirection * slideDistance;
targetX = Math.max(otherBall.radius, Math.min(localWidth - otherBall.radius, targetX));
// Animate the slide using tween
tween(otherBall, {
x: targetX
}, {
duration: 200,
easing: tween.easeOut
});
// Temporarily make the static ball dynamic to allow movement
otherBall.isStatic = false;
otherBall.velocityX = slideDirection * slideForce;
} else if (isStaticBallHitByFalling) {
// Make the static ball slide horizontally
var slideForce = Math.abs(otherBall.velocityY) * 0.3; // Base slide force on falling speed
var slideDirection = normalX < 0 ? 1 : -1; // Slide away from collision
var slideDistance = slideForce * 15; // Convert force to distance
// Ensure slide stays within bounds
var localWidth = gameAreaRight - gameAreaLeft;
var targetX = self.x + slideDirection * slideDistance;
targetX = Math.max(self.radius, Math.min(localWidth - self.radius, targetX));
// Animate the slide using tween
tween(self, {
x: targetX
}, {
duration: 200,
easing: tween.easeOut
});
// Temporarily make the static ball dynamic to allow movement
self.isStatic = false;
self.velocityX = slideDirection * slideForce;
}
// Apply different friction based on collision angle
var collisionAngle = Math.atan2(normalY, normalX);
var frictionFactor = Math.abs(Math.sin(collisionAngle)) * 0.8 + 0.2;
self.velocityX *= self.friction * frictionFactor;
self.velocityY *= self.friction * frictionFactor;
otherBall.velocityX *= otherBall.friction * frictionFactor;
otherBall.velocityY *= otherBall.friction * frictionFactor;
// Enhanced static detection for stacking
if (Math.abs(self.velocityY) < 0.5 && Math.abs(self.velocityX) < 0.5) {
// Check if ball is supported (has another ball below it or touching ground)
var supportFound = false;
var localBottom = gameAreaHeight - 20;
// Check ground support
if (self.y + self.radius >= localBottom - 5) {
supportFound = true;
} else {
// Check support from other balls
for (var j = 0; j < balls.length; j++) {
var supportBall = balls[j];
if (supportBall === self || supportBall.hasBeenMerged) continue;
var supportDx = supportBall.x - self.x;
var supportDy = supportBall.y - self.y;
var supportDistance = Math.sqrt(supportDx * supportDx + supportDy * supportDy);
// Check if this ball is resting on another ball (other ball is below)
if (supportDistance < (self.radius + supportBall.radius) * 1.1 && supportDy > 0) {
supportFound = true;
break;
}
}
}
if (supportFound) {
self.isStatic = true;
self.velocityX = 0;
self.velocityY = 0;
}
}
}
}
collisionOccurred = true;
break;
}
}
}
// If no collision occurred, move to intended position
if (!collisionOccurred) {
self.x = newX;
self.y = newY;
}
// Boundary collisions after position update
var localWidth = gameAreaRight - gameAreaLeft;
var localBottom = gameAreaHeight - 20; // gameAreaHeight minus bottom wall height
// Ground collision
if (self.y + self.radius > localBottom) {
self.y = localBottom - self.radius;
self.velocityY *= -self.bounce;
self.velocityX *= self.friction;
if (Math.abs(self.velocityY) < 1) {
self.velocityY = 0;
self.isStatic = true;
}
}
// Side walls collision
if (self.x - self.radius < 0) {
self.x = self.radius;
self.velocityX *= -self.bounce;
}
if (self.x + self.radius > localWidth) {
self.x = localWidth - self.radius;
self.velocityX *= -self.bounce;
}
}
// Enhanced merge detection pass - check for nearby same-numbered balls
for (var i = 0; i < balls.length; i++) {
var otherBall = balls[i];
if (otherBall === self || otherBall.hasBeenMerged || otherBall.mergeTimer > 0 || self.mergeTimer > 0) continue;
var dx = otherBall.x - self.x;
var dy = otherBall.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = self.radius + otherBall.radius;
// Enhanced merge sensitivity for same-numbered balls
if (self.value === otherBall.value) {
var mergeTriggerDistance = minDistance * 1.3; // 30% more sensitive
if (distance < mergeTriggerDistance) {
// Special case: when two 2048 balls merge, they explode and disappear
if (self.value === 2048) {
// Create explosion effect for both 2048 balls
tween(self, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut
});
tween(otherBall, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut
});
// Mark both balls for removal
self.hasBeenMerged = true;
otherBall.hasBeenMerged = true;
LK.getSound('merge').play();
if (scoringActive) {
LK.setScore(LK.getScore() + 1);
scoreText.setText(LK.getScore());
}
return;
}
var newValue = self.value * 2;
if (newValue <= 2048) {
var newBall = new Ball(newValue);
newBall.x = (self.x + otherBall.x) / 2;
newBall.y = (self.y + otherBall.y) / 2;
newBall.velocityX = (self.velocityX + otherBall.velocityX) / 2;
newBall.velocityY = (self.velocityY + otherBall.velocityY) / 2;
newBall.mergeTimer = 10;
// Add merge animation - scale and pulse effect
newBall.scaleX = 0.2; // Start very small
newBall.scaleY = 0.2; // Start very small
newBall.alpha = 0.7; // Start semi-transparent
tween(newBall, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Pulse back to normal size
tween(newBall, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
// Animate merging balls with shrink and fade
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: tween.easeIn
});
tween(otherBall, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: tween.easeIn
});
balls.push(newBall);
gameArea.addChild(newBall);
// Mark for removal
self.hasBeenMerged = true;
otherBall.hasBeenMerged = true;
LK.getSound('merge').play();
if (scoringActive) {
LK.setScore(LK.getScore() + 1);
scoreText.setText(LK.getScore());
}
// No win condition - game continues even after reaching 2048
return;
}
}
}
// Enhanced separation for stable stacking
if (distance < minDistance && distance > 0) {
var overlap = minDistance - distance;
var normalX = dx / distance;
var normalY = dy / distance;
// Different separation strategies based on ball positions
var separationForce = overlap * 0.5;
// If one ball is static and the other is moving, adjust separation
if (self.isStatic && !otherBall.isStatic) {
// Static ball moves less
self.x -= normalX * separationForce * 0.2;
self.y -= normalY * separationForce * 0.2;
otherBall.x += normalX * separationForce * 0.8;
otherBall.y += normalY * separationForce * 0.8;
} else if (!self.isStatic && otherBall.isStatic) {
// Other ball is static
self.x -= normalX * separationForce * 0.8;
self.y -= normalY * separationForce * 0.8;
otherBall.x += normalX * separationForce * 0.2;
otherBall.y += normalY * separationForce * 0.2;
} else {
// Both moving or both static - equal separation
self.x -= normalX * separationForce;
self.y -= normalY * separationForce;
otherBall.x += normalX * separationForce;
otherBall.y += normalY * separationForce;
}
// Ensure balls stay within bounds after separation
var localWidth = gameAreaRight - gameAreaLeft;
var localBottom = gameAreaHeight - 20; // gameAreaHeight minus bottom wall height
self.x = Math.max(self.radius, Math.min(localWidth - self.radius, self.x));
self.y = Math.max(self.radius, Math.min(localBottom - self.radius, self.y));
// Re-check static state after separation
if (self.isStatic && (Math.abs(self.x - (self.x - normalX * separationForce)) > 1 || Math.abs(self.y - (self.y - normalY * separationForce)) > 1)) {
self.isStatic = false; // Ball was moved significantly, make it dynamic again
}
}
}
// Periodic re-evaluation of static state for better stacking
if (LK.ticks % 10 === 0 && !self.hasBeenMerged) {
// Check every 10 frames
if (!self.isStatic && Math.abs(self.velocityX) < 0.3 && Math.abs(self.velocityY) < 0.3) {
// Check if ball should become static
var supportFound = false;
var localBottom = gameAreaHeight - 20;
// Check ground support
if (self.y + self.radius >= localBottom - 2) {
supportFound = true;
} else {
// Check support from other static balls
for (var k = 0; k < balls.length; k++) {
var checkBall = balls[k];
if (checkBall === self || checkBall.hasBeenMerged || !checkBall.isStatic) continue;
var checkDx = checkBall.x - self.x;
var checkDy = checkBall.y - self.y;
var checkDistance = Math.sqrt(checkDx * checkDx + checkDy * checkDy);
// Ball is resting on another static ball
if (checkDistance < (self.radius + checkBall.radius) * 1.05 && checkDy > 0) {
supportFound = true;
break;
}
}
}
if (supportFound) {
self.isStatic = true;
self.velocityX = 0;
self.velocityY = 0;
}
} else if (self.isStatic) {
// Check if static ball should become dynamic (lost support)
var stillSupported = false;
var localBottom = gameAreaHeight - 20;
// Check ground support
if (self.y + self.radius >= localBottom - 2) {
stillSupported = true;
} else {
// Check support from other balls
for (var k = 0; k < balls.length; k++) {
var supportBall = balls[k];
if (supportBall === self || supportBall.hasBeenMerged) continue;
var supportDx = supportBall.x - self.x;
var supportDy = supportBall.y - self.y;
var supportDistance = Math.sqrt(supportDx * supportDx + supportDy * supportDy);
if (supportDistance < (self.radius + supportBall.radius) * 1.05 && supportDy > 0) {
stillSupported = true;
break;
}
}
}
if (!stillSupported) {
self.isStatic = false; // Ball lost support, make it fall
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf5f5dc
});
/****
* Game Code
****/
var balls = [];
var nextBallValue = 2;
var gameAreaLeft = 0;
var gameAreaRight = 2048;
var gameAreaTop = 0;
var gameAreaBottom = 2732;
var dropCooldown = 0;
var gameAreaHeight = gameAreaBottom - gameAreaTop;
var dangerZoneImmunity = 300; // 5 seconds at 60fps
var scoringActive = false; // Flag to track when scoring should be active
// Create game area background
var gameAreaBg = game.addChild(LK.getAsset('gameArea', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: gameAreaTop
}));
// Create game area container for balls
var gameArea = new Container();
gameArea.x = gameAreaLeft;
gameArea.y = gameAreaTop;
game.addChild(gameArea);
// Create visible walls
var leftWall = game.addChild(LK.getAsset('leftWall', {
anchorX: 1,
anchorY: 0,
x: gameAreaLeft,
y: gameAreaTop
}));
var rightWall = game.addChild(LK.getAsset('rightWall', {
anchorX: 0,
anchorY: 0,
x: gameAreaRight,
y: gameAreaTop
}));
var bottomWall = game.addChild(LK.getAsset('bottomWall', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: gameAreaBottom
}));
// Create danger zone line (visible red line showing danger threshold)
var dangerZoneLine = game.addChild(LK.getAsset('gameOverLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: gameAreaTop + 350,
alpha: 0.8,
visible: true
}));
// Create score display
var scoreText = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -50; // Small offset from right edge
scoreText.y = 100;
// Create next ball preview
var nextBallPreview = LK.getAsset('ball' + nextBallValue, {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: gameAreaTop + 350,
scaleX: 1.4,
scaleY: 1.4
});
game.addChild(nextBallPreview);
// Create next ball value text
var nextBallText = new Text2(nextBallValue.toString(), {
size: 48,
fill: 0xFFFFFF
});
nextBallText.anchor.set(0.5, 0.5);
nextBallText.x = 1024;
nextBallText.y = gameAreaTop + 200;
game.addChild(nextBallText);
function getRandomNextBallValue() {
var values = [2, 4, 8];
return values[Math.floor(Math.random() * values.length)];
}
function updateNextBallPreview() {
game.removeChild(nextBallPreview);
game.removeChild(nextBallText);
nextBallPreview = LK.getAsset('ball' + nextBallValue, {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: gameAreaTop + 350,
scaleX: 1.4,
scaleY: 1.4
});
game.addChild(nextBallPreview);
nextBallText = new Text2(nextBallValue.toString(), {
size: 48,
fill: 0xFFFFFF
});
nextBallText.anchor.set(0.5, 0.5);
nextBallText.x = 1024;
nextBallText.y = gameAreaTop + 200;
game.addChild(nextBallText);
}
function checkGameOver() {
// Skip game over check during immunity period
if (dangerZoneImmunity > 0) return;
for (var i = 0; i < balls.length; i++) {
// Check if ball touches the danger zone (first 350 pixels of game area)
// Since balls are in gameArea container, we use local coordinates
// Only trigger game over if ball is static (not actively falling) and in danger zone
if (balls[i].y - balls[i].radius <= 350 && balls[i].isStatic) {
LK.showGameOver();
return;
}
}
}
function cleanupMergedBalls() {
for (var i = balls.length - 1; i >= 0; i--) {
if (balls[i].hasBeenMerged) {
gameArea.removeChild(balls[i]);
balls[i].destroy();
balls.splice(i, 1);
}
}
}
// Create initial balls to fill container to the top
function createInitialBalls() {
var localWidth = gameAreaRight - gameAreaLeft;
var ballsPerRow = 7; // Fixed number of balls per row for consistent layout
var rowBalls = []; // Track balls in current row for spacing calculation
var rowHeights = []; // Track row heights for stacking
var previousRowBalls = []; // Track balls from previous row for vertical spacing
for (var row = 0; row < 12; row++) {
rowBalls = []; // Reset for each row
var maxRadiusInRow = 0; // Track largest ball in current row
// Create balls for this row first to know their sizes
var ballsData = [];
for (var col = 0; col < ballsPerRow; col++) {
var ballValue = getRandomNextBallValue();
var radiusMap = {
2: 140,
4: 145,
8: 155,
16: 165,
32: 175,
64: 185,
128: 195,
256: 205,
512: 215,
1024: 220,
2048: 230
};
var ballRadius = radiusMap[ballValue] || 60;
ballsData.push({
value: ballValue,
radius: ballRadius
});
maxRadiusInRow = Math.max(maxRadiusInRow, ballRadius);
}
// Calculate X positions to make balls touch horizontally based on adjacent radii
var xPositions = [];
var currentX = 0;
for (var col = 0; col < ballsPerRow; col++) {
if (col === 0) {
// First ball starts at its radius
currentX = ballsData[col].radius;
} else {
// Each subsequent ball is positioned so it touches the previous ball
var prevRadius = ballsData[col - 1].radius;
var currentRadius = ballsData[col].radius;
currentX += prevRadius + currentRadius; // Distance between centers = sum of radii
}
xPositions.push(currentX);
}
// Center the entire row
var totalRowWidth = xPositions[xPositions.length - 1] + ballsData[ballsPerRow - 1].radius;
var offsetX = (localWidth - totalRowWidth) / 2;
// Create and position the balls
for (var col = 0; col < ballsPerRow; col++) {
var initialBall = new Ball(ballsData[col].value);
initialBall.x = xPositions[col] + offsetX;
initialBall.velocityX = 0;
initialBall.velocityY = 0;
initialBall.isStatic = true; // Start as static for stable stacking
rowBalls.push(initialBall);
balls.push(initialBall);
gameArea.addChild(initialBall);
}
// Calculate Y position to make balls touch vertically with previous row
var rowY;
if (row === 0) {
// First row sits on bottom
rowY = gameAreaHeight - 100 - maxRadiusInRow;
} else {
// For subsequent rows, find the minimum Y position that avoids collision with previous row
rowY = gameAreaHeight; // Start with max possible Y
for (var currentCol = 0; currentCol < rowBalls.length; currentCol++) {
var currentBall = rowBalls[currentCol];
var currentBallRadius = currentBall.radius;
var minYForThisBall = gameAreaHeight - 100 - currentBallRadius; // Default to bottom
// Check against all balls in previous row
for (var prevCol = 0; prevCol < previousRowBalls.length; prevCol++) {
var prevBall = previousRowBalls[prevCol];
var dx = currentBall.x - prevBall.x;
var dy = 0; // We'll calculate the required dy
var horizontalDistance = Math.abs(dx);
var requiredCenterDistance = currentBallRadius + prevBall.radius;
// If balls are close enough horizontally to potentially touch
if (horizontalDistance < requiredCenterDistance) {
// Calculate the vertical distance needed for balls to just touch
var verticalDistance = Math.sqrt(requiredCenterDistance * requiredCenterDistance - dx * dx);
var requiredY = prevBall.y - verticalDistance;
minYForThisBall = Math.min(minYForThisBall, requiredY);
}
}
// The row Y is determined by the ball that needs to be highest (smallest Y)
rowY = Math.min(rowY, minYForThisBall);
}
}
// Apply the calculated Y position to all balls in this row
for (var j = 0; j < rowBalls.length; j++) {
rowBalls[j].y = rowY;
}
// Store this row's balls for next row calculation
previousRowBalls = rowBalls.slice(); // Copy the array
}
}
// Initialize the starting balls
createInitialBalls();
game.down = function (x, y, obj) {
if (dropCooldown > 0) return;
// Activate scoring system when first ball is dropped
if (!scoringActive) {
scoringActive = true;
}
// Constrain drop position to game area (convert to local coordinates)
var localWidth = gameAreaRight - gameAreaLeft;
var localX = x - gameAreaLeft;
var dropX = Math.max(80, Math.min(localWidth - 80, localX));
var newBall = new Ball(nextBallValue);
newBall.x = dropX;
newBall.y = 420; // Start below the danger zone (350px) plus ball radius (60px) plus buffer
newBall.velocityX = 0;
newBall.velocityY = 0;
balls.push(newBall);
gameArea.addChild(newBall);
LK.getSound('drop').play();
// Update next ball
nextBallValue = getRandomNextBallValue();
updateNextBallPreview();
dropCooldown = 30; // 0.5 seconds at 60fps
};
game.update = function () {
if (dropCooldown > 0) {
dropCooldown--;
}
// Handle danger zone immunity countdown
if (dangerZoneImmunity > 0) {
dangerZoneImmunity--;
}
cleanupMergedBalls();
checkGameOver();
// Update score display
scoreText.setText(LK.getScore());
}; ===================================================================
--- original.js
+++ change.js
@@ -188,62 +188,63 @@
// No win condition - game continues even after reaching 2048
return;
}
}
- // Check for impact effect - when a fast-moving ball hits static balls
- var impactSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
- if (impactSpeed > 3 && !self.isStatic && otherBall.isStatic) {
- // Create impact effect - make nearby balls scatter
- var impactRadius = self.radius * 3; // Area of effect
- for (var impactIndex = 0; impactIndex < balls.length; impactIndex++) {
- var impactBall = balls[impactIndex];
- if (impactBall === self || impactBall.hasBeenMerged || !impactBall.isStatic) continue;
- var impactDx = impactBall.x - self.x;
- var impactDy = impactBall.y - self.y;
- var impactDistance = Math.sqrt(impactDx * impactDx + impactDy * impactDy);
- if (impactDistance < impactRadius && impactDistance > 0) {
- // Calculate scatter force based on distance and impact speed
- var forceMultiplier = (impactRadius - impactDistance) / impactRadius;
- var scatterForce = impactSpeed * forceMultiplier * 0.5;
- var scatterDirection = Math.atan2(impactDy, impactDx);
- var scatterX = Math.cos(scatterDirection) * scatterForce * 15;
- var scatterY = Math.sin(scatterDirection) * scatterForce * 8;
- // Make the ball dynamic again and apply scatter velocity
- impactBall.isStatic = false;
- impactBall.velocityX = scatterX;
- impactBall.velocityY = scatterY;
- // Add visual shake effect to scattered balls
- tween(impactBall, {
- scaleX: 1.2,
- scaleY: 1.2
- }, {
- duration: 150,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(impactBall, {
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 150,
- easing: tween.easeIn
- });
- }
- });
- }
- }
- }
// Enhanced collision response for better stacking
var relativeVelX = self.velocityX - otherBall.velocityX;
var relativeVelY = self.velocityY - otherBall.velocityY;
var relativeSpeed = relativeVelX * normalX + relativeVelY * normalY;
if (relativeSpeed > 0) {
+ // Check if this is a falling ball hitting a static ball
+ var isFallingBallHittingStatic = !self.isStatic && otherBall.isStatic && self.velocityY > 2;
+ var isStaticBallHitByFalling = self.isStatic && !otherBall.isStatic && otherBall.velocityY > 2;
// Calculate mass-based collision response (assume equal mass)
var restitution = 0.3; // Lower restitution for more stable stacking
var impulse = (1 + restitution) * relativeSpeed * 0.5;
self.velocityX -= impulse * normalX;
self.velocityY -= impulse * normalY;
otherBall.velocityX += impulse * normalX;
otherBall.velocityY += impulse * normalY;
+ // Add sliding effect for static balls when hit by falling balls
+ if (isFallingBallHittingStatic) {
+ // Make the static ball slide horizontally
+ var slideForce = Math.abs(self.velocityY) * 0.3; // Base slide force on falling speed
+ var slideDirection = normalX > 0 ? 1 : -1; // Slide away from collision
+ var slideDistance = slideForce * 15; // Convert force to distance
+ // Ensure slide stays within bounds
+ var localWidth = gameAreaRight - gameAreaLeft;
+ var targetX = otherBall.x + slideDirection * slideDistance;
+ targetX = Math.max(otherBall.radius, Math.min(localWidth - otherBall.radius, targetX));
+ // Animate the slide using tween
+ tween(otherBall, {
+ x: targetX
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+ // Temporarily make the static ball dynamic to allow movement
+ otherBall.isStatic = false;
+ otherBall.velocityX = slideDirection * slideForce;
+ } else if (isStaticBallHitByFalling) {
+ // Make the static ball slide horizontally
+ var slideForce = Math.abs(otherBall.velocityY) * 0.3; // Base slide force on falling speed
+ var slideDirection = normalX < 0 ? 1 : -1; // Slide away from collision
+ var slideDistance = slideForce * 15; // Convert force to distance
+ // Ensure slide stays within bounds
+ var localWidth = gameAreaRight - gameAreaLeft;
+ var targetX = self.x + slideDirection * slideDistance;
+ targetX = Math.max(self.radius, Math.min(localWidth - self.radius, targetX));
+ // Animate the slide using tween
+ tween(self, {
+ x: targetX
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+ // Temporarily make the static ball dynamic to allow movement
+ self.isStatic = false;
+ self.velocityX = slideDirection * slideForce;
+ }
// Apply different friction based on collision angle
var collisionAngle = Math.atan2(normalY, normalX);
var frictionFactor = Math.abs(Math.sin(collisionAngle)) * 0.8 + 0.2;
self.velocityX *= self.friction * frictionFactor;
@@ -303,29 +304,17 @@
self.velocityY = 0;
self.isStatic = true;
}
}
- // Side walls collision with enhanced bounce back for scattered balls
+ // Side walls collision
if (self.x - self.radius < 0) {
self.x = self.radius;
self.velocityX *= -self.bounce;
- // Apply additional damping to prevent excessive bouncing
- self.velocityX *= 0.8;
- self.velocityY *= 0.9;
}
if (self.x + self.radius > localWidth) {
self.x = localWidth - self.radius;
self.velocityX *= -self.bounce;
- // Apply additional damping to prevent excessive bouncing
- self.velocityX *= 0.8;
- self.velocityY *= 0.9;
}
- // Top boundary collision to prevent balls from flying off screen upward
- if (self.y - self.radius < 0) {
- self.y = self.radius;
- self.velocityY *= -self.bounce;
- self.velocityY = Math.max(self.velocityY, 0.5); // Ensure downward movement
- }
}
// Enhanced merge detection pass - check for nearby same-numbered balls
for (var i = 0; i < balls.length; i++) {
var otherBall = balls[i];
Koyu yeşil bilye. In-Game asset. 2d. High contrast. No shadows
Kahverengi bilye. In-Game asset. 2d. High contrast. No shadows
Bprdo renk bilye. In-Game asset. 2d. High contrast. No shadows
Açık kahve bilye. In-Game asset. 2d. High contrast. No shadows
Gri bilye. In-Game asset. 2d. High contrast. No shadows