User prompt
All Text in HOW TO PLAY should grow downwards, not upwards. Please make sure that when an element (such as a menu, panel, or drop-down list) expands, its height increases downwards, not upwards.
User prompt
Ensure the pivot or anchor point is set correctly so the expansion happens downward. If it's growing upward, adjust the pivot to the top of the element.
User prompt
The element should grow downwards, not upwards. Please make sure that when the element (like a menu, panel, or dropdown) expands, it increases its height toward the bottom, not the top.
User prompt
The UI element (or object) needs to scale or expand downward instead of upward. Make sure the growth animation or layout direction extends vertically down from the original position. This ensures it doesn't overlap or misalign with elements above it.
User prompt
The UI element (or object) needs to scale or expand downward instead of upward. Make sure the growth animation or layout direction extends vertically down from the original position. This ensures it doesn't overlap or misalign with elements above it.
User prompt
Enlarge Texts in HOW TO PLAY Section
User prompt
Acceleration – Speeds up your paddle Reverse Beat – Temporarily reverses the music tempo and ball rhythm Auto Beat – Locks your paddle to the ball for 7 seconds Rhythm Shield – Temporary shield that saves you once Add these Power-ups to the Game
User prompt
Enlarge Texts in English HOW TO PLAY Section
User prompt
English Instruction for Upit AI: Language Selection System Please implement a language selection system for the game. In the main menu, add a "Language" button. When the player clicks it, display two flag buttons: One with the British flag and the text "English" One with the Turkish flag and the text "Türkçe" When a player selects a language: All game UI texts (e.g., buttons, instructions, menus) should switch to the selected language. Default language is English. If Turkish is selected, all English texts should be translated to Turkish. If English is selected again, it should switch back to English. Make sure this toggle works dynamically during runtime without needing to reload the game. All static text should have localized versions, and the toggle should affect all game scenes and UI elements.
User prompt
Extend and Enlarge the Text in the HOW TO PLAY Section
User prompt
Enlarge Text in HOW TO PLAY Section
User prompt
Enlarge Text in HOW TO PLAY Section Until Screen Borders Are Reached
User prompt
Shrink the Text in the HOW TO PLAY Section Until It Reaches the Screen Borders
User prompt
Shrink Text in HOW TO PLAY Section
User prompt
Explain the Final Version of the Game in the HOW TO PLAY Section
User prompt
Reduce the Chance of a Long Note Spawning
User prompt
Please fix the bug: 'Uncaught ReferenceError: pauseBtn is not defined' in or related to this line: 'if (!pauseBtn) {' Line Number: 857
User prompt
🎯 Goal: Do it in Hard Mode paddle_ai should analyze where the player hits the ball (top, middle or bottom of the paddle). It will store this data and use it to better predict where it will move in the future.
User prompt
When a Long Note Comes, Sesar music should be played only once, and no other Sesar music should be played.
User prompt
When a Long Note is born, the sound of the sesar should only be heard once.
User prompt
Mute Backround_Music when Long Note Appears Return Backround_Music when Long Note Disappears
User prompt
If the Ball Hits a Long Note It Should Always Rebound The Ball Cannot Pass Through the Long Note
User prompt
Long Note can also rotate vertically and horizontally in an extra way attached to its other 5 notes.
/**** * 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; }); // LongNote class: 5 musical notes in a fixed, centered, circular formation, spinning as one, with vertical and horizontal bars attached var LongNote = Container.expand(function () { var self = Container.call(this); var noteColor = 0x00e6e6; var noteCount = 5; var radius = 180; // distance from center to each note var noteScale = 2.5; self.noteSprites = []; // Place 5 notes in a circle, evenly spaced for (var i = 0; i < noteCount; i++) { var angle = Math.PI * 2 / noteCount * i; var x = Math.cos(angle) * radius; var y = Math.sin(angle) * radius; var note = self.attachAsset('musical_note', { anchorX: 0.5, anchorY: 0.5, tint: noteColor, scaleX: noteScale, scaleY: noteScale, x: x, y: y }); self.noteSprites.push(note); } // Add vertical and horizontal bars (long rectangles) attached to the center // Vertical bar var barColor = 0x00e6e6; var barThickness = 38; var barLength = radius * 2 + 110 * noteScale * 0.7; // slightly shorter than full diameter self.verticalBar = self.attachAsset('net', { anchorX: 0.5, anchorY: 0.5, width: barThickness, height: barLength, tint: barColor, x: 0, y: 0 }); // Horizontal bar self.horizontalBar = self.attachAsset('net', { anchorX: 0.5, anchorY: 0.5, width: barLength, height: barThickness, tint: barColor, x: 0, y: 0 }); // Set bounding box for collision (encompass all notes and bars) self.width = barLength; self.height = barLength; self.rotationTween = null; self.isRotating = false; self.duration = 1800; // 30s at 60fps self.timer = 0; self.hit = false; // Center in the middle of the table self.x = 2048 / 2; self.y = 2732 / 2; // Play 'sesar' sound immediately when LongNote appears (only once, not on update) if (typeof LK.getSound === "function" && LK.getSound('sesar')) { LK.getSound('sesar').play(); self.sesarPlayed = true; } // Mute background music when LongNote appears if (typeof LK.stopMusic === "function") { LK.stopMusic(); } // Start continuous rotation for 30s self.startRotation = function () { if (self.isRotating) return; self.isRotating = true; // Use tween to rotate 360deg (2*PI) every 2s, repeat for 30s var _doSpin = function doSpin() { if (!self.isRotating) return; self.rotation = 0; self.rotationTween = tween(self, { rotation: Math.PI * 2 }, { duration: 2000, easing: tween.linear, onFinish: _doSpin }); }; _doSpin(); }; self.stopRotation = function () { self.isRotating = false; if (self.rotationTween) { tween.stop(self, { rotation: true }); self.rotationTween = null; } self.rotation = 0; }; self.update = function () { self.timer++; if (!self.isRotating) self.startRotation(); // Remove after 30s if (self.timer > self.duration) { self.stopRotation(); // Unmute/return background music when LongNote disappears if (typeof LK.playMusic === "function") { LK.playMusic('Backround_Music'); } if (self.parent) self.parent.removeChild(self); if (typeof longNoteInstance !== "undefined") longNoteInstance = null; } // If already hit, do not check collision again if (self.hit) return; // Defensive: check ball exists // Check collision with the whole LongNote (including bars) if (typeof ball !== "undefined" && ball && rectsIntersect(self, ball)) { self.hit = true; // Reverse ball direction ball.vx *= -1; ball.vy *= -1; // Do not play 'sesar' sound again on hit (only play on creation) // Continue rotating for the rest of 30s (do not remove) } }; 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', 'reverse_ball' 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', 'reverse_ball']; self.type = types[Math.floor(Math.random() * types.length)]; } // Attach musical note asset for all power-ups // Use updated musical_note color scheme for each power-up type var noteColor = 0x00e6e6; // default: cyan blue for musical notes if (self.type === 'big_paddle') { noteColor = 0x99ff99; // green (EASY) } if (self.type === 'multi_ball') { noteColor = 0xffcc33; // orange-yellow (new for multi_ball) } if (self.type === 'slow_ball') { noteColor = 0x6699ff; // blue (new for slow_ball) } if (self.type === 'speed_ball') { noteColor = 0xff3366; // pink-red (new for speed_ball) } if (self.type === 'blur_screen') { noteColor = 0xbebebe; // gray (blur_screen) } if (self.type === 'invert_screen') { noteColor = 0xbb66ff; // purple (invert_screen) } if (self.type === 'reverse_ball') { noteColor = 0xffff00; // yellow (reverse_ball) } // 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 ****/ // --- Language Selection System --- // Localization dictionary var LANGUAGES = { en: { title: "PING PONG RHYTHM SMASH", easy: "EASY", normal: "NORMAL", hard: "HARD", howToPlay: "HOW TO PLAY", powerUps: "Power-Ups", close: "CLOSE", "return": "RETURN", powerUpsTitle: "Power-Ups", // Power-up descriptions big_paddle: "BIG PADDLE\nBoth paddles grow much larger for 7 seconds. Making it easier to hit the ball.", multi_ball: "MULTI BALL\nA second ball appears for 7 seconds. Score with either ball to earn points!", slow_ball: "SLOW BALL\nThe ball moves at half speed for 7 seconds. Giving you more time to react.", speed_ball: "SPEED BALL\nThe ball moves much faster for 7 seconds. Can you keep up?", blur_screen: "BLUR SCREEN\nThe whole game blurs for 7 seconds. Try to play through the haze!", invert_screen: "INVERT SCREEN\nThe entire game flips upside down for 7 seconds. Can you play inverted?", reverse_ball: "REVERSE BALL\nThe ball instantly reverses direction! Surprise your opponent.", howToPlayText: "How to Play\n\n- Drag your paddle left/right to hit the ball.\n- The ball moves in sync with the music beat. Watch for the rhythm boosts!\n- Score points by getting the ball past the AI paddle at the top.\n- First to 7 points wins the match.\n- Collect musical note power-ups for wild effects: big paddles, multi-ball, speed changes, and more!\n- Sometimes, a spinning Long Note appears. It bounces the ball back and changes the music—hit it only once per appearance!\n- The AI learns from where you hit the ball on your paddle. Mix up your shots to stay unpredictable!\n- Choose your difficulty: higher levels make the AI faster and smarter.\n- Good luck, and keep the beat!", language: "Language", english: "English", turkish: "Türkçe" }, tr: { title: "RİTİM PİNG PONG", easy: "KOLAY", normal: "ORTA", hard: "ZOR", howToPlay: "NASIL OYNANIR", powerUps: "Güçlendiriciler", close: "KAPAT", "return": "GERİ DÖN", powerUpsTitle: "Güçlendiriciler", big_paddle: "BÜYÜK RAKET\nHer iki raket de 7 saniyeliğine büyür. Topa vurmak daha kolay!", multi_ball: "ÇOKLU TOP\n7 saniyeliğine ikinci bir top çıkar. Her iki topla da puan kazanabilirsin!", slow_ball: "YAVAŞ TOP\nTop 7 saniyeliğine yarı hızda hareket eder. Tepki vermek için daha fazla zaman!", speed_ball: "HIZLI TOP\nTop 7 saniyeliğine çok daha hızlı hareket eder. Yetişebilecek misin?", blur_screen: "BULANIK EKRAN\nOyun 7 saniyeliğine bulanıklaşır. Sis içinde oynamayı dene!", invert_screen: "TERS EKRAN\nOyun 7 saniyeliğine baş aşağı döner. Ters oynayabilir misin?", reverse_ball: "TERS TOP\nTop anında yön değiştirir! Rakibini şaşırt.", howToPlayText: "Nasıl Oynanır\n\n- Raketini sola/sağa sürükleyerek topa vur.\n- Top, müzik ritmiyle uyumlu hareket eder. Ritim güçlendirmelerine dikkat et!\n- Topu AI raketinin arkasına geçirerek puan kazan.\n- 7 puana ilk ulaşan maçı kazanır.\n- Müzikal nota güçlendiricilerini topla: büyük raket, çoklu top, hız değişimi ve daha fazlası!\n- Bazen dönen Uzun Nota çıkar. Topu geri sektirir ve müziği değiştirir—her çıkışında sadece bir kez vur!\n- AI, topa raketinin neresinden vurduğunu öğrenir. Vuruşlarını çeşitlendir!\n- Zorluk seç: daha yüksek seviyelerde AI daha hızlı ve akıllı olur.\n- Bol şans, ritmi yakala!", language: "Dil", english: "İngilizce", turkish: "Türkçe" } }; var currentLang = "en"; // Default language // Helper to get localized string function t(key) { if (LANGUAGES[currentLang] && LANGUAGES[currentLang][key]) return LANGUAGES[currentLang][key]; if (LANGUAGES["en"][key]) return LANGUAGES["en"][key]; return key; } // --- Main Menu and Difficulty Selection --- var menuContainer = new Container(); var menuBg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: 0x1a1a40 // New background color for main menu (deep blue/purple) }); menuContainer.addChild(menuBg); var titleText = new Text2(t('title'), { size: 120, fill: 0xffffff }); titleText.anchor.set(0.5, 0); titleText.x = 2048 / 2; titleText.y = 320; menuContainer.addChild(titleText); var easyBtn = new Text2(t('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(t('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(t('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(t('howToPlay'), { 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(t('powerUps'), { size: 90, fill: 0xffff00 }); powerUpsBtn.anchor.set(0.5, 0.5); powerUpsBtn.x = 2048 / 2; powerUpsBtn.y = 1700; menuContainer.addChild(powerUpsBtn); // --- Language Button --- // Language button (bottom center) var languageBtn = new Text2(t('language'), { size: 80, fill: 0xffffff }); languageBtn.anchor.set(0.5, 0.5); languageBtn.x = 2048 / 2; languageBtn.y = 2100; menuContainer.addChild(languageBtn); // Language selection overlay var languageOverlay = null; languageBtn.down = function (x, y, obj) { if (languageOverlay && languageOverlay.parent) { languageOverlay.parent.removeChild(languageOverlay); } languageOverlay = new Container(); // Hide menu text when overlay is open for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) { child.visible = false; } } // Semi-transparent background var bg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: 0x1a1a40 }); bg.alpha = 0.92; languageOverlay.addChild(bg); // British flag button var ukFlag = LK.getAsset('musical_note', { anchorX: 0.5, anchorY: 0.5, tint: 0x2345a0, // blue scaleX: 2.2, scaleY: 2.2, x: 2048 / 2 - 260, y: 1100 }); languageOverlay.addChild(ukFlag); var enText = new Text2(t('english'), { size: 90, fill: 0xffffff }); enText.anchor.set(0, 0.5); enText.x = 2048 / 2 - 180; enText.y = 1100; languageOverlay.addChild(enText); // Turkish flag button var trFlag = LK.getAsset('musical_note', { anchorX: 0.5, anchorY: 0.5, tint: 0xe30a17, // red scaleX: 2.2, scaleY: 2.2, x: 2048 / 2 - 260, y: 1300 }); languageOverlay.addChild(trFlag); var trText = new Text2(t('turkish'), { size: 90, fill: 0xffffff }); trText.anchor.set(0, 0.5); trText.x = 2048 / 2 - 180; trText.y = 1300; languageOverlay.addChild(trText); // Button handlers ukFlag.down = function () { setLanguage('en'); if (languageOverlay && languageOverlay.parent) languageOverlay.parent.removeChild(languageOverlay); // Restore menu for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) child.visible = true; } }; enText.down = ukFlag.down; trFlag.down = function () { setLanguage('tr'); if (languageOverlay && languageOverlay.parent) languageOverlay.parent.removeChild(languageOverlay); // Restore menu for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) child.visible = true; } }; trText.down = trFlag.down; // Close button var closeBtn = new Text2(t('close'), { size: 90, fill: 0xffff00 }); closeBtn.anchor.set(0.5, 0.5); closeBtn.x = 2048 / 2; closeBtn.y = 1700; closeBtn.down = function () { if (languageOverlay && languageOverlay.parent) languageOverlay.parent.removeChild(languageOverlay); for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) child.visible = true; } }; languageOverlay.addChild(closeBtn); game.addChild(languageOverlay); }; // --- Language switching logic --- // Update all static UI text to match currentLang function setLanguage(lang) { if (lang !== "en" && lang !== "tr") lang = "en"; currentLang = lang; // Main menu titleText.setText(t('title')); easyBtn.setText(t('easy')); normalBtn.setText(t('normal')); hardBtn.setText(t('hard')); howToPlayBtn.setText(t('howToPlay')); powerUpsBtn.setText(t('powerUps')); languageBtn.setText(t('language')); returnMenuBtn.setText(t('return')); // If overlays are open, update their text as well if (howToPlayOverlay && howToPlayOverlay.parent) { // Remove and re-open to update text howToPlayOverlay.parent.removeChild(howToPlayOverlay); howToPlayBtn.down(); } if (powerUpsOverlay && powerUpsOverlay.parent) { powerUpsOverlay.parent.removeChild(powerUpsOverlay); powerUpsBtn.down(); } } // --- END Language Selection System --- // 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(); // Hide menu text when overlay is open for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) { child.visible = false; } } // Semi-transparent background, match main menu color (deep blue/purple) var bg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: 0x1a1a40 // match main menu background color }); bg.alpha = 0.92; powerUpsOverlay.addChild(bg); // Title var title = new Text2(t('powerUpsTitle'), { size: 90, fill: 0xffff00 }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 220; powerUpsOverlay.addChild(title); // Show all power-ups as colored musical notes with description var powerupY = title.y + title.height + 60; var powerupSpacing = 220; // more vertical space between power-ups var powerupTypes = [{ type: "big_paddle", color: 0x99ff99 }, { type: "multi_ball", color: 0xffcc33 }, { type: "slow_ball", color: 0x6699ff }, { type: "speed_ball", color: 0xff3366 }, { type: "blur_screen", color: 0xbebebe }, { type: "invert_screen", color: 0xab66aa }, { type: "reverse_ball", color: 0xffff00 }]; // Move all musical notes and their texts to the left, stack notes, enlarge text, and add spacing var stackX = 180; // further left margin for notes var textX = stackX + 140; // text starts to the right of the stacked notes for (var i = 0; i < powerupTypes.length; i++) { var p = powerupTypes[i]; // Note icon (stacked vertically, left-aligned) var noteIcon = LK.getAsset('musical_note', { anchorX: 0.5, anchorY: 0.5, tint: p.color, scaleX: 1.7, scaleY: 1.7, x: stackX, y: powerupY + i * powerupSpacing }); powerUpsOverlay.addChild(noteIcon); // Description text (centered vertically with note, left-aligned to right of note) var descText = new Text2(t(p.type), { size: 48, fill: p.color }); descText.anchor.set(0, 0.5); // left align text, center vertically descText.x = textX; descText.y = powerupY + i * powerupSpacing; powerUpsOverlay.addChild(descText); } // 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 + 70; var lastDescText = new Text2(t(powerupTypes[powerupTypes.length - 1].type), { size: 70 }); var bottomLineY = lastDescY + lastDescText.height / 2; var screenHeight = LK.height ? LK.height : 2732; var scrollTo = bottomLineY - screenHeight + 120; // 120px margin from bottom if (scrollTo < 0) { scrollTo = 0; } powerUpsOverlay.y = -scrollTo; } // Close button var closeBtn = new Text2(t('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); } // Restore menu text visibility when overlay is closed for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) { child.visible = true; } } }; powerUpsOverlay.addChild(closeBtn); game.addChild(powerUpsOverlay); }; // Add a "Return to Main Menu" button (hidden by default, shown on stop) var returnMenuBtn = new Text2(t('return'), { size: 90, fill: 0xffff00 }); returnMenuBtn.anchor.set(0.5, 0.5); returnMenuBtn.x = 2048 / 2; returnMenuBtn.y = 2200; returnMenuBtn.visible = false; // Removed MAIN MENU text and button as requested // 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(); // Hide menu text when overlay is open for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) { child.visible = false; } } // Semi-transparent background, match main menu color (deep blue/purple) var bg = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: 0, y: 0, tint: 0x1a1a40 // match main menu background color }); bg.alpha = 0.92; howToPlayOverlay.addChild(bg); // Instructions text // Enlarge text until it reaches the screen borders (target width: 100% of 2048px) var howToPlayString = t('howToPlayText'); var maxWidth = 2048 * 1.0; // 100% of screen width var minSize = 24; var maxSize = 400; // allow even larger var testSize = minSize; var howText = null; while (testSize < maxSize) { var testText = new Text2(howToPlayString, { size: testSize, fill: 0xffffff }); testText.anchor.set(0.5, 0); testText.x = 2048 / 2; if (testText.width > maxWidth) { break; } howText = testText; testSize += 4; // increase faster for larger text } if (!howText) { // fallback howText = new Text2(howToPlayString, { size: minSize, fill: 0xffffff }); howText.anchor.set(0.5, 0); howText.x = 2048 / 2; } howText.x = 2048 / 2; // Set anchor to top-center so text expands downward from y=400 howText.anchor.set(0.5, 0); howText.y = 400; // This ensures the text grows downward from y=400, not upward // The anchorY = 0 ensures the text grows downward from y=400, not upward howToPlayOverlay.addChild(howText); // Close button var closeBtn = new Text2(t('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); } // Restore menu text visibility when overlay is closed for (var i = 0; i < menuContainer.children.length; i++) { var child = menuContainer.children[i]; if (child !== menuBg) { child.visible = true; } } }; 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 (global for menu/game access) --- var pauseBtn = null; // --- AI learning: memory of where player hits the ball on the paddle --- // We'll store counts for 'top', 'middle', 'bottom' hits var aiPlayerHitMemory = { top: 1, // Start with 1 to avoid divide-by-zero middle: 1, bottom: 1 }; var aiPlayerHitMemoryTotal = 3; // sum of all above // Helper to classify hit location: returns 'top', 'middle', or 'bottom' function classifyPlayerPaddleHit(ballY, paddleY, paddleHeight) { var rel = (ballY - (paddleY - paddleHeight / 2)) / paddleHeight; if (rel < 0.33) return 'top'; if (rel > 0.66) return 'bottom'; return 'middle'; } // --- Power-up variables (global scope) --- var powerUps = []; var powerUpActive = false; var powerUpTimer = 0; var powerUpEffectTimer = 0; var powerUpEffectType = null; // --- LongNote instance (only one at a time) --- var longNoteInstance = 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 and LongNote 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) // Allow up to 2 power-ups on the field at once if (typeof tableBg !== "undefined" && tableBg && typeof powerUps !== "undefined" && !powerUpActive && powerUps.length < 2) { if (typeof powerUpTimer === "undefined") { powerUpTimer = 0; } powerUpTimer++; // Try to spawn every 6-10 seconds if (powerUpTimer > 360 + Math.floor(Math.random() * 240)) { powerUpTimer = 0; // Reduce chance of LongNote spawning: only spawn if random() < 0.25 (25% chance) if ((typeof longNoteInstance === "undefined" || !longNoteInstance) && Math.random() < 0.25) { longNoteInstance = new LongNote(); longNoteInstance.x = 2048 / 2; longNoteInstance.y = 2732 / 2; game.addChild(longNoteInstance); } else { // If longNoteInstance exists, only spawn regular powerups var types = ['big_paddle', 'multi_ball', 'slow_ball', 'speed_ball', 'blur_screen', 'blur_screen', 'blur_screen', // Increase blur_screen weight '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; } // --- LongNote update and cleanup --- if (typeof longNoteInstance !== "undefined" && longNoteInstance && typeof longNoteInstance.update === "function") { longNoteInstance.update(); // Remove reference if destroyed if (!longNoteInstance || !longNoteInstance.parent) { longNoteInstance = null; } // Ball collision with LongNote: always rebound, never pass through if (typeof ball !== "undefined" && ball && rectsIntersect(longNoteInstance, ball)) { // Calculate the angle from the center of the LongNote to the ball var dx = ball.x - longNoteInstance.x; var dy = ball.y - longNoteInstance.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) dist = 1; // Prevent division by zero // Normalized direction var nx = dx / dist; var ny = dy / dist; // Project ball velocity onto normal and reflect var dot = ball.vx * nx + ball.vy * ny; // Only reflect if ball is moving toward the LongNote if (dot < 0) { // Reflect velocity ball.vx = ball.vx - 2 * dot * nx; ball.vy = ball.vy - 2 * dot * ny; // Move ball just outside the LongNote to prevent sticking var overlap = longNoteInstance.width / 2 + ball.width / 2 - dist; if (overlap > 0) { ball.x += nx * overlap; ball.y += ny * overlap; } // Optional: flash the ball for feedback LK.effects.flashObject(ball, 0x00e6e6, 120); // Do NOT play 'sesar' sound here; only play on LongNote creation } } // Extra ball collision with LongNote (if present) if (typeof extraBall !== "undefined" && extraBall && rectsIntersect(longNoteInstance, extraBall)) { var dx2 = extraBall.x - longNoteInstance.x; var dy2 = extraBall.y - longNoteInstance.y; var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (dist2 === 0) dist2 = 1; var nx2 = dx2 / dist2; var ny2 = dy2 / dist2; var dot2 = extraBall.vx * nx2 + extraBall.vy * ny2; if (dot2 < 0) { extraBall.vx = extraBall.vx - 2 * dot2 * nx2; extraBall.vy = extraBall.vy - 2 * dot2 * ny2; var overlap2 = longNoteInstance.width / 2 + extraBall.width / 2 - dist2; if (overlap2 > 0) { extraBall.x += nx2 * overlap2; extraBall.y += ny2 * overlap2; } LK.effects.flashObject(extraBall, 0x00e6e6, 120); // Do NOT play 'sesar' sound here; only play on LongNote creation } } } // --- Power-up update and collision --- var _loop = function _loop() { 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(); } // Remove any previous attribute texts if (typeof powerUpAttributeText !== "undefined" && powerUpAttributeText && powerUpAttributeText.parent) { powerUpAttributeText.parent.removeChild(powerUpAttributeText); } // Show only the duration of the musical note power-up (the one just hit) if (p.type === "big_paddle" || p.type === "multi_ball" || p.type === "slow_ball" || p.type === "speed_ball" || p.type === "blur_screen" || p.type === "invert_screen" || p.type === "reverse_ball") { var durationText = "7s"; var txt = new Text2(durationText, { size: 90, fill: 0xffff00 }); txt.anchor.set(0.5, 0.5); txt.x = 2048 / 2; txt.y = 2732 / 2; LK.gui.center.addChild(txt); powerUpAttributeText = { attrTexts: [txt] }; // store for removal LK.setTimeout(function () { if (powerUpAttributeText && powerUpAttributeText.attrTexts) { for (var i = 0; i < powerUpAttributeText.attrTexts.length; i++) { var t = powerUpAttributeText.attrTexts[i]; if (t && t.parent) { t.parent.removeChild(t); } } powerUpAttributeText = undefined; } }, 1200); } } // Only allow one power-up effect at a time if (!powerUpActive) { // Helper: random color generator var _getRandomColor = function _getRandomColor() { // Avoid too dark colors for visibility var min = 0x222222, max = 0xFFFFFF; var c = Math.floor(Math.random() * (max - min)) + min; // Ensure it's not too close to black if ((c & 0xFF) < 0x22 && (c >> 8 & 0xFF) < 0x22 && (c >> 16 & 0xFF) < 0x22) { c += 0x222222; } return c; }; // Store original tints to restore later var storeOriginalTint = function storeOriginalTint(obj, key) { if (!obj) { return; } if (typeof obj.tint !== "undefined" && typeof originalTints[key] === "undefined") { originalTints[key] = obj.tint; } }; // 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 color randomization effect for all game elements (no-op) // 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.92; // Increased blur strength (was 0.7) blurOverlay.tint = 0xbebebe; // #bebebe blurOverlay.tint = 0xbebebe; // #bebebe 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 (p.type === 'reverse_ball') { // Reverse the ball's direction instantly if (typeof ball !== "undefined" && ball) { ball.vx *= -1; ball.vy *= -1; // Optional: flash the ball for feedback LK.effects.flashObject(ball, 0x00e6e6, 300); } // Also reverse extraBall if present if (typeof extraBall !== "undefined" && extraBall) { extraBall.vx *= -1; extraBall.vy *= -1; LK.effects.flashObject(extraBall, 0x00e6e6, 300); } } } else { // If effect is already active, just remove the powerup from the field p.destroy(); powerUps.splice(i, 1); } } }, p, attrText, ci; for (var i = powerUps.length - 1; i >= 0; i--) { _loop(); } // Color randomization effect update removed // --- 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(); } // --- AI learning: record where player hit the ball on the paddle --- if (typeof aiPlayerHitMemory !== "undefined" && typeof classifyPlayerPaddleHit === "function") { var hitLoc = classifyPlayerPaddleHit(ball.y + ball.height / 2, playerPaddle.y, playerPaddle.height); if (aiPlayerHitMemory[hitLoc] !== undefined) { aiPlayerHitMemory[hitLoc]++; aiPlayerHitMemoryTotal++; } } // 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") { // --- AI learning: bias target X based on where player tends to hit the ball --- var bias = 0; if (typeof aiPlayerHitMemory !== "undefined" && aiPlayerHitMemoryTotal > 0) { // Calculate weighted bias: top = -1, middle = 0, bottom = +1 var topW = aiPlayerHitMemory.top / aiPlayerHitMemoryTotal; var midW = aiPlayerHitMemory.middle / aiPlayerHitMemoryTotal; var botW = aiPlayerHitMemory.bottom / aiPlayerHitMemoryTotal; // If player hits top more, AI should move more toward left (anticipate higher bounce) // If player hits bottom more, AI should move more toward right (anticipate lower bounce) // We'll bias by up to 180px left/right bias = (-topW + botW) * 180; } aiMoveTargetX = ball.x + (Math.random() - 0.5) * aiRandomOffset + bias; } 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();
===================================================================
--- original.js
+++ change.js