/**** * Plugins ****/ var tween = LK.import("@upit/tween.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 }); // Ball speed and direction self.vx = 0; self.vy = 0; // Ball radius for collision self.radius = ballAsset.width / 2; // Ball update self.update = function () { self.x += self.vx; self.y += self.vy; }; return self; }); // Paddle class (for both player and AI) var Paddle = Container.expand(function () { var self = Container.call(this); // Default to player paddle var paddleAsset = self.attachAsset('paddlePlayer', { anchorX: 0.5, anchorY: 0.5 }); self.setAI = function () { // Remove old asset if any if (self.children.length > 0) { self.removeChild(self.children[0]); } paddleAsset = self.attachAsset('paddleAI', { anchorX: 0.5, anchorY: 0.5 }); }; // Paddle width/height for collision self.getWidth = function () { return paddleAsset.width; }; self.getHeight = function () { return paddleAsset.height; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Flash for rally // Ball // Paddle (AI) // Paddle (player) // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PADDLE_Y_OFFSET = 120; // Distance from top/bottom for paddles var BALL_START_SPEED = 22; var BALL_SPEED_INCREMENT = 2.5; var MAX_LEVEL = 10; // Game state var playerScore = 0; var aiScore = 0; var level = 1; var rallyCount = 0; var isGameActive = true; // Create paddles and ball var playerPaddle = new Paddle(); var aiPaddle = new Paddle(); aiPaddle.setAI(); var ball = new Ball(); // Add to game game.addChild(playerPaddle); game.addChild(aiPaddle); game.addChild(ball); // Position paddles and ball function resetPositions() { // Center paddles playerPaddle.x = GAME_WIDTH / 2; playerPaddle.y = GAME_HEIGHT - PADDLE_Y_OFFSET; aiPaddle.x = GAME_WIDTH / 2; aiPaddle.y = PADDLE_Y_OFFSET; // Center ball ball.x = GAME_WIDTH / 2; ball.y = GAME_HEIGHT / 2; } // Ball initial direction (randomize angle) function launchBall() { var angle = Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3; // Downwards, randomize a bit if (Math.random() < 0.5) angle = -angle; // Sometimes start upwards var speed = BALL_START_SPEED + (level - 1) * BALL_SPEED_INCREMENT; ball.vx = Math.cos(angle) * speed; ball.vy = Math.sin(angle) * speed; // Ensure vy is not too small (avoid boring horizontal) if (Math.abs(ball.vy) < speed * 0.5) { ball.vy = (ball.vy < 0 ? -1 : 1) * speed * 0.7; } } // Score text var scoreTxt = new Text2('0 : 0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Level text var levelTxt = new Text2('Level 1', { size: 80, fill: 0xFFD700 }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); levelTxt.y = 130; // Rally feedback var rallyTxt = new Text2('', { size: 90, fill: 0x00FF00 }); rallyTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(rallyTxt); // Flash overlay for rally/miss var rallyFlash = LK.getAsset('rallyFlash', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0 }); game.addChild(rallyFlash); // Reset game state for new round/level function startLevel(newLevel) { level = newLevel; playerScore = 0; aiScore = 0; rallyCount = 0; isGameActive = true; updateScoreText(); updateLevelText(); rallyTxt.setText(''); resetPositions(); launchBall(); } // Update score display function updateScoreText() { scoreTxt.setText(playerScore + ' : ' + aiScore); } // Update level display function updateLevelText() { levelTxt.setText('Level ' + level); } // Show rally feedback function showRally(count) { if (count > 1) { rallyTxt.setText('Rally! x' + count); rallyTxt.alpha = 1; tween(rallyTxt, { alpha: 0 }, { duration: 900, easing: tween.linear }); // Flash white rallyFlash.alpha = 0.18; tween(rallyFlash, { alpha: 0 }, { duration: 400, easing: tween.linear }); } } // Show miss feedback function showMiss(who) { rallyTxt.setText(who + ' Miss!'); rallyTxt.alpha = 1; tween(rallyTxt, { alpha: 0 }, { duration: 900, easing: tween.linear }); // Flash red rallyFlash.tint = 0xff0000; rallyFlash.alpha = 0.18; tween(rallyFlash, { alpha: 0 }, { duration: 400, easing: tween.linear, onFinish: function onFinish() { rallyFlash.tint = 0xffffff; } }); } // End game (player lost) function endGame() { isGameActive = false; LK.effects.flashScreen(0xff0000, 900); LK.showGameOver(); } // Win game (player beat all levels) function winGame() { isGameActive = false; LK.effects.flashScreen(0x00ff00, 900); LK.showYouWin(); } // Paddle movement (player drag) var dragPaddle = null; function handleMove(x, y, obj) { if (!isGameActive) return; if (dragPaddle) { // Clamp paddle within game bounds var halfW = dragPaddle.getWidth() / 2; var newX = x; if (newX < halfW) newX = halfW; if (newX > GAME_WIDTH - halfW) newX = GAME_WIDTH - halfW; dragPaddle.x = newX; } } game.move = handleMove; game.down = function (x, y, obj) { // Only allow drag if touch is on/near player paddle var paddleTop = playerPaddle.y - playerPaddle.getHeight() / 2; var paddleBottom = playerPaddle.y + playerPaddle.getHeight() / 2; var paddleLeft = playerPaddle.x - playerPaddle.getWidth() / 2; var paddleRight = playerPaddle.x + playerPaddle.getWidth() / 2; if (y >= paddleTop - 60 && y <= paddleBottom + 60 && x >= paddleLeft - 60 && x <= paddleRight + 60) { dragPaddle = playerPaddle; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragPaddle = null; }; // AI paddle logic function updateAIPaddle() { // AI difficulty increases with level var aiSpeed = 16 + (level - 1) * 3.5; // px per frame var aiReact = 0.18 + (level - 1) * 0.07; // how much of the distance to move per frame if (aiReact > 0.5) aiReact = 0.5; // Only move if ball is moving towards AI if (ball.vy < 0) { var dx = ball.x - aiPaddle.x; var move = dx * aiReact; if (Math.abs(move) > aiSpeed) move = (move > 0 ? 1 : -1) * aiSpeed; aiPaddle.x += move; // Clamp var halfW = aiPaddle.getWidth() / 2; if (aiPaddle.x < halfW) aiPaddle.x = halfW; if (aiPaddle.x > GAME_WIDTH - halfW) aiPaddle.x = GAME_WIDTH - halfW; } } // Ball collision with paddle function ballIntersectsPaddle(paddle) { var px = paddle.x; var py = paddle.y; var pw = paddle.getWidth(); var ph = paddle.getHeight(); // Closest point on paddle to ball var closestX = ball.x; if (ball.x < px - pw / 2) closestX = px - pw / 2;else if (ball.x > px + pw / 2) closestX = px + pw / 2; var closestY = ball.y; if (ball.y < py - ph / 2) closestY = py - ph / 2;else if (ball.y > py + ph / 2) closestY = py + ph / 2; // Distance to ball center var dx = ball.x - closestX; var dy = ball.y - closestY; return dx * dx + dy * dy <= ball.radius * ball.radius; } // Ball collision with wall function ballWallBounce() { // Left/right if (ball.x - ball.radius < 0) { ball.x = ball.radius; ball.vx = -ball.vx; } if (ball.x + ball.radius > GAME_WIDTH) { ball.x = GAME_WIDTH - ball.radius; ball.vx = -ball.vx; } } // Ball out of bounds (top/bottom) function checkScore() { if (ball.y - ball.radius < 0) { // Player scores playerScore += 1; updateScoreText(); showMiss('AI'); rallyCount = 0; if (playerScore >= 5) { // Next level or win if (level >= MAX_LEVEL) { winGame(); } else { startLevel(level + 1); } } else { resetPositions(); launchBall(); } return true; } if (ball.y + ball.radius > GAME_HEIGHT) { // AI scores aiScore += 1; updateScoreText(); showMiss('Player'); rallyCount = 0; if (aiScore >= 3) { endGame(); } else { resetPositions(); launchBall(); } return true; } return false; } // Ball-paddle bounce logic function handleBallPaddleBounce() { // Player paddle if (ball.vy > 0 && ballIntersectsPaddle(playerPaddle)) { // Bounce up ball.y = playerPaddle.y - playerPaddle.getHeight() / 2 - ball.radius; ball.vy = -Math.abs(ball.vy); // Add spin based on where hit var offset = (ball.x - playerPaddle.x) / (playerPaddle.getWidth() / 2); ball.vx += offset * 7; // Clamp vx var maxV = BALL_START_SPEED + (level - 1) * BALL_SPEED_INCREMENT + 7; if (ball.vx > maxV) ball.vx = maxV; if (ball.vx < -maxV) ball.vx = -maxV; rallyCount += 1; showRally(rallyCount); } // AI paddle if (ball.vy < 0 && ballIntersectsPaddle(aiPaddle)) { // Bounce down ball.y = aiPaddle.y + aiPaddle.getHeight() / 2 + ball.radius; ball.vy = Math.abs(ball.vy); // Add spin var offset = (ball.x - aiPaddle.x) / (aiPaddle.getWidth() / 2); ball.vx += offset * 7; // Clamp vx var maxV = BALL_START_SPEED + (level - 1) * BALL_SPEED_INCREMENT + 7; if (ball.vx > maxV) ball.vx = maxV; if (ball.vx < -maxV) ball.vx = -maxV; rallyCount += 1; showRally(rallyCount); } } // Game update loop game.update = function () { if (!isGameActive) return; // Ball movement ball.update(); // Wall bounce ballWallBounce(); // Paddle bounce handleBallPaddleBounce(); // AI paddle updateAIPaddle(); // Out of bounds/score checkScore(); }; // Start first level startLevel(1);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.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
});
// Ball speed and direction
self.vx = 0;
self.vy = 0;
// Ball radius for collision
self.radius = ballAsset.width / 2;
// Ball update
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Paddle class (for both player and AI)
var Paddle = Container.expand(function () {
var self = Container.call(this);
// Default to player paddle
var paddleAsset = self.attachAsset('paddlePlayer', {
anchorX: 0.5,
anchorY: 0.5
});
self.setAI = function () {
// Remove old asset if any
if (self.children.length > 0) {
self.removeChild(self.children[0]);
}
paddleAsset = self.attachAsset('paddleAI', {
anchorX: 0.5,
anchorY: 0.5
});
};
// Paddle width/height for collision
self.getWidth = function () {
return paddleAsset.width;
};
self.getHeight = function () {
return paddleAsset.height;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Flash for rally
// Ball
// Paddle (AI)
// Paddle (player)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PADDLE_Y_OFFSET = 120; // Distance from top/bottom for paddles
var BALL_START_SPEED = 22;
var BALL_SPEED_INCREMENT = 2.5;
var MAX_LEVEL = 10;
// Game state
var playerScore = 0;
var aiScore = 0;
var level = 1;
var rallyCount = 0;
var isGameActive = true;
// Create paddles and ball
var playerPaddle = new Paddle();
var aiPaddle = new Paddle();
aiPaddle.setAI();
var ball = new Ball();
// Add to game
game.addChild(playerPaddle);
game.addChild(aiPaddle);
game.addChild(ball);
// Position paddles and ball
function resetPositions() {
// Center paddles
playerPaddle.x = GAME_WIDTH / 2;
playerPaddle.y = GAME_HEIGHT - PADDLE_Y_OFFSET;
aiPaddle.x = GAME_WIDTH / 2;
aiPaddle.y = PADDLE_Y_OFFSET;
// Center ball
ball.x = GAME_WIDTH / 2;
ball.y = GAME_HEIGHT / 2;
}
// Ball initial direction (randomize angle)
function launchBall() {
var angle = Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3; // Downwards, randomize a bit
if (Math.random() < 0.5) angle = -angle; // Sometimes start upwards
var speed = BALL_START_SPEED + (level - 1) * BALL_SPEED_INCREMENT;
ball.vx = Math.cos(angle) * speed;
ball.vy = Math.sin(angle) * speed;
// Ensure vy is not too small (avoid boring horizontal)
if (Math.abs(ball.vy) < speed * 0.5) {
ball.vy = (ball.vy < 0 ? -1 : 1) * speed * 0.7;
}
}
// Score text
var scoreTxt = new Text2('0 : 0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Level text
var levelTxt = new Text2('Level 1', {
size: 80,
fill: 0xFFD700
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
levelTxt.y = 130;
// Rally feedback
var rallyTxt = new Text2('', {
size: 90,
fill: 0x00FF00
});
rallyTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(rallyTxt);
// Flash overlay for rally/miss
var rallyFlash = LK.getAsset('rallyFlash', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0
});
game.addChild(rallyFlash);
// Reset game state for new round/level
function startLevel(newLevel) {
level = newLevel;
playerScore = 0;
aiScore = 0;
rallyCount = 0;
isGameActive = true;
updateScoreText();
updateLevelText();
rallyTxt.setText('');
resetPositions();
launchBall();
}
// Update score display
function updateScoreText() {
scoreTxt.setText(playerScore + ' : ' + aiScore);
}
// Update level display
function updateLevelText() {
levelTxt.setText('Level ' + level);
}
// Show rally feedback
function showRally(count) {
if (count > 1) {
rallyTxt.setText('Rally! x' + count);
rallyTxt.alpha = 1;
tween(rallyTxt, {
alpha: 0
}, {
duration: 900,
easing: tween.linear
});
// Flash white
rallyFlash.alpha = 0.18;
tween(rallyFlash, {
alpha: 0
}, {
duration: 400,
easing: tween.linear
});
}
}
// Show miss feedback
function showMiss(who) {
rallyTxt.setText(who + ' Miss!');
rallyTxt.alpha = 1;
tween(rallyTxt, {
alpha: 0
}, {
duration: 900,
easing: tween.linear
});
// Flash red
rallyFlash.tint = 0xff0000;
rallyFlash.alpha = 0.18;
tween(rallyFlash, {
alpha: 0
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
rallyFlash.tint = 0xffffff;
}
});
}
// End game (player lost)
function endGame() {
isGameActive = false;
LK.effects.flashScreen(0xff0000, 900);
LK.showGameOver();
}
// Win game (player beat all levels)
function winGame() {
isGameActive = false;
LK.effects.flashScreen(0x00ff00, 900);
LK.showYouWin();
}
// Paddle movement (player drag)
var dragPaddle = null;
function handleMove(x, y, obj) {
if (!isGameActive) return;
if (dragPaddle) {
// Clamp paddle within game bounds
var halfW = dragPaddle.getWidth() / 2;
var newX = x;
if (newX < halfW) newX = halfW;
if (newX > GAME_WIDTH - halfW) newX = GAME_WIDTH - halfW;
dragPaddle.x = newX;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only allow drag if touch is on/near player paddle
var paddleTop = playerPaddle.y - playerPaddle.getHeight() / 2;
var paddleBottom = playerPaddle.y + playerPaddle.getHeight() / 2;
var paddleLeft = playerPaddle.x - playerPaddle.getWidth() / 2;
var paddleRight = playerPaddle.x + playerPaddle.getWidth() / 2;
if (y >= paddleTop - 60 && y <= paddleBottom + 60 && x >= paddleLeft - 60 && x <= paddleRight + 60) {
dragPaddle = playerPaddle;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragPaddle = null;
};
// AI paddle logic
function updateAIPaddle() {
// AI difficulty increases with level
var aiSpeed = 16 + (level - 1) * 3.5; // px per frame
var aiReact = 0.18 + (level - 1) * 0.07; // how much of the distance to move per frame
if (aiReact > 0.5) aiReact = 0.5;
// Only move if ball is moving towards AI
if (ball.vy < 0) {
var dx = ball.x - aiPaddle.x;
var move = dx * aiReact;
if (Math.abs(move) > aiSpeed) move = (move > 0 ? 1 : -1) * aiSpeed;
aiPaddle.x += move;
// Clamp
var halfW = aiPaddle.getWidth() / 2;
if (aiPaddle.x < halfW) aiPaddle.x = halfW;
if (aiPaddle.x > GAME_WIDTH - halfW) aiPaddle.x = GAME_WIDTH - halfW;
}
}
// Ball collision with paddle
function ballIntersectsPaddle(paddle) {
var px = paddle.x;
var py = paddle.y;
var pw = paddle.getWidth();
var ph = paddle.getHeight();
// Closest point on paddle to ball
var closestX = ball.x;
if (ball.x < px - pw / 2) closestX = px - pw / 2;else if (ball.x > px + pw / 2) closestX = px + pw / 2;
var closestY = ball.y;
if (ball.y < py - ph / 2) closestY = py - ph / 2;else if (ball.y > py + ph / 2) closestY = py + ph / 2;
// Distance to ball center
var dx = ball.x - closestX;
var dy = ball.y - closestY;
return dx * dx + dy * dy <= ball.radius * ball.radius;
}
// Ball collision with wall
function ballWallBounce() {
// Left/right
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx = -ball.vx;
}
if (ball.x + ball.radius > GAME_WIDTH) {
ball.x = GAME_WIDTH - ball.radius;
ball.vx = -ball.vx;
}
}
// Ball out of bounds (top/bottom)
function checkScore() {
if (ball.y - ball.radius < 0) {
// Player scores
playerScore += 1;
updateScoreText();
showMiss('AI');
rallyCount = 0;
if (playerScore >= 5) {
// Next level or win
if (level >= MAX_LEVEL) {
winGame();
} else {
startLevel(level + 1);
}
} else {
resetPositions();
launchBall();
}
return true;
}
if (ball.y + ball.radius > GAME_HEIGHT) {
// AI scores
aiScore += 1;
updateScoreText();
showMiss('Player');
rallyCount = 0;
if (aiScore >= 3) {
endGame();
} else {
resetPositions();
launchBall();
}
return true;
}
return false;
}
// Ball-paddle bounce logic
function handleBallPaddleBounce() {
// Player paddle
if (ball.vy > 0 && ballIntersectsPaddle(playerPaddle)) {
// Bounce up
ball.y = playerPaddle.y - playerPaddle.getHeight() / 2 - ball.radius;
ball.vy = -Math.abs(ball.vy);
// Add spin based on where hit
var offset = (ball.x - playerPaddle.x) / (playerPaddle.getWidth() / 2);
ball.vx += offset * 7;
// Clamp vx
var maxV = BALL_START_SPEED + (level - 1) * BALL_SPEED_INCREMENT + 7;
if (ball.vx > maxV) ball.vx = maxV;
if (ball.vx < -maxV) ball.vx = -maxV;
rallyCount += 1;
showRally(rallyCount);
}
// AI paddle
if (ball.vy < 0 && ballIntersectsPaddle(aiPaddle)) {
// Bounce down
ball.y = aiPaddle.y + aiPaddle.getHeight() / 2 + ball.radius;
ball.vy = Math.abs(ball.vy);
// Add spin
var offset = (ball.x - aiPaddle.x) / (aiPaddle.getWidth() / 2);
ball.vx += offset * 7;
// Clamp vx
var maxV = BALL_START_SPEED + (level - 1) * BALL_SPEED_INCREMENT + 7;
if (ball.vx > maxV) ball.vx = maxV;
if (ball.vx < -maxV) ball.vx = -maxV;
rallyCount += 1;
showRally(rallyCount);
}
}
// Game update loop
game.update = function () {
if (!isGameActive) return;
// Ball movement
ball.update();
// Wall bounce
ballWallBounce();
// Paddle bounce
handleBallPaddleBounce();
// AI paddle
updateAIPaddle();
// Out of bounds/score
checkScore();
};
// Start first level
startLevel(1);