User prompt
When the ball collides with the racket near its sides then make the ball curve and go more to the side
User prompt
Remove curveball
User prompt
Remove bounce effects
User prompt
Add bounce effects to the ball ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'ball.y = servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80;' Line Number: 628
User prompt
Revert the gameplay back to the original
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween(ball, {' Line Number: 632 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween.to(ball, {' Line Number: 632 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make the gameplay look more polished
User prompt
Make the menu screen look polished
User prompt
Change the insane difficulty name to impossible
User prompt
Add one more difficulty named insane
User prompt
Make the ai’s racket a random one from the list of rackets
User prompt
Put the difficulty selection lower down on the screen
User prompt
When you pick a racket at the bottom of the screen but a description of its attributes
User prompt
Make all rackets visible on screen by putting them in rows
User prompt
Give each racket certain attributes
User prompt
Add more racket designs
User prompt
Make the energy bar blue
User prompt
Get extra energy when your opponent scores
User prompt
Add a energy bar to each player and when it is full do a power shot
User prompt
Remove powerups
User prompt
Add power ups which can go to you or your opponent which can do power shots or increase ball size or decrease it or slow down the ball.
User prompt
Revert sprites back to how they were
User prompt
Make all sprites look realistic
/**** * 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; // Ball shadow follows ball, offset for depth if (self.shadow) { self.shadow.x = self.x + 18; self.shadow.y = self.y + 24; self.shadow.scaleX = 1.15; self.shadow.scaleY = 0.7; } }; 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 }); // Apply racket attributes var attr = RACKET_ATTRIBUTES[type] || { speed: 1.0, power: 1.0, control: 1.0, size: 1.0 }; self.speed = attr.speed; self.power = attr.power; self.control = attr.control; self.size = attr.size; // Optionally scale racket width for size attribute if (self.asset && self.size && self.size !== 1.0) { self.asset.width = self.asset.width * self.size; } // For 'rounded' design, tint the box to look different if (type === 'racket_rounded') { self.asset.tint = 0x27ae60; } // Tint for new racket types if (type === 'racket_pink') { self.asset.tint = 0xff69b4; } if (type === 'racket_orange') { self.asset.tint = 0xffa500; } if (type === 'racket_purple') { self.asset.tint = 0x8e44ad; } if (type === 'racket_green') { self.asset.tint = 0x27ae60; } if (type === 'racket_red') { self.asset.tint = 0xe74c3c; } }; // 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 ****/ // New racket designs // --- Game constants --- // Racket shapes (3 designs) // Will tint for rounded look // Ball // Table (background, not interactive) // Net // Sounds (optional, not used in MVP as per instructions) // LK.init.sound('hit', {volume:0.5}); // LK.init.sound('score', {volume:0.5}); 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', 'racket_pink', 'racket_orange', 'racket_purple', 'racket_green', 'racket_red']; // Racket attributes for each type // speed: how fast the racket can move (higher is faster) // power: how much extra speed is added to the ball on hit (higher is more powerful) // control: how much spin/curve can be applied (higher is more curve) // size: multiplier for racket width (1 = normal, <1 = smaller, >1 = larger) var RACKET_ATTRIBUTES = { racket_rect: { speed: 1.0, power: 1.0, control: 1.0, size: 1.0 }, racket_ellipse: { speed: 1.1, power: 0.9, control: 1.2, size: 1.0 }, racket_rounded: { speed: 1.0, power: 1.1, control: 1.0, size: 1.1 }, racket_pink: { speed: 1.2, power: 0.8, control: 1.3, size: 0.95 }, racket_orange: { speed: 0.9, power: 1.3, control: 0.9, size: 1.05 }, racket_purple: { speed: 1.0, power: 1.0, control: 1.5, size: 0.9 }, racket_green: { speed: 1.3, power: 0.7, control: 1.1, size: 0.9 }, racket_red: { speed: 0.8, power: 1.5, control: 0.8, size: 1.1 } }; 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 }, { name: 'Impossible', aiSpeed: 52, aiError: 5, aiReact: 1.0 }]; // --- 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 // --- Energy bar state --- var playerEnergy = 0; var aiEnergy = 0; var maxEnergy = 100; var playerPowerShotReady = false; var aiPowerShotReady = false; var playerPowerShotActive = false; var aiPowerShotActive = false; // --- 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: 140, fill: 0xF1C40F, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); title.anchor.set(0.5, 0); title.x = TABLE_WIDTH / 2; title.y = 120; menuContainer.addChild(title); // Decorative underline for title var titleUnderline = new Text2('─'.repeat(18), { size: 80, fill: 0xF1C40F }); titleUnderline.anchor.set(0.5, 0); titleUnderline.x = TABLE_WIDTH / 2; titleUnderline.y = title.y + 120; menuContainer.addChild(titleUnderline); // Racket selection var racketLabel = new Text2('Choose your racket:', { size: 80, fill: 0xFFFFFF, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); racketLabel.anchor.set(0.5, 0); racketLabel.x = TABLE_WIDTH / 2; racketLabel.y = titleUnderline.y + 80; menuContainer.addChild(racketLabel); var racketButtons = []; // Layout: 4 rackets per row, multiple rows var racketsPerRow = 4; var rowSpacing = 240; var colSpacing = 420; var startY = racketLabel.y + 120; var startX = TABLE_WIDTH / 2 - (racketsPerRow - 1) * colSpacing / 2; // --- Racket attribute description text --- var racketAttrDesc = new Text2('', { size: 64, fill: 0xF1C40F, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); racketAttrDesc.anchor.set(0.5, 0); racketAttrDesc.x = TABLE_WIDTH / 2; racketAttrDesc.y = startY + 2 * rowSpacing + 40; // below rackets menuContainer.addChild(racketAttrDesc); // Helper to format attribute description function getRacketAttrDesc(type) { var attr = RACKET_ATTRIBUTES[type]; if (!attr) return ""; return "Speed: " + attr.speed + " Power: " + attr.power + " Control: " + attr.control + " Size: " + attr.size; } // Add a subtle background panel for rackets var racketPanel = LK.getAsset('table', { anchorX: 0.5, anchorY: 0, x: TABLE_WIDTH / 2, y: startY - 60, width: 1800, height: rowSpacing * 2 + 120, color: 0x1b3c1a }); racketPanel.alpha = 0.7; menuContainer.addChild(racketPanel); for (var i = 0; i < RACKET_TYPES.length; i++) { var type = RACKET_TYPES[i]; var row = Math.floor(i / racketsPerRow); var col = i % racketsPerRow; var btn = LK.getAsset(type, { anchorX: 0.5, anchorY: 0.5, x: startX + col * colSpacing, y: startY + row * rowSpacing }); // Tint for rounded if (type === 'racket_rounded') btn.tint = 0x27ae60; if (type === 'racket_pink') btn.tint = 0xff69b4; if (type === 'racket_orange') btn.tint = 0xffa500; if (type === 'racket_purple') btn.tint = 0x8e44ad; if (type === 'racket_green') btn.tint = 0x27ae60; if (type === 'racket_red') btn.tint = 0xe74c3c; btn.interactive = true; btn.buttonMode = true; // Add a subtle drop shadow effect by duplicating the asset behind var shadow = LK.getAsset(type, { anchorX: 0.5, anchorY: 0.5, x: btn.x + 10, y: btn.y + 16 }); shadow.alpha = 0.18; shadow.tint = 0x000000; menuContainer.addChild(shadow); (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; } // Update attribute description racketAttrDesc.setText(getRacketAttrDesc(selectedRacketType)); }; })(i, btn); if (i === 0) btn.alpha = 1;else btn.alpha = 0.5; racketButtons.push(btn); menuContainer.addChild(btn); } // Set initial attribute description racketAttrDesc.setText(getRacketAttrDesc(selectedRacketType)); // Difficulty selection var diffLabel = new Text2('Select difficulty:', { size: 80, fill: 0xFFFFFF, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); diffLabel.anchor.set(0.5, 0); diffLabel.x = TABLE_WIDTH / 2; // Move difficulty selection lower, below racket attribute description diffLabel.y = startY + 2 * rowSpacing + 180; menuContainer.addChild(diffLabel); // Add a subtle background panel for difficulty var diffPanel = LK.getAsset('table', { anchorX: 0.5, anchorY: 0, x: TABLE_WIDTH / 2, y: diffLabel.y - 30, width: 1200, height: 200, color: 0x1b3c1a }); diffPanel.alpha = 0.7; menuContainer.addChild(diffPanel); var diffButtons = []; for (var d = 0; d < DIFFICULTIES.length; d++) { var diff = DIFFICULTIES[d]; var diffBtn = new Text2(diff.name, { size: 80, fill: 0xF1C40F, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); diffBtn.anchor.set(0.5, 0.5); // Spread buttons horizontally, allow for 4 difficulties diffBtn.x = TABLE_WIDTH / 2 + (d - 1.5) * 350; diffBtn.y = diffLabel.y + 100; 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); // Default to Medium selected 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: 120, fill: 0xF1C40F, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); startBtn.anchor.set(0.5, 0.5); startBtn.x = TABLE_WIDTH / 2; startBtn.y = diffLabel.y + 320; startBtn.interactive = true; startBtn.buttonMode = true; // Add a subtle shadow for the start button var startBtnShadow = new Text2('START', { size: 120, fill: 0x000000, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); startBtnShadow.anchor.set(0.5, 0.5); startBtnShadow.x = startBtn.x + 10; startBtnShadow.y = startBtn.y + 16; startBtnShadow.alpha = 0.18; menuContainer.addChild(startBtnShadow); startBtn.down = function (x, y, obj) { // Remove menu and start game game.removeChild(menuContainer); menuContainer = null; startGame(); }; menuContainer.addChild(startBtn); // Add a small footer var footer = new Text2('© FRVR Ping Pong Demo', { size: 48, fill: 0x888888 }); footer.anchor.set(0.5, 1); footer.x = TABLE_WIDTH / 2; footer.y = TABLE_HEIGHT - 60; menuContainer.addChild(footer); 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); if (typeof playerEnergyBar !== "undefined" && playerEnergyBar) LK.gui.top.removeChild(playerEnergyBar); if (typeof aiEnergyBar !== "undefined" && aiEnergyBar) LK.gui.top.removeChild(aiEnergyBar); // Table shadow (subtle, offset, below table) var tableShadow = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 18, y: 24, width: TABLE_WIDTH, height: TABLE_HEIGHT, color: 0x000000 }); tableShadow.alpha = 0.10; game.addChild(tableShadow); // Table table = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(table); // Net shadow (subtle, offset, below net) var netShadow = LK.getAsset('net', { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2 + 10, y: NET_Y + 16, width: 2048, height: 24, color: 0x000000 }); netShadow.alpha = 0.13; game.addChild(netShadow); // Net net = LK.getAsset('net', { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2, y: NET_Y }); game.addChild(net); // Add net highlight (thin white line) var netHighlight = LK.getAsset('net', { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2, y: NET_Y - 8, width: 2048, height: 6, color: 0xffffff }); netHighlight.alpha = 0.22; game.addChild(netHighlight); // Player racket shadow var playerRacketShadow = LK.getAsset(selectedRacketType, { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2 + 16, y: PLAYER_Y + 18 }); playerRacketShadow.alpha = 0.16; playerRacketShadow.tint = 0x000000; game.addChild(playerRacketShadow); // Player racket playerRacket = new Racket(); playerRacket.setType(selectedRacketType); playerRacket.x = TABLE_WIDTH / 2; playerRacket.y = PLAYER_Y; game.addChild(playerRacket); // AI racket shadow var aiRacketType = RACKET_TYPES[Math.floor(Math.random() * RACKET_TYPES.length)]; var aiRacketShadow = LK.getAsset(aiRacketType, { anchorX: 0.5, anchorY: 0.5, x: TABLE_WIDTH / 2 + 16, y: AI_Y + 18 }); aiRacketShadow.alpha = 0.16; aiRacketShadow.tint = 0x000000; game.addChild(aiRacketShadow); // AI racket aiRacket = new Racket(); aiRacket.setType(aiRacketType); aiRacket.x = TABLE_WIDTH / 2; aiRacket.y = AI_Y; game.addChild(aiRacket); // Store shadows for update playerRacket.shadow = playerRacketShadow; aiRacket.shadow = aiRacketShadow; // Ball shadow (created as a separate asset, follows ball) ball = new Ball(); ball.shadow = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); ball.shadow.tint = 0x000000; ball.shadow.alpha = 0.18; game.addChild(ball.shadow); resetBall('player'); game.addChild(ball); // No entry animation in original gameplay; set positions directly if (ball.shadow) ball.shadow.y = ball.y + 24; playerRacket.y = PLAYER_Y; if (playerRacket.shadow) playerRacket.shadow.y = playerRacket.y + 18; aiRacket.y = AI_Y; if (aiRacket.shadow) aiRacket.shadow.y = aiRacket.y + 18; // 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); // Energy bar UI playerEnergy = 0; aiEnergy = 0; playerPowerShotReady = false; aiPowerShotReady = false; playerPowerShotActive = false; aiPowerShotActive = false; // Player energy bar (bottom left, above player label) playerEnergyBar = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 120, y: 110, width: 400, height: 30, color: 0x333333 }); playerEnergyBarBar = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 120, y: 110, width: 0, height: 30, color: 0x3498db }); LK.gui.top.addChild(playerEnergyBar); LK.gui.top.addChild(playerEnergyBarBar); // AI energy bar (top right, above AI label) aiEnergyBar = LK.getAsset('table', { anchorX: 1, anchorY: 0, x: LK.gui.top.width - 120, y: 110, width: 400, height: 30, color: 0x333333 }); aiEnergyBarBar = LK.getAsset('table', { anchorX: 1, anchorY: 0, x: LK.gui.top.width - 120, y: 110, width: 0, height: 30, color: 0x3498db }); LK.gui.top.addChild(aiEnergyBar); LK.gui.top.addChild(aiEnergyBarBar); // 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(); // Reset energy and power shot state playerEnergy = 0; aiEnergy = 0; playerPowerShotReady = false; aiPowerShotReady = false; playerPowerShotActive = false; aiPowerShotActive = false; if (playerEnergyBarBar) playerEnergyBarBar.width = 0; if (aiEnergyBarBar) aiEnergyBarBar.width = 0; if (playerScore >= maxScore) { LK.showYouWin(); return; } showInfo("You scored!", 1000); resetBall('ai'); return; } if (ball.y > TABLE_HEIGHT) { // AI scores aiScore++; updateScoreDisplay(); // Give player extra energy when AI scores playerEnergy += 40; if (playerEnergy > maxEnergy) { playerEnergy = maxEnergy; playerPowerShotReady = true; } // Reset energy and power shot state for AI aiEnergy = 0; aiPowerShotReady = false; aiPowerShotActive = false; // Reset player power shot state if not ready if (!playerPowerShotReady) { playerPowerShotActive = false; playerPowerShotReady = false; } if (playerEnergyBarBar) playerEnergyBarBar.width = 400 * (playerEnergy / maxEnergy); if (aiEnergyBarBar) aiEnergyBarBar.width = 0; 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 // Add bounce tween effect tween.stop(ball, { y: true }); var bounceY = r.y - ball.asset.height / 2 - 30; tween(ball, { y: bounceY }, { duration: 60, easing: tween.bounceOut, onFinish: function onFinish() { tween(ball, { y: r.y - ball.asset.height / 2 }, { duration: 60, easing: tween.linear }); } }); ball.vy *= -1; // Add spin based on where hit var dx = (ball.x - playerRacket.x) / (playerRacket.asset.width / 2); ball.vx += dx * 10 * (playerRacket.control || 1.0); // Add power attribute to outgoing ball speed ball.vx *= playerRacket.power || 1.0; ball.vy *= playerRacket.power || 1.0; // Energy gain if (!playerPowerShotReady) { playerEnergy += 20; if (playerEnergy >= maxEnergy) { playerEnergy = maxEnergy; playerPowerShotReady = true; showInfo("Power Shot Ready!", 1000); } } // Power shot activation if (playerPowerShotReady && dragging) { playerPowerShotActive = true; playerPowerShotReady = false; playerEnergy = 0; // Power shot: boost ball speed and color var powerShotSpeed = 40; var angle = Math.atan2(ball.vy, ball.vx); ball.vx = Math.cos(angle) * powerShotSpeed; ball.vy = -Math.abs(Math.sin(angle) * powerShotSpeed); ball.asset.tint = 0xF1C40F; showInfo("Power Shot!", 800); } else { playerPowerShotActive = false; ball.asset.tint = 0xffffff; } // Clamp speed var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); var maxSpeed = playerPowerShotActive ? 40 : 25; if (speed > maxSpeed) { ball.vx *= maxSpeed / speed; ball.vy *= maxSpeed / 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 // Add bounce tween effect tween.stop(ball, { y: true }); var bounceY = r.y + r.height + ball.asset.height / 2 + 30; tween(ball, { y: bounceY }, { duration: 60, easing: tween.bounceOut, onFinish: function onFinish() { tween(ball, { y: r.y + r.height + ball.asset.height / 2 }, { duration: 60, easing: tween.linear }); } }); ball.vy *= -1; // Add spin based on where hit var dx = (ball.x - aiRacket.x) / (aiRacket.asset.width / 2); ball.vx += dx * 10 * (aiRacket.control || 1.0); // Add power attribute to outgoing ball speed ball.vx *= aiRacket.power || 1.0; ball.vy *= aiRacket.power || 1.0; // Energy gain if (!aiPowerShotReady) { aiEnergy += 20; if (aiEnergy >= maxEnergy) { aiEnergy = maxEnergy; aiPowerShotReady = true; } } // Power shot activation (AI triggers automatically if ready) if (aiPowerShotReady) { aiPowerShotActive = true; aiPowerShotReady = false; aiEnergy = 0; // Power shot: boost ball speed and color var powerShotSpeed = 40; var angle = Math.atan2(ball.vy, ball.vx); ball.vx = Math.cos(angle) * powerShotSpeed; ball.vy = Math.abs(Math.sin(angle) * powerShotSpeed); ball.asset.tint = 0xF1C40F; showInfo("AI Power Shot!", 800); } else { aiPowerShotActive = false; if (!playerPowerShotActive) ball.asset.tint = 0xffffff; } // Clamp speed var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); var maxSpeed = aiPowerShotActive ? 40 : 25; if (speed > maxSpeed) { ball.vx *= maxSpeed / speed; ball.vy *= maxSpeed / 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 * (aiRacket.speed || 1.0); if (aiRacket.x > aiTargetX) aiRacket.x = aiTargetX; } else { aiRacket.x -= selectedDifficulty.aiSpeed * (aiRacket.speed || 1.0); 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); // Update racket shadows to follow rackets if (playerRacket.shadow) { playerRacket.shadow.x = playerRacket.x + 16; playerRacket.shadow.y = playerRacket.y + 18; playerRacket.shadow.scaleX = 1.08; playerRacket.shadow.scaleY = 0.92; } if (aiRacket.shadow) { aiRacket.shadow.x = aiRacket.x + 16; aiRacket.shadow.y = aiRacket.y + 18; aiRacket.shadow.scaleX = 1.08; aiRacket.shadow.scaleY = 0.92; } // Update energy bar UI if (typeof playerEnergyBarBar !== "undefined" && playerEnergyBarBar) { playerEnergyBarBar.width = 400 * (playerEnergy / maxEnergy); playerEnergyBarBar.tint = 0x3498db; } if (typeof aiEnergyBarBar !== "undefined" && aiEnergyBarBar) { aiEnergyBarBar.width = 400 * (aiEnergy / maxEnergy); aiEnergyBarBar.tint = 0x3498db; } } // --- Touch/mouse controls --- function gameMove(x, y, obj) { if (dragging) { // Move player racket horizontally only // Move with speed attribute (lerp for smoothness) var targetX = clamp(x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2); var moveSpeed = 40 * (playerRacket.speed || 1.0); if (Math.abs(playerRacket.x - targetX) > moveSpeed) { if (playerRacket.x < targetX) playerRacket.x += moveSpeed;else playerRacket.x -= moveSpeed; } else { playerRacket.x = targetX; } } } 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(); ; // Allow player to tap energy bar to trigger power shot if ready function energyBarDown(x, y, obj) { if (playerPowerShotReady) { playerPowerShotActive = true; playerPowerShotReady = false; playerEnergy = 0; showInfo("Power Shot!", 800); } } if (typeof playerEnergyBar !== "undefined" && playerEnergyBar) { playerEnergyBar.down = energyBarDown; playerEnergyBarBar.down = energyBarDown; }
===================================================================
--- original.js
+++ change.js
@@ -114,18 +114,18 @@
/****
* 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 ---
// New racket designs
+// --- Game constants ---
+// Racket shapes (3 designs)
+// Will tint for rounded look
+// Ball
+// Table (background, not interactive)
+// Net
+// Sounds (optional, not used in MVP as per instructions)
+// LK.init.sound('hit', {volume:0.5});
+// LK.init.sound('score', {volume:0.5});
var TABLE_WIDTH = 2048;
var TABLE_HEIGHT = 2732;
var NET_Y = TABLE_HEIGHT / 2;
var PLAYER_Y = TABLE_HEIGHT - 200;
@@ -761,9 +761,27 @@
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;
+ // Add bounce tween effect
+ tween.stop(ball, {
+ y: true
+ });
+ var bounceY = r.y - ball.asset.height / 2 - 30;
+ tween(ball, {
+ y: bounceY
+ }, {
+ duration: 60,
+ easing: tween.bounceOut,
+ onFinish: function onFinish() {
+ tween(ball, {
+ y: r.y - ball.asset.height / 2
+ }, {
+ duration: 60,
+ easing: tween.linear
+ });
+ }
+ });
ball.vy *= -1;
// Add spin based on where hit
var dx = (ball.x - playerRacket.x) / (playerRacket.asset.width / 2);
ball.vx += dx * 10 * (playerRacket.control || 1.0);
@@ -809,9 +827,27 @@
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;
+ // Add bounce tween effect
+ tween.stop(ball, {
+ y: true
+ });
+ var bounceY = r.y + r.height + ball.asset.height / 2 + 30;
+ tween(ball, {
+ y: bounceY
+ }, {
+ duration: 60,
+ easing: tween.bounceOut,
+ onFinish: function onFinish() {
+ tween(ball, {
+ y: r.y + r.height + ball.asset.height / 2
+ }, {
+ duration: 60,
+ easing: tween.linear
+ });
+ }
+ });
ball.vy *= -1;
// Add spin based on where hit
var dx = (ball.x - aiRacket.x) / (aiRacket.asset.width / 2);
ball.vx += dx * 10 * (aiRacket.control || 1.0);