/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.baseSpeed = 6;
self.speedMultiplier = 3;
self.reset = function () {
self.x = 2048 / 2;
self.y = 2732 / 2;
self.speedMultiplier = 3;
// Only set velocity if game has started
if (gameStarted) {
// Exactly 30 degrees in any random direction
var angle = Math.PI / 6; // 30 degrees
var direction = Math.floor(Math.random() * 8); // 8 possible directions
var finalAngle = angle + direction * Math.PI / 4; // 30°, 75°, 120°, 165°, 210°, 255°, 300°, 345°
self.velocityX = Math.cos(finalAngle) * self.baseSpeed;
self.velocityY = Math.sin(finalAngle) * self.baseSpeed;
} else {
self.velocityX = 0;
self.velocityY = 0;
}
};
self.update = function () {
if (!gameStarted) return;
self.x += self.velocityX * self.speedMultiplier;
self.y += self.velocityY * self.speedMultiplier;
// Check paddle collisions
if (self.intersects(playerPaddle)) {
if (self.velocityY > 0) {
// Ball moving down
self.velocityY = -Math.abs(self.velocityY);
var paddleCenter = playerPaddle.x;
var hitPos = (self.x - paddleCenter) / 100; // Normalize hit position
self.velocityX = hitPos * self.baseSpeed;
self.speedMultiplier = Math.min(self.speedMultiplier + 0.1, 6.0);
LK.getSound('paddleHit').play();
}
}
if (self.intersects(aiPaddle)) {
if (self.velocityY < 0) {
// Ball moving up
self.velocityY = Math.abs(self.velocityY);
var paddleCenter = aiPaddle.x;
var hitPos = (self.x - paddleCenter) / 100; // Normalize hit position
self.velocityX = hitPos * self.baseSpeed;
self.speedMultiplier = Math.min(self.speedMultiplier + 0.1, 6.0);
LK.getSound('paddleHit').play();
}
}
// Bounce off left and right walls
if (self.x <= 15 || self.x >= 2048 - 15) {
self.velocityX = -self.velocityX;
LK.getSound('wallHit').play();
}
// Check for goals - top and bottom walls
if (self.y <= 0) {
// Ball hit top wall - player scores
playerScore++;
updateScoreDisplay();
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
self.reset();
if (playerScore >= targetScore) {
LK.showYouWin();
}
} else if (self.y >= 2732) {
// Ball hit bottom wall - AI scores
aiScore++;
updateScoreDisplay();
LK.getSound('goal').play();
LK.effects.flashScreen(0xff0000, 500);
self.reset();
if (aiScore >= targetScore) {
LK.showGameOver();
}
}
};
return self;
});
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleGraphics = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetY = 0;
self.isPlayer = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
var playerScore = 0;
var aiScore = 0;
var targetScore = 10;
var dragNode = null;
var gameStarted = false;
var countdownActive = true;
var countdownNumber = 3;
// Create center line
var centerLine = game.addChild(LK.getAsset('centerLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
}));
// Create paddles
var playerPaddle = game.addChild(new Paddle());
playerPaddle.x = 2048 / 2;
playerPaddle.y = 2732 - 100;
playerPaddle.isPlayer = true;
var aiPaddle = game.addChild(new Paddle());
aiPaddle.x = 2048 / 2;
aiPaddle.y = 100;
aiPaddle.targetY = aiPaddle.y;
// Create ball
var ball = game.addChild(new Ball());
ball.reset();
// Create score display
var scoreText = new Text2('0 - 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(scoreText);
// Create countdown text
var countdownText = new Text2('3', {
size: 200,
fill: 0xFFFFFF
});
countdownText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(countdownText);
function updateScoreDisplay() {
scoreText.setText('Player ' + playerScore + ' - ' + aiScore + ' AI');
}
function startCountdown() {
if (countdownNumber > 0) {
countdownText.setText(countdownNumber.toString());
countdownText.scaleX = 0.5;
countdownText.scaleY = 0.5;
countdownText.alpha = 1;
// Animate countdown number
tween(countdownText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownNumber--;
if (countdownNumber > 0) {
startCountdown();
} else {
countdownText.setText('GO!');
countdownText.scaleX = 0.5;
countdownText.scaleY = 0.5;
countdownText.alpha = 1;
tween(countdownText, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownActive = false;
gameStarted = true;
countdownText.visible = false;
ball.reset();
}
});
}
}
});
}
}
// AI behavior
function updateAI() {
var ballCenterX = ball.x;
var ballCenterY = ball.y;
var paddleCenterX = aiPaddle.x;
var paddleCenterY = aiPaddle.y;
var difference = ballCenterX - paddleCenterX;
// Dynamic difficulty based on score difference
var scoreDifference = aiScore - playerScore;
var aiDifficulty = Math.max(0.3, Math.min(1.0, 0.7 + scoreDifference * 0.1));
var adjustedSpeed = aiPaddle.speed * aiDifficulty;
var reactionThreshold = 30 / aiDifficulty;
var predictionAccuracy = aiDifficulty;
// Predict where ball will be when it reaches AI's Y position
var predictedX = ballCenterX;
if (ball.velocityY < 0 && Math.abs(ball.velocityY) > 0.1) {
var timeToReachAI = (ballCenterY - paddleCenterY) / ball.velocityY;
if (timeToReachAI > 0) {
predictedX = ballCenterX + ball.velocityX * ball.speedMultiplier * timeToReachAI * predictionAccuracy;
// Account for wall bounces in prediction
if (predictedX < 15) {
predictedX = 15 + (15 - predictedX);
} else if (predictedX > 2048 - 15) {
predictedX = 2048 - 15 - (predictedX - (2048 - 15));
}
}
}
// Strategic positioning
var targetX = paddleCenterX;
if (ball.velocityY < 0) {
// Ball coming toward AI - move to predicted position
targetX = predictedX;
} else if (ball.velocityY > 0) {
// Ball going away - position strategically
var distanceFromCenter = Math.abs(ballCenterX - 2048 / 2);
if (distanceFromCenter < 300) {
// Ball near center, stay in center
targetX = 2048 / 2;
} else {
// Ball on sides, position slightly toward center from ball
targetX = ballCenterX + (2048 / 2 - ballCenterX) * 0.3;
}
} else {
// Ball not moving vertically much, stay centered
targetX = 2048 / 2;
}
// Move toward target position with some randomness for realism
var moveDirection = targetX - paddleCenterX;
if (Math.abs(moveDirection) > reactionThreshold) {
var moveAmount = Math.sign(moveDirection) * adjustedSpeed;
// Add slight randomness to movement (less at higher difficulty)
var randomFactor = (1 - aiDifficulty) * 0.3;
moveAmount *= 1 + (Math.random() - 0.5) * randomFactor;
aiPaddle.x += moveAmount;
}
// Keep AI paddle in bounds
if (aiPaddle.x < 100) aiPaddle.x = 100;
if (aiPaddle.x > 2048 - 100) aiPaddle.x = 2048 - 100;
}
// Touch controls
game.down = function (x, y, obj) {
// Check if touch is near player paddle
var distance = Math.sqrt(Math.pow(x - playerPaddle.x, 2) + Math.pow(y - playerPaddle.y, 2));
if (distance < 150 || y > 2732 / 2) {
dragNode = playerPaddle;
}
};
game.move = function (x, y, obj) {
if (dragNode === playerPaddle) {
playerPaddle.x = x;
// Keep paddle in bounds
if (playerPaddle.x < 100) playerPaddle.x = 100;
if (playerPaddle.x > 2048 - 100) playerPaddle.x = 2048 - 100;
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game loop
game.update = function () {
if (gameStarted) {
updateAI();
}
};
// Initialize score display
updateScoreDisplay();
// Start countdown
startCountdown(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.baseSpeed = 6;
self.speedMultiplier = 3;
self.reset = function () {
self.x = 2048 / 2;
self.y = 2732 / 2;
self.speedMultiplier = 3;
// Only set velocity if game has started
if (gameStarted) {
// Exactly 30 degrees in any random direction
var angle = Math.PI / 6; // 30 degrees
var direction = Math.floor(Math.random() * 8); // 8 possible directions
var finalAngle = angle + direction * Math.PI / 4; // 30°, 75°, 120°, 165°, 210°, 255°, 300°, 345°
self.velocityX = Math.cos(finalAngle) * self.baseSpeed;
self.velocityY = Math.sin(finalAngle) * self.baseSpeed;
} else {
self.velocityX = 0;
self.velocityY = 0;
}
};
self.update = function () {
if (!gameStarted) return;
self.x += self.velocityX * self.speedMultiplier;
self.y += self.velocityY * self.speedMultiplier;
// Check paddle collisions
if (self.intersects(playerPaddle)) {
if (self.velocityY > 0) {
// Ball moving down
self.velocityY = -Math.abs(self.velocityY);
var paddleCenter = playerPaddle.x;
var hitPos = (self.x - paddleCenter) / 100; // Normalize hit position
self.velocityX = hitPos * self.baseSpeed;
self.speedMultiplier = Math.min(self.speedMultiplier + 0.1, 6.0);
LK.getSound('paddleHit').play();
}
}
if (self.intersects(aiPaddle)) {
if (self.velocityY < 0) {
// Ball moving up
self.velocityY = Math.abs(self.velocityY);
var paddleCenter = aiPaddle.x;
var hitPos = (self.x - paddleCenter) / 100; // Normalize hit position
self.velocityX = hitPos * self.baseSpeed;
self.speedMultiplier = Math.min(self.speedMultiplier + 0.1, 6.0);
LK.getSound('paddleHit').play();
}
}
// Bounce off left and right walls
if (self.x <= 15 || self.x >= 2048 - 15) {
self.velocityX = -self.velocityX;
LK.getSound('wallHit').play();
}
// Check for goals - top and bottom walls
if (self.y <= 0) {
// Ball hit top wall - player scores
playerScore++;
updateScoreDisplay();
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
self.reset();
if (playerScore >= targetScore) {
LK.showYouWin();
}
} else if (self.y >= 2732) {
// Ball hit bottom wall - AI scores
aiScore++;
updateScoreDisplay();
LK.getSound('goal').play();
LK.effects.flashScreen(0xff0000, 500);
self.reset();
if (aiScore >= targetScore) {
LK.showGameOver();
}
}
};
return self;
});
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleGraphics = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetY = 0;
self.isPlayer = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
var playerScore = 0;
var aiScore = 0;
var targetScore = 10;
var dragNode = null;
var gameStarted = false;
var countdownActive = true;
var countdownNumber = 3;
// Create center line
var centerLine = game.addChild(LK.getAsset('centerLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
}));
// Create paddles
var playerPaddle = game.addChild(new Paddle());
playerPaddle.x = 2048 / 2;
playerPaddle.y = 2732 - 100;
playerPaddle.isPlayer = true;
var aiPaddle = game.addChild(new Paddle());
aiPaddle.x = 2048 / 2;
aiPaddle.y = 100;
aiPaddle.targetY = aiPaddle.y;
// Create ball
var ball = game.addChild(new Ball());
ball.reset();
// Create score display
var scoreText = new Text2('0 - 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(scoreText);
// Create countdown text
var countdownText = new Text2('3', {
size: 200,
fill: 0xFFFFFF
});
countdownText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(countdownText);
function updateScoreDisplay() {
scoreText.setText('Player ' + playerScore + ' - ' + aiScore + ' AI');
}
function startCountdown() {
if (countdownNumber > 0) {
countdownText.setText(countdownNumber.toString());
countdownText.scaleX = 0.5;
countdownText.scaleY = 0.5;
countdownText.alpha = 1;
// Animate countdown number
tween(countdownText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownNumber--;
if (countdownNumber > 0) {
startCountdown();
} else {
countdownText.setText('GO!');
countdownText.scaleX = 0.5;
countdownText.scaleY = 0.5;
countdownText.alpha = 1;
tween(countdownText, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
countdownActive = false;
gameStarted = true;
countdownText.visible = false;
ball.reset();
}
});
}
}
});
}
}
// AI behavior
function updateAI() {
var ballCenterX = ball.x;
var ballCenterY = ball.y;
var paddleCenterX = aiPaddle.x;
var paddleCenterY = aiPaddle.y;
var difference = ballCenterX - paddleCenterX;
// Dynamic difficulty based on score difference
var scoreDifference = aiScore - playerScore;
var aiDifficulty = Math.max(0.3, Math.min(1.0, 0.7 + scoreDifference * 0.1));
var adjustedSpeed = aiPaddle.speed * aiDifficulty;
var reactionThreshold = 30 / aiDifficulty;
var predictionAccuracy = aiDifficulty;
// Predict where ball will be when it reaches AI's Y position
var predictedX = ballCenterX;
if (ball.velocityY < 0 && Math.abs(ball.velocityY) > 0.1) {
var timeToReachAI = (ballCenterY - paddleCenterY) / ball.velocityY;
if (timeToReachAI > 0) {
predictedX = ballCenterX + ball.velocityX * ball.speedMultiplier * timeToReachAI * predictionAccuracy;
// Account for wall bounces in prediction
if (predictedX < 15) {
predictedX = 15 + (15 - predictedX);
} else if (predictedX > 2048 - 15) {
predictedX = 2048 - 15 - (predictedX - (2048 - 15));
}
}
}
// Strategic positioning
var targetX = paddleCenterX;
if (ball.velocityY < 0) {
// Ball coming toward AI - move to predicted position
targetX = predictedX;
} else if (ball.velocityY > 0) {
// Ball going away - position strategically
var distanceFromCenter = Math.abs(ballCenterX - 2048 / 2);
if (distanceFromCenter < 300) {
// Ball near center, stay in center
targetX = 2048 / 2;
} else {
// Ball on sides, position slightly toward center from ball
targetX = ballCenterX + (2048 / 2 - ballCenterX) * 0.3;
}
} else {
// Ball not moving vertically much, stay centered
targetX = 2048 / 2;
}
// Move toward target position with some randomness for realism
var moveDirection = targetX - paddleCenterX;
if (Math.abs(moveDirection) > reactionThreshold) {
var moveAmount = Math.sign(moveDirection) * adjustedSpeed;
// Add slight randomness to movement (less at higher difficulty)
var randomFactor = (1 - aiDifficulty) * 0.3;
moveAmount *= 1 + (Math.random() - 0.5) * randomFactor;
aiPaddle.x += moveAmount;
}
// Keep AI paddle in bounds
if (aiPaddle.x < 100) aiPaddle.x = 100;
if (aiPaddle.x > 2048 - 100) aiPaddle.x = 2048 - 100;
}
// Touch controls
game.down = function (x, y, obj) {
// Check if touch is near player paddle
var distance = Math.sqrt(Math.pow(x - playerPaddle.x, 2) + Math.pow(y - playerPaddle.y, 2));
if (distance < 150 || y > 2732 / 2) {
dragNode = playerPaddle;
}
};
game.move = function (x, y, obj) {
if (dragNode === playerPaddle) {
playerPaddle.x = x;
// Keep paddle in bounds
if (playerPaddle.x < 100) playerPaddle.x = 100;
if (playerPaddle.x > 2048 - 100) playerPaddle.x = 2048 - 100;
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game loop
game.update = function () {
if (gameStarted) {
updateAI();
}
};
// Initialize score display
updateScoreDisplay();
// Start countdown
startCountdown();