/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballAsset = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.radius = ballAsset.width / 2; // Ball velocity self.vx = 0; self.vy = 0; // Ball speed (pixels per frame) self.speed = 9; // Ball update self.update = function () { self.x += self.vx; self.y += self.vy; }; return self; }); // Paddle class var Paddle = Container.expand(function () { var self = Container.call(this); var paddleAsset = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); // Paddle width/height for collision self.width = paddleAsset.width; self.height = paddleAsset.height; return self; }); // Wall class (for left, right, and top) var Wall = Container.expand(function () { var self = Container.call(this); var wallAsset = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); self.width = wallAsset.width; self.height = wallAsset.height; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game area // Paddle: wide, short rectangle // Ball: circle // Wall: thin, tall rectangle (for left/right/top walls) var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; // Paddle setup var paddle = new Paddle(); game.addChild(paddle); paddle.x = GAME_WIDTH / 2; paddle.y = GAME_HEIGHT - 180; // AI Paddle setup (top) var aiPaddle = new Paddle(); game.addChild(aiPaddle); aiPaddle.x = GAME_WIDTH / 2; aiPaddle.y = wallThickness + aiPaddle.height / 2 + 60; // 60px below top wall // Ball setup var ball = new Ball(); game.addChild(ball); // Start ball above player paddle ball.x = GAME_WIDTH / 2; ball.y = paddle.y - paddle.height / 2 - ball.radius - 10; // Ball initial direction: randomize left/right var angle = Math.PI / 4 + (Math.random() < 0.5 ? -1 : 1) * Math.PI / 8; ball.vx = Math.cos(angle) * ball.speed; ball.vy = -Math.abs(Math.sin(angle) * ball.speed); // Walls (left, right, top) var wallThickness = 40; var wallLength = GAME_HEIGHT + 200; // cover full height var leftWall = new Wall(); leftWall.x = wallThickness / 2; leftWall.y = GAME_HEIGHT / 2; leftWall.height = wallLength; game.addChild(leftWall); var rightWall = new Wall(); rightWall.x = GAME_WIDTH - wallThickness / 2; rightWall.y = GAME_HEIGHT / 2; rightWall.height = wallLength; game.addChild(rightWall); var topWall = new Wall(); topWall.width = GAME_WIDTH; topWall.height = wallThickness; topWall.x = GAME_WIDTH / 2; topWall.y = wallThickness / 2; game.addChild(topWall); // Score var score = 0; // Retrieve best score from storage (if any) var bestScore = storage.bestScore || 0; // Score text var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Best score text (smaller, above score) var bestScoreTxt = new Text2('Best: ' + bestScore, { size: 60, fill: 0xFFD700 }); bestScoreTxt.anchor.set(0.5, 0); bestScoreTxt.y = 160; // moved up by 130 pixels LK.gui.top.addChild(bestScoreTxt); // Dragging paddle var dragging = false; var dragOffsetX = 0; // Prevent paddle from going off screen function clampPaddleX(x) { var halfWidth = paddle.width / 2; if (x < wallThickness + halfWidth) return wallThickness + halfWidth; if (x > GAME_WIDTH - wallThickness - halfWidth) return GAME_WIDTH - wallThickness - halfWidth; return x; } // Touch/mouse events for paddle control game.down = function (x, y, obj) { // Only start drag if touch is near paddle var dx = x - paddle.x; var dy = y - paddle.y; if (Math.abs(dx) <= paddle.width / 2 + 60 && Math.abs(dy) <= paddle.height / 2 + 80) { dragging = true; dragOffsetX = paddle.x - x; // Move paddle immediately paddle.x = clampPaddleX(x + dragOffsetX); } }; game.move = function (x, y, obj) { if (dragging) { var prevX = paddle.x; var newX = clampPaddleX(x + dragOffsetX); paddle.x = newX; // Calculate movement direction and speed var dx = newX - prevX; // Clamp rotation to max ±0.28 radians (~16deg) var maxRot = 0.28; // Scale: full paddle width in one frame = maxRot, otherwise proportional var rot = Math.max(-maxRot, Math.min(maxRot, dx / 40)); paddle.rotation = rot; } else { // Smoothly return to 0 rotation when not dragging if (Math.abs(paddle.rotation) > 0.01) { paddle.rotation *= 0.7; } else { paddle.rotation = 0; } } }; game.up = function (x, y, obj) { dragging = false; paddle.rotation = 0; }; // Ball speed increase timer var speedupInterval = 2000; // ms var speedupAmount = 0.75; // pixels/frame var maxSpeed = 24; var speedupTimer = LK.setInterval(function () { if (ball.speed < maxSpeed) { ball.speed += speedupAmount; // Normalize direction var mag = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); ball.vx = ball.vx / mag * ball.speed; ball.vy = ball.vy / mag * ball.speed; } }, speedupInterval); // Main game update game.update = function () { // Ball movement ball.update(); // Ball collision with left/right walls if (ball.x - ball.radius <= wallThickness) { ball.x = wallThickness + ball.radius; ball.vx = Math.abs(ball.vx); } if (ball.x + ball.radius >= GAME_WIDTH - wallThickness) { ball.x = GAME_WIDTH - wallThickness - ball.radius; ball.vx = -Math.abs(ball.vx); } // Ball collision with top wall if (ball.y - ball.radius <= wallThickness) { ball.y = wallThickness + ball.radius; ball.vy = Math.abs(ball.vy); } // Ball collision with paddle // Simple AABB check var paddleTop = paddle.y - paddle.height / 2; var paddleBottom = paddle.y + paddle.height / 2; var paddleLeft = paddle.x - paddle.width / 2; var paddleRight = paddle.x + paddle.width / 2; var ballBottom = ball.y + ball.radius; var ballTop = ball.y - ball.radius; var ballLeft = ball.x - ball.radius; var ballRight = ball.x + ball.radius; // AI Paddle follows ball.x (with some speed limit for realism) var aiSpeed = 32; // max px per frame var aiTargetX = ball.x; var aiHalfWidth = aiPaddle.width / 2; var aiMinX = wallThickness + aiHalfWidth; var aiMaxX = GAME_WIDTH - wallThickness - aiHalfWidth; var aiDelta = aiTargetX - aiPaddle.x; if (Math.abs(aiDelta) > aiSpeed) { aiDelta = aiSpeed * (aiDelta > 0 ? 1 : -1); } aiPaddle.x = Math.max(aiMinX, Math.min(aiMaxX, aiPaddle.x + aiDelta)); // Ball collision with AI paddle (top) var aiPaddleTop = aiPaddle.y - aiPaddle.height / 2; var aiPaddleBottom = aiPaddle.y + aiPaddle.height / 2; var aiPaddleLeft = aiPaddle.x - aiPaddle.width / 2; var aiPaddleRight = aiPaddle.x + aiPaddle.width / 2; // Only check if ball is moving up if (ball.vy < 0 && ballTop <= aiPaddleBottom && ballBottom >= aiPaddleTop && ballRight >= aiPaddleLeft && ballLeft <= aiPaddleRight) { // Bounce ball down ball.y = aiPaddleBottom + ball.radius; // Calculate bounce angle based on where it hit the AI paddle var aiHitPos = (ball.x - aiPaddle.x) / (aiPaddle.width / 2); // -1 (left) to 1 (right) var aiBounceAngle = aiHitPos * (Math.PI / 3); // up to 60deg left/right var aiSpeedVal = ball.speed; ball.vx = Math.sin(aiBounceAngle) * aiSpeedVal; ball.vy = Math.abs(Math.cos(aiBounceAngle) * aiSpeedVal); } // Only check if ball is moving down if (ball.vy > 0 && ballBottom >= paddleTop && ballTop <= paddleBottom && ballRight >= paddleLeft && ballLeft <= paddleRight) { // Bounce ball up ball.y = paddleTop - ball.radius; // Calculate bounce angle based on where it hit the paddle var hitPos = (ball.x - paddle.x) / (paddle.width / 2); // -1 (left) to 1 (right) var bounceAngle = hitPos * (Math.PI / 3); // up to 60deg left/right var speed = ball.speed; ball.vx = Math.sin(bounceAngle) * speed; ball.vy = -Math.abs(Math.cos(bounceAngle) * speed); // Score up score += 1; scoreTxt.setText(score); // Check for new best score if (score > bestScore) { bestScore = score; storage.bestScore = bestScore; bestScoreTxt.setText('Best: ' + bestScore); // Show a quick message for new best if (score === bestScore) { var msg = new Text2('New Best!', { size: 100, fill: 0x00FF00 }); msg.anchor.set(0.5, 0.5); msg.x = GAME_WIDTH / 2; msg.y = GAME_HEIGHT / 2; game.addChild(msg); tween(msg, { alpha: 0 }, { duration: 1200, onFinish: function onFinish() { msg.destroy(); } }); } } // Increase ball speed every point, up to maxSpeed if (ball.speed < maxSpeed) { ball.speed += 0.5; // Normalize direction var mag = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); ball.vx = ball.vx / mag * ball.speed; ball.vy = ball.vy / mag * ball.speed; } } // Ball falls below paddle (game over) if (ball.y - ball.radius > GAME_HEIGHT) { LK.effects.flashScreen(0xff0000, 800); LK.clearInterval(speedupTimer); // Show game over after ball falls below paddle LK.effects.flashScreen(0xff0000, 800); LK.clearInterval(speedupTimer); LK.showGameOver(); return; } }; // Game will start immediately
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = ballAsset.width / 2;
// Ball velocity
self.vx = 0;
self.vy = 0;
// Ball speed (pixels per frame)
self.speed = 9;
// Ball update
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleAsset = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
// Paddle width/height for collision
self.width = paddleAsset.width;
self.height = paddleAsset.height;
return self;
});
// Wall class (for left, right, and top)
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallAsset = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = wallAsset.width;
self.height = wallAsset.height;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game area
// Paddle: wide, short rectangle
// Ball: circle
// Wall: thin, tall rectangle (for left/right/top walls)
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// Paddle setup
var paddle = new Paddle();
game.addChild(paddle);
paddle.x = GAME_WIDTH / 2;
paddle.y = GAME_HEIGHT - 180;
// AI Paddle setup (top)
var aiPaddle = new Paddle();
game.addChild(aiPaddle);
aiPaddle.x = GAME_WIDTH / 2;
aiPaddle.y = wallThickness + aiPaddle.height / 2 + 60; // 60px below top wall
// Ball setup
var ball = new Ball();
game.addChild(ball);
// Start ball above player paddle
ball.x = GAME_WIDTH / 2;
ball.y = paddle.y - paddle.height / 2 - ball.radius - 10;
// Ball initial direction: randomize left/right
var angle = Math.PI / 4 + (Math.random() < 0.5 ? -1 : 1) * Math.PI / 8;
ball.vx = Math.cos(angle) * ball.speed;
ball.vy = -Math.abs(Math.sin(angle) * ball.speed);
// Walls (left, right, top)
var wallThickness = 40;
var wallLength = GAME_HEIGHT + 200; // cover full height
var leftWall = new Wall();
leftWall.x = wallThickness / 2;
leftWall.y = GAME_HEIGHT / 2;
leftWall.height = wallLength;
game.addChild(leftWall);
var rightWall = new Wall();
rightWall.x = GAME_WIDTH - wallThickness / 2;
rightWall.y = GAME_HEIGHT / 2;
rightWall.height = wallLength;
game.addChild(rightWall);
var topWall = new Wall();
topWall.width = GAME_WIDTH;
topWall.height = wallThickness;
topWall.x = GAME_WIDTH / 2;
topWall.y = wallThickness / 2;
game.addChild(topWall);
// Score
var score = 0;
// Retrieve best score from storage (if any)
var bestScore = storage.bestScore || 0;
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Best score text (smaller, above score)
var bestScoreTxt = new Text2('Best: ' + bestScore, {
size: 60,
fill: 0xFFD700
});
bestScoreTxt.anchor.set(0.5, 0);
bestScoreTxt.y = 160; // moved up by 130 pixels
LK.gui.top.addChild(bestScoreTxt);
// Dragging paddle
var dragging = false;
var dragOffsetX = 0;
// Prevent paddle from going off screen
function clampPaddleX(x) {
var halfWidth = paddle.width / 2;
if (x < wallThickness + halfWidth) return wallThickness + halfWidth;
if (x > GAME_WIDTH - wallThickness - halfWidth) return GAME_WIDTH - wallThickness - halfWidth;
return x;
}
// Touch/mouse events for paddle control
game.down = function (x, y, obj) {
// Only start drag if touch is near paddle
var dx = x - paddle.x;
var dy = y - paddle.y;
if (Math.abs(dx) <= paddle.width / 2 + 60 && Math.abs(dy) <= paddle.height / 2 + 80) {
dragging = true;
dragOffsetX = paddle.x - x;
// Move paddle immediately
paddle.x = clampPaddleX(x + dragOffsetX);
}
};
game.move = function (x, y, obj) {
if (dragging) {
var prevX = paddle.x;
var newX = clampPaddleX(x + dragOffsetX);
paddle.x = newX;
// Calculate movement direction and speed
var dx = newX - prevX;
// Clamp rotation to max ±0.28 radians (~16deg)
var maxRot = 0.28;
// Scale: full paddle width in one frame = maxRot, otherwise proportional
var rot = Math.max(-maxRot, Math.min(maxRot, dx / 40));
paddle.rotation = rot;
} else {
// Smoothly return to 0 rotation when not dragging
if (Math.abs(paddle.rotation) > 0.01) {
paddle.rotation *= 0.7;
} else {
paddle.rotation = 0;
}
}
};
game.up = function (x, y, obj) {
dragging = false;
paddle.rotation = 0;
};
// Ball speed increase timer
var speedupInterval = 2000; // ms
var speedupAmount = 0.75; // pixels/frame
var maxSpeed = 24;
var speedupTimer = LK.setInterval(function () {
if (ball.speed < maxSpeed) {
ball.speed += speedupAmount;
// Normalize direction
var mag = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
ball.vx = ball.vx / mag * ball.speed;
ball.vy = ball.vy / mag * ball.speed;
}
}, speedupInterval);
// Main game update
game.update = function () {
// Ball movement
ball.update();
// Ball collision with left/right walls
if (ball.x - ball.radius <= wallThickness) {
ball.x = wallThickness + ball.radius;
ball.vx = Math.abs(ball.vx);
}
if (ball.x + ball.radius >= GAME_WIDTH - wallThickness) {
ball.x = GAME_WIDTH - wallThickness - ball.radius;
ball.vx = -Math.abs(ball.vx);
}
// Ball collision with top wall
if (ball.y - ball.radius <= wallThickness) {
ball.y = wallThickness + ball.radius;
ball.vy = Math.abs(ball.vy);
}
// Ball collision with paddle
// Simple AABB check
var paddleTop = paddle.y - paddle.height / 2;
var paddleBottom = paddle.y + paddle.height / 2;
var paddleLeft = paddle.x - paddle.width / 2;
var paddleRight = paddle.x + paddle.width / 2;
var ballBottom = ball.y + ball.radius;
var ballTop = ball.y - ball.radius;
var ballLeft = ball.x - ball.radius;
var ballRight = ball.x + ball.radius;
// AI Paddle follows ball.x (with some speed limit for realism)
var aiSpeed = 32; // max px per frame
var aiTargetX = ball.x;
var aiHalfWidth = aiPaddle.width / 2;
var aiMinX = wallThickness + aiHalfWidth;
var aiMaxX = GAME_WIDTH - wallThickness - aiHalfWidth;
var aiDelta = aiTargetX - aiPaddle.x;
if (Math.abs(aiDelta) > aiSpeed) {
aiDelta = aiSpeed * (aiDelta > 0 ? 1 : -1);
}
aiPaddle.x = Math.max(aiMinX, Math.min(aiMaxX, aiPaddle.x + aiDelta));
// Ball collision with AI paddle (top)
var aiPaddleTop = aiPaddle.y - aiPaddle.height / 2;
var aiPaddleBottom = aiPaddle.y + aiPaddle.height / 2;
var aiPaddleLeft = aiPaddle.x - aiPaddle.width / 2;
var aiPaddleRight = aiPaddle.x + aiPaddle.width / 2;
// Only check if ball is moving up
if (ball.vy < 0 && ballTop <= aiPaddleBottom && ballBottom >= aiPaddleTop && ballRight >= aiPaddleLeft && ballLeft <= aiPaddleRight) {
// Bounce ball down
ball.y = aiPaddleBottom + ball.radius;
// Calculate bounce angle based on where it hit the AI paddle
var aiHitPos = (ball.x - aiPaddle.x) / (aiPaddle.width / 2); // -1 (left) to 1 (right)
var aiBounceAngle = aiHitPos * (Math.PI / 3); // up to 60deg left/right
var aiSpeedVal = ball.speed;
ball.vx = Math.sin(aiBounceAngle) * aiSpeedVal;
ball.vy = Math.abs(Math.cos(aiBounceAngle) * aiSpeedVal);
}
// Only check if ball is moving down
if (ball.vy > 0 && ballBottom >= paddleTop && ballTop <= paddleBottom && ballRight >= paddleLeft && ballLeft <= paddleRight) {
// Bounce ball up
ball.y = paddleTop - ball.radius;
// Calculate bounce angle based on where it hit the paddle
var hitPos = (ball.x - paddle.x) / (paddle.width / 2); // -1 (left) to 1 (right)
var bounceAngle = hitPos * (Math.PI / 3); // up to 60deg left/right
var speed = ball.speed;
ball.vx = Math.sin(bounceAngle) * speed;
ball.vy = -Math.abs(Math.cos(bounceAngle) * speed);
// Score up
score += 1;
scoreTxt.setText(score);
// Check for new best score
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
bestScoreTxt.setText('Best: ' + bestScore);
// Show a quick message for new best
if (score === bestScore) {
var msg = new Text2('New Best!', {
size: 100,
fill: 0x00FF00
});
msg.anchor.set(0.5, 0.5);
msg.x = GAME_WIDTH / 2;
msg.y = GAME_HEIGHT / 2;
game.addChild(msg);
tween(msg, {
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
msg.destroy();
}
});
}
}
// Increase ball speed every point, up to maxSpeed
if (ball.speed < maxSpeed) {
ball.speed += 0.5;
// Normalize direction
var mag = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
ball.vx = ball.vx / mag * ball.speed;
ball.vy = ball.vy / mag * ball.speed;
}
}
// Ball falls below paddle (game over)
if (ball.y - ball.radius > GAME_HEIGHT) {
LK.effects.flashScreen(0xff0000, 800);
LK.clearInterval(speedupTimer);
// Show game over after ball falls below paddle
LK.effects.flashScreen(0xff0000, 800);
LK.clearInterval(speedupTimer);
LK.showGameOver();
return;
}
};
// Game will start immediately