/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Ball = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9 }); var glowBall = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, alpha: 0.4, scaleX: 1.5, scaleY: 1.5 }); self.velocityX = 0; self.velocityY = 0; self.speed = 10; self.gravityAffected = true; self.lastX = 0; self.lastY = 0; self.trailElements = []; self.maxTrailLength = 5; self.createTrailElement = function () { var trailElement = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, scaleX: 0.7, scaleY: 0.7, tint: 0x87CEFA }); return trailElement; }; self.updateTrail = function () { // Add new trail element if (Math.abs(self.x - self.lastX) > 5 || Math.abs(self.y - self.lastY) > 5) { var trail = self.createTrailElement(); trail.x = self.x; trail.y = self.y; game.addChildAt(trail, game.getChildIndex(self)); self.trailElements.push(trail); // Fade out and remove tween(trail, { alpha: 0 }, { duration: 400, easing: tween.linear, onFinish: function onFinish() { if (trail.parent) { trail.parent.removeChild(trail); } } }); // Remove old elements if too many if (self.trailElements.length > self.maxTrailLength) { var oldTrail = self.trailElements.shift(); if (oldTrail.parent) { oldTrail.parent.removeChild(oldTrail); } } } // Update last position self.lastX = self.x; self.lastY = self.y; }; self.reset = function () { self.x = 2048 / 2; self.y = 2732 / 2; self.velocityX = (Math.random() > 0.5 ? 1 : -1) * self.speed; self.velocityY = (Math.random() * 2 - 1) * self.speed * 0.5; self.lastX = self.x; self.lastY = self.y; // Clear any existing trail elements for (var i = 0; i < self.trailElements.length; i++) { if (self.trailElements[i].parent) { self.trailElements[i].parent.removeChild(self.trailElements[i]); } } self.trailElements = []; // Add pulsating glow effect self.startGlowAnimation(); }; self.startGlowAnimation = function () { tween(glowBall.scale, { x: 1.8, y: 1.8 }, { duration: 800, easing: tween.sinceOut, onFinish: function onFinish() { tween(glowBall.scale, { x: 1.5, y: 1.5 }, { duration: 800, easing: tween.sinceIn, onFinish: function onFinish() { self.startGlowAnimation(); } }); } }); }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Update the trail self.updateTrail(); // Handle wall collisions if (self.x < 0 + ballGraphics.width / 2) { self.x = ballGraphics.width / 2; self.velocityX = -self.velocityX; // Play wall bounce sound LK.getSound('bounce').play(); } else if (self.x > 2048 - ballGraphics.width / 2) { self.x = 2048 - ballGraphics.width / 2; self.velocityX = -self.velocityX; // Play wall bounce sound LK.getSound('bounce').play(); } }; return self; }); var GravityField = Container.expand(function () { var self = Container.call(this); var fieldGraphics = self.attachAsset('gravitationField', { anchorX: 0.5, anchorY: 0.5, alpha: 0.2 }); self.strength = 0.1; self.maxStrength = 0.5; self.setup = function () { self.x = 2048 / 2; self.y = 2732 / 2; self.strength = 0.1; self.scale.set(1); }; self.increaseStrength = function () { self.strength = Math.min(self.maxStrength, self.strength + 0.05); // Play powerup sound when gravity field increases strength LK.getSound('powerup').play(); tween(self.scale, { x: 1 + self.strength, y: 1 + self.strength }, { duration: 1000, easing: tween.elasticOut }); }; self.reverseGravity = function () { self.strength = -self.strength; // Play sound for gravity reversal LK.getSound('powerup').play(); tween(fieldGraphics, { tint: 0xFF5733 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { tween(fieldGraphics, { tint: 0x4287f5 }, { duration: 5000, easing: tween.linear, onFinish: function onFinish() { self.strength = Math.abs(self.strength); } }); } }); }; self.applyGravityTo = function (ball) { if (!ball.gravityAffected) { return; } var dx = self.x - ball.x; var dy = self.y - ball.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 10) { var forceX = dx / distance * self.strength; var forceY = dy / distance * self.strength; ball.velocityX += forceX; ball.velocityY += forceY; // Cap maximum velocity var speed = Math.sqrt(ball.velocityX * ball.velocityX + ball.velocityY * ball.velocityY); if (speed > ball.speed * 2) { ball.velocityX = ball.velocityX / speed * ball.speed * 2; ball.velocityY = ball.velocityY / speed * ball.speed * 2; } } }; return self; }); var Paddle = Container.expand(function () { var self = Container.call(this); // Create shadow effect var paddleShadow = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, scaleX: 1.05, scaleY: 1.8, y: 8 }); var paddleGraphics = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 0; self.speed = 15; self.isPlayer = false; self.score = 0; self.setup = function (isPlayer) { self.isPlayer = isPlayer; if (isPlayer) { self.y = 2732 - 200; paddleGraphics.tint = 0x4287f5; // Blue for player } else { self.y = 200; paddleGraphics.tint = 0xff5733; // Orange for AI } self.x = 2048 / 2; self.targetX = self.x; // Create entrance animation self.scale.set(0.1); // Play sound for paddle appearance LK.getSound('powerup').play(); tween(self.scale, { x: 1, y: 1 }, { duration: 800, easing: tween.elasticOut }); }; self.update = function () { // Move toward target position with easing if (Math.abs(self.x - self.targetX) > 1) { self.x += (self.targetX - self.x) * 0.2; } // Boundary check if (self.x < paddleGraphics.width / 2) { self.x = paddleGraphics.width / 2; } else if (self.x > 2048 - paddleGraphics.width / 2) { self.x = 2048 - paddleGraphics.width / 2; } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var type = 'gravity'; // Default type var powerUpGraphics; self.setup = function (powerType) { type = powerType; if (self.children.length > 0) { self.removeChildAt(0); } if (type === 'gravity') { powerUpGraphics = self.attachAsset('powerUpGravity', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'multiball') { powerUpGraphics = self.attachAsset('powerUpMultiBall', { anchorX: 0.5, anchorY: 0.5 }); } self.x = Math.random() * (2048 - 200) + 100; self.y = Math.random() * (2732 - 600) + 300; self.alpha = 1; // Play sound on first appearance (not on pulse refresh) if (arguments.length > 0) { LK.getSound('powerup').play(); } // Pulsating animation tween(self, { alpha: 0.6 }, { duration: 800, easing: tween.sinceOut, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 800, easing: tween.sinceIn, onFinish: function onFinish() { if (self.parent) { self.setup(type); } } }); } }); }; self.getType = function () { return type; }; return self; }); var PredictionLine = Container.expand(function () { var self = Container.call(this); var lineSegments = []; var numSegments = 10; var segmentLength = 15; var segmentGap = 10; self.setup = function () { // Create line segments for (var i = 0; i < numSegments; i++) { var segment = LK.getAsset('paddle', { anchorX: 0.5, anchorY: 0.5, width: 5, height: segmentLength, alpha: 0.6 - i * 0.05, tint: 0x4287f5 }); lineSegments.push(segment); self.addChild(segment); } self.visible = false; }; self.updatePosition = function (startX, startY, targetX, targetY) { if (!self.visible) { self.visible = true; } var dx = targetX - startX; var dy = targetY - startY; var angle = Math.atan2(dy, dx); for (var i = 0; i < lineSegments.length; i++) { var distance = i * (segmentLength + segmentGap); lineSegments[i].x = startX + Math.cos(angle) * distance; lineSegments[i].y = startY + Math.sin(angle) * distance; lineSegments[i].rotation = angle; } }; self.hide = function () { self.visible = false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Set a beautiful dark gradient background game.setBackgroundColor(0x0a0a2a); // Add "Glaud" text in small orange to the upper right corner var glaudText = new Text2('Glaud', { size: 40, fill: 0xff8c00 // Orange color }); glaudText.anchor.set(1, 0); // Anchor to top right glaudText.x = 2048 - 20; // 20px margin from right edge glaudText.y = 20; // 20px margin from top edge game.addChild(glaudText); // Create a star field background function createStarfield() { var starfield = new Container(); // Create stars with different sizes and opacities for (var i = 0; i < 100; i++) { var size = Math.random() * 4 + 1; var star = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, width: size, height: size, alpha: Math.random() * 0.7 + 0.3 }); star.x = Math.random() * 2048; star.y = Math.random() * 2732; // Create twinkling animation var duration = Math.random() * 3000 + 2000; // Create closure to keep reference to the current star (function (starElement) { function animateStar() { tween(starElement, { alpha: Math.random() * 0.5 + 0.1 }, { duration: duration, easing: tween.sinceInOut, onFinish: function onFinish() { tween(starElement, { alpha: Math.random() * 0.7 + 0.3 }, { duration: duration, easing: tween.sinceInOut, onFinish: animateStar }); } }); } animateStar(); })(star); starfield.addChild(star); } game.addChildAt(starfield, 0); // Add at the bottom layer return starfield; } // Create starfield var starfield = createStarfield(); // Create game elements var playerPaddle = new Paddle(); var aiPaddle = new Paddle(); var gravityField = new GravityField(); var balls = []; var powerUps = []; var centerLine; var playerScoreText; var aiScoreText; var dragTarget = null; var difficultyTimer; var powerUpTimer; var gameActive = false; var predictionLine; var mouseX = 0; var mouseY = 0; var isMouseOver = false; // Animate score text function function animateScoreText(textObject, newValue) { // Store original scale var originalScale = { x: textObject.scale.x, y: textObject.scale.y }; // Set the new text textObject.setText(newValue); // Animate scale up and back down tween.stop(textObject.scale); textObject.scale.set(originalScale.x * 1.5, originalScale.y * 1.5); tween(textObject.scale, { x: originalScale.x, y: originalScale.y }, { duration: 500, easing: tween.elasticOut }); } // Initialize game state function initGame() { // Setup players playerPaddle.setup(true); aiPaddle.setup(false); game.addChild(playerPaddle); game.addChild(aiPaddle); // Setup gravity field gravityField.setup(); game.addChild(gravityField); // Create center line with animation centerLine = new Container(); centerLine.alpha = 0; for (var i = 0; i < 20; i++) { var dash = LK.getAsset('paddle', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 10, alpha: 0.5 }); dash.x = i * 110; // Add staggered animation to each dash (function (element, delay) { element.alpha = 0; element.scale.set(0.5); LK.setTimeout(function () { tween(element, { alpha: 0.5 }, { duration: 400, easing: tween.linear }); tween(element.scale, { x: 1, y: 1 }, { duration: 600, easing: tween.elasticOut }); }, delay); })(dash, i * 30); centerLine.addChild(dash); } centerLine.x = 50; centerLine.y = 2732 / 2; game.addChild(centerLine); tween(centerLine, { alpha: 1 }, { duration: 800, easing: tween.linear }); // Create score text with animated appearance playerScoreText = new Text2('0', { size: 150, fill: 0xFFFFFF }); playerScoreText.anchor.set(0.5, 1); playerScoreText.x = 2048 / 2; playerScoreText.y = 2732 - 50; playerScoreText.alpha = 0; playerScoreText.scale.set(0.5); game.addChild(playerScoreText); aiScoreText = new Text2('0', { size: 150, fill: 0xFFFFFF }); aiScoreText.anchor.set(0.5, 0); aiScoreText.x = 2048 / 2; aiScoreText.y = 50; aiScoreText.alpha = 0; aiScoreText.scale.set(0.5); game.addChild(aiScoreText); // Animate score text appearance tween(playerScoreText, { alpha: 1 }, { duration: 800, easing: tween.linear }); tween(playerScoreText.scale, { x: 1, y: 1 }, { duration: 1000, easing: tween.elasticOut }); tween(aiScoreText, { alpha: 1 }, { duration: 800, easing: tween.linear }); tween(aiScoreText.scale, { x: 1, y: 1 }, { duration: 1000, easing: tween.elasticOut }); // Create first ball with delay to allow UI elements to animate in LK.setTimeout(function () { createBall(); }, 1200); // Setup difficulty increases difficultyTimer = LK.setInterval(function () { if (gameActive) { gravityField.increaseStrength(); } }, 10000); // Setup power-up spawning powerUpTimer = LK.setInterval(function () { if (gameActive && powerUps.length < 1) { spawnPowerUp(); } }, 15000); // Create prediction line predictionLine = new PredictionLine(); predictionLine.setup(); game.addChild(predictionLine); // Start game gameActive = true; LK.playMusic('gameMusic'); } // Function to create a new ball function createBall() { var ball = new Ball(); ball.reset(); balls.push(ball); game.addChild(ball); // Play sound when creating a new ball LK.getSound('powerup').play(); return ball; } // Function to spawn a power-up function spawnPowerUp() { var powerUp = new PowerUp(); var type = Math.random() > 0.5 ? 'gravity' : 'multiball'; powerUp.setup(type); powerUps.push(powerUp); game.addChild(powerUp); // Play sound when spawning power-up (now handled in PowerUp.setup) } // Check for collisions between ball and paddle function checkPaddleCollision(ball, paddle) { if (ball.y + 25 >= paddle.y - 25 && ball.y - 25 <= paddle.y + 25) { if (ball.x + 25 >= paddle.x - 125 && ball.x - 25 <= paddle.x + 125) { // Calculate bounce angle based on where ball hit the paddle var relativeIntersectX = ball.x - paddle.x; var normalizedRelativeIntersectionX = relativeIntersectX / 125; var bounceAngle = normalizedRelativeIntersectionX * (Math.PI / 3); // Maximum angle: 60 degrees // Invert Y velocity and adjust X velocity based on bounce angle ball.velocityY = -ball.velocityY; ball.velocityX = ball.speed * Math.sin(bounceAngle); // Increase speed slightly ball.speed = Math.min(20, ball.speed * 1.05); // Play bounce sound LK.getSound('bounce').play(); // Enhanced paddle hit effect - scale and flash tween.stop(paddle.scale); paddle.scale.set(1.2, 0.8); tween(paddle.scale, { x: 1, y: 1 }, { duration: 300, easing: tween.elasticOut }); // Flash paddle with color based on player LK.effects.flashObject(paddle, paddle.isPlayer ? 0x4287f5 : 0xff5733, 300); return true; } } return false; } // Check for collisions between ball and power-up function checkPowerUpCollision(ball) { for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; if (ball.intersects(powerUp)) { // Create explosion effect at power-up location var explosionEffect = new Container(); explosionEffect.x = powerUp.x; explosionEffect.y = powerUp.y; game.addChild(explosionEffect); // Create multiple particles for explosion for (var p = 0; p < 10; p++) { var particle = LK.getAsset(powerUp.getType() === 'gravity' ? 'powerUpGravity' : 'powerUpMultiBall', { anchorX: 0.5, anchorY: 0.5, scale: 0.5, alpha: 0.8 }); // Randomize particle position and movement var angle = Math.random() * Math.PI * 2; var distance = Math.random() * 100 + 50; var duration = Math.random() * 500 + 500; particle.x = 0; particle.y = 0; explosionEffect.addChild(particle); // Animate particle movement and fade tween(particle, { x: Math.cos(angle) * distance, y: Math.sin(angle) * distance, alpha: 0 }, { duration: duration, easing: tween.cubicOut }); // Ensure particle has proper scale object particle.scale = particle.scale || { x: 0.5, y: 0.5 }; // Ensure particle has scale object before tweening if (!particle.scale || typeof particle.scale === 'number') { particle.scale = { x: 0.5, y: 0.5 }; } // Now safely tween the scale property tween(particle.scale, { x: 0.1, y: 0.1 }, { duration: duration, easing: tween.linear }); } // Remove explosion container after animation completes LK.setTimeout(function () { explosionEffect.destroy(); }, 1000); // Apply power-up effect with enhanced visuals if (powerUp.getType() === 'gravity') { // Shake gravity field for dramatic effect var originalX = gravityField.x; var originalY = gravityField.y; var shakeCount = 0; var shakeInterval = LK.setInterval(function () { gravityField.x = originalX + (Math.random() * 20 - 10); gravityField.y = originalY + (Math.random() * 20 - 10); shakeCount++; if (shakeCount >= 10) { LK.clearInterval(shakeInterval); gravityField.x = originalX; gravityField.y = originalY; gravityField.reverseGravity(); } }, 50); } else if (powerUp.getType() === 'multiball') { for (var j = 0; j < 2; j++) { var newBall = createBall(); // Add a scale animation to new balls newBall.scale.set(0.2); tween(newBall.scale, { x: 1, y: 1 }, { duration: 500, easing: tween.elasticOut }); } } // Play sound and remove power-up LK.getSound('powerup').play(); LK.effects.flashScreen(0x33FF57, 300); powerUp.destroy(); powerUps.splice(i, 1); } } } // Handle AI movement function updateAI() { // Find the closest ball var closestBall = null; var closestDistance = Infinity; for (var i = 0; i < balls.length; i++) { var ball = balls[i]; if (ball.velocityY < 0) { // Ball is moving towards AI var distance = Math.abs(ball.x - aiPaddle.x); if (distance < closestDistance) { closestDistance = distance; closestBall = ball; } } } if (closestBall) { // Add some prediction based on velocity and gravity var predictedX = closestBall.x + closestBall.velocityX * (aiPaddle.y - closestBall.y) / Math.abs(closestBall.velocityY); aiPaddle.targetX = predictedX; // Add some difficulty scaling var difficultyFactor = 0.3 + gravityField.strength / gravityField.maxStrength * 0.6; aiPaddle.targetX = aiPaddle.x + (predictedX - aiPaddle.x) * difficultyFactor; } } // Event handler for touch/mouse down game.down = function (x, y, obj) { // Only allow dragging the player paddle if (y > 2732 / 2) { dragTarget = playerPaddle; playerPaddle.targetX = x; isMouseOver = true; mouseX = x; mouseY = y; } }; // Event handler for touch/mouse move game.move = function (x, y, obj) { mouseX = x; mouseY = y; isMouseOver = true; if (dragTarget) { playerPaddle.targetX = x; } }; // Event handler for touch/mouse up game.up = function (x, y, obj) { dragTarget = null; }; // Game update loop game.update = function () { if (!gameActive) { return; } // Update AI updateAI(); // Update paddles playerPaddle.update(); aiPaddle.update(); // Update prediction line if (isMouseOver && balls.length > 0) { // Show prediction line from closest ball to mouse position var closestBall = null; var minDistance = Infinity; for (var i = 0; i < balls.length; i++) { var dist = Math.sqrt(Math.pow(balls[i].x - mouseX, 2) + Math.pow(balls[i].y - mouseY, 2)); if (dist < minDistance) { minDistance = dist; closestBall = balls[i]; } } if (closestBall) { predictionLine.updatePosition(closestBall.x, closestBall.y, mouseX, playerPaddle.y); } } else { predictionLine.hide(); } // Reset mouse tracking after update isMouseOver = false; // Update balls for (var i = balls.length - 1; i >= 0; i--) { var ball = balls[i]; // Apply gravity gravityField.applyGravityTo(ball); // Update ball position ball.update(); // Check for paddle collisions var hitPlayer = checkPaddleCollision(ball, playerPaddle); var hitAI = checkPaddleCollision(ball, aiPaddle); // Check if ball is out of bounds (scoring) if (ball.y > 2732 + 50) { // AI scores aiPaddle.score++; // Animated score update animateScoreText(aiScoreText, aiPaddle.score.toString()); LK.getSound('score').play(); // Create a visual effect at ball position before destroying var scoreEffect = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: ball.x, y: 2732, alpha: 0.8, scaleX: 1, scaleY: 1, tint: 0xff5733 }); game.addChild(scoreEffect); tween(scoreEffect.scale, { x: 3, y: 3 }, { duration: 500, easing: tween.cubicOut, onFinish: function onFinish() { tween(scoreEffect, { alpha: 0 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { scoreEffect.destroy(); } }); } }); ball.destroy(); balls.splice(i, 1); if (balls.length === 0) { createBall(); } // Check for game over if (aiPaddle.score >= 10) { gameActive = false; LK.showGameOver(); } } else if (ball.y < -50) { // Player scores playerPaddle.score++; // Animated score update animateScoreText(playerScoreText, playerPaddle.score.toString()); LK.getSound('score').play(); // Create a visual effect at ball position before destroying var scoreEffect = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: ball.x, y: 0, alpha: 0.8, scaleX: 1, scaleY: 1, tint: 0x4287f5 }); game.addChild(scoreEffect); tween(scoreEffect.scale, { x: 3, y: 3 }, { duration: 500, easing: tween.cubicOut, onFinish: function onFinish() { tween(scoreEffect, { alpha: 0 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { scoreEffect.destroy(); } }); } }); ball.destroy(); balls.splice(i, 1); if (balls.length === 0) { createBall(); } // Check for win if (playerPaddle.score >= 10) { gameActive = false; LK.showYouWin(); } } // Check for power-up collisions checkPowerUpCollision(ball); } }; // Initialize the game when code loads initGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
var glowBall = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.4,
scaleX: 1.5,
scaleY: 1.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 10;
self.gravityAffected = true;
self.lastX = 0;
self.lastY = 0;
self.trailElements = [];
self.maxTrailLength = 5;
self.createTrailElement = function () {
var trailElement = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: 0.7,
scaleY: 0.7,
tint: 0x87CEFA
});
return trailElement;
};
self.updateTrail = function () {
// Add new trail element
if (Math.abs(self.x - self.lastX) > 5 || Math.abs(self.y - self.lastY) > 5) {
var trail = self.createTrailElement();
trail.x = self.x;
trail.y = self.y;
game.addChildAt(trail, game.getChildIndex(self));
self.trailElements.push(trail);
// Fade out and remove
tween(trail, {
alpha: 0
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
}
});
// Remove old elements if too many
if (self.trailElements.length > self.maxTrailLength) {
var oldTrail = self.trailElements.shift();
if (oldTrail.parent) {
oldTrail.parent.removeChild(oldTrail);
}
}
}
// Update last position
self.lastX = self.x;
self.lastY = self.y;
};
self.reset = function () {
self.x = 2048 / 2;
self.y = 2732 / 2;
self.velocityX = (Math.random() > 0.5 ? 1 : -1) * self.speed;
self.velocityY = (Math.random() * 2 - 1) * self.speed * 0.5;
self.lastX = self.x;
self.lastY = self.y;
// Clear any existing trail elements
for (var i = 0; i < self.trailElements.length; i++) {
if (self.trailElements[i].parent) {
self.trailElements[i].parent.removeChild(self.trailElements[i]);
}
}
self.trailElements = [];
// Add pulsating glow effect
self.startGlowAnimation();
};
self.startGlowAnimation = function () {
tween(glowBall.scale, {
x: 1.8,
y: 1.8
}, {
duration: 800,
easing: tween.sinceOut,
onFinish: function onFinish() {
tween(glowBall.scale, {
x: 1.5,
y: 1.5
}, {
duration: 800,
easing: tween.sinceIn,
onFinish: function onFinish() {
self.startGlowAnimation();
}
});
}
});
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Update the trail
self.updateTrail();
// Handle wall collisions
if (self.x < 0 + ballGraphics.width / 2) {
self.x = ballGraphics.width / 2;
self.velocityX = -self.velocityX;
// Play wall bounce sound
LK.getSound('bounce').play();
} else if (self.x > 2048 - ballGraphics.width / 2) {
self.x = 2048 - ballGraphics.width / 2;
self.velocityX = -self.velocityX;
// Play wall bounce sound
LK.getSound('bounce').play();
}
};
return self;
});
var GravityField = Container.expand(function () {
var self = Container.call(this);
var fieldGraphics = self.attachAsset('gravitationField', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2
});
self.strength = 0.1;
self.maxStrength = 0.5;
self.setup = function () {
self.x = 2048 / 2;
self.y = 2732 / 2;
self.strength = 0.1;
self.scale.set(1);
};
self.increaseStrength = function () {
self.strength = Math.min(self.maxStrength, self.strength + 0.05);
// Play powerup sound when gravity field increases strength
LK.getSound('powerup').play();
tween(self.scale, {
x: 1 + self.strength,
y: 1 + self.strength
}, {
duration: 1000,
easing: tween.elasticOut
});
};
self.reverseGravity = function () {
self.strength = -self.strength;
// Play sound for gravity reversal
LK.getSound('powerup').play();
tween(fieldGraphics, {
tint: 0xFF5733
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
tween(fieldGraphics, {
tint: 0x4287f5
}, {
duration: 5000,
easing: tween.linear,
onFinish: function onFinish() {
self.strength = Math.abs(self.strength);
}
});
}
});
};
self.applyGravityTo = function (ball) {
if (!ball.gravityAffected) {
return;
}
var dx = self.x - ball.x;
var dy = self.y - ball.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
var forceX = dx / distance * self.strength;
var forceY = dy / distance * self.strength;
ball.velocityX += forceX;
ball.velocityY += forceY;
// Cap maximum velocity
var speed = Math.sqrt(ball.velocityX * ball.velocityX + ball.velocityY * ball.velocityY);
if (speed > ball.speed * 2) {
ball.velocityX = ball.velocityX / speed * ball.speed * 2;
ball.velocityY = ball.velocityY / speed * ball.speed * 2;
}
}
};
return self;
});
var Paddle = Container.expand(function () {
var self = Container.call(this);
// Create shadow effect
var paddleShadow = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: 1.05,
scaleY: 1.8,
y: 8
});
var paddleGraphics = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.speed = 15;
self.isPlayer = false;
self.score = 0;
self.setup = function (isPlayer) {
self.isPlayer = isPlayer;
if (isPlayer) {
self.y = 2732 - 200;
paddleGraphics.tint = 0x4287f5; // Blue for player
} else {
self.y = 200;
paddleGraphics.tint = 0xff5733; // Orange for AI
}
self.x = 2048 / 2;
self.targetX = self.x;
// Create entrance animation
self.scale.set(0.1);
// Play sound for paddle appearance
LK.getSound('powerup').play();
tween(self.scale, {
x: 1,
y: 1
}, {
duration: 800,
easing: tween.elasticOut
});
};
self.update = function () {
// Move toward target position with easing
if (Math.abs(self.x - self.targetX) > 1) {
self.x += (self.targetX - self.x) * 0.2;
}
// Boundary check
if (self.x < paddleGraphics.width / 2) {
self.x = paddleGraphics.width / 2;
} else if (self.x > 2048 - paddleGraphics.width / 2) {
self.x = 2048 - paddleGraphics.width / 2;
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var type = 'gravity'; // Default type
var powerUpGraphics;
self.setup = function (powerType) {
type = powerType;
if (self.children.length > 0) {
self.removeChildAt(0);
}
if (type === 'gravity') {
powerUpGraphics = self.attachAsset('powerUpGravity', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'multiball') {
powerUpGraphics = self.attachAsset('powerUpMultiBall', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.x = Math.random() * (2048 - 200) + 100;
self.y = Math.random() * (2732 - 600) + 300;
self.alpha = 1;
// Play sound on first appearance (not on pulse refresh)
if (arguments.length > 0) {
LK.getSound('powerup').play();
}
// Pulsating animation
tween(self, {
alpha: 0.6
}, {
duration: 800,
easing: tween.sinceOut,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 800,
easing: tween.sinceIn,
onFinish: function onFinish() {
if (self.parent) {
self.setup(type);
}
}
});
}
});
};
self.getType = function () {
return type;
};
return self;
});
var PredictionLine = Container.expand(function () {
var self = Container.call(this);
var lineSegments = [];
var numSegments = 10;
var segmentLength = 15;
var segmentGap = 10;
self.setup = function () {
// Create line segments
for (var i = 0; i < numSegments; i++) {
var segment = LK.getAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: segmentLength,
alpha: 0.6 - i * 0.05,
tint: 0x4287f5
});
lineSegments.push(segment);
self.addChild(segment);
}
self.visible = false;
};
self.updatePosition = function (startX, startY, targetX, targetY) {
if (!self.visible) {
self.visible = true;
}
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
for (var i = 0; i < lineSegments.length; i++) {
var distance = i * (segmentLength + segmentGap);
lineSegments[i].x = startX + Math.cos(angle) * distance;
lineSegments[i].y = startY + Math.sin(angle) * distance;
lineSegments[i].rotation = angle;
}
};
self.hide = function () {
self.visible = false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Set a beautiful dark gradient background
game.setBackgroundColor(0x0a0a2a);
// Add "Glaud" text in small orange to the upper right corner
var glaudText = new Text2('Glaud', {
size: 40,
fill: 0xff8c00 // Orange color
});
glaudText.anchor.set(1, 0); // Anchor to top right
glaudText.x = 2048 - 20; // 20px margin from right edge
glaudText.y = 20; // 20px margin from top edge
game.addChild(glaudText);
// Create a star field background
function createStarfield() {
var starfield = new Container();
// Create stars with different sizes and opacities
for (var i = 0; i < 100; i++) {
var size = Math.random() * 4 + 1;
var star = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: size,
height: size,
alpha: Math.random() * 0.7 + 0.3
});
star.x = Math.random() * 2048;
star.y = Math.random() * 2732;
// Create twinkling animation
var duration = Math.random() * 3000 + 2000;
// Create closure to keep reference to the current star
(function (starElement) {
function animateStar() {
tween(starElement, {
alpha: Math.random() * 0.5 + 0.1
}, {
duration: duration,
easing: tween.sinceInOut,
onFinish: function onFinish() {
tween(starElement, {
alpha: Math.random() * 0.7 + 0.3
}, {
duration: duration,
easing: tween.sinceInOut,
onFinish: animateStar
});
}
});
}
animateStar();
})(star);
starfield.addChild(star);
}
game.addChildAt(starfield, 0); // Add at the bottom layer
return starfield;
}
// Create starfield
var starfield = createStarfield();
// Create game elements
var playerPaddle = new Paddle();
var aiPaddle = new Paddle();
var gravityField = new GravityField();
var balls = [];
var powerUps = [];
var centerLine;
var playerScoreText;
var aiScoreText;
var dragTarget = null;
var difficultyTimer;
var powerUpTimer;
var gameActive = false;
var predictionLine;
var mouseX = 0;
var mouseY = 0;
var isMouseOver = false;
// Animate score text function
function animateScoreText(textObject, newValue) {
// Store original scale
var originalScale = {
x: textObject.scale.x,
y: textObject.scale.y
};
// Set the new text
textObject.setText(newValue);
// Animate scale up and back down
tween.stop(textObject.scale);
textObject.scale.set(originalScale.x * 1.5, originalScale.y * 1.5);
tween(textObject.scale, {
x: originalScale.x,
y: originalScale.y
}, {
duration: 500,
easing: tween.elasticOut
});
}
// Initialize game state
function initGame() {
// Setup players
playerPaddle.setup(true);
aiPaddle.setup(false);
game.addChild(playerPaddle);
game.addChild(aiPaddle);
// Setup gravity field
gravityField.setup();
game.addChild(gravityField);
// Create center line with animation
centerLine = new Container();
centerLine.alpha = 0;
for (var i = 0; i < 20; i++) {
var dash = LK.getAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 10,
alpha: 0.5
});
dash.x = i * 110;
// Add staggered animation to each dash
(function (element, delay) {
element.alpha = 0;
element.scale.set(0.5);
LK.setTimeout(function () {
tween(element, {
alpha: 0.5
}, {
duration: 400,
easing: tween.linear
});
tween(element.scale, {
x: 1,
y: 1
}, {
duration: 600,
easing: tween.elasticOut
});
}, delay);
})(dash, i * 30);
centerLine.addChild(dash);
}
centerLine.x = 50;
centerLine.y = 2732 / 2;
game.addChild(centerLine);
tween(centerLine, {
alpha: 1
}, {
duration: 800,
easing: tween.linear
});
// Create score text with animated appearance
playerScoreText = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
playerScoreText.anchor.set(0.5, 1);
playerScoreText.x = 2048 / 2;
playerScoreText.y = 2732 - 50;
playerScoreText.alpha = 0;
playerScoreText.scale.set(0.5);
game.addChild(playerScoreText);
aiScoreText = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
aiScoreText.anchor.set(0.5, 0);
aiScoreText.x = 2048 / 2;
aiScoreText.y = 50;
aiScoreText.alpha = 0;
aiScoreText.scale.set(0.5);
game.addChild(aiScoreText);
// Animate score text appearance
tween(playerScoreText, {
alpha: 1
}, {
duration: 800,
easing: tween.linear
});
tween(playerScoreText.scale, {
x: 1,
y: 1
}, {
duration: 1000,
easing: tween.elasticOut
});
tween(aiScoreText, {
alpha: 1
}, {
duration: 800,
easing: tween.linear
});
tween(aiScoreText.scale, {
x: 1,
y: 1
}, {
duration: 1000,
easing: tween.elasticOut
});
// Create first ball with delay to allow UI elements to animate in
LK.setTimeout(function () {
createBall();
}, 1200);
// Setup difficulty increases
difficultyTimer = LK.setInterval(function () {
if (gameActive) {
gravityField.increaseStrength();
}
}, 10000);
// Setup power-up spawning
powerUpTimer = LK.setInterval(function () {
if (gameActive && powerUps.length < 1) {
spawnPowerUp();
}
}, 15000);
// Create prediction line
predictionLine = new PredictionLine();
predictionLine.setup();
game.addChild(predictionLine);
// Start game
gameActive = true;
LK.playMusic('gameMusic');
}
// Function to create a new ball
function createBall() {
var ball = new Ball();
ball.reset();
balls.push(ball);
game.addChild(ball);
// Play sound when creating a new ball
LK.getSound('powerup').play();
return ball;
}
// Function to spawn a power-up
function spawnPowerUp() {
var powerUp = new PowerUp();
var type = Math.random() > 0.5 ? 'gravity' : 'multiball';
powerUp.setup(type);
powerUps.push(powerUp);
game.addChild(powerUp);
// Play sound when spawning power-up (now handled in PowerUp.setup)
}
// Check for collisions between ball and paddle
function checkPaddleCollision(ball, paddle) {
if (ball.y + 25 >= paddle.y - 25 && ball.y - 25 <= paddle.y + 25) {
if (ball.x + 25 >= paddle.x - 125 && ball.x - 25 <= paddle.x + 125) {
// Calculate bounce angle based on where ball hit the paddle
var relativeIntersectX = ball.x - paddle.x;
var normalizedRelativeIntersectionX = relativeIntersectX / 125;
var bounceAngle = normalizedRelativeIntersectionX * (Math.PI / 3); // Maximum angle: 60 degrees
// Invert Y velocity and adjust X velocity based on bounce angle
ball.velocityY = -ball.velocityY;
ball.velocityX = ball.speed * Math.sin(bounceAngle);
// Increase speed slightly
ball.speed = Math.min(20, ball.speed * 1.05);
// Play bounce sound
LK.getSound('bounce').play();
// Enhanced paddle hit effect - scale and flash
tween.stop(paddle.scale);
paddle.scale.set(1.2, 0.8);
tween(paddle.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.elasticOut
});
// Flash paddle with color based on player
LK.effects.flashObject(paddle, paddle.isPlayer ? 0x4287f5 : 0xff5733, 300);
return true;
}
}
return false;
}
// Check for collisions between ball and power-up
function checkPowerUpCollision(ball) {
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
if (ball.intersects(powerUp)) {
// Create explosion effect at power-up location
var explosionEffect = new Container();
explosionEffect.x = powerUp.x;
explosionEffect.y = powerUp.y;
game.addChild(explosionEffect);
// Create multiple particles for explosion
for (var p = 0; p < 10; p++) {
var particle = LK.getAsset(powerUp.getType() === 'gravity' ? 'powerUpGravity' : 'powerUpMultiBall', {
anchorX: 0.5,
anchorY: 0.5,
scale: 0.5,
alpha: 0.8
});
// Randomize particle position and movement
var angle = Math.random() * Math.PI * 2;
var distance = Math.random() * 100 + 50;
var duration = Math.random() * 500 + 500;
particle.x = 0;
particle.y = 0;
explosionEffect.addChild(particle);
// Animate particle movement and fade
tween(particle, {
x: Math.cos(angle) * distance,
y: Math.sin(angle) * distance,
alpha: 0
}, {
duration: duration,
easing: tween.cubicOut
});
// Ensure particle has proper scale object
particle.scale = particle.scale || {
x: 0.5,
y: 0.5
};
// Ensure particle has scale object before tweening
if (!particle.scale || typeof particle.scale === 'number') {
particle.scale = {
x: 0.5,
y: 0.5
};
}
// Now safely tween the scale property
tween(particle.scale, {
x: 0.1,
y: 0.1
}, {
duration: duration,
easing: tween.linear
});
}
// Remove explosion container after animation completes
LK.setTimeout(function () {
explosionEffect.destroy();
}, 1000);
// Apply power-up effect with enhanced visuals
if (powerUp.getType() === 'gravity') {
// Shake gravity field for dramatic effect
var originalX = gravityField.x;
var originalY = gravityField.y;
var shakeCount = 0;
var shakeInterval = LK.setInterval(function () {
gravityField.x = originalX + (Math.random() * 20 - 10);
gravityField.y = originalY + (Math.random() * 20 - 10);
shakeCount++;
if (shakeCount >= 10) {
LK.clearInterval(shakeInterval);
gravityField.x = originalX;
gravityField.y = originalY;
gravityField.reverseGravity();
}
}, 50);
} else if (powerUp.getType() === 'multiball') {
for (var j = 0; j < 2; j++) {
var newBall = createBall();
// Add a scale animation to new balls
newBall.scale.set(0.2);
tween(newBall.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.elasticOut
});
}
}
// Play sound and remove power-up
LK.getSound('powerup').play();
LK.effects.flashScreen(0x33FF57, 300);
powerUp.destroy();
powerUps.splice(i, 1);
}
}
}
// Handle AI movement
function updateAI() {
// Find the closest ball
var closestBall = null;
var closestDistance = Infinity;
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
if (ball.velocityY < 0) {
// Ball is moving towards AI
var distance = Math.abs(ball.x - aiPaddle.x);
if (distance < closestDistance) {
closestDistance = distance;
closestBall = ball;
}
}
}
if (closestBall) {
// Add some prediction based on velocity and gravity
var predictedX = closestBall.x + closestBall.velocityX * (aiPaddle.y - closestBall.y) / Math.abs(closestBall.velocityY);
aiPaddle.targetX = predictedX;
// Add some difficulty scaling
var difficultyFactor = 0.3 + gravityField.strength / gravityField.maxStrength * 0.6;
aiPaddle.targetX = aiPaddle.x + (predictedX - aiPaddle.x) * difficultyFactor;
}
}
// Event handler for touch/mouse down
game.down = function (x, y, obj) {
// Only allow dragging the player paddle
if (y > 2732 / 2) {
dragTarget = playerPaddle;
playerPaddle.targetX = x;
isMouseOver = true;
mouseX = x;
mouseY = y;
}
};
// Event handler for touch/mouse move
game.move = function (x, y, obj) {
mouseX = x;
mouseY = y;
isMouseOver = true;
if (dragTarget) {
playerPaddle.targetX = x;
}
};
// Event handler for touch/mouse up
game.up = function (x, y, obj) {
dragTarget = null;
};
// Game update loop
game.update = function () {
if (!gameActive) {
return;
}
// Update AI
updateAI();
// Update paddles
playerPaddle.update();
aiPaddle.update();
// Update prediction line
if (isMouseOver && balls.length > 0) {
// Show prediction line from closest ball to mouse position
var closestBall = null;
var minDistance = Infinity;
for (var i = 0; i < balls.length; i++) {
var dist = Math.sqrt(Math.pow(balls[i].x - mouseX, 2) + Math.pow(balls[i].y - mouseY, 2));
if (dist < minDistance) {
minDistance = dist;
closestBall = balls[i];
}
}
if (closestBall) {
predictionLine.updatePosition(closestBall.x, closestBall.y, mouseX, playerPaddle.y);
}
} else {
predictionLine.hide();
}
// Reset mouse tracking after update
isMouseOver = false;
// Update balls
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
// Apply gravity
gravityField.applyGravityTo(ball);
// Update ball position
ball.update();
// Check for paddle collisions
var hitPlayer = checkPaddleCollision(ball, playerPaddle);
var hitAI = checkPaddleCollision(ball, aiPaddle);
// Check if ball is out of bounds (scoring)
if (ball.y > 2732 + 50) {
// AI scores
aiPaddle.score++;
// Animated score update
animateScoreText(aiScoreText, aiPaddle.score.toString());
LK.getSound('score').play();
// Create a visual effect at ball position before destroying
var scoreEffect = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: ball.x,
y: 2732,
alpha: 0.8,
scaleX: 1,
scaleY: 1,
tint: 0xff5733
});
game.addChild(scoreEffect);
tween(scoreEffect.scale, {
x: 3,
y: 3
}, {
duration: 500,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(scoreEffect, {
alpha: 0
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
scoreEffect.destroy();
}
});
}
});
ball.destroy();
balls.splice(i, 1);
if (balls.length === 0) {
createBall();
}
// Check for game over
if (aiPaddle.score >= 10) {
gameActive = false;
LK.showGameOver();
}
} else if (ball.y < -50) {
// Player scores
playerPaddle.score++;
// Animated score update
animateScoreText(playerScoreText, playerPaddle.score.toString());
LK.getSound('score').play();
// Create a visual effect at ball position before destroying
var scoreEffect = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: ball.x,
y: 0,
alpha: 0.8,
scaleX: 1,
scaleY: 1,
tint: 0x4287f5
});
game.addChild(scoreEffect);
tween(scoreEffect.scale, {
x: 3,
y: 3
}, {
duration: 500,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(scoreEffect, {
alpha: 0
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
scoreEffect.destroy();
}
});
}
});
ball.destroy();
balls.splice(i, 1);
if (balls.length === 0) {
createBall();
}
// Check for win
if (playerPaddle.score >= 10) {
gameActive = false;
LK.showYouWin();
}
}
// Check for power-up collisions
checkPowerUpCollision(ball);
}
};
// Initialize the game when code loads
initGame();