/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); // Ball velocity self.vx = 0; self.vy = 0; // For collision, get bounds self.getBounds = function () { return { x: self.x - self.asset.width / 2, y: self.y - self.asset.height / 2, width: self.asset.width, height: self.asset.height }; }; // Ball update self.update = function () { self.x += self.vx; self.y += self.vy; }; return self; }); // Racket class (player or AI) var Racket = Container.expand(function () { var self = Container.call(this); // Default: rectangle self.racketType = 'racket_rect'; self.asset = null; // Set racket type and color self.setType = function (type) { if (self.asset) { self.removeChild(self.asset); } self.racketType = type; self.asset = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5 }); // For 'rounded' design, tint the box to look different if (type === 'racket_rounded') { self.asset.tint = 0x27ae60; } }; // Set initial type self.setType('racket_rect'); // For collision, get bounds self.getBounds = function () { return { x: self.x - self.asset.width / 2, y: self.y - self.asset.height / 2, width: self.asset.width, height: self.asset.height }; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x145a32 // Table green }); /**** * Game Code ****/ // LK.init.sound('score', {volume:0.5}); // LK.init.sound('hit', {volume:0.5}); // Sounds (optional, not used in MVP as per instructions) // Net // Table (background, not interactive) // Ball // Will tint for rounded look // Racket shapes (3 designs) // --- Game constants --- var TABLE_WIDTH = 2048; var TABLE_HEIGHT = 2732; var NET_Y = TABLE_HEIGHT / 2; var PLAYER_Y = TABLE_HEIGHT - 200; var AI_Y = 200; var RACKET_TYPES = ['racket_rect', 'racket_ellipse', 'racket_rounded']; var DIFFICULTIES = [{ name: 'Easy', aiSpeed: 16, aiError: 120, aiReact: 0.5 }, { name: 'Medium', aiSpeed: 24, aiError: 60, aiReact: 0.7 }, { name: 'Hard', aiSpeed: 36, aiError: 20, aiReact: 0.9 }]; // --- Game state --- var playerScore = 0; var aiScore = 0; var maxScore = 7; // First to 7 wins var selectedRacketType = RACKET_TYPES[0]; var selectedDifficulty = DIFFICULTIES[1]; // Default: Medium // --- UI elements --- var scoreText = null; var aiScoreText = null; var playerScoreText = null; var infoText = null; var menuContainer = null; // --- Game objects --- var playerRacket = null; var aiRacket = null; var ball = null; var net = null; var table = null; // --- Drag state --- var dragging = false; // --- AI state --- var aiTargetX = 0; var aiReactTimer = 0; // --- Utility: collision detection (AABB) --- function rectsIntersect(a, b) { return !(a.x + a.width < b.x || a.x > b.x + b.width || a.y + a.height < b.y || a.y > b.y + b.height); } // --- Utility: clamp --- function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // --- Show menu for racket and difficulty selection --- function showMenu() { menuContainer = new Container(); // Title var title = new Text2('Ping Pong AI', { size: 120, fill: 0xFFFFFF }); title.anchor.set(0.5, 0); title.x = TABLE_WIDTH / 2; title.y = 200; menuContainer.addChild(title); // Racket selection var racketLabel = new Text2('Choose your racket:', { size: 70, fill: 0xFFFFFF }); racketLabel.anchor.set(0.5, 0); racketLabel.x = TABLE_WIDTH / 2; racketLabel.y = 400; menuContainer.addChild(racketLabel); var racketButtons = []; for (var i = 0; i < RACKET_TYPES.length; i++) { var type = RACKET_TYPES[i]; var btn = LK.getAsset(type, { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2 + (i - 1) * 400, y: 600 }); // Tint for rounded if (type === 'racket_rounded') btn.tint = 0x27ae60; btn.interactive = true; btn.buttonMode = true; (function (idx, btnObj) { btnObj.down = function (x, y, obj) { selectedRacketType = RACKET_TYPES[idx]; // Highlight selection for (var j = 0; j < racketButtons.length; j++) { racketButtons[j].alpha = j === idx ? 1 : 0.5; } }; })(i, btn); if (i === 0) btn.alpha = 1;else btn.alpha = 0.5; racketButtons.push(btn); menuContainer.addChild(btn); } // Difficulty selection var diffLabel = new Text2('Select difficulty:', { size: 70, fill: 0xFFFFFF }); diffLabel.anchor.set(0.5, 0); diffLabel.x = TABLE_WIDTH / 2; diffLabel.y = 800; menuContainer.addChild(diffLabel); var diffButtons = []; for (var d = 0; d < DIFFICULTIES.length; d++) { var diff = DIFFICULTIES[d]; var diffBtn = new Text2(diff.name, { size: 70, fill: 0xFFFFFF }); diffBtn.anchor.set(0.5, 0.5); diffBtn.x = TABLE_WIDTH / 2 + (d - 1) * 350; diffBtn.y = 950; diffBtn.interactive = true; diffBtn.buttonMode = true; (function (idx, btnObj) { btnObj.down = function (x, y, obj) { selectedDifficulty = DIFFICULTIES[idx]; for (var j = 0; j < diffButtons.length; j++) { diffButtons[j].alpha = j === idx ? 1 : 0.5; } }; })(d, diffBtn); if (d === 1) diffBtn.alpha = 1;else diffBtn.alpha = 0.5; diffButtons.push(diffBtn); menuContainer.addChild(diffBtn); } // Start button var startBtn = new Text2('START', { size: 100, fill: 0xF1C40F }); startBtn.anchor.set(0.5, 0.5); startBtn.x = TABLE_WIDTH / 2; startBtn.y = 1200; startBtn.interactive = true; startBtn.buttonMode = true; startBtn.down = function (x, y, obj) { // Remove menu and start game game.removeChild(menuContainer); menuContainer = null; startGame(); }; menuContainer.addChild(startBtn); game.addChild(menuContainer); } // --- Start or restart the game --- function startGame() { // Reset scores playerScore = 0; aiScore = 0; // Remove previous objects if any if (table) game.removeChild(table); if (net) game.removeChild(net); if (playerRacket) game.removeChild(playerRacket); if (aiRacket) game.removeChild(aiRacket); if (ball) game.removeChild(ball); if (scoreText) LK.gui.top.removeChild(scoreText); if (aiScoreText) LK.gui.top.removeChild(aiScoreText); if (playerScoreText) LK.gui.top.removeChild(playerScoreText); if (infoText) LK.gui.top.removeChild(infoText); // Table table = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(table); // Net net = LK.getAsset('net', { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2, y: NET_Y }); game.addChild(net); // Player racket playerRacket = new Racket(); playerRacket.setType(selectedRacketType); playerRacket.x = TABLE_WIDTH / 2; playerRacket.y = PLAYER_Y; game.addChild(playerRacket); // AI racket aiRacket = new Racket(); aiRacket.setType(selectedRacketType); aiRacket.x = TABLE_WIDTH / 2; aiRacket.y = AI_Y; game.addChild(aiRacket); // Ball ball = new Ball(); resetBall('player'); game.addChild(ball); // Score display scoreText = new Text2(playerScore + " : " + aiScore, { size: 120, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Player and AI score (smaller, left/right) playerScoreText = new Text2("You", { size: 60, fill: 0xFFFFFF }); playerScoreText.anchor.set(0, 0); LK.gui.top.addChild(playerScoreText); aiScoreText = new Text2("AI", { size: 60, fill: 0xFFFFFF }); aiScoreText.anchor.set(1, 0); LK.gui.top.addChild(aiScoreText); // Info text (centered, for serve etc) infoText = new Text2("", { size: 80, fill: 0xF1C40F }); infoText.anchor.set(0.5, 0); LK.gui.top.addChild(infoText); // Position GUI scoreText.x = LK.gui.top.width / 2; scoreText.y = 20; playerScoreText.x = 120; playerScoreText.y = 40; aiScoreText.x = LK.gui.top.width - 120; aiScoreText.y = 40; infoText.x = LK.gui.top.width / 2; infoText.y = 170; // Reset drag dragging = false; // AI state aiTargetX = aiRacket.x; aiReactTimer = 0; // Show serve info infoText.setText("Tap and drag to move your racket!"); // Start update loop game.update = gameUpdate; game.move = gameMove; game.down = gameDown; game.up = gameUp; } // --- Reset ball to serve (by: 'player' or 'ai') --- function resetBall(servedBy) { ball.x = TABLE_WIDTH / 2; ball.y = servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80; // Ball speed var angle = servedBy === 'player' ? -Math.PI / 2 + (Math.random() - 0.5) * 0.3 : Math.PI / 2 + (Math.random() - 0.5) * 0.3; var speed = 20; ball.vx = Math.cos(angle) * speed; ball.vy = Math.sin(angle) * speed; } // --- Update score display --- function updateScoreDisplay() { scoreText.setText(playerScore + " : " + aiScore); } // --- Show info text for a short time --- function showInfo(msg, duration) { infoText.setText(msg); if (duration) { LK.setTimeout(function () { infoText.setText(""); }, duration); } } // --- Game update loop --- function gameUpdate() { // Ball movement ball.update(); // Ball wall bounce (left/right) if (ball.x < ball.asset.width / 2) { ball.x = ball.asset.width / 2; ball.vx *= -1; } if (ball.x > TABLE_WIDTH - ball.asset.width / 2) { ball.x = TABLE_WIDTH - ball.asset.width / 2; ball.vx *= -1; } // Ball out of bounds (top/bottom) if (ball.y < 0) { // Player scores playerScore++; updateScoreDisplay(); if (playerScore >= maxScore) { LK.showYouWin(); return; } showInfo("You scored!", 1000); resetBall('ai'); return; } if (ball.y > TABLE_HEIGHT) { // AI scores aiScore++; updateScoreDisplay(); if (aiScore >= maxScore) { LK.showGameOver(); return; } showInfo("AI scored!", 1000); resetBall('player'); return; } // Ball collision with player racket if (ball.vy > 0) { var b = ball.getBounds(); var r = playerRacket.getBounds(); if (rectsIntersect(b, r) && ball.y < playerRacket.y) { // Bounce ball.y = r.y - ball.asset.height / 2; ball.vy *= -1; // Add spin based on where hit var dx = (ball.x - playerRacket.x) / (playerRacket.asset.width / 2); ball.vx += dx * 10; // Clamp speed var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); if (speed > 25) { ball.vx *= 25 / speed; ball.vy *= 25 / speed; } } } // Ball collision with AI racket if (ball.vy < 0) { var b = ball.getBounds(); var r = aiRacket.getBounds(); if (rectsIntersect(b, r) && ball.y > aiRacket.y) { // Bounce ball.y = r.y + r.height + ball.asset.height / 2; ball.vy *= -1; // Add spin based on where hit var dx = (ball.x - aiRacket.x) / (aiRacket.asset.width / 2); ball.vx += dx * 10; // Clamp speed var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); if (speed > 25) { ball.vx *= 25 / speed; ball.vy *= 25 / speed; } } } // AI movement aiReactTimer++; if (aiReactTimer > (1 - selectedDifficulty.aiReact) * 30) { aiReactTimer = 0; // AI targets ball x, with error var error = (Math.random() - 0.5) * selectedDifficulty.aiError; aiTargetX = clamp(ball.x + error, aiRacket.asset.width / 2, TABLE_WIDTH - aiRacket.asset.width / 2); } // Move AI racket towards target if (Math.abs(aiRacket.x - aiTargetX) > 5) { if (aiRacket.x < aiTargetX) { aiRacket.x += selectedDifficulty.aiSpeed; if (aiRacket.x > aiTargetX) aiRacket.x = aiTargetX; } else { aiRacket.x -= selectedDifficulty.aiSpeed; if (aiRacket.x < aiTargetX) aiRacket.x = aiTargetX; } // Clamp aiRacket.x = clamp(aiRacket.x, aiRacket.asset.width / 2, TABLE_WIDTH - aiRacket.asset.width / 2); } // Clamp player racket (in case of fast drag) playerRacket.x = clamp(playerRacket.x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2); } // --- Touch/mouse controls --- function gameMove(x, y, obj) { if (dragging) { // Move player racket horizontally only playerRacket.x = clamp(x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2); } } function gameDown(x, y, obj) { // Only start drag if touch is near player racket var localY = y; if (Math.abs(localY - playerRacket.y) < 120) { dragging = true; // Move immediately playerRacket.x = clamp(x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2); } } function gameUp(x, y, obj) { dragging = false; } // --- Start with menu --- showMenu();
===================================================================
--- original.js
+++ change.js
@@ -339,9 +339,9 @@
ball.x = TABLE_WIDTH / 2;
ball.y = servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80;
// Ball speed
var angle = servedBy === 'player' ? -Math.PI / 2 + (Math.random() - 0.5) * 0.3 : Math.PI / 2 + (Math.random() - 0.5) * 0.3;
- var speed = 32;
+ var speed = 20;
ball.vx = Math.cos(angle) * speed;
ball.vy = Math.sin(angle) * speed;
}
// --- Update score display ---
@@ -407,11 +407,11 @@
var dx = (ball.x - playerRacket.x) / (playerRacket.asset.width / 2);
ball.vx += dx * 10;
// Clamp speed
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
- if (speed > 40) {
- ball.vx *= 40 / speed;
- ball.vy *= 40 / speed;
+ if (speed > 25) {
+ ball.vx *= 25 / speed;
+ ball.vy *= 25 / speed;
}
}
}
// Ball collision with AI racket
@@ -426,11 +426,11 @@
var dx = (ball.x - aiRacket.x) / (aiRacket.asset.width / 2);
ball.vx += dx * 10;
// Clamp speed
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
- if (speed > 40) {
- ball.vx *= 40 / speed;
- ball.vy *= 40 / speed;
+ if (speed > 25) {
+ ball.vx *= 25 / speed;
+ ball.vy *= 25 / speed;
}
}
}
// AI movement