User prompt
Please fix the bug: 'returnMenuBtn is not defined' in or related to this line: 'returnMenuBtn.down = function (x, y, obj) {' Line Number: 541
User prompt
Remove MAIN MENU Text
User prompt
Scroll left on musical_notes and their text in the Power-Ups Sectionc
User prompt
Move the musical_notes in the Power-Ups Section to the left and put them all in a stack at the beginning of the text
User prompt
Center Everything in the Power-Ups Section
User prompt
Swipe Left on everything in the Power-Ups Section
User prompt
Move the musical_notes in the Power-Ups Section to the left of their own text so that they are at the top, one below the other.
User prompt
Remove MAIN MENU text
User prompt
Hide the text behind any menu you enter
User prompt
Shrink and Center All Text in Power-Ups Section
User prompt
Scroll All Posts in the Power-Ups Section to the Left
User prompt
Leave Spaces Between All Sentences in the Strengths Section
User prompt
Play Backround_Music in Main Menu
User prompt
Increase Blur Power
User prompt
Reduce Blur Power
User prompt
Increase the Chance of Blur Power-up Spawning
User prompt
remove color changing buff
User prompt
Write all Power-ups to Power-up menu
User prompt
Create a New Power-Up When Power-Up Is Picked Up, Randomly Change the Color of Everything for 7 Seconds
User prompt
When Power-Up is Picked Up, Change Color of Everything Randomly for 7 Seconds
User prompt
2 power-ups can spawn in the game
User prompt
Ana Menü Arkaplanını Değiştir
Code edit (1 edits merged)
Please save this source code
User prompt
Increase Blur in blur boost
User prompt
Add Invert Screen boost instead of 360 degree boost
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballSprite = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); // Ball velocity self.vx = 0; self.vy = 0; // Ball speed (pixels per tick) self.speed = 18; // Used for rhythm sync self.baseSpeed = 18; self.rhythmBoost = 0; // Used to prevent multiple scoring on one pass self.lastScored = false; // Ball update self.update = function () { self.x += self.vx * (self.speed + self.rhythmBoost); self.y += self.vy * (self.speed + self.rhythmBoost); }; // Reset ball to center, random direction self.reset = function (direction) { self.x = 2048 / 2; self.y = 2732 / 2; // Start ball slow, then accelerate after 1 second self.speed = 4; self.rhythmBoost = 0; LK.setTimeout(function () { // Only accelerate if this ball is still in play and hasn't been reset again if (self && typeof self.speed !== "undefined" && self.speed === 4) { self.speed = self.baseSpeed; } }, 1000); // --- Fakeout logic --- // direction: 1 = to player, -1 = to AI // Sometimes (30% chance) the ball will "fake" left or right before going the real direction // Sometimes (20% chance) the ball will "fake" up or down before going the real direction // Rarely (5% chance), the ball will go to a completely random direction var rareRandomChance = 0.05; var fakeoutLRChance = 0.3; var fakeoutUDChance = 0.2; var doRareRandom = Math.random() < rareRandomChance; var doFakeoutLR = !doRareRandom && Math.random() < fakeoutLRChance; var doFakeoutUD = !doRareRandom && !doFakeoutLR && Math.random() < fakeoutUDChance; // Only one fakeout at a time var fakeoutDuration = 220; // ms var realAngle, fakeAngle; if (doRareRandom) { // Ball goes in a completely random direction (but not straight up/down) var angle = Math.random() * Math.PI * 2; // Avoid perfectly vertical if (Math.abs(Math.cos(angle)) < 0.2) { angle += 0.3; } self.vx = Math.sin(angle); self.vy = Math.cos(angle); } else if (doFakeoutLR) { // Left/right fakeout (original logic) var goLeft = Math.random() < 0.5; realAngle = (goLeft ? -0.35 : 0.35) + (direction === 1 ? Math.PI / 2 : -Math.PI / 2); fakeAngle = (goLeft ? 0.35 : -0.35) + (direction === 1 ? Math.PI / 2 : -Math.PI / 2); self.vx = Math.sin(fakeAngle); self.vy = Math.cos(fakeAngle) * direction; LK.setTimeout(function () { if (self && typeof self.vx !== "undefined" && typeof self.vy !== "undefined") { self.vx = Math.sin(realAngle); self.vy = Math.cos(realAngle) * direction; } }, fakeoutDuration); } else if (doFakeoutUD) { // Up/down fakeout // Ball will appear to go up (or down) for a moment, then go the real direction var goUpFirst = Math.random() < 0.5; // Real angle: normal random angle toward the correct side realAngle = Math.random() * 0.5 - 0.25 + (direction === 1 ? Math.PI / 2 : -Math.PI / 2); // Fake angle: sharply up or down, but not toward the goal if (goUpFirst) { // Fake up: angle close to 0 (straight up) fakeAngle = -Math.PI / 2 + (Math.random() - 0.5) * 0.2; } else { // Fake down: angle close to PI (straight down) fakeAngle = Math.PI / 2 + (Math.random() - 0.5) * 0.2; } self.vx = Math.sin(fakeAngle); self.vy = Math.cos(fakeAngle) * (goUpFirst ? -1 : 1); LK.setTimeout(function () { if (self && typeof self.vx !== "undefined" && typeof self.vy !== "undefined") { self.vx = Math.sin(realAngle); self.vy = Math.cos(realAngle) * direction; } }, fakeoutDuration); } else { // Normal random angle var angle = Math.random() * 0.5 - 0.25 + (direction === 1 ? Math.PI / 2 : -Math.PI / 2); self.vx = Math.sin(angle); self.vy = Math.cos(angle) * direction; } self.lastScored = false; }; return self; }); // Paddle class var Paddle = Container.expand(function () { var self = Container.call(this); // Set in init self.isPlayer = false; // Attach asset var paddleSprite = self.attachAsset('paddle_player', { anchorX: 0.5, anchorY: 0.5 }); // Set color for AI self.setAI = function () { paddleSprite.destroy(); self.attachAsset('paddle_ai', { anchorX: 0.5, anchorY: 0.5 }); self.isPlayer = false; }; // Set color for player self.setPlayer = function () { paddleSprite.destroy(); self.attachAsset('paddle_player', { anchorX: 0.5, anchorY: 0.5 }); self.isPlayer = true; }; // Clamp paddle inside table self.clamp = function () { var halfW = self.width / 2; if (self.x < halfW) { self.x = halfW; } if (self.x > 2048 - halfW) { self.x = 2048 - halfW; } }; return self; }); // PowerUp class var PowerUp = Container.expand(function () { var self = Container.call(this); // Types: 'big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'invert_screen' self.type = 'big_paddle'; self.active = false; // Randomly pick a type if not set if (arguments.length > 0 && typeof arguments[0] === "string") { self.type = arguments[0]; } else { var types = ['big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'invert_screen']; self.type = types[Math.floor(Math.random() * types.length)]; } // Attach musical note asset for all power-ups // Use a unique color per type for the note, but always a musical note shape var noteColor = 0xffe066; if (self.type === 'big_paddle') { noteColor = 0x66ff99; } if (self.type === 'multi_ball') { noteColor = 0x66ccff; } if (self.type === 'slow_ball') { noteColor = 0xff99cc; } if (self.type === 'speed_ball') { noteColor = 0xff6600; } if (self.type === 'blur_screen') { noteColor = 0xcccccc; } if (self.type === 'invert_screen') { noteColor = 0xbb66ff; } // Use 'musical_note' asset (ellipse with note color, or a dedicated note asset if available) var powerupSprite = self.attachAsset('musical_note', { anchorX: 0.5, anchorY: 0.5, tint: noteColor, scaleX: 2.0, scaleY: 2.0 }); // Set size for collision self.width = powerupSprite.width * 2.0; self.height = powerupSprite.height * 2.0; // PowerUp update (spin and move to random places) self.floatDir = 1; self.floatTimer = 0; self.targetX = self.x; self.targetY = self.y; self.moveTimer = 0; self.rotationSpeed = 0.07 + Math.random() * 0.07; // random spin speed self.update = function () { self.floatTimer++; // Spin the note if (powerupSprite && typeof powerupSprite.rotation === "number") { powerupSprite.rotation += self.rotationSpeed; } // Move toward target position var moveSpeed = 1.2; // slow movement if (typeof self.targetX === "number" && typeof self.targetY === "number") { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > moveSpeed) { self.x += dx / dist * moveSpeed; self.y += dy / dist * moveSpeed; } else { self.x = self.targetX; self.y = self.targetY; } } // Every 120-240 frames, pick a new random target position self.moveTimer++; if (self.moveTimer > 120 + Math.floor(Math.random() * 120)) { self.moveTimer = 0; // Stay within table bounds var margin = 120; self.targetX = margin + Math.random() * (2048 - 2 * margin); self.targetY = 2732 / 2 - 400 + Math.random() * 800; } // Float up/down for a little extra effect self.y += Math.sin(self.floatTimer / 20) * 0.8; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 // Table green }); /**** * Game Code ****/ // --- Main Menu and Difficulty Selection --- var menuContainer = new Container(); var menuBg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); menuContainer.addChild(menuBg); var titleText = new Text2('PING PONG RHYTHM SMASH', { size: 120, fill: 0xffffff }); titleText.anchor.set(0.5, 0); titleText.x = 2048 / 2; titleText.y = 320; menuContainer.addChild(titleText); var easyBtn = new Text2('EASY', { size: 100, fill: 0x99ff99 }); easyBtn.anchor.set(0.5, 0.5); easyBtn.x = 2048 / 2; easyBtn.y = 800; menuContainer.addChild(easyBtn); var normalBtn = new Text2('NORMAL', { size: 100, fill: 0xffff99 }); normalBtn.anchor.set(0.5, 0.5); normalBtn.x = 2048 / 2; normalBtn.y = 1050; menuContainer.addChild(normalBtn); var hardBtn = new Text2('HARD', { size: 100, fill: 0xff9999 }); hardBtn.anchor.set(0.5, 0.5); hardBtn.x = 2048 / 2; hardBtn.y = 1300; menuContainer.addChild(hardBtn); // Add a "How to Play" button var howToPlayBtn = new Text2('HOW TO PLAY', { size: 90, fill: 0xffffff }); howToPlayBtn.anchor.set(0.5, 0.5); howToPlayBtn.x = 2048 / 2; howToPlayBtn.y = 1550; menuContainer.addChild(howToPlayBtn); // Add a "Power-Ups" button var powerUpsBtn = new Text2('Power-Ups', { size: 90, fill: 0xffff00 }); powerUpsBtn.anchor.set(0.5, 0.5); powerUpsBtn.x = 2048 / 2; powerUpsBtn.y = 1700; menuContainer.addChild(powerUpsBtn); // Power-Ups overlay (shown when Power-Ups button is clicked) var powerUpsOverlay = null; powerUpsBtn.down = function (x, y, obj) { if (powerUpsOverlay && powerUpsOverlay.parent) { powerUpsOverlay.parent.removeChild(powerUpsOverlay); } powerUpsOverlay = new Container(); // Semi-transparent background var bg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); bg.alpha = 0.92; powerUpsOverlay.addChild(bg); // Title var title = new Text2("Power-Ups", { size: 110, fill: 0xffff00 }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 320; powerUpsOverlay.addChild(title); // Show all power-ups as colored musical notes with description var powerupY = title.y + title.height + 40; var powerupSpacing = 220; var powerupTypes = [{ type: "big_paddle", color: 0x66ff99, label: "BIG PADDLE\nBoth paddles grow much larger for 7 seconds, making it easier to hit the ball." }, { type: "multi_ball", color: 0x66ccff, label: "MULTI BALL\nA second ball appears for 7 seconds. Score with either ball to earn points!" }, { type: "slow_ball", color: 0xff99cc, label: "SLOW BALL\nThe ball moves at half speed for 7 seconds, giving you more time to react." }, { type: "speed_ball", color: 0xff6600, label: "SPEED BALL\nThe ball moves much faster for 7 seconds. Can you keep up?" }, { type: "blur_screen", color: 0xcccccc, label: "BLUR SCREEN\nThe whole game blurs for 7 seconds. Try to play through the haze!" }, { type: "invert_screen", color: 0xbb66ff, label: "INVERT SCREEN\nThe entire game flips upside down for 7 seconds. Can you play inverted?" }]; // Align power-up icons and text to the left side var leftMargin = 220; for (var i = 0; i < powerupTypes.length; i++) { var p = powerupTypes[i]; // Note icon var noteIcon = LK.getAsset('musical_note', { anchorX: 0.5, anchorY: 0.5, tint: p.color, scaleX: 2.0, scaleY: 2.0, x: leftMargin + 80, y: powerupY + i * powerupSpacing }); powerUpsOverlay.addChild(noteIcon); // Description text var descText = new Text2(p.label, { size: 70, fill: p.color }); descText.anchor.set(0, 0.5); descText.x = leftMargin + 180; descText.y = powerupY + i * powerupSpacing; powerUpsOverlay.addChild(descText); // After all power-ups are added, scroll the overlay so the last power-up text is at the bottom of the screen } // Scroll overlay so the last power-up text is at the bottom of the screen if (powerupTypes.length > 0) { var lastDescY = powerupY + (powerupTypes.length - 1) * powerupSpacing; var lastDescText = new Text2(powerupTypes[powerupTypes.length - 1].label, { size: 70 }); var bottomLineY = lastDescY + lastDescText.height / 2; var screenHeight = LK.height ? LK.height : 2732; var scrollTo = bottomLineY - screenHeight + 180; // 180px margin from bottom if (scrollTo < 0) scrollTo = 0; powerUpsOverlay.y = -scrollTo; } // Close button var closeBtn = new Text2('CLOSE', { size: 90, fill: 0xffff00 }); closeBtn.anchor.set(0.5, 0.5); closeBtn.x = 2048 / 2; closeBtn.y = 2000; closeBtn.down = function (x, y, obj) { if (powerUpsOverlay && powerUpsOverlay.parent) { powerUpsOverlay.parent.removeChild(powerUpsOverlay); } }; powerUpsOverlay.addChild(closeBtn); game.addChild(powerUpsOverlay); }; // Add a "Return to Main Menu" button (hidden by default, shown on stop) var returnMenuBtn = new Text2('MAIN MENU', { size: 90, fill: 0xffffff }); returnMenuBtn.anchor.set(0.5, 0.5); returnMenuBtn.x = 2048 / 2; returnMenuBtn.y = 1800; returnMenuBtn.visible = false; menuContainer.addChild(returnMenuBtn); // Show main menu function function showMainMenu() { // Remove all children except menuBg, titleText, and buttons while (menuContainer.children.length > 0) { menuContainer.removeChild(menuContainer.children[0]); } menuContainer.addChild(menuBg); menuContainer.addChild(titleText); menuContainer.addChild(easyBtn); menuContainer.addChild(normalBtn); menuContainer.addChild(hardBtn); menuContainer.addChild(howToPlayBtn); menuContainer.addChild(returnMenuBtn); returnMenuBtn.visible = false; game.addChild(menuContainer); // Hide pause button when in menu if (pauseBtn && pauseBtn.parent) { pauseBtn.visible = false; } // Start background music in main menu LK.playMusic('Backround_Music'); } // How to Play overlay var howToPlayOverlay = null; howToPlayBtn.down = function (x, y, obj) { if (howToPlayOverlay && howToPlayOverlay.parent) { howToPlayOverlay.parent.removeChild(howToPlayOverlay); } howToPlayOverlay = new Container(); // Semi-transparent background var bg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); bg.alpha = 0.92; howToPlayOverlay.addChild(bg); // Instructions text var howText = new Text2("How to Play\n\n" + "- Drag your paddle left/right to hit the ball.\n" + "- The ball moves in sync with the music beat.\n" + "- Score points by getting the ball past the AI paddle.\n" + "- First to 7 points wins!\n" + "- Collect power-ups for special effects.\n" + "- Difficulty affects AI speed and accuracy.\n", { size: 80, fill: 0xffffff }); howText.anchor.set(0.5, 0); howText.x = 2048 / 2; howText.y = 400; howToPlayOverlay.addChild(howText); // Close button var closeBtn = new Text2('CLOSE', { size: 90, fill: 0xffff00 }); closeBtn.anchor.set(0.5, 0.5); closeBtn.x = 2048 / 2; closeBtn.y = 2000; closeBtn.down = function (x, y, obj) { if (howToPlayOverlay && howToPlayOverlay.parent) { howToPlayOverlay.parent.removeChild(howToPlayOverlay); } }; howToPlayOverlay.addChild(closeBtn); game.addChild(howToPlayOverlay); }; // Listen for stop event to show return to menu button game.onStop = function () { // Show the return to menu button returnMenuBtn.visible = true; // Hide pause button when game ends if (pauseBtn && pauseBtn.parent) { pauseBtn.visible = false; } // (Do not stop background music here to allow music in all modes) // Make sure menu is visible if (!menuContainer.parent) { game.addChild(menuContainer); } }; // Return to menu button handler returnMenuBtn.down = function (x, y, obj) { // Hide the button and show the main menu returnMenuBtn.visible = false; showMainMenu(); }; var selectedDifficulty = null; var aiDifficulty = "normal"; // default function startGameWithDifficulty(diff) { selectedDifficulty = diff; if (diff === "easy") { aiDifficulty = "easy"; } else if (diff === "normal") { aiDifficulty = "normal"; } else { aiDifficulty = "hard"; } menuContainer.destroy(); // Only start the game (and thus pause logic) after menu is gone initGame(); } easyBtn.down = function (x, y, obj) { startGameWithDifficulty("easy"); }; normalBtn.down = function (x, y, obj) { startGameWithDifficulty("normal"); }; hardBtn.down = function (x, y, obj) { startGameWithDifficulty("hard"); }; game.addChild(menuContainer); // --- Game variables (initialized in initGame) --- var tableBg, net, playerPaddle, aiPaddle, ball, playerScore, aiScore, scoreText; var rhythmInterval, rhythmTimer, lastRhythmTick, rhythmBoostAmount, rhythmBoostDuration; var dragging; // --- Pause button (only visible during gameplay) --- var pauseBtn = null; // --- Power-up variables (global scope) --- var powerUps = []; var powerUpActive = false; var powerUpTimer = 0; var powerUpEffectTimer = 0; var powerUpEffectType = null; // --- Power-up attribute text (global for removal timeout) var powerUpAttributeText = undefined; // --- New power-up effect globals --- var blurOverlay = null; var blurText = null; var spinScreenActive = false; var spinScreenTimer = 0; var spinOverlay = null; // --- Game initialization function --- function initGame() { // Table background tableBg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(tableBg); // Net net = LK.getAsset('net', { anchorX: 0, anchorY: 0.5, x: 0, y: 2732 / 2 }); game.addChild(net); // Player paddle playerPaddle = new Paddle(); playerPaddle.setPlayer(); playerPaddle.x = 2048 / 2; playerPaddle.y = 2732 - 180; game.addChild(playerPaddle); // AI paddle aiPaddle = new Paddle(); aiPaddle.setAI(); aiPaddle.x = 2048 / 2; aiPaddle.y = 180; game.addChild(aiPaddle); // Ball ball = new Ball(); // Set ball speed based on selectedDifficulty if (selectedDifficulty === "easy") { ball.baseSpeed = 10; ball.speed = 10; } else if (selectedDifficulty === "hard") { ball.baseSpeed = 26; ball.speed = 26; } else { // normal ball.baseSpeed = 18; ball.speed = 18; } ball.reset(Math.random() < 0.5 ? 1 : -1); game.addChild(ball); // Score playerScore = 0; aiScore = 0; // Score display scoreText = new Text2('0 : 0', { size: 120, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Rhythm variables rhythmInterval = 600; // ms per beat (100 BPM) rhythmTimer = 0; lastRhythmTick = 0; rhythmBoostAmount = 8; rhythmBoostDuration = 180; // ms // Power-up variables powerUps = []; powerUpActive = false; powerUpTimer = 0; powerUpEffectTimer = 0; powerUpEffectType = null; // Remove any power-up effects from previous game if (typeof playerPaddle !== "undefined" && playerPaddle) { playerPaddle.scale.x = 1; playerPaddle.scale.y = 1; } if (typeof extraBall !== "undefined" && extraBall) { extraBall.destroy(); extraBall = null; } if (typeof ball !== "undefined" && ball) { ball.speed = ball.baseSpeed; } // Start music LK.playMusic('Backround_Music'); // Dragging dragging = false; // Create Pause button if not already created if (!pauseBtn) { pauseBtn = new Text2('PAUSE', { size: 90, fill: 0xffffff }); pauseBtn.anchor.set(0.5, 0.5); pauseBtn.x = 2048 - 180; pauseBtn.y = 180; pauseBtn.visible = true; pauseBtn.down = function (x, y, obj) { LK.pauseGame(); }; } // Show pause button in GUI (top right, but not in top left 100x100) if (!pauseBtn.parent) { LK.gui.topRight.addChild(pauseBtn); } pauseBtn.visible = true; } // Move handler (player paddle) function handleMove(x, y, obj) { if (dragging) { // Clamp to table var newX = x; var halfW = playerPaddle.width / 2; if (newX < halfW) { newX = halfW; } if (newX > 2048 - halfW) { newX = 2048 - halfW; } playerPaddle.x = newX; } } game.move = handleMove; game.down = function (x, y, obj) { // Only start drag if touch is on/near player paddle var localY = y; if (typeof playerPaddle !== "undefined" && playerPaddle && typeof playerPaddle.y !== "undefined" && typeof playerPaddle.height !== "undefined" && localY > playerPaddle.y - playerPaddle.height / 2 - 80) { dragging = true; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragging = false; }; // Helper: rectangle collision function rectsIntersect(a, b) { // Defensive: check all required properties exist and are numbers if (!a || !b || typeof a.x !== "number" || typeof a.y !== "number" || typeof a.width !== "number" || typeof a.height !== "number" || typeof b.x !== "number" || typeof b.y !== "number" || typeof b.width !== "number" || typeof b.height !== "number") { return false; } return !(a.x + a.width / 2 < b.x - b.width / 2 || a.x - a.width / 2 > b.x + b.width / 2 || a.y + a.height / 2 < b.y - b.height / 2 || a.y - a.height / 2 > b.y + b.height / 2); } // Update score display function updateScore() { if (typeof scoreText !== "undefined" && scoreText && typeof scoreText.setText === "function") { scoreText.setText(playerScore + ' : ' + aiScore); } } // Game update game.update = function () { // Rhythm sync: every rhythmInterval ms, boost ball speed and randomize direction slightly var now = LK.ticks * 1000 / 60; if (now - lastRhythmTick > rhythmInterval) { lastRhythmTick = now; // Boost ball.rhythmBoost = rhythmBoostAmount; // Add a small random angle to ball direction var angle = Math.atan2(ball.vy, ball.vx); var delta = (Math.random() - 0.5) * 0.3; // -0.15 to 0.15 radians angle += delta; var mag = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); ball.vx = Math.cos(angle) * (mag > 0 ? 1 : 1); ball.vy = Math.sin(angle) * (mag > 0 ? 1 : 1); // Animate ball (flash) LK.effects.flashObject(ball, 0xffff00, 120); } // Remove boost after duration if (typeof ball !== "undefined" && ball && ball.rhythmBoost > 0 && now - lastRhythmTick > rhythmBoostDuration) { ball.rhythmBoost = 0; } // --- Power-up spawn logic --- // Only spawn if not already active and not too many on field // Prevent power-ups from spawning in the main menu (before game starts) if (typeof tableBg !== "undefined" && tableBg && typeof powerUps !== "undefined" && !powerUpActive && powerUps.length < 1) { if (typeof powerUpTimer === "undefined") { powerUpTimer = 0; } powerUpTimer++; // Try to spawn every 6-10 seconds if (powerUpTimer > 360 + Math.floor(Math.random() * 240)) { powerUpTimer = 0; // Randomly pick type var types = ['big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'invert_screen']; var pType = types[Math.floor(Math.random() * types.length)]; var p = new PowerUp(pType); // Place in random X, mid Y p.x = 200 + Math.random() * (2048 - 400); p.y = 2732 / 2 + (Math.random() - 0.5) * 400; powerUps.push(p); game.addChild(p); } } else if (typeof powerUpTimer !== "undefined") { powerUpTimer = 0; } // --- Power-up update and collision --- for (var i = powerUps.length - 1; i >= 0; i--) { var p = powerUps[i]; if (typeof p.update === "function") { p.update(); } // If player paddle or ball collides with powerup if (rectsIntersect(playerPaddle, p) || typeof ball !== "undefined" && ball && rectsIntersect(ball, p)) { // Play power-up collect sound ONCE when ball hits powerup if (typeof ball !== "undefined" && ball && rectsIntersect(ball, p)) { if (typeof LK.getSound === "function" && LK.getSound('powerup_collect')) { LK.getSound('powerup_collect').play(); } // Show power-up name text for 0.5s when ball hits power-up if (typeof powerUpAttributeText !== "undefined" && powerUpAttributeText && powerUpAttributeText.parent) { powerUpAttributeText.parent.removeChild(powerUpAttributeText); } var attrText = ""; if (p.type === "big_paddle") { attrText = "BIG PADDLE"; } else if (p.type === "multi_ball") { attrText = "MULTI BALL"; } else if (p.type === "slow_ball") { attrText = "SLOW BALL"; } else if (p.type === "speed_ball") { attrText = "SPEED BALL"; } else if (p.type === "blur_screen") { attrText = "BLUR SCREEN"; } else if (p.type === "invert_screen") { attrText = "INVERT SCREEN"; } powerUpAttributeText = new Text2(attrText, { size: 120, fill: 0xffff00 }); powerUpAttributeText.anchor.set(0.5, 0.5); powerUpAttributeText.x = 2048 / 2; powerUpAttributeText.y = 2732 / 2; LK.gui.center.addChild(powerUpAttributeText); LK.setTimeout(function () { if (typeof powerUpAttributeText !== "undefined" && powerUpAttributeText && powerUpAttributeText.parent) { powerUpAttributeText.parent.removeChild(powerUpAttributeText); } }, 500); } // Only allow one power-up effect at a time if (!powerUpActive) { // Remove any previous effect before applying new one // Reset paddle size playerPaddle.scale.x = 1; playerPaddle.scale.y = 1; if (typeof aiPaddle !== "undefined" && aiPaddle) { aiPaddle.scale.x = 1; aiPaddle.scale.y = 1; } // Remove extra ball if present if (typeof extraBall !== "undefined" && extraBall) { extraBall.destroy(); extraBall = null; } // Reset ball speed if it was slowed if (typeof ball !== "undefined" && ball) { ball.speed = ball.baseSpeed; } // Now apply new effect powerUpActive = true; powerUpEffectType = p.type; powerUpEffectTimer = 0; // Remove from field p.destroy(); powerUps.splice(i, 1); // Visual feedback for both paddles LK.effects.flashObject(playerPaddle, 0xffff99, 200); LK.effects.flashObject(aiPaddle, 0xffff99, 200); // Apply effect to both player and AI if (p.type === 'big_paddle') { // Enlarge both paddles playerPaddle.scale.x = 1.7; playerPaddle.scale.y = 1.2; if (typeof aiPaddle !== "undefined" && aiPaddle) { aiPaddle.scale.x = 1.7; aiPaddle.scale.y = 1.2; } } else if (p.type === 'multi_ball') { // Add a second ball (if not already present) if (typeof extraBall === "undefined" || !extraBall) { extraBall = new Ball(); extraBall.x = ball.x; extraBall.y = ball.y; extraBall.vx = -ball.vx; extraBall.vy = ball.vy; // Set extraBall speed based on selectedDifficulty if (selectedDifficulty === "easy") { extraBall.baseSpeed = 10; extraBall.speed = 10; } else if (selectedDifficulty === "hard") { extraBall.baseSpeed = 26; extraBall.speed = 26; } else { // normal extraBall.baseSpeed = 18; extraBall.speed = 18; } extraBall.rhythmBoost = ball.rhythmBoost; extraBall.lastScored = false; game.addChild(extraBall); } } else if (p.type === 'slow_ball') { // Slow down ball if (typeof ball !== "undefined" && ball) { ball.speed = Math.max(6, ball.speed * 0.5); } } else if (p.type === 'speed_ball') { // Speed up ball if (typeof ball !== "undefined" && ball) { ball.speed = Math.min(ball.baseSpeed * 2.2, 48); } if (typeof extraBall !== "undefined" && extraBall) { extraBall.speed = Math.min(extraBall.baseSpeed * 2.2, 48); } } else if (p.type === 'blur_screen') { // Blur effect: overlay a semi-transparent blurred rectangle if (typeof blurOverlay === "undefined" || !blurOverlay) { blurOverlay = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); blurOverlay.alpha = 0.45; blurOverlay.tint = 0xcccccc; blurOverlay.width = 2048; blurOverlay.height = 2732; // Add a "blur" text for fun blurText = new Text2("BLUR!", { size: 200, fill: 0xffffff }); blurText.anchor.set(0.5, 0.5); blurText.x = 2048 / 2; blurText.y = 2732 / 2; blurOverlay.addChild(blurText); game.addChild(blurOverlay); } } else if (p.type === 'invert_screen') { // Invert screen: flip the game vertically (upside down) for 7 seconds spinScreenActive = true; spinScreenTimer = 0; if (typeof spinOverlay === "undefined" || !spinOverlay) { spinOverlay = new Container(); } // Instantly flip the game vertically by setting scale.y to -1 and anchor to center if (typeof game.scale !== "undefined") { game.pivot.x = 2048 / 2; game.pivot.y = 2732 / 2; game.position.x = 2048 / 2; game.position.y = 2732 / 2; game.scale.y = -1; } } } else { // If effect is already active, just remove the powerup from the field p.destroy(); powerUps.splice(i, 1); } } } // --- Power-up effect duration --- if (powerUpActive) { powerUpEffectTimer++; // Effect lasts 7 seconds if (powerUpEffectTimer > 420) { // Remove all effects and reset state if (typeof playerPaddle !== "undefined" && playerPaddle) { playerPaddle.scale.x = 1; playerPaddle.scale.y = 1; } if (typeof extraBall !== "undefined" && extraBall) { extraBall.destroy(); extraBall = null; } if (typeof ball !== "undefined" && ball) { ball.speed = ball.baseSpeed; } // Remove blur overlay if present if (typeof blurOverlay !== "undefined" && blurOverlay && blurOverlay.parent) { blurOverlay.parent.removeChild(blurOverlay); blurOverlay = null; blurText = null; } // Remove invert_screen effect (reset vertical flip) if (typeof spinScreenActive !== "undefined" && spinScreenActive) { spinScreenActive = false; spinScreenTimer = 0; if (typeof game.scale !== "undefined") { game.scale.y = 1; game.pivot.x = 0; game.pivot.y = 0; game.position.x = 0; game.position.y = 0; } } // Clean up any other power-up objects on the field for (var i = powerUps.length - 1; i >= 0; i--) { if (powerUps[i]) { powerUps[i].destroy(); } } powerUps = []; powerUpActive = false; powerUpEffectType = null; powerUpEffectTimer = 0; } } // Ball update if (typeof ball !== "undefined" && ball && typeof ball.update === "function") { ball.update(); } // Extra ball update (if present) if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.update === "function") { extraBall.update(); // Ball collision with left/right walls if (extraBall.x < extraBall.width / 2) { extraBall.x = extraBall.width / 2; extraBall.vx *= -1; } if (extraBall.x > 2048 - extraBall.width / 2) { extraBall.x = 2048 - extraBall.width / 2; extraBall.vx *= -1; } } // Ball collision with left/right walls if (typeof ball !== "undefined" && ball && typeof ball.x !== "undefined" && typeof ball.width !== "undefined") { if (ball.x < ball.width / 2) { ball.x = ball.width / 2; ball.vx *= -1; } if (ball.x > 2048 - ball.width / 2) { ball.x = 2048 - ball.width / 2; ball.vx *= -1; } } // Ball collision with player paddle if (typeof ball !== "undefined" && ball && typeof ball.vy !== "undefined" && ball.vy > 0 && rectsIntersect(ball, playerPaddle)) { ball.y = playerPaddle.y - playerPaddle.height / 2 - ball.height / 2; ball.vy *= -1; // Play p1 sound when ball hits player paddle if (typeof LK.getSound === "function" && LK.getSound('p1')) { LK.getSound('p1').play(); } // Add a bit of angle based on where it hit the paddle var offset = (ball.x - playerPaddle.x) / (playerPaddle.width / 2); var angle = offset * 0.5; // up to ~30deg var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); var newAngle = Math.atan2(-ball.vy, ball.vx) + angle; ball.vx = Math.cos(newAngle); ball.vy = -Math.abs(Math.sin(newAngle)); // Increase speed ball.speed += 1.2; // Flash paddle LK.effects.flashObject(playerPaddle, 0x99ccff, 120); } // Ball collision with AI paddle if (typeof ball !== "undefined" && ball && typeof ball.vy !== "undefined" && ball.vy < 0 && rectsIntersect(ball, aiPaddle)) { ball.y = aiPaddle.y + aiPaddle.height / 2 + ball.height / 2; ball.vy *= -1; // Play pAI sound when ball hits AI paddle if (typeof LK.getSound === "function" && LK.getSound('pAI')) { LK.getSound('pAI').play(); } // Add a bit of angle based on where it hit the paddle var offset = (ball.x - aiPaddle.x) / (aiPaddle.width / 2); var angle = offset * 0.5; var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); var newAngle = Math.atan2(-ball.vy, ball.vx) + angle; ball.vx = Math.cos(newAngle); ball.vy = Math.abs(Math.sin(newAngle)); // Increase speed ball.speed += 1.2; // Flash paddle LK.effects.flashObject(aiPaddle, 0xff99bb, 120); } // Ball out of bounds (top/bottom) if (typeof ball !== "undefined" && ball && typeof ball.y !== "undefined" && typeof ball.height !== "undefined" && !ball.lastScored && ball.y < -ball.height / 2) { // Player scores playerScore += 1; updateScore(); ball.lastScored = true; LK.effects.flashScreen(0x3399ff, 400); if (playerScore >= 7) { if (typeof LK.getSound === "function" && LK.getSound('p1won')) { LK.getSound('p1won').play(); } LK.showYouWin(); return; } ball.reset(-1); } if (typeof ball !== "undefined" && ball && typeof ball.y !== "undefined" && typeof ball.height !== "undefined" && !ball.lastScored && ball.y > 2732 + ball.height / 2) { // AI scores aiScore += 1; updateScore(); ball.lastScored = true; LK.effects.flashScreen(0xff3366, 400); if (aiScore >= 7) { LK.showGameOver(); return; } ball.reset(1); } // Extra ball scoring if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.y !== "undefined" && typeof extraBall.height !== "undefined" && !extraBall.lastScored) { if (extraBall.y < -extraBall.height / 2) { // Player scores playerScore += 1; updateScore(); extraBall.lastScored = true; LK.effects.flashScreen(0x3399ff, 400); if (playerScore >= 7) { if (typeof LK.getSound === "function" && LK.getSound('p1won')) { LK.getSound('p1won').play(); } LK.showYouWin(); return; } extraBall.reset(-1); } else if (extraBall.y > 2732 + extraBall.height / 2) { // AI scores aiScore += 1; updateScore(); extraBall.lastScored = true; LK.effects.flashScreen(0xff3366, 400); if (aiScore >= 7) { LK.showGameOver(); return; } extraBall.reset(1); } } // Extra ball paddle collision if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.vy !== "undefined" && extraBall.vy > 0 && rectsIntersect(extraBall, playerPaddle)) { extraBall.y = playerPaddle.y - playerPaddle.height / 2 - extraBall.height / 2; extraBall.vy *= -1; var offset = (extraBall.x - playerPaddle.x) / (playerPaddle.width / 2); var angle = offset * 0.5; var speed = Math.sqrt(extraBall.vx * extraBall.vx + extraBall.vy * extraBall.vy); var newAngle = Math.atan2(-extraBall.vy, extraBall.vx) + angle; extraBall.vx = Math.cos(newAngle); extraBall.vy = -Math.abs(Math.sin(newAngle)); extraBall.speed += 1.2; LK.effects.flashObject(playerPaddle, 0x99ccff, 120); } if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.vy !== "undefined" && extraBall.vy < 0 && rectsIntersect(extraBall, aiPaddle)) { extraBall.y = aiPaddle.y + aiPaddle.height / 2 + extraBall.height / 2; extraBall.vy *= -1; var offset = (extraBall.x - aiPaddle.x) / (aiPaddle.width / 2); var angle = offset * 0.5; var speed = Math.sqrt(extraBall.vx * extraBall.vx + extraBall.vy * extraBall.vy); var newAngle = Math.atan2(-extraBall.vy, extraBall.vx) + angle; extraBall.vx = Math.cos(newAngle); extraBall.vy = Math.abs(Math.sin(newAngle)); extraBall.speed += 1.2; LK.effects.flashObject(aiPaddle, 0xff99bb, 120); } // --- AI paddle movement: difficulty-based --- if (typeof aiMoveMode === "undefined") { var aiMoveMode = "random"; // "random" or "track" var aiMoveTimer = 0; var aiMoveTargetX = typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined" ? aiPaddle.x : 2048 / 2; var aiMoveDir = 0; } // Difficulty parameters var aiTrackChance, aiRandomOffset, aiSpeedBase, aiSpeedMax, aiStandStillChance, aiReactDelay; if (typeof aiDifficulty === "undefined") { aiDifficulty = "normal"; } if (aiDifficulty === "easy") { aiTrackChance = 0.10; aiRandomOffset = 700; aiSpeedBase = 9; aiSpeedMax = 18; aiStandStillChance = 0.40; aiReactDelay = 110; } else if (aiDifficulty === "hard") { aiTrackChance = 0.6; aiRandomOffset = 180; aiSpeedBase = 24; aiSpeedMax = 44; aiStandStillChance = 0.10; aiReactDelay = 40; } else { // normal aiTrackChance = 0.20; aiRandomOffset = 500; aiSpeedBase = 15; aiSpeedMax = 28; aiStandStillChance = 0.25; aiReactDelay = 80; } aiMoveTimer--; if (aiMoveTimer <= 0) { // Switch mode every 0.5-2 seconds depending on difficulty if (Math.random() < aiTrackChance) { aiMoveMode = "track"; if (typeof ball !== "undefined" && ball && typeof ball.x !== "undefined") { aiMoveTargetX = ball.x + (Math.random() - 0.5) * aiRandomOffset; } else { aiMoveTargetX = 2048 / 2; } aiMoveTimer = aiReactDelay + Math.floor(Math.random() * aiReactDelay); } else { aiMoveMode = "random"; if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.width !== "undefined") { aiMoveTargetX = Math.random() * (2048 - aiPaddle.width) + aiPaddle.width / 2; } else { aiMoveTargetX = 2048 / 2; } aiMoveTimer = aiReactDelay + Math.floor(Math.random() * (aiReactDelay + 30)); } // Randomly sometimes just stand still if (Math.random() < aiStandStillChance) { if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined") { aiMoveTargetX = aiPaddle.x; } else { aiMoveTargetX = 2048 / 2; } } } var aiSpeed = aiSpeedBase; if (typeof ball !== "undefined" && ball && typeof ball.speed !== "undefined") { aiSpeed += Math.min(ball.speed * 1.2, aiSpeedMax); } if (aiMoveMode === "track") { if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined" && typeof aiMoveTargetX !== "undefined" && Math.abs(aiPaddle.x - aiMoveTargetX) > 8) { if (aiPaddle.x < aiMoveTargetX) { aiPaddle.x += aiSpeed; if (aiPaddle.x > aiMoveTargetX) { aiPaddle.x = aiMoveTargetX; } } else { aiPaddle.x -= aiSpeed; if (aiPaddle.x < aiMoveTargetX) { aiPaddle.x = aiMoveTargetX; } } aiPaddle.clamp(); } } else { // random mode: move toward random target if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined" && typeof aiMoveTargetX !== "undefined" && Math.abs(aiPaddle.x - aiMoveTargetX) > 8) { if (aiPaddle.x < aiMoveTargetX) { aiPaddle.x += aiSpeed * 0.7; if (aiPaddle.x > aiMoveTargetX) { aiPaddle.x = aiMoveTargetX; } } else { aiPaddle.x -= aiSpeed * 0.7; if (aiPaddle.x < aiMoveTargetX) { aiPaddle.x = aiMoveTargetX; } } aiPaddle.clamp(); } } // (Gun and bullet logic removed) // --- Spin screen effect update --- // No continuous spin for screen rotation boost; handled by effect application and removal }; // Initial score updateScore();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballSprite = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Ball velocity
self.vx = 0;
self.vy = 0;
// Ball speed (pixels per tick)
self.speed = 18;
// Used for rhythm sync
self.baseSpeed = 18;
self.rhythmBoost = 0;
// Used to prevent multiple scoring on one pass
self.lastScored = false;
// Ball update
self.update = function () {
self.x += self.vx * (self.speed + self.rhythmBoost);
self.y += self.vy * (self.speed + self.rhythmBoost);
};
// Reset ball to center, random direction
self.reset = function (direction) {
self.x = 2048 / 2;
self.y = 2732 / 2;
// Start ball slow, then accelerate after 1 second
self.speed = 4;
self.rhythmBoost = 0;
LK.setTimeout(function () {
// Only accelerate if this ball is still in play and hasn't been reset again
if (self && typeof self.speed !== "undefined" && self.speed === 4) {
self.speed = self.baseSpeed;
}
}, 1000);
// --- Fakeout logic ---
// direction: 1 = to player, -1 = to AI
// Sometimes (30% chance) the ball will "fake" left or right before going the real direction
// Sometimes (20% chance) the ball will "fake" up or down before going the real direction
// Rarely (5% chance), the ball will go to a completely random direction
var rareRandomChance = 0.05;
var fakeoutLRChance = 0.3;
var fakeoutUDChance = 0.2;
var doRareRandom = Math.random() < rareRandomChance;
var doFakeoutLR = !doRareRandom && Math.random() < fakeoutLRChance;
var doFakeoutUD = !doRareRandom && !doFakeoutLR && Math.random() < fakeoutUDChance; // Only one fakeout at a time
var fakeoutDuration = 220; // ms
var realAngle, fakeAngle;
if (doRareRandom) {
// Ball goes in a completely random direction (but not straight up/down)
var angle = Math.random() * Math.PI * 2;
// Avoid perfectly vertical
if (Math.abs(Math.cos(angle)) < 0.2) {
angle += 0.3;
}
self.vx = Math.sin(angle);
self.vy = Math.cos(angle);
} else if (doFakeoutLR) {
// Left/right fakeout (original logic)
var goLeft = Math.random() < 0.5;
realAngle = (goLeft ? -0.35 : 0.35) + (direction === 1 ? Math.PI / 2 : -Math.PI / 2);
fakeAngle = (goLeft ? 0.35 : -0.35) + (direction === 1 ? Math.PI / 2 : -Math.PI / 2);
self.vx = Math.sin(fakeAngle);
self.vy = Math.cos(fakeAngle) * direction;
LK.setTimeout(function () {
if (self && typeof self.vx !== "undefined" && typeof self.vy !== "undefined") {
self.vx = Math.sin(realAngle);
self.vy = Math.cos(realAngle) * direction;
}
}, fakeoutDuration);
} else if (doFakeoutUD) {
// Up/down fakeout
// Ball will appear to go up (or down) for a moment, then go the real direction
var goUpFirst = Math.random() < 0.5;
// Real angle: normal random angle toward the correct side
realAngle = Math.random() * 0.5 - 0.25 + (direction === 1 ? Math.PI / 2 : -Math.PI / 2);
// Fake angle: sharply up or down, but not toward the goal
if (goUpFirst) {
// Fake up: angle close to 0 (straight up)
fakeAngle = -Math.PI / 2 + (Math.random() - 0.5) * 0.2;
} else {
// Fake down: angle close to PI (straight down)
fakeAngle = Math.PI / 2 + (Math.random() - 0.5) * 0.2;
}
self.vx = Math.sin(fakeAngle);
self.vy = Math.cos(fakeAngle) * (goUpFirst ? -1 : 1);
LK.setTimeout(function () {
if (self && typeof self.vx !== "undefined" && typeof self.vy !== "undefined") {
self.vx = Math.sin(realAngle);
self.vy = Math.cos(realAngle) * direction;
}
}, fakeoutDuration);
} else {
// Normal random angle
var angle = Math.random() * 0.5 - 0.25 + (direction === 1 ? Math.PI / 2 : -Math.PI / 2);
self.vx = Math.sin(angle);
self.vy = Math.cos(angle) * direction;
}
self.lastScored = false;
};
return self;
});
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
// Set in init
self.isPlayer = false;
// Attach asset
var paddleSprite = self.attachAsset('paddle_player', {
anchorX: 0.5,
anchorY: 0.5
});
// Set color for AI
self.setAI = function () {
paddleSprite.destroy();
self.attachAsset('paddle_ai', {
anchorX: 0.5,
anchorY: 0.5
});
self.isPlayer = false;
};
// Set color for player
self.setPlayer = function () {
paddleSprite.destroy();
self.attachAsset('paddle_player', {
anchorX: 0.5,
anchorY: 0.5
});
self.isPlayer = true;
};
// Clamp paddle inside table
self.clamp = function () {
var halfW = self.width / 2;
if (self.x < halfW) {
self.x = halfW;
}
if (self.x > 2048 - halfW) {
self.x = 2048 - halfW;
}
};
return self;
});
// PowerUp class
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Types: 'big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'invert_screen'
self.type = 'big_paddle';
self.active = false;
// Randomly pick a type if not set
if (arguments.length > 0 && typeof arguments[0] === "string") {
self.type = arguments[0];
} else {
var types = ['big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'invert_screen'];
self.type = types[Math.floor(Math.random() * types.length)];
}
// Attach musical note asset for all power-ups
// Use a unique color per type for the note, but always a musical note shape
var noteColor = 0xffe066;
if (self.type === 'big_paddle') {
noteColor = 0x66ff99;
}
if (self.type === 'multi_ball') {
noteColor = 0x66ccff;
}
if (self.type === 'slow_ball') {
noteColor = 0xff99cc;
}
if (self.type === 'speed_ball') {
noteColor = 0xff6600;
}
if (self.type === 'blur_screen') {
noteColor = 0xcccccc;
}
if (self.type === 'invert_screen') {
noteColor = 0xbb66ff;
}
// Use 'musical_note' asset (ellipse with note color, or a dedicated note asset if available)
var powerupSprite = self.attachAsset('musical_note', {
anchorX: 0.5,
anchorY: 0.5,
tint: noteColor,
scaleX: 2.0,
scaleY: 2.0
});
// Set size for collision
self.width = powerupSprite.width * 2.0;
self.height = powerupSprite.height * 2.0;
// PowerUp update (spin and move to random places)
self.floatDir = 1;
self.floatTimer = 0;
self.targetX = self.x;
self.targetY = self.y;
self.moveTimer = 0;
self.rotationSpeed = 0.07 + Math.random() * 0.07; // random spin speed
self.update = function () {
self.floatTimer++;
// Spin the note
if (powerupSprite && typeof powerupSprite.rotation === "number") {
powerupSprite.rotation += self.rotationSpeed;
}
// Move toward target position
var moveSpeed = 1.2; // slow movement
if (typeof self.targetX === "number" && typeof self.targetY === "number") {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > moveSpeed) {
self.x += dx / dist * moveSpeed;
self.y += dy / dist * moveSpeed;
} else {
self.x = self.targetX;
self.y = self.targetY;
}
}
// Every 120-240 frames, pick a new random target position
self.moveTimer++;
if (self.moveTimer > 120 + Math.floor(Math.random() * 120)) {
self.moveTimer = 0;
// Stay within table bounds
var margin = 120;
self.targetX = margin + Math.random() * (2048 - 2 * margin);
self.targetY = 2732 / 2 - 400 + Math.random() * 800;
}
// Float up/down for a little extra effect
self.y += Math.sin(self.floatTimer / 20) * 0.8;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22 // Table green
});
/****
* Game Code
****/
// --- Main Menu and Difficulty Selection ---
var menuContainer = new Container();
var menuBg = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
menuContainer.addChild(menuBg);
var titleText = new Text2('PING PONG RHYTHM SMASH', {
size: 120,
fill: 0xffffff
});
titleText.anchor.set(0.5, 0);
titleText.x = 2048 / 2;
titleText.y = 320;
menuContainer.addChild(titleText);
var easyBtn = new Text2('EASY', {
size: 100,
fill: 0x99ff99
});
easyBtn.anchor.set(0.5, 0.5);
easyBtn.x = 2048 / 2;
easyBtn.y = 800;
menuContainer.addChild(easyBtn);
var normalBtn = new Text2('NORMAL', {
size: 100,
fill: 0xffff99
});
normalBtn.anchor.set(0.5, 0.5);
normalBtn.x = 2048 / 2;
normalBtn.y = 1050;
menuContainer.addChild(normalBtn);
var hardBtn = new Text2('HARD', {
size: 100,
fill: 0xff9999
});
hardBtn.anchor.set(0.5, 0.5);
hardBtn.x = 2048 / 2;
hardBtn.y = 1300;
menuContainer.addChild(hardBtn);
// Add a "How to Play" button
var howToPlayBtn = new Text2('HOW TO PLAY', {
size: 90,
fill: 0xffffff
});
howToPlayBtn.anchor.set(0.5, 0.5);
howToPlayBtn.x = 2048 / 2;
howToPlayBtn.y = 1550;
menuContainer.addChild(howToPlayBtn);
// Add a "Power-Ups" button
var powerUpsBtn = new Text2('Power-Ups', {
size: 90,
fill: 0xffff00
});
powerUpsBtn.anchor.set(0.5, 0.5);
powerUpsBtn.x = 2048 / 2;
powerUpsBtn.y = 1700;
menuContainer.addChild(powerUpsBtn);
// Power-Ups overlay (shown when Power-Ups button is clicked)
var powerUpsOverlay = null;
powerUpsBtn.down = function (x, y, obj) {
if (powerUpsOverlay && powerUpsOverlay.parent) {
powerUpsOverlay.parent.removeChild(powerUpsOverlay);
}
powerUpsOverlay = new Container();
// Semi-transparent background
var bg = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
bg.alpha = 0.92;
powerUpsOverlay.addChild(bg);
// Title
var title = new Text2("Power-Ups", {
size: 110,
fill: 0xffff00
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 320;
powerUpsOverlay.addChild(title);
// Show all power-ups as colored musical notes with description
var powerupY = title.y + title.height + 40;
var powerupSpacing = 220;
var powerupTypes = [{
type: "big_paddle",
color: 0x66ff99,
label: "BIG PADDLE\nBoth paddles grow much larger for 7 seconds, making it easier to hit the ball."
}, {
type: "multi_ball",
color: 0x66ccff,
label: "MULTI BALL\nA second ball appears for 7 seconds. Score with either ball to earn points!"
}, {
type: "slow_ball",
color: 0xff99cc,
label: "SLOW BALL\nThe ball moves at half speed for 7 seconds, giving you more time to react."
}, {
type: "speed_ball",
color: 0xff6600,
label: "SPEED BALL\nThe ball moves much faster for 7 seconds. Can you keep up?"
}, {
type: "blur_screen",
color: 0xcccccc,
label: "BLUR SCREEN\nThe whole game blurs for 7 seconds. Try to play through the haze!"
}, {
type: "invert_screen",
color: 0xbb66ff,
label: "INVERT SCREEN\nThe entire game flips upside down for 7 seconds. Can you play inverted?"
}];
// Align power-up icons and text to the left side
var leftMargin = 220;
for (var i = 0; i < powerupTypes.length; i++) {
var p = powerupTypes[i];
// Note icon
var noteIcon = LK.getAsset('musical_note', {
anchorX: 0.5,
anchorY: 0.5,
tint: p.color,
scaleX: 2.0,
scaleY: 2.0,
x: leftMargin + 80,
y: powerupY + i * powerupSpacing
});
powerUpsOverlay.addChild(noteIcon);
// Description text
var descText = new Text2(p.label, {
size: 70,
fill: p.color
});
descText.anchor.set(0, 0.5);
descText.x = leftMargin + 180;
descText.y = powerupY + i * powerupSpacing;
powerUpsOverlay.addChild(descText);
// After all power-ups are added, scroll the overlay so the last power-up text is at the bottom of the screen
}
// Scroll overlay so the last power-up text is at the bottom of the screen
if (powerupTypes.length > 0) {
var lastDescY = powerupY + (powerupTypes.length - 1) * powerupSpacing;
var lastDescText = new Text2(powerupTypes[powerupTypes.length - 1].label, {
size: 70
});
var bottomLineY = lastDescY + lastDescText.height / 2;
var screenHeight = LK.height ? LK.height : 2732;
var scrollTo = bottomLineY - screenHeight + 180; // 180px margin from bottom
if (scrollTo < 0) scrollTo = 0;
powerUpsOverlay.y = -scrollTo;
}
// Close button
var closeBtn = new Text2('CLOSE', {
size: 90,
fill: 0xffff00
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.x = 2048 / 2;
closeBtn.y = 2000;
closeBtn.down = function (x, y, obj) {
if (powerUpsOverlay && powerUpsOverlay.parent) {
powerUpsOverlay.parent.removeChild(powerUpsOverlay);
}
};
powerUpsOverlay.addChild(closeBtn);
game.addChild(powerUpsOverlay);
};
// Add a "Return to Main Menu" button (hidden by default, shown on stop)
var returnMenuBtn = new Text2('MAIN MENU', {
size: 90,
fill: 0xffffff
});
returnMenuBtn.anchor.set(0.5, 0.5);
returnMenuBtn.x = 2048 / 2;
returnMenuBtn.y = 1800;
returnMenuBtn.visible = false;
menuContainer.addChild(returnMenuBtn);
// Show main menu function
function showMainMenu() {
// Remove all children except menuBg, titleText, and buttons
while (menuContainer.children.length > 0) {
menuContainer.removeChild(menuContainer.children[0]);
}
menuContainer.addChild(menuBg);
menuContainer.addChild(titleText);
menuContainer.addChild(easyBtn);
menuContainer.addChild(normalBtn);
menuContainer.addChild(hardBtn);
menuContainer.addChild(howToPlayBtn);
menuContainer.addChild(returnMenuBtn);
returnMenuBtn.visible = false;
game.addChild(menuContainer);
// Hide pause button when in menu
if (pauseBtn && pauseBtn.parent) {
pauseBtn.visible = false;
}
// Start background music in main menu
LK.playMusic('Backround_Music');
}
// How to Play overlay
var howToPlayOverlay = null;
howToPlayBtn.down = function (x, y, obj) {
if (howToPlayOverlay && howToPlayOverlay.parent) {
howToPlayOverlay.parent.removeChild(howToPlayOverlay);
}
howToPlayOverlay = new Container();
// Semi-transparent background
var bg = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
bg.alpha = 0.92;
howToPlayOverlay.addChild(bg);
// Instructions text
var howText = new Text2("How to Play\n\n" + "- Drag your paddle left/right to hit the ball.\n" + "- The ball moves in sync with the music beat.\n" + "- Score points by getting the ball past the AI paddle.\n" + "- First to 7 points wins!\n" + "- Collect power-ups for special effects.\n" + "- Difficulty affects AI speed and accuracy.\n", {
size: 80,
fill: 0xffffff
});
howText.anchor.set(0.5, 0);
howText.x = 2048 / 2;
howText.y = 400;
howToPlayOverlay.addChild(howText);
// Close button
var closeBtn = new Text2('CLOSE', {
size: 90,
fill: 0xffff00
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.x = 2048 / 2;
closeBtn.y = 2000;
closeBtn.down = function (x, y, obj) {
if (howToPlayOverlay && howToPlayOverlay.parent) {
howToPlayOverlay.parent.removeChild(howToPlayOverlay);
}
};
howToPlayOverlay.addChild(closeBtn);
game.addChild(howToPlayOverlay);
};
// Listen for stop event to show return to menu button
game.onStop = function () {
// Show the return to menu button
returnMenuBtn.visible = true;
// Hide pause button when game ends
if (pauseBtn && pauseBtn.parent) {
pauseBtn.visible = false;
}
// (Do not stop background music here to allow music in all modes)
// Make sure menu is visible
if (!menuContainer.parent) {
game.addChild(menuContainer);
}
};
// Return to menu button handler
returnMenuBtn.down = function (x, y, obj) {
// Hide the button and show the main menu
returnMenuBtn.visible = false;
showMainMenu();
};
var selectedDifficulty = null;
var aiDifficulty = "normal"; // default
function startGameWithDifficulty(diff) {
selectedDifficulty = diff;
if (diff === "easy") {
aiDifficulty = "easy";
} else if (diff === "normal") {
aiDifficulty = "normal";
} else {
aiDifficulty = "hard";
}
menuContainer.destroy();
// Only start the game (and thus pause logic) after menu is gone
initGame();
}
easyBtn.down = function (x, y, obj) {
startGameWithDifficulty("easy");
};
normalBtn.down = function (x, y, obj) {
startGameWithDifficulty("normal");
};
hardBtn.down = function (x, y, obj) {
startGameWithDifficulty("hard");
};
game.addChild(menuContainer);
// --- Game variables (initialized in initGame) ---
var tableBg, net, playerPaddle, aiPaddle, ball, playerScore, aiScore, scoreText;
var rhythmInterval, rhythmTimer, lastRhythmTick, rhythmBoostAmount, rhythmBoostDuration;
var dragging;
// --- Pause button (only visible during gameplay) ---
var pauseBtn = null;
// --- Power-up variables (global scope) ---
var powerUps = [];
var powerUpActive = false;
var powerUpTimer = 0;
var powerUpEffectTimer = 0;
var powerUpEffectType = null;
// --- Power-up attribute text (global for removal timeout)
var powerUpAttributeText = undefined;
// --- New power-up effect globals ---
var blurOverlay = null;
var blurText = null;
var spinScreenActive = false;
var spinScreenTimer = 0;
var spinOverlay = null;
// --- Game initialization function ---
function initGame() {
// Table background
tableBg = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(tableBg);
// Net
net = LK.getAsset('net', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 2732 / 2
});
game.addChild(net);
// Player paddle
playerPaddle = new Paddle();
playerPaddle.setPlayer();
playerPaddle.x = 2048 / 2;
playerPaddle.y = 2732 - 180;
game.addChild(playerPaddle);
// AI paddle
aiPaddle = new Paddle();
aiPaddle.setAI();
aiPaddle.x = 2048 / 2;
aiPaddle.y = 180;
game.addChild(aiPaddle);
// Ball
ball = new Ball();
// Set ball speed based on selectedDifficulty
if (selectedDifficulty === "easy") {
ball.baseSpeed = 10;
ball.speed = 10;
} else if (selectedDifficulty === "hard") {
ball.baseSpeed = 26;
ball.speed = 26;
} else {
// normal
ball.baseSpeed = 18;
ball.speed = 18;
}
ball.reset(Math.random() < 0.5 ? 1 : -1);
game.addChild(ball);
// Score
playerScore = 0;
aiScore = 0;
// Score display
scoreText = new Text2('0 : 0', {
size: 120,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Rhythm variables
rhythmInterval = 600; // ms per beat (100 BPM)
rhythmTimer = 0;
lastRhythmTick = 0;
rhythmBoostAmount = 8;
rhythmBoostDuration = 180; // ms
// Power-up variables
powerUps = [];
powerUpActive = false;
powerUpTimer = 0;
powerUpEffectTimer = 0;
powerUpEffectType = null;
// Remove any power-up effects from previous game
if (typeof playerPaddle !== "undefined" && playerPaddle) {
playerPaddle.scale.x = 1;
playerPaddle.scale.y = 1;
}
if (typeof extraBall !== "undefined" && extraBall) {
extraBall.destroy();
extraBall = null;
}
if (typeof ball !== "undefined" && ball) {
ball.speed = ball.baseSpeed;
}
// Start music
LK.playMusic('Backround_Music');
// Dragging
dragging = false;
// Create Pause button if not already created
if (!pauseBtn) {
pauseBtn = new Text2('PAUSE', {
size: 90,
fill: 0xffffff
});
pauseBtn.anchor.set(0.5, 0.5);
pauseBtn.x = 2048 - 180;
pauseBtn.y = 180;
pauseBtn.visible = true;
pauseBtn.down = function (x, y, obj) {
LK.pauseGame();
};
}
// Show pause button in GUI (top right, but not in top left 100x100)
if (!pauseBtn.parent) {
LK.gui.topRight.addChild(pauseBtn);
}
pauseBtn.visible = true;
}
// Move handler (player paddle)
function handleMove(x, y, obj) {
if (dragging) {
// Clamp to table
var newX = x;
var halfW = playerPaddle.width / 2;
if (newX < halfW) {
newX = halfW;
}
if (newX > 2048 - halfW) {
newX = 2048 - halfW;
}
playerPaddle.x = newX;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only start drag if touch is on/near player paddle
var localY = y;
if (typeof playerPaddle !== "undefined" && playerPaddle && typeof playerPaddle.y !== "undefined" && typeof playerPaddle.height !== "undefined" && localY > playerPaddle.y - playerPaddle.height / 2 - 80) {
dragging = true;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Helper: rectangle collision
function rectsIntersect(a, b) {
// Defensive: check all required properties exist and are numbers
if (!a || !b || typeof a.x !== "number" || typeof a.y !== "number" || typeof a.width !== "number" || typeof a.height !== "number" || typeof b.x !== "number" || typeof b.y !== "number" || typeof b.width !== "number" || typeof b.height !== "number") {
return false;
}
return !(a.x + a.width / 2 < b.x - b.width / 2 || a.x - a.width / 2 > b.x + b.width / 2 || a.y + a.height / 2 < b.y - b.height / 2 || a.y - a.height / 2 > b.y + b.height / 2);
}
// Update score display
function updateScore() {
if (typeof scoreText !== "undefined" && scoreText && typeof scoreText.setText === "function") {
scoreText.setText(playerScore + ' : ' + aiScore);
}
}
// Game update
game.update = function () {
// Rhythm sync: every rhythmInterval ms, boost ball speed and randomize direction slightly
var now = LK.ticks * 1000 / 60;
if (now - lastRhythmTick > rhythmInterval) {
lastRhythmTick = now;
// Boost
ball.rhythmBoost = rhythmBoostAmount;
// Add a small random angle to ball direction
var angle = Math.atan2(ball.vy, ball.vx);
var delta = (Math.random() - 0.5) * 0.3; // -0.15 to 0.15 radians
angle += delta;
var mag = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
ball.vx = Math.cos(angle) * (mag > 0 ? 1 : 1);
ball.vy = Math.sin(angle) * (mag > 0 ? 1 : 1);
// Animate ball (flash)
LK.effects.flashObject(ball, 0xffff00, 120);
}
// Remove boost after duration
if (typeof ball !== "undefined" && ball && ball.rhythmBoost > 0 && now - lastRhythmTick > rhythmBoostDuration) {
ball.rhythmBoost = 0;
}
// --- Power-up spawn logic ---
// Only spawn if not already active and not too many on field
// Prevent power-ups from spawning in the main menu (before game starts)
if (typeof tableBg !== "undefined" && tableBg && typeof powerUps !== "undefined" && !powerUpActive && powerUps.length < 1) {
if (typeof powerUpTimer === "undefined") {
powerUpTimer = 0;
}
powerUpTimer++;
// Try to spawn every 6-10 seconds
if (powerUpTimer > 360 + Math.floor(Math.random() * 240)) {
powerUpTimer = 0;
// Randomly pick type
var types = ['big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'invert_screen'];
var pType = types[Math.floor(Math.random() * types.length)];
var p = new PowerUp(pType);
// Place in random X, mid Y
p.x = 200 + Math.random() * (2048 - 400);
p.y = 2732 / 2 + (Math.random() - 0.5) * 400;
powerUps.push(p);
game.addChild(p);
}
} else if (typeof powerUpTimer !== "undefined") {
powerUpTimer = 0;
}
// --- Power-up update and collision ---
for (var i = powerUps.length - 1; i >= 0; i--) {
var p = powerUps[i];
if (typeof p.update === "function") {
p.update();
}
// If player paddle or ball collides with powerup
if (rectsIntersect(playerPaddle, p) || typeof ball !== "undefined" && ball && rectsIntersect(ball, p)) {
// Play power-up collect sound ONCE when ball hits powerup
if (typeof ball !== "undefined" && ball && rectsIntersect(ball, p)) {
if (typeof LK.getSound === "function" && LK.getSound('powerup_collect')) {
LK.getSound('powerup_collect').play();
}
// Show power-up name text for 0.5s when ball hits power-up
if (typeof powerUpAttributeText !== "undefined" && powerUpAttributeText && powerUpAttributeText.parent) {
powerUpAttributeText.parent.removeChild(powerUpAttributeText);
}
var attrText = "";
if (p.type === "big_paddle") {
attrText = "BIG PADDLE";
} else if (p.type === "multi_ball") {
attrText = "MULTI BALL";
} else if (p.type === "slow_ball") {
attrText = "SLOW BALL";
} else if (p.type === "speed_ball") {
attrText = "SPEED BALL";
} else if (p.type === "blur_screen") {
attrText = "BLUR SCREEN";
} else if (p.type === "invert_screen") {
attrText = "INVERT SCREEN";
}
powerUpAttributeText = new Text2(attrText, {
size: 120,
fill: 0xffff00
});
powerUpAttributeText.anchor.set(0.5, 0.5);
powerUpAttributeText.x = 2048 / 2;
powerUpAttributeText.y = 2732 / 2;
LK.gui.center.addChild(powerUpAttributeText);
LK.setTimeout(function () {
if (typeof powerUpAttributeText !== "undefined" && powerUpAttributeText && powerUpAttributeText.parent) {
powerUpAttributeText.parent.removeChild(powerUpAttributeText);
}
}, 500);
}
// Only allow one power-up effect at a time
if (!powerUpActive) {
// Remove any previous effect before applying new one
// Reset paddle size
playerPaddle.scale.x = 1;
playerPaddle.scale.y = 1;
if (typeof aiPaddle !== "undefined" && aiPaddle) {
aiPaddle.scale.x = 1;
aiPaddle.scale.y = 1;
}
// Remove extra ball if present
if (typeof extraBall !== "undefined" && extraBall) {
extraBall.destroy();
extraBall = null;
}
// Reset ball speed if it was slowed
if (typeof ball !== "undefined" && ball) {
ball.speed = ball.baseSpeed;
}
// Now apply new effect
powerUpActive = true;
powerUpEffectType = p.type;
powerUpEffectTimer = 0;
// Remove from field
p.destroy();
powerUps.splice(i, 1);
// Visual feedback for both paddles
LK.effects.flashObject(playerPaddle, 0xffff99, 200);
LK.effects.flashObject(aiPaddle, 0xffff99, 200);
// Apply effect to both player and AI
if (p.type === 'big_paddle') {
// Enlarge both paddles
playerPaddle.scale.x = 1.7;
playerPaddle.scale.y = 1.2;
if (typeof aiPaddle !== "undefined" && aiPaddle) {
aiPaddle.scale.x = 1.7;
aiPaddle.scale.y = 1.2;
}
} else if (p.type === 'multi_ball') {
// Add a second ball (if not already present)
if (typeof extraBall === "undefined" || !extraBall) {
extraBall = new Ball();
extraBall.x = ball.x;
extraBall.y = ball.y;
extraBall.vx = -ball.vx;
extraBall.vy = ball.vy;
// Set extraBall speed based on selectedDifficulty
if (selectedDifficulty === "easy") {
extraBall.baseSpeed = 10;
extraBall.speed = 10;
} else if (selectedDifficulty === "hard") {
extraBall.baseSpeed = 26;
extraBall.speed = 26;
} else {
// normal
extraBall.baseSpeed = 18;
extraBall.speed = 18;
}
extraBall.rhythmBoost = ball.rhythmBoost;
extraBall.lastScored = false;
game.addChild(extraBall);
}
} else if (p.type === 'slow_ball') {
// Slow down ball
if (typeof ball !== "undefined" && ball) {
ball.speed = Math.max(6, ball.speed * 0.5);
}
} else if (p.type === 'speed_ball') {
// Speed up ball
if (typeof ball !== "undefined" && ball) {
ball.speed = Math.min(ball.baseSpeed * 2.2, 48);
}
if (typeof extraBall !== "undefined" && extraBall) {
extraBall.speed = Math.min(extraBall.baseSpeed * 2.2, 48);
}
} else if (p.type === 'blur_screen') {
// Blur effect: overlay a semi-transparent blurred rectangle
if (typeof blurOverlay === "undefined" || !blurOverlay) {
blurOverlay = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
blurOverlay.alpha = 0.45;
blurOverlay.tint = 0xcccccc;
blurOverlay.width = 2048;
blurOverlay.height = 2732;
// Add a "blur" text for fun
blurText = new Text2("BLUR!", {
size: 200,
fill: 0xffffff
});
blurText.anchor.set(0.5, 0.5);
blurText.x = 2048 / 2;
blurText.y = 2732 / 2;
blurOverlay.addChild(blurText);
game.addChild(blurOverlay);
}
} else if (p.type === 'invert_screen') {
// Invert screen: flip the game vertically (upside down) for 7 seconds
spinScreenActive = true;
spinScreenTimer = 0;
if (typeof spinOverlay === "undefined" || !spinOverlay) {
spinOverlay = new Container();
}
// Instantly flip the game vertically by setting scale.y to -1 and anchor to center
if (typeof game.scale !== "undefined") {
game.pivot.x = 2048 / 2;
game.pivot.y = 2732 / 2;
game.position.x = 2048 / 2;
game.position.y = 2732 / 2;
game.scale.y = -1;
}
}
} else {
// If effect is already active, just remove the powerup from the field
p.destroy();
powerUps.splice(i, 1);
}
}
}
// --- Power-up effect duration ---
if (powerUpActive) {
powerUpEffectTimer++;
// Effect lasts 7 seconds
if (powerUpEffectTimer > 420) {
// Remove all effects and reset state
if (typeof playerPaddle !== "undefined" && playerPaddle) {
playerPaddle.scale.x = 1;
playerPaddle.scale.y = 1;
}
if (typeof extraBall !== "undefined" && extraBall) {
extraBall.destroy();
extraBall = null;
}
if (typeof ball !== "undefined" && ball) {
ball.speed = ball.baseSpeed;
}
// Remove blur overlay if present
if (typeof blurOverlay !== "undefined" && blurOverlay && blurOverlay.parent) {
blurOverlay.parent.removeChild(blurOverlay);
blurOverlay = null;
blurText = null;
}
// Remove invert_screen effect (reset vertical flip)
if (typeof spinScreenActive !== "undefined" && spinScreenActive) {
spinScreenActive = false;
spinScreenTimer = 0;
if (typeof game.scale !== "undefined") {
game.scale.y = 1;
game.pivot.x = 0;
game.pivot.y = 0;
game.position.x = 0;
game.position.y = 0;
}
}
// Clean up any other power-up objects on the field
for (var i = powerUps.length - 1; i >= 0; i--) {
if (powerUps[i]) {
powerUps[i].destroy();
}
}
powerUps = [];
powerUpActive = false;
powerUpEffectType = null;
powerUpEffectTimer = 0;
}
}
// Ball update
if (typeof ball !== "undefined" && ball && typeof ball.update === "function") {
ball.update();
}
// Extra ball update (if present)
if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.update === "function") {
extraBall.update();
// Ball collision with left/right walls
if (extraBall.x < extraBall.width / 2) {
extraBall.x = extraBall.width / 2;
extraBall.vx *= -1;
}
if (extraBall.x > 2048 - extraBall.width / 2) {
extraBall.x = 2048 - extraBall.width / 2;
extraBall.vx *= -1;
}
}
// Ball collision with left/right walls
if (typeof ball !== "undefined" && ball && typeof ball.x !== "undefined" && typeof ball.width !== "undefined") {
if (ball.x < ball.width / 2) {
ball.x = ball.width / 2;
ball.vx *= -1;
}
if (ball.x > 2048 - ball.width / 2) {
ball.x = 2048 - ball.width / 2;
ball.vx *= -1;
}
}
// Ball collision with player paddle
if (typeof ball !== "undefined" && ball && typeof ball.vy !== "undefined" && ball.vy > 0 && rectsIntersect(ball, playerPaddle)) {
ball.y = playerPaddle.y - playerPaddle.height / 2 - ball.height / 2;
ball.vy *= -1;
// Play p1 sound when ball hits player paddle
if (typeof LK.getSound === "function" && LK.getSound('p1')) {
LK.getSound('p1').play();
}
// Add a bit of angle based on where it hit the paddle
var offset = (ball.x - playerPaddle.x) / (playerPaddle.width / 2);
var angle = offset * 0.5; // up to ~30deg
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
var newAngle = Math.atan2(-ball.vy, ball.vx) + angle;
ball.vx = Math.cos(newAngle);
ball.vy = -Math.abs(Math.sin(newAngle));
// Increase speed
ball.speed += 1.2;
// Flash paddle
LK.effects.flashObject(playerPaddle, 0x99ccff, 120);
}
// Ball collision with AI paddle
if (typeof ball !== "undefined" && ball && typeof ball.vy !== "undefined" && ball.vy < 0 && rectsIntersect(ball, aiPaddle)) {
ball.y = aiPaddle.y + aiPaddle.height / 2 + ball.height / 2;
ball.vy *= -1;
// Play pAI sound when ball hits AI paddle
if (typeof LK.getSound === "function" && LK.getSound('pAI')) {
LK.getSound('pAI').play();
}
// Add a bit of angle based on where it hit the paddle
var offset = (ball.x - aiPaddle.x) / (aiPaddle.width / 2);
var angle = offset * 0.5;
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
var newAngle = Math.atan2(-ball.vy, ball.vx) + angle;
ball.vx = Math.cos(newAngle);
ball.vy = Math.abs(Math.sin(newAngle));
// Increase speed
ball.speed += 1.2;
// Flash paddle
LK.effects.flashObject(aiPaddle, 0xff99bb, 120);
}
// Ball out of bounds (top/bottom)
if (typeof ball !== "undefined" && ball && typeof ball.y !== "undefined" && typeof ball.height !== "undefined" && !ball.lastScored && ball.y < -ball.height / 2) {
// Player scores
playerScore += 1;
updateScore();
ball.lastScored = true;
LK.effects.flashScreen(0x3399ff, 400);
if (playerScore >= 7) {
if (typeof LK.getSound === "function" && LK.getSound('p1won')) {
LK.getSound('p1won').play();
}
LK.showYouWin();
return;
}
ball.reset(-1);
}
if (typeof ball !== "undefined" && ball && typeof ball.y !== "undefined" && typeof ball.height !== "undefined" && !ball.lastScored && ball.y > 2732 + ball.height / 2) {
// AI scores
aiScore += 1;
updateScore();
ball.lastScored = true;
LK.effects.flashScreen(0xff3366, 400);
if (aiScore >= 7) {
LK.showGameOver();
return;
}
ball.reset(1);
}
// Extra ball scoring
if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.y !== "undefined" && typeof extraBall.height !== "undefined" && !extraBall.lastScored) {
if (extraBall.y < -extraBall.height / 2) {
// Player scores
playerScore += 1;
updateScore();
extraBall.lastScored = true;
LK.effects.flashScreen(0x3399ff, 400);
if (playerScore >= 7) {
if (typeof LK.getSound === "function" && LK.getSound('p1won')) {
LK.getSound('p1won').play();
}
LK.showYouWin();
return;
}
extraBall.reset(-1);
} else if (extraBall.y > 2732 + extraBall.height / 2) {
// AI scores
aiScore += 1;
updateScore();
extraBall.lastScored = true;
LK.effects.flashScreen(0xff3366, 400);
if (aiScore >= 7) {
LK.showGameOver();
return;
}
extraBall.reset(1);
}
}
// Extra ball paddle collision
if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.vy !== "undefined" && extraBall.vy > 0 && rectsIntersect(extraBall, playerPaddle)) {
extraBall.y = playerPaddle.y - playerPaddle.height / 2 - extraBall.height / 2;
extraBall.vy *= -1;
var offset = (extraBall.x - playerPaddle.x) / (playerPaddle.width / 2);
var angle = offset * 0.5;
var speed = Math.sqrt(extraBall.vx * extraBall.vx + extraBall.vy * extraBall.vy);
var newAngle = Math.atan2(-extraBall.vy, extraBall.vx) + angle;
extraBall.vx = Math.cos(newAngle);
extraBall.vy = -Math.abs(Math.sin(newAngle));
extraBall.speed += 1.2;
LK.effects.flashObject(playerPaddle, 0x99ccff, 120);
}
if (typeof extraBall !== "undefined" && extraBall && typeof extraBall.vy !== "undefined" && extraBall.vy < 0 && rectsIntersect(extraBall, aiPaddle)) {
extraBall.y = aiPaddle.y + aiPaddle.height / 2 + extraBall.height / 2;
extraBall.vy *= -1;
var offset = (extraBall.x - aiPaddle.x) / (aiPaddle.width / 2);
var angle = offset * 0.5;
var speed = Math.sqrt(extraBall.vx * extraBall.vx + extraBall.vy * extraBall.vy);
var newAngle = Math.atan2(-extraBall.vy, extraBall.vx) + angle;
extraBall.vx = Math.cos(newAngle);
extraBall.vy = Math.abs(Math.sin(newAngle));
extraBall.speed += 1.2;
LK.effects.flashObject(aiPaddle, 0xff99bb, 120);
}
// --- AI paddle movement: difficulty-based ---
if (typeof aiMoveMode === "undefined") {
var aiMoveMode = "random"; // "random" or "track"
var aiMoveTimer = 0;
var aiMoveTargetX = typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined" ? aiPaddle.x : 2048 / 2;
var aiMoveDir = 0;
}
// Difficulty parameters
var aiTrackChance, aiRandomOffset, aiSpeedBase, aiSpeedMax, aiStandStillChance, aiReactDelay;
if (typeof aiDifficulty === "undefined") {
aiDifficulty = "normal";
}
if (aiDifficulty === "easy") {
aiTrackChance = 0.10;
aiRandomOffset = 700;
aiSpeedBase = 9;
aiSpeedMax = 18;
aiStandStillChance = 0.40;
aiReactDelay = 110;
} else if (aiDifficulty === "hard") {
aiTrackChance = 0.6;
aiRandomOffset = 180;
aiSpeedBase = 24;
aiSpeedMax = 44;
aiStandStillChance = 0.10;
aiReactDelay = 40;
} else {
// normal
aiTrackChance = 0.20;
aiRandomOffset = 500;
aiSpeedBase = 15;
aiSpeedMax = 28;
aiStandStillChance = 0.25;
aiReactDelay = 80;
}
aiMoveTimer--;
if (aiMoveTimer <= 0) {
// Switch mode every 0.5-2 seconds depending on difficulty
if (Math.random() < aiTrackChance) {
aiMoveMode = "track";
if (typeof ball !== "undefined" && ball && typeof ball.x !== "undefined") {
aiMoveTargetX = ball.x + (Math.random() - 0.5) * aiRandomOffset;
} else {
aiMoveTargetX = 2048 / 2;
}
aiMoveTimer = aiReactDelay + Math.floor(Math.random() * aiReactDelay);
} else {
aiMoveMode = "random";
if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.width !== "undefined") {
aiMoveTargetX = Math.random() * (2048 - aiPaddle.width) + aiPaddle.width / 2;
} else {
aiMoveTargetX = 2048 / 2;
}
aiMoveTimer = aiReactDelay + Math.floor(Math.random() * (aiReactDelay + 30));
}
// Randomly sometimes just stand still
if (Math.random() < aiStandStillChance) {
if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined") {
aiMoveTargetX = aiPaddle.x;
} else {
aiMoveTargetX = 2048 / 2;
}
}
}
var aiSpeed = aiSpeedBase;
if (typeof ball !== "undefined" && ball && typeof ball.speed !== "undefined") {
aiSpeed += Math.min(ball.speed * 1.2, aiSpeedMax);
}
if (aiMoveMode === "track") {
if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined" && typeof aiMoveTargetX !== "undefined" && Math.abs(aiPaddle.x - aiMoveTargetX) > 8) {
if (aiPaddle.x < aiMoveTargetX) {
aiPaddle.x += aiSpeed;
if (aiPaddle.x > aiMoveTargetX) {
aiPaddle.x = aiMoveTargetX;
}
} else {
aiPaddle.x -= aiSpeed;
if (aiPaddle.x < aiMoveTargetX) {
aiPaddle.x = aiMoveTargetX;
}
}
aiPaddle.clamp();
}
} else {
// random mode: move toward random target
if (typeof aiPaddle !== "undefined" && aiPaddle && typeof aiPaddle.x !== "undefined" && typeof aiMoveTargetX !== "undefined" && Math.abs(aiPaddle.x - aiMoveTargetX) > 8) {
if (aiPaddle.x < aiMoveTargetX) {
aiPaddle.x += aiSpeed * 0.7;
if (aiPaddle.x > aiMoveTargetX) {
aiPaddle.x = aiMoveTargetX;
}
} else {
aiPaddle.x -= aiSpeed * 0.7;
if (aiPaddle.x < aiMoveTargetX) {
aiPaddle.x = aiMoveTargetX;
}
}
aiPaddle.clamp();
}
}
// (Gun and bullet logic removed)
// --- Spin screen effect update ---
// No continuous spin for screen rotation boost; handled by effect application and removal
};
// Initial score
updateScore();