User prompt
"Do not display the high score on the main menu screen."
User prompt
"Place the pause and main menu buttons clearly visible in the top-right corner during gameplay."
Code edit (2 edits merged)
Please save this source code
User prompt
"Show the pause and main menu buttons only during gameplay after a game mode is selected. These buttons should NOT be visible on the main menu screen."
User prompt
"When returning to the main menu, hide the high score display completely. Also, fix the title text so that 'Letter Pop!' and 'Pop the right letter bubbles!' stay on the same line without breaking into two lines."
Code edit (1 edits merged)
Please save this source code
User prompt
"When the user returns to the main menu via the button, completely stop and reset the game. Ensure that no game elements like bubbles remain visible or active in the background after exiting the game."
User prompt
"Add a second button next to the existing game control button during gameplay. This new button should take the user back to the main menu."
User prompt
"Add a game control button above the real-time score display during gameplay."
Code edit (1 edits merged)
Please save this source code
User prompt
make the pause button flower shaped
User prompt
"After selecting a game mode and starting the game, add a visible button in the top-right corner that allows the user to pause the game or return to the main menu. Make sure this button works properly and is always accessible during gameplay."
User prompt
add pause button in endless mode
User prompt
add pause button in endless mode
User prompt
i cant see pause/menu button. Bigger and front above
User prompt
Please fix the bug: 'actuallyStartGame is not defined' in or related to this line: 'var _oldActuallyStartGame = actuallyStartGame;' Line Number: 921
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'visible')' in or related to this line: 'pauseBtn.visible = !!visible;' Line Number: 911
User prompt
"After selecting a game mode and starting the game, add a visible button that allows the user to pause the game or return to the main menu. Make sure this button works properly and is always accessible during gameplay."
User prompt
"After selecting a game mode and starting the game, add a visible button in the top-right corner that allows the user to pause the game or return to the main menu. Make sure this button works properly and is always accessible during gameplay."
User prompt
"Place the back button only on the game screen after a game mode has been selected. This button should appear in the top-right corner and return the user to the game mode selection screen — not the main menu."
User prompt
"Add a back button in the top-right corner of the game mode selection screen that allows the user to return to the main menu."
User prompt
"Redesign the game mode selection bubbles on the main screen to ensure the text fits properly inside each bubble. Adjust the font size, bubble size, or layout as needed to create a cleaner and more visually appealing design."
User prompt
"Make the pause and main menu button clearly visible in the top-right corner during gameplay. Fix its position, increase its size if needed, and bring it to the front so it’s never hidden behind any elements."
User prompt
i cant see home button
User prompt
i cant see home button on the right corner top
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // LetterBubble: A bubble with a letter inside, touchable var LetterBubble = Container.expand(function () { var self = Container.call(this); // Properties to be set after creation: // self.letter (string, e.g. "A") // self.isTarget (bool, is this the correct letter to pop?) // We'll assign a color randomly from the available bubble assets // Pick a random bubble color asset for the background var bubbleAssetIds = ['bubbleBlue', 'bubbleGreen', 'bubbleRed', 'bubbleYellow', 'bubblePurple', 'bubbleOrange']; var bubbleIdx = Math.floor(Math.random() * bubbleAssetIds.length); self.bubbleBg = LK.getAsset(bubbleAssetIds[bubbleIdx], { anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.bubbleBg); // Letter color should contrast with bubble color for readability self.letterText = new Text2('A', { size: 120, fill: 0xFFFFFF, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); self.letterText.anchor.set(0.5, 0.5); // Add a subtle drop shadow for pop self.letterText.setStyle({ dropShadow: true, dropShadowColor: "#222", dropShadowBlur: 8, dropShadowDistance: 4 }); self.addChild(self.letterText); // Animate in (pop effect) self.scale.set(0.1, 0.1); tween(self.scale, { x: 1, y: 1 }, { duration: 300, easing: tween.elasticOut }); // Touch event self.down = function (x, y, obj) { // Only allow popping if not already popped if (self.popped) { return; } self.popped = true; onBubbleTapped(self); }; // Pop animation self.pop = function (_onFinish) { // Play pop sound LK.getSound('pop').play(); // Animate: scale up, fade out, then destroy tween(self.scale, { x: 1.3, y: 1.3 }, { duration: 120, easing: tween.easeOut }); tween(self, { alpha: 0 }, { duration: 180, delay: 100, onFinish: function onFinish() { if (_onFinish) { _onFinish(); } self.destroy(); } }); }; // Gentle shake for incorrect self.shake = function () { // Animate left-right shake var origX = self.x; tween(self, { x: origX - 20 }, { duration: 60, easing: tween.easeIn, onFinish: function onFinish() { tween(self, { x: origX + 20 }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { x: origX }, { duration: 60 }); } }); } }); }; return self; }); // ZigzagLetterBubble: A harder bubble with zigzag movement and faded color var ZigzagLetterBubble = Container.expand(function () { var self = Container.call(this); // Properties to be set after creation: // self.letter (string) // self.isTarget (bool) // Pick a faded color bubble (use alpha) var bubbleAssetIds = ['bubbleBlue', 'bubbleGreen', 'bubbleRed', 'bubbleYellow', 'bubblePurple', 'bubbleOrange']; var bubbleIdx = Math.floor(Math.random() * bubbleAssetIds.length); self.bubbleBg = LK.getAsset(bubbleAssetIds[bubbleIdx], { anchorX: 0.5, anchorY: 0.5 }); self.bubbleBg.alpha = 0.45 + Math.random() * 0.25; // faded look self.addChild(self.bubbleBg); // Letter text, more faded self.letterText = new Text2('A', { size: 120, fill: 0xFFFFFF, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); self.letterText.anchor.set(0.5, 0.5); self.letterText.alpha = 0.7; self.letterText.setStyle({ dropShadow: true, dropShadowColor: "#222", dropShadowBlur: 8, dropShadowDistance: 4 }); self.addChild(self.letterText); // Animate in (pop effect) self.scale.set(0.1, 0.1); tween(self.scale, { x: 1, y: 1 }, { duration: 300, easing: tween.elasticOut }); // Zigzag movement parameters self._zigzagPhase = Math.random() * Math.PI * 2; self._zigzagSpeed = 0.015 + Math.random() * 0.01; // radians per tick self._zigzagAmp = 30 + Math.random() * 30; // amplitude in px self._zigzagBaseX = 0; // will be set on placement // Touch event self.down = function (x, y, obj) { if (self.popped) { return; } self.popped = true; onBubbleTapped(self); }; // Pop animation self.pop = function (_onFinish) { LK.getSound('pop').play(); tween(self.scale, { x: 1.3, y: 1.3 }, { duration: 120, easing: tween.easeOut }); tween(self, { alpha: 0 }, { duration: 180, delay: 100, onFinish: function onFinish() { if (_onFinish) { _onFinish(); } self.destroy(); } }); }; // Gentle shake for incorrect self.shake = function () { var origX = self.x; tween(self, { x: origX - 20 }, { duration: 60, easing: tween.easeIn, onFinish: function onFinish() { tween(self, { x: origX + 20 }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { x: origX }, { duration: 60 }); } }); } }); }; // Zigzag update self.update = function () { if (typeof self._zigzagBaseX === "number") { self._zigzagPhase += self._zigzagSpeed; self.x = self._zigzagBaseX + Math.sin(self._zigzagPhase) * self._zigzagAmp; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xE3F6FD // Light blue background for kid-friendly look }); /**** * Game Code ****/ // Make sure no elements are in top-left 100x100 (all UI is top/center/right);; // --- Pause/Menu Button (top-right corner) --- // Make the pause/menu button extra large and always on top for visibility var pauseBtnSize = 240; // Even larger for best visibility and touch var pauseBtn = LK.getAsset('bubblePurple', { anchorX: 1, anchorY: 0, x: 2048 - 20, // 20px from right edge y: 20, // 20px from top edge scaleX: pauseBtnSize / 300, scaleY: pauseBtnSize / 300 }); var pauseIcon = new Text2("⏸", { size: 150, // Larger icon for clarity fill: "#fff", font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); pauseIcon.anchor.set(0.5, 0.5); pauseIcon.x = pauseBtn.width / 2; pauseIcon.y = pauseBtn.height / 2; pauseBtn.addChild(pauseIcon); // Remove and re-add to always bring to front if (LK.gui.topRight.children.indexOf(pauseBtn) !== -1) { LK.gui.topRight.removeChild(pauseBtn); } LK.gui.topRight.addChild(pauseBtn); // Pause/menu handler pauseBtn.down = function () { LK.pauseGame(); // Show a simple overlay to confirm returning to menu var overlay = new Container(); overlay.interactive = true; overlay.hitArea = new Rectangle(0, 0, 2048, 2732); // Dim background var dim = LK.getAsset('bubbleBlue', { anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 2048 / 300, scaleY: 2732 / 300 }); dim.alpha = 0.45; overlay.addChild(dim); // Centered panel var panel = LK.getAsset('bubbleYellow', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 2.2, scaleY: 1.3 }); overlay.addChild(panel); // Title var pausedText = new Text2("Paused", { size: 120, fill: 0x4A90E2, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); pausedText.anchor.set(0.5, 0.5); pausedText.x = 2048 / 2; pausedText.y = 2732 / 2 - 180; overlay.addChild(pausedText); // Resume button var resumeBtn = LK.getAsset('bubbleGreen', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 40, scaleX: 1.1, scaleY: 1.1 }); overlay.addChild(resumeBtn); var resumeText = new Text2("Resume", { size: 90, fill: "#fff", font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); resumeText.anchor.set(0.5, 0.5); resumeText.x = resumeBtn.x; resumeText.y = resumeBtn.y; overlay.addChild(resumeText); // Main menu button var menuBtn = LK.getAsset('bubbleRed', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 220, scaleX: 1.1, scaleY: 1.1 }); overlay.addChild(menuBtn); var menuText = new Text2("Main Menu", { size: 90, fill: "#fff", font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); menuText.anchor.set(0.5, 0.5); menuText.x = menuBtn.x; menuText.y = menuBtn.y; overlay.addChild(menuText); // Resume handler resumeBtn.down = function () { LK.gui.top.removeChild(overlay); LK.resumeGame(); }; resumeText.down = resumeBtn.down; // Main menu handler menuBtn.down = function () { LK.gui.top.removeChild(overlay); LK.resumeGame(); // Reset to home screen startGame.hasRun = undefined; // Remove all game objects for (var i = 0; i < bubbles.length; i++) { bubbles[i].destroy(); } bubbles = []; if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } timerActive = false; timerText.setText(''); feedbackText.alpha = 0; promptText.setText(''); scoreText.setText('0'); highScoreText.setText('High Score: ' + highScore); // Show home screen startGame(); }; menuText.down = menuBtn.down; LK.gui.top.addChild(overlay); }; // Sounds for correct/incorrect feedback // We'll use 26 different colors for variety, but for MVP, 5-6 colors are enough and can be reused. // Letter bubbles: We'll use colored ellipses for bubbles, and overlay Text2 for letters. // Alphabet array var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; // Game state var selectedGameMode = "endless"; // "endless", "timed", or "hard" var currentLetterIdx = 0; // Index in alphabet for the current target letter var bubbles = []; // Array of LetterBubble instances var lettersPerRound = 4; // Start with 4, will increase as player gets correct answers var correctInARow = 0; // Track correct answers in a row for difficulty var roundActive = false; // Is a round currently active? var score = 0; // Number of correct letters popped // Endless mode: after all letters, reshuffle and continue var endlessMode = true; // Timer variables var timerActive = false; var timerValue = 0; // seconds left var timerInterval = null; var TIMER_START = 310; // seconds to start with when timer mode begins var TIMER_ADD = 3; // seconds to add per correct answer // High score per mode var highScores = { endless: typeof storage.letterpop_highscore_endless !== "undefined" ? storage.letterpop_highscore_endless : 0, timed: typeof storage.letterpop_highscore_timed !== "undefined" ? storage.letterpop_highscore_timed : 0, hard: typeof storage.letterpop_highscore_hard !== "undefined" ? storage.letterpop_highscore_hard : 0 }; var highScore = 0; // Will be set per mode // UI elements var promptText = new Text2('', { size: 110, fill: 0x333333, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); promptText.anchor.set(0.5, 0); promptText.y = 90; // Move prompt lower from the very top LK.gui.top.addChild(promptText); var scoreText = new Text2('0', { size: 110, fill: 0x4A90E2, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); scoreText.anchor.set(0.5, 1); scoreText.y = -120; // Move it higher above the very bottom LK.gui.bottom.addChild(scoreText); // High score text (top center, under prompt) var highScoreText = new Text2('High Score: ' + highScore, { size: 60, fill: 0x888888, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); highScoreText.anchor.set(0.5, 0); highScoreText.y = 240; // Add more spacing below the promptText LK.gui.top.addChild(highScoreText); // Timer text (placed just above the score) var timerText = new Text2('', { size: 80, fill: 0xD0021B, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); timerText.anchor.set(0.5, 1); // We'll position timerText just above the score timerText.y = scoreText.y - 100; LK.gui.bottom.addChild(timerText); // Timer change effect (shows +2s/-1s) var timerChangeText = new Text2('', { size: 70, fill: 0x43A047, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); timerChangeText.anchor.set(0.5, 1); timerChangeText.alpha = 0; timerChangeText.y = timerText.y - 60; LK.gui.bottom.addChild(timerChangeText); // Helper to show timer change effect function showTimerChangeEffect(text, color) { timerChangeText.setText(text); timerChangeText.setStyle({ fill: color }); timerChangeText.alpha = 0; timerChangeText.y = timerText.y - 80; tween(timerChangeText, { alpha: 1, y: timerChangeText.y - 40 }, { duration: 200, onFinish: function onFinish() { LK.setTimeout(function () { tween(timerChangeText, { alpha: 0 }, { duration: 300 }); }, 400); } }); } // Feedback text (centered, fades in/out) var feedbackText = new Text2('', { size: 130, fill: 0x43A047, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); feedbackText.anchor.set(0.5, 0.5); feedbackText.alpha = 0; LK.gui.center.addChild(feedbackText); // Helper: Shuffle array (Fisher-Yates) function shuffleArray(arr) { var a = arr.slice(); for (var i = a.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = a[i]; a[i] = a[j]; a[j] = t; } return a; } // Helper: Start a new round function startRound() { // Remove old bubbles for (var i = 0; i < bubbles.length; i++) { bubbles[i].destroy(); } bubbles = []; if (currentLetterIdx >= alphabet.length) { if (endlessMode) { // Reshuffle for endless play, keep score and difficulty currentLetterIdx = 0; // Shuffle alphabet for next endless cycle alphabet = shuffleArray(alphabet); } else { // All letters done! showCelebration(); return; } } roundActive = true; // Set prompt var targetLetter = alphabet[currentLetterIdx]; promptText.setText("Find: " + targetLetter); // Pick distractor letters (not the target) var distractors = []; var pool = []; for (var i = 0; i < alphabet.length; i++) { if (i !== currentLetterIdx) { pool.push(alphabet[i]); } } pool = shuffleArray(pool); for (var i = 0; i < lettersPerRound - 1; i++) { distractors.push(pool[i]); } // Combine and shuffle var roundLetters = distractors.concat([targetLetter]); roundLetters = shuffleArray(roundLetters); // Dynamically arrange bubbles in a centered grid, scaling and spacing to fit up to 20 bubbles var numBubbles = roundLetters.length; var maxBubbles = 20; var minBubbleSize = 110; var maxBubbleSize = 300; var minMargin = 24; var maxMargin = 60; // Calculate grid: try to make it as square as possible var cols = Math.ceil(Math.sqrt(numBubbles)); var rows = Math.ceil(numBubbles / cols); // Compute available width/height (leave some padding) var padX = 80, padY = 200; var availW = 2048 - padX * 2; var availH = 1800 - padY * 2; // keep bubbles in upper 2/3 of screen // Compute max bubble size that fits var bubbleSizeW = Math.floor((availW - (cols - 1) * minMargin) / cols); var bubbleSizeH = Math.floor((availH - (rows - 1) * minMargin) / rows); var bubbleSize = Math.max(minBubbleSize, Math.min(maxBubbleSize, Math.min(bubbleSizeW, bubbleSizeH))); // Compute margin to center bubbles var marginX = Math.max(minMargin, Math.min(maxMargin, Math.floor((availW - cols * bubbleSize) / Math.max(1, cols - 1)))); var marginY = Math.max(minMargin, Math.min(maxMargin, Math.floor((availH - rows * bubbleSize) / Math.max(1, rows - 1)))); // Compute grid start var totalGridW = cols * bubbleSize + (cols - 1) * marginX; var totalGridH = rows * bubbleSize + (rows - 1) * marginY; // Vertically center grid in the middle of the screen, but never closer than 200px to the top // 2732 is the screen height. Center the grid, but keep at least 200px from the top and 200px from the bottom. var minTop = 200; var minBottom = 200; var availableHeight = 2732 - minTop - minBottom; var gridTop = minTop + Math.max(0, (availableHeight - totalGridH) / 2); var startX = (2048 - totalGridW) / 2 + bubbleSize / 2; var startY = gridTop + bubbleSize / 2; // Place bubbles in grid var positions = []; for (var i = 0; i < numBubbles; i++) { var row = Math.floor(i / cols); var col = i % cols; positions.push({ x: startX + col * (bubbleSize + marginX), y: startY + row * (bubbleSize + marginY) }); } // Timer text is now statically positioned above the score in LK.gui.bottom // Create and add bubbles, scale them to fit for (var i = 0; i < roundLetters.length; i++) { var useZigzag = false; // Hard mode: all bubbles are zigzag/faded if (typeof selectedGameMode !== "undefined" && selectedGameMode === "hard") { useZigzag = true; } else { // Timed/Endless: introduce zigzag as difficulty increases if (roundLetters.length >= 7) { var zigzagCount = Math.floor((roundLetters.length - 6) * 0.7); if (i < zigzagCount) { useZigzag = true; } } } var bubble; if (useZigzag) { bubble = new ZigzagLetterBubble(); } else { bubble = new LetterBubble(); } bubble.letter = roundLetters[i]; bubble.letterText.setText(bubble.letter); bubble.isTarget = bubble.letter === targetLetter; // Position bubble.x = positions[i].x; bubble.y = positions[i].y; // For zigzag, set baseX for zigzag movement if (useZigzag) { bubble._zigzagBaseX = bubble.x; } // Scale bubble to fit var scale = bubbleSize / 300; // 300 is the asset's base size bubble.scale.set(scale, scale); // Also scale letter text for readability bubble.letterText.setStyle({ size: Math.floor(120 * scale) }); // Add to game game.addChild(bubble); bubbles.push(bubble); } } // Handle bubble tap function onBubbleTapped(bubble) { if (!roundActive) { return; } if (bubble.isTarget) { // Correct! roundActive = false; score++; scoreText.setText(score); // Update high score if needed (per mode) if (score > highScore) { highScore = score; highScores[selectedGameMode] = score; highScoreText.setText('High Score: ' + highScore); if (selectedGameMode === "endless") { storage.letterpop_highscore_endless = score; } else if (selectedGameMode === "timed") { storage.letterpop_highscore_timed = score; } else if (selectedGameMode === "hard") { storage.letterpop_highscore_hard = score; } } // Start timer mode after 10 points (only in Timed Mode), or always active in Hard Mode if (selectedGameMode === "timed" && !timerActive && score >= 10 || selectedGameMode === "hard" && !timerActive) { timerActive = true; timerValue = TIMER_START; timerText.setText('Time: ' + timerValue); if (timerInterval) { LK.clearInterval(timerInterval); } timerInterval = LK.setInterval(function () { if (!timerActive) { return; } timerValue--; timerText.setText('Time: ' + timerValue); if (timerValue <= 0) { timerValue = 0; timerText.setText('Time: 0'); endGameTimeout(); } }, 1000); } // Add time for correct answer if timer is active if (timerActive) { timerValue += 2; timerText.setText('Time: ' + timerValue); showTimerChangeEffect("+2s", "#43A047"); } // Track correct answers in a row for difficulty increase if (typeof correctInARow === "undefined") { correctInARow = 0; } correctInARow++; // After every two correct answers, increase number of options (up to 20) if (correctInARow % 2 === 0) { if (typeof lettersPerRound === "undefined") { lettersPerRound = 4; } lettersPerRound = Math.min(lettersPerRound + 1, 20); } // Feedback showFeedback("Great!", "#43A047"); LK.getSound('ding').play(); // Pop animation, then next round bubble.pop(function () { // Remove other bubbles with fade out for (var i = 0; i < bubbles.length; i++) { if (bubbles[i] !== bubble) { tween(bubbles[i], { alpha: 0 }, { duration: 200, onFinish: function (bub) { return function () { bub.destroy(); }; }(bubbles[i]) }); } } // Next letter after short delay LK.setTimeout(function () { currentLetterIdx++; startRound(); }, 600); }); } else { // Reset streak on incorrect answer correctInARow = 0; // Incorrect showFeedback("Try again!", "#D0021B"); LK.getSound('oops').play(); bubble.shake(); // Subtract 1s for wrong answer if timer is active (including Hard Mode) if (timerActive) { timerValue = Math.max(0, timerValue - 1); timerText.setText('Time: ' + timerValue); showTimerChangeEffect("-1s", "#D0021B"); if (timerValue <= 0) { timerValue = 0; timerText.setText('Time: 0'); endGameTimeout(); } } // Allow another try (do not end round) } } // Show feedback text in center, fade in/out function showFeedback(msg, color) { feedbackText.setText(msg); // Use setStyle to update fill color safely feedbackText.setStyle({ fill: color }); feedbackText.alpha = 0; tween(feedbackText, { alpha: 1 }, { duration: 120, onFinish: function onFinish() { LK.setTimeout(function () { tween(feedbackText, { alpha: 0 }, { duration: 200 }); }, 500); } }); } // End game due to timer running out function endGameTimeout() { timerActive = false; if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } // Remove any remaining bubbles for (var i = 0; i < bubbles.length; i++) { bubbles[i].destroy(); } bubbles = []; promptText.setText("Time's up!"); feedbackText.setText("Score: " + score); feedbackText.setStyle({ fill: 0xD0021B }); feedbackText.alpha = 0; tween(feedbackText, { alpha: 1 }, { duration: 300 }); // Play oops sound LK.getSound('oops').play(); // Show game over after a short delay (triggers LK's game over popup) LK.setTimeout(function () { LK.showGameOver(); }, 1200); } // Show celebration screen function showCelebration() { // Remove any remaining bubbles for (var i = 0; i < bubbles.length; i++) { bubbles[i].destroy(); } bubbles = []; promptText.setText("All done!"); feedbackText.setText("You did it!"); // Use setStyle to update fill color safely feedbackText.setStyle({ fill: 0xF5A623 }); feedbackText.alpha = 0; tween(feedbackText, { alpha: 1 }, { duration: 300 }); // Play cheer sound LK.getSound('cheer').play(); // Show "You Win" after a short delay (triggers LK's win popup) LK.setTimeout(function () { LK.showYouWin(); }, 1200); } // Start game function startGame() { // Show a visually appealing home screen before the first round if (typeof startGame.hasRun === "undefined") { var isFarFromOthers = function isFarFromOthers(x, y, minDist) { for (var i = 0; i < placedPositions.length; i++) { var dx = x - placedPositions[i].x; var dy = y - placedPositions[i].y; if (Math.sqrt(dx * dx + dy * dy) < minDist) { return false; } } return true; }; // Only show on first load, not after game over startGame.hasRun = true; // Create a custom home screen container var homeScreen = new Container(); // Big colorful title var title = new Text2("Letter Pop!", { size: 220, fill: ["#4A90E2", "#F5A623", "#7ED321", "#D0021B"], font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); title.anchor.set(0.5, 0.5); title.x = 2048 / 2; title.y = 700; homeScreen.addChild(title); // Fun subtitle var subtitle = new Text2("Pop the right letter bubbles!", { size: 90, fill: 0x9013FE, font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); subtitle.anchor.set(0.5, 0.5); subtitle.x = 2048 / 2; subtitle.y = 900; homeScreen.addChild(subtitle); // (Sample letters removed for a cleaner home screen) // Game mode buttons var modeBtnY = 1450; var modeBtnSpacing = 220; var modeBtnScale = 1.15; // Endless Mode Button var endlessBtn = LK.getAsset('bubbleBlue', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: modeBtnY, scaleX: modeBtnScale, scaleY: modeBtnScale }); homeScreen.addChild(endlessBtn); var endlessText = new Text2("Endless Mode", { size: 90, fill: "#fff", font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); endlessText.anchor.set(0.5, 0.5); endlessText.x = endlessBtn.x; endlessText.y = endlessBtn.y; homeScreen.addChild(endlessText); // Timed Mode Button var timedBtn = LK.getAsset('bubbleOrange', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: modeBtnY + modeBtnSpacing, scaleX: modeBtnScale, scaleY: modeBtnScale }); homeScreen.addChild(timedBtn); var timedText = new Text2("Timed Mode", { size: 90, fill: "#fff", font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); timedText.anchor.set(0.5, 0.5); timedText.x = timedBtn.x; timedText.y = timedBtn.y; homeScreen.addChild(timedText); // Hard Mode Button var hardBtn = LK.getAsset('bubbleRed', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: modeBtnY + 2 * modeBtnSpacing, scaleX: modeBtnScale, scaleY: modeBtnScale }); homeScreen.addChild(hardBtn); var hardText = new Text2("Hard Mode", { size: 90, fill: "#fff", font: "GillSans-Bold,Impact,'Arial Black',Tahoma" }); hardText.anchor.set(0.5, 0.5); hardText.x = hardBtn.x; hardText.y = hardBtn.y; homeScreen.addChild(hardText); // Add homeScreen to game game.addChild(homeScreen); // Button interactions endlessBtn.down = function () { game.removeChild(homeScreen); selectedGameMode = "endless"; actuallyStartGame(); }; endlessText.down = endlessBtn.down; timedBtn.down = function () { game.removeChild(homeScreen); selectedGameMode = "timed"; actuallyStartGame(); }; timedText.down = timedBtn.down; hardBtn.down = function () { game.removeChild(homeScreen); selectedGameMode = "hard"; actuallyStartGame(); }; hardText.down = hardBtn.down; // Don't start the game yet! return; } actuallyStartGame(); function actuallyStartGame() { currentLetterIdx = 0; score = 0; scoreText.setText(score); feedbackText.alpha = 0; correctInARow = 0; lettersPerRound = 4; // Set game mode defaults if (typeof selectedGameMode === "undefined") { selectedGameMode = "endless"; } // Endless Mode: no timer, endless play, normal bubbles // Timed Mode: timer, increasing difficulty, normal/zigzag bubbles // Hard Mode: no timer, all bubbles zigzag/faded, increasing count if (selectedGameMode === "endless") { endlessMode = true; timerActive = false; timerValue = 0; timerText.setText(''); if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } } else if (selectedGameMode === "timed") { endlessMode = false; timerActive = true; timerValue = TIMER_START; timerText.setText('Time: ' + timerValue); if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } timerInterval = LK.setInterval(function () { if (!timerActive) return; timerValue--; timerText.setText('Time: ' + timerValue); if (timerValue <= 0) { timerValue = 0; timerText.setText('Time: 0'); endGameTimeout(); } }, 1000); } else if (selectedGameMode === "hard") { endlessMode = false; timerActive = true; timerValue = TIMER_START; timerText.setText('Time: ' + timerValue); if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } timerInterval = LK.setInterval(function () { if (!timerActive) return; timerValue--; timerText.setText('Time: ' + timerValue); if (timerValue <= 0) { timerValue = 0; timerText.setText('Time: 0'); endGameTimeout(); } }, 1000); } // Reload high score from storage for the selected mode if (selectedGameMode === "endless") { highScore = typeof storage.letterpop_highscore_endless !== "undefined" ? storage.letterpop_highscore_endless : 0; } else if (selectedGameMode === "timed") { highScore = typeof storage.letterpop_highscore_timed !== "undefined" ? storage.letterpop_highscore_timed : 0; } else if (selectedGameMode === "hard") { highScore = typeof storage.letterpop_highscore_hard !== "undefined" ? storage.letterpop_highscore_hard : 0; } else { highScore = 0; } highScoreText.setText('High Score: ' + highScore); startRound(); } } // Start on load startGame(); // Animate zigzag bubbles (if any) each frame game.update = function () { for (var i = 0; i < bubbles.length; i++) { if (typeof bubbles[i].update === "function") { bubbles[i].update(); } } }; // Touchscreen: No drag/move needed, only tap (down) on bubbles // Make sure no elements are in top-left 100x100 (all UI is top/center/right);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// LetterBubble: A bubble with a letter inside, touchable
var LetterBubble = Container.expand(function () {
var self = Container.call(this);
// Properties to be set after creation:
// self.letter (string, e.g. "A")
// self.isTarget (bool, is this the correct letter to pop?)
// We'll assign a color randomly from the available bubble assets
// Pick a random bubble color asset for the background
var bubbleAssetIds = ['bubbleBlue', 'bubbleGreen', 'bubbleRed', 'bubbleYellow', 'bubblePurple', 'bubbleOrange'];
var bubbleIdx = Math.floor(Math.random() * bubbleAssetIds.length);
self.bubbleBg = LK.getAsset(bubbleAssetIds[bubbleIdx], {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.bubbleBg);
// Letter color should contrast with bubble color for readability
self.letterText = new Text2('A', {
size: 120,
fill: 0xFFFFFF,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
self.letterText.anchor.set(0.5, 0.5);
// Add a subtle drop shadow for pop
self.letterText.setStyle({
dropShadow: true,
dropShadowColor: "#222",
dropShadowBlur: 8,
dropShadowDistance: 4
});
self.addChild(self.letterText);
// Animate in (pop effect)
self.scale.set(0.1, 0.1);
tween(self.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.elasticOut
});
// Touch event
self.down = function (x, y, obj) {
// Only allow popping if not already popped
if (self.popped) {
return;
}
self.popped = true;
onBubbleTapped(self);
};
// Pop animation
self.pop = function (_onFinish) {
// Play pop sound
LK.getSound('pop').play();
// Animate: scale up, fade out, then destroy
tween(self.scale, {
x: 1.3,
y: 1.3
}, {
duration: 120,
easing: tween.easeOut
});
tween(self, {
alpha: 0
}, {
duration: 180,
delay: 100,
onFinish: function onFinish() {
if (_onFinish) {
_onFinish();
}
self.destroy();
}
});
};
// Gentle shake for incorrect
self.shake = function () {
// Animate left-right shake
var origX = self.x;
tween(self, {
x: origX - 20
}, {
duration: 60,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
x: origX + 20
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
x: origX
}, {
duration: 60
});
}
});
}
});
};
return self;
});
// ZigzagLetterBubble: A harder bubble with zigzag movement and faded color
var ZigzagLetterBubble = Container.expand(function () {
var self = Container.call(this);
// Properties to be set after creation:
// self.letter (string)
// self.isTarget (bool)
// Pick a faded color bubble (use alpha)
var bubbleAssetIds = ['bubbleBlue', 'bubbleGreen', 'bubbleRed', 'bubbleYellow', 'bubblePurple', 'bubbleOrange'];
var bubbleIdx = Math.floor(Math.random() * bubbleAssetIds.length);
self.bubbleBg = LK.getAsset(bubbleAssetIds[bubbleIdx], {
anchorX: 0.5,
anchorY: 0.5
});
self.bubbleBg.alpha = 0.45 + Math.random() * 0.25; // faded look
self.addChild(self.bubbleBg);
// Letter text, more faded
self.letterText = new Text2('A', {
size: 120,
fill: 0xFFFFFF,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
self.letterText.anchor.set(0.5, 0.5);
self.letterText.alpha = 0.7;
self.letterText.setStyle({
dropShadow: true,
dropShadowColor: "#222",
dropShadowBlur: 8,
dropShadowDistance: 4
});
self.addChild(self.letterText);
// Animate in (pop effect)
self.scale.set(0.1, 0.1);
tween(self.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.elasticOut
});
// Zigzag movement parameters
self._zigzagPhase = Math.random() * Math.PI * 2;
self._zigzagSpeed = 0.015 + Math.random() * 0.01; // radians per tick
self._zigzagAmp = 30 + Math.random() * 30; // amplitude in px
self._zigzagBaseX = 0; // will be set on placement
// Touch event
self.down = function (x, y, obj) {
if (self.popped) {
return;
}
self.popped = true;
onBubbleTapped(self);
};
// Pop animation
self.pop = function (_onFinish) {
LK.getSound('pop').play();
tween(self.scale, {
x: 1.3,
y: 1.3
}, {
duration: 120,
easing: tween.easeOut
});
tween(self, {
alpha: 0
}, {
duration: 180,
delay: 100,
onFinish: function onFinish() {
if (_onFinish) {
_onFinish();
}
self.destroy();
}
});
};
// Gentle shake for incorrect
self.shake = function () {
var origX = self.x;
tween(self, {
x: origX - 20
}, {
duration: 60,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
x: origX + 20
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
x: origX
}, {
duration: 60
});
}
});
}
});
};
// Zigzag update
self.update = function () {
if (typeof self._zigzagBaseX === "number") {
self._zigzagPhase += self._zigzagSpeed;
self.x = self._zigzagBaseX + Math.sin(self._zigzagPhase) * self._zigzagAmp;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xE3F6FD // Light blue background for kid-friendly look
});
/****
* Game Code
****/
// Make sure no elements are in top-left 100x100 (all UI is top/center/right);;
// --- Pause/Menu Button (top-right corner) ---
// Make the pause/menu button extra large and always on top for visibility
var pauseBtnSize = 240; // Even larger for best visibility and touch
var pauseBtn = LK.getAsset('bubblePurple', {
anchorX: 1,
anchorY: 0,
x: 2048 - 20,
// 20px from right edge
y: 20,
// 20px from top edge
scaleX: pauseBtnSize / 300,
scaleY: pauseBtnSize / 300
});
var pauseIcon = new Text2("⏸", {
size: 150,
// Larger icon for clarity
fill: "#fff",
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
pauseIcon.anchor.set(0.5, 0.5);
pauseIcon.x = pauseBtn.width / 2;
pauseIcon.y = pauseBtn.height / 2;
pauseBtn.addChild(pauseIcon);
// Remove and re-add to always bring to front
if (LK.gui.topRight.children.indexOf(pauseBtn) !== -1) {
LK.gui.topRight.removeChild(pauseBtn);
}
LK.gui.topRight.addChild(pauseBtn);
// Pause/menu handler
pauseBtn.down = function () {
LK.pauseGame();
// Show a simple overlay to confirm returning to menu
var overlay = new Container();
overlay.interactive = true;
overlay.hitArea = new Rectangle(0, 0, 2048, 2732);
// Dim background
var dim = LK.getAsset('bubbleBlue', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 2048 / 300,
scaleY: 2732 / 300
});
dim.alpha = 0.45;
overlay.addChild(dim);
// Centered panel
var panel = LK.getAsset('bubbleYellow', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2.2,
scaleY: 1.3
});
overlay.addChild(panel);
// Title
var pausedText = new Text2("Paused", {
size: 120,
fill: 0x4A90E2,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
pausedText.anchor.set(0.5, 0.5);
pausedText.x = 2048 / 2;
pausedText.y = 2732 / 2 - 180;
overlay.addChild(pausedText);
// Resume button
var resumeBtn = LK.getAsset('bubbleGreen', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 40,
scaleX: 1.1,
scaleY: 1.1
});
overlay.addChild(resumeBtn);
var resumeText = new Text2("Resume", {
size: 90,
fill: "#fff",
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
resumeText.anchor.set(0.5, 0.5);
resumeText.x = resumeBtn.x;
resumeText.y = resumeBtn.y;
overlay.addChild(resumeText);
// Main menu button
var menuBtn = LK.getAsset('bubbleRed', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 220,
scaleX: 1.1,
scaleY: 1.1
});
overlay.addChild(menuBtn);
var menuText = new Text2("Main Menu", {
size: 90,
fill: "#fff",
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
menuText.anchor.set(0.5, 0.5);
menuText.x = menuBtn.x;
menuText.y = menuBtn.y;
overlay.addChild(menuText);
// Resume handler
resumeBtn.down = function () {
LK.gui.top.removeChild(overlay);
LK.resumeGame();
};
resumeText.down = resumeBtn.down;
// Main menu handler
menuBtn.down = function () {
LK.gui.top.removeChild(overlay);
LK.resumeGame();
// Reset to home screen
startGame.hasRun = undefined;
// Remove all game objects
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].destroy();
}
bubbles = [];
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
timerActive = false;
timerText.setText('');
feedbackText.alpha = 0;
promptText.setText('');
scoreText.setText('0');
highScoreText.setText('High Score: ' + highScore);
// Show home screen
startGame();
};
menuText.down = menuBtn.down;
LK.gui.top.addChild(overlay);
};
// Sounds for correct/incorrect feedback
// We'll use 26 different colors for variety, but for MVP, 5-6 colors are enough and can be reused.
// Letter bubbles: We'll use colored ellipses for bubbles, and overlay Text2 for letters.
// Alphabet array
var alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
// Game state
var selectedGameMode = "endless"; // "endless", "timed", or "hard"
var currentLetterIdx = 0; // Index in alphabet for the current target letter
var bubbles = []; // Array of LetterBubble instances
var lettersPerRound = 4; // Start with 4, will increase as player gets correct answers
var correctInARow = 0; // Track correct answers in a row for difficulty
var roundActive = false; // Is a round currently active?
var score = 0; // Number of correct letters popped
// Endless mode: after all letters, reshuffle and continue
var endlessMode = true;
// Timer variables
var timerActive = false;
var timerValue = 0; // seconds left
var timerInterval = null;
var TIMER_START = 310; // seconds to start with when timer mode begins
var TIMER_ADD = 3; // seconds to add per correct answer
// High score per mode
var highScores = {
endless: typeof storage.letterpop_highscore_endless !== "undefined" ? storage.letterpop_highscore_endless : 0,
timed: typeof storage.letterpop_highscore_timed !== "undefined" ? storage.letterpop_highscore_timed : 0,
hard: typeof storage.letterpop_highscore_hard !== "undefined" ? storage.letterpop_highscore_hard : 0
};
var highScore = 0; // Will be set per mode
// UI elements
var promptText = new Text2('', {
size: 110,
fill: 0x333333,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
promptText.anchor.set(0.5, 0);
promptText.y = 90; // Move prompt lower from the very top
LK.gui.top.addChild(promptText);
var scoreText = new Text2('0', {
size: 110,
fill: 0x4A90E2,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
scoreText.anchor.set(0.5, 1);
scoreText.y = -120; // Move it higher above the very bottom
LK.gui.bottom.addChild(scoreText);
// High score text (top center, under prompt)
var highScoreText = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0x888888,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
highScoreText.anchor.set(0.5, 0);
highScoreText.y = 240; // Add more spacing below the promptText
LK.gui.top.addChild(highScoreText);
// Timer text (placed just above the score)
var timerText = new Text2('', {
size: 80,
fill: 0xD0021B,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
timerText.anchor.set(0.5, 1);
// We'll position timerText just above the score
timerText.y = scoreText.y - 100;
LK.gui.bottom.addChild(timerText);
// Timer change effect (shows +2s/-1s)
var timerChangeText = new Text2('', {
size: 70,
fill: 0x43A047,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
timerChangeText.anchor.set(0.5, 1);
timerChangeText.alpha = 0;
timerChangeText.y = timerText.y - 60;
LK.gui.bottom.addChild(timerChangeText);
// Helper to show timer change effect
function showTimerChangeEffect(text, color) {
timerChangeText.setText(text);
timerChangeText.setStyle({
fill: color
});
timerChangeText.alpha = 0;
timerChangeText.y = timerText.y - 80;
tween(timerChangeText, {
alpha: 1,
y: timerChangeText.y - 40
}, {
duration: 200,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(timerChangeText, {
alpha: 0
}, {
duration: 300
});
}, 400);
}
});
}
// Feedback text (centered, fades in/out)
var feedbackText = new Text2('', {
size: 130,
fill: 0x43A047,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
feedbackText.anchor.set(0.5, 0.5);
feedbackText.alpha = 0;
LK.gui.center.addChild(feedbackText);
// Helper: Shuffle array (Fisher-Yates)
function shuffleArray(arr) {
var a = arr.slice();
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = a[i];
a[i] = a[j];
a[j] = t;
}
return a;
}
// Helper: Start a new round
function startRound() {
// Remove old bubbles
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].destroy();
}
bubbles = [];
if (currentLetterIdx >= alphabet.length) {
if (endlessMode) {
// Reshuffle for endless play, keep score and difficulty
currentLetterIdx = 0;
// Shuffle alphabet for next endless cycle
alphabet = shuffleArray(alphabet);
} else {
// All letters done!
showCelebration();
return;
}
}
roundActive = true;
// Set prompt
var targetLetter = alphabet[currentLetterIdx];
promptText.setText("Find: " + targetLetter);
// Pick distractor letters (not the target)
var distractors = [];
var pool = [];
for (var i = 0; i < alphabet.length; i++) {
if (i !== currentLetterIdx) {
pool.push(alphabet[i]);
}
}
pool = shuffleArray(pool);
for (var i = 0; i < lettersPerRound - 1; i++) {
distractors.push(pool[i]);
}
// Combine and shuffle
var roundLetters = distractors.concat([targetLetter]);
roundLetters = shuffleArray(roundLetters);
// Dynamically arrange bubbles in a centered grid, scaling and spacing to fit up to 20 bubbles
var numBubbles = roundLetters.length;
var maxBubbles = 20;
var minBubbleSize = 110;
var maxBubbleSize = 300;
var minMargin = 24;
var maxMargin = 60;
// Calculate grid: try to make it as square as possible
var cols = Math.ceil(Math.sqrt(numBubbles));
var rows = Math.ceil(numBubbles / cols);
// Compute available width/height (leave some padding)
var padX = 80,
padY = 200;
var availW = 2048 - padX * 2;
var availH = 1800 - padY * 2; // keep bubbles in upper 2/3 of screen
// Compute max bubble size that fits
var bubbleSizeW = Math.floor((availW - (cols - 1) * minMargin) / cols);
var bubbleSizeH = Math.floor((availH - (rows - 1) * minMargin) / rows);
var bubbleSize = Math.max(minBubbleSize, Math.min(maxBubbleSize, Math.min(bubbleSizeW, bubbleSizeH)));
// Compute margin to center bubbles
var marginX = Math.max(minMargin, Math.min(maxMargin, Math.floor((availW - cols * bubbleSize) / Math.max(1, cols - 1))));
var marginY = Math.max(minMargin, Math.min(maxMargin, Math.floor((availH - rows * bubbleSize) / Math.max(1, rows - 1))));
// Compute grid start
var totalGridW = cols * bubbleSize + (cols - 1) * marginX;
var totalGridH = rows * bubbleSize + (rows - 1) * marginY;
// Vertically center grid in the middle of the screen, but never closer than 200px to the top
// 2732 is the screen height. Center the grid, but keep at least 200px from the top and 200px from the bottom.
var minTop = 200;
var minBottom = 200;
var availableHeight = 2732 - minTop - minBottom;
var gridTop = minTop + Math.max(0, (availableHeight - totalGridH) / 2);
var startX = (2048 - totalGridW) / 2 + bubbleSize / 2;
var startY = gridTop + bubbleSize / 2;
// Place bubbles in grid
var positions = [];
for (var i = 0; i < numBubbles; i++) {
var row = Math.floor(i / cols);
var col = i % cols;
positions.push({
x: startX + col * (bubbleSize + marginX),
y: startY + row * (bubbleSize + marginY)
});
}
// Timer text is now statically positioned above the score in LK.gui.bottom
// Create and add bubbles, scale them to fit
for (var i = 0; i < roundLetters.length; i++) {
var useZigzag = false;
// Hard mode: all bubbles are zigzag/faded
if (typeof selectedGameMode !== "undefined" && selectedGameMode === "hard") {
useZigzag = true;
} else {
// Timed/Endless: introduce zigzag as difficulty increases
if (roundLetters.length >= 7) {
var zigzagCount = Math.floor((roundLetters.length - 6) * 0.7);
if (i < zigzagCount) {
useZigzag = true;
}
}
}
var bubble;
if (useZigzag) {
bubble = new ZigzagLetterBubble();
} else {
bubble = new LetterBubble();
}
bubble.letter = roundLetters[i];
bubble.letterText.setText(bubble.letter);
bubble.isTarget = bubble.letter === targetLetter;
// Position
bubble.x = positions[i].x;
bubble.y = positions[i].y;
// For zigzag, set baseX for zigzag movement
if (useZigzag) {
bubble._zigzagBaseX = bubble.x;
}
// Scale bubble to fit
var scale = bubbleSize / 300; // 300 is the asset's base size
bubble.scale.set(scale, scale);
// Also scale letter text for readability
bubble.letterText.setStyle({
size: Math.floor(120 * scale)
});
// Add to game
game.addChild(bubble);
bubbles.push(bubble);
}
}
// Handle bubble tap
function onBubbleTapped(bubble) {
if (!roundActive) {
return;
}
if (bubble.isTarget) {
// Correct!
roundActive = false;
score++;
scoreText.setText(score);
// Update high score if needed (per mode)
if (score > highScore) {
highScore = score;
highScores[selectedGameMode] = score;
highScoreText.setText('High Score: ' + highScore);
if (selectedGameMode === "endless") {
storage.letterpop_highscore_endless = score;
} else if (selectedGameMode === "timed") {
storage.letterpop_highscore_timed = score;
} else if (selectedGameMode === "hard") {
storage.letterpop_highscore_hard = score;
}
}
// Start timer mode after 10 points (only in Timed Mode), or always active in Hard Mode
if (selectedGameMode === "timed" && !timerActive && score >= 10 || selectedGameMode === "hard" && !timerActive) {
timerActive = true;
timerValue = TIMER_START;
timerText.setText('Time: ' + timerValue);
if (timerInterval) {
LK.clearInterval(timerInterval);
}
timerInterval = LK.setInterval(function () {
if (!timerActive) {
return;
}
timerValue--;
timerText.setText('Time: ' + timerValue);
if (timerValue <= 0) {
timerValue = 0;
timerText.setText('Time: 0');
endGameTimeout();
}
}, 1000);
}
// Add time for correct answer if timer is active
if (timerActive) {
timerValue += 2;
timerText.setText('Time: ' + timerValue);
showTimerChangeEffect("+2s", "#43A047");
}
// Track correct answers in a row for difficulty increase
if (typeof correctInARow === "undefined") {
correctInARow = 0;
}
correctInARow++;
// After every two correct answers, increase number of options (up to 20)
if (correctInARow % 2 === 0) {
if (typeof lettersPerRound === "undefined") {
lettersPerRound = 4;
}
lettersPerRound = Math.min(lettersPerRound + 1, 20);
}
// Feedback
showFeedback("Great!", "#43A047");
LK.getSound('ding').play();
// Pop animation, then next round
bubble.pop(function () {
// Remove other bubbles with fade out
for (var i = 0; i < bubbles.length; i++) {
if (bubbles[i] !== bubble) {
tween(bubbles[i], {
alpha: 0
}, {
duration: 200,
onFinish: function (bub) {
return function () {
bub.destroy();
};
}(bubbles[i])
});
}
}
// Next letter after short delay
LK.setTimeout(function () {
currentLetterIdx++;
startRound();
}, 600);
});
} else {
// Reset streak on incorrect answer
correctInARow = 0;
// Incorrect
showFeedback("Try again!", "#D0021B");
LK.getSound('oops').play();
bubble.shake();
// Subtract 1s for wrong answer if timer is active (including Hard Mode)
if (timerActive) {
timerValue = Math.max(0, timerValue - 1);
timerText.setText('Time: ' + timerValue);
showTimerChangeEffect("-1s", "#D0021B");
if (timerValue <= 0) {
timerValue = 0;
timerText.setText('Time: 0');
endGameTimeout();
}
}
// Allow another try (do not end round)
}
}
// Show feedback text in center, fade in/out
function showFeedback(msg, color) {
feedbackText.setText(msg);
// Use setStyle to update fill color safely
feedbackText.setStyle({
fill: color
});
feedbackText.alpha = 0;
tween(feedbackText, {
alpha: 1
}, {
duration: 120,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(feedbackText, {
alpha: 0
}, {
duration: 200
});
}, 500);
}
});
}
// End game due to timer running out
function endGameTimeout() {
timerActive = false;
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
// Remove any remaining bubbles
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].destroy();
}
bubbles = [];
promptText.setText("Time's up!");
feedbackText.setText("Score: " + score);
feedbackText.setStyle({
fill: 0xD0021B
});
feedbackText.alpha = 0;
tween(feedbackText, {
alpha: 1
}, {
duration: 300
});
// Play oops sound
LK.getSound('oops').play();
// Show game over after a short delay (triggers LK's game over popup)
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
}
// Show celebration screen
function showCelebration() {
// Remove any remaining bubbles
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].destroy();
}
bubbles = [];
promptText.setText("All done!");
feedbackText.setText("You did it!");
// Use setStyle to update fill color safely
feedbackText.setStyle({
fill: 0xF5A623
});
feedbackText.alpha = 0;
tween(feedbackText, {
alpha: 1
}, {
duration: 300
});
// Play cheer sound
LK.getSound('cheer').play();
// Show "You Win" after a short delay (triggers LK's win popup)
LK.setTimeout(function () {
LK.showYouWin();
}, 1200);
}
// Start game
function startGame() {
// Show a visually appealing home screen before the first round
if (typeof startGame.hasRun === "undefined") {
var isFarFromOthers = function isFarFromOthers(x, y, minDist) {
for (var i = 0; i < placedPositions.length; i++) {
var dx = x - placedPositions[i].x;
var dy = y - placedPositions[i].y;
if (Math.sqrt(dx * dx + dy * dy) < minDist) {
return false;
}
}
return true;
};
// Only show on first load, not after game over
startGame.hasRun = true;
// Create a custom home screen container
var homeScreen = new Container();
// Big colorful title
var title = new Text2("Letter Pop!", {
size: 220,
fill: ["#4A90E2", "#F5A623", "#7ED321", "#D0021B"],
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 700;
homeScreen.addChild(title);
// Fun subtitle
var subtitle = new Text2("Pop the right letter bubbles!", {
size: 90,
fill: 0x9013FE,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
subtitle.anchor.set(0.5, 0.5);
subtitle.x = 2048 / 2;
subtitle.y = 900;
homeScreen.addChild(subtitle);
// (Sample letters removed for a cleaner home screen)
// Game mode buttons
var modeBtnY = 1450;
var modeBtnSpacing = 220;
var modeBtnScale = 1.15;
// Endless Mode Button
var endlessBtn = LK.getAsset('bubbleBlue', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: modeBtnY,
scaleX: modeBtnScale,
scaleY: modeBtnScale
});
homeScreen.addChild(endlessBtn);
var endlessText = new Text2("Endless Mode", {
size: 90,
fill: "#fff",
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
endlessText.anchor.set(0.5, 0.5);
endlessText.x = endlessBtn.x;
endlessText.y = endlessBtn.y;
homeScreen.addChild(endlessText);
// Timed Mode Button
var timedBtn = LK.getAsset('bubbleOrange', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: modeBtnY + modeBtnSpacing,
scaleX: modeBtnScale,
scaleY: modeBtnScale
});
homeScreen.addChild(timedBtn);
var timedText = new Text2("Timed Mode", {
size: 90,
fill: "#fff",
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
timedText.anchor.set(0.5, 0.5);
timedText.x = timedBtn.x;
timedText.y = timedBtn.y;
homeScreen.addChild(timedText);
// Hard Mode Button
var hardBtn = LK.getAsset('bubbleRed', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: modeBtnY + 2 * modeBtnSpacing,
scaleX: modeBtnScale,
scaleY: modeBtnScale
});
homeScreen.addChild(hardBtn);
var hardText = new Text2("Hard Mode", {
size: 90,
fill: "#fff",
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
hardText.anchor.set(0.5, 0.5);
hardText.x = hardBtn.x;
hardText.y = hardBtn.y;
homeScreen.addChild(hardText);
// Add homeScreen to game
game.addChild(homeScreen);
// Button interactions
endlessBtn.down = function () {
game.removeChild(homeScreen);
selectedGameMode = "endless";
actuallyStartGame();
};
endlessText.down = endlessBtn.down;
timedBtn.down = function () {
game.removeChild(homeScreen);
selectedGameMode = "timed";
actuallyStartGame();
};
timedText.down = timedBtn.down;
hardBtn.down = function () {
game.removeChild(homeScreen);
selectedGameMode = "hard";
actuallyStartGame();
};
hardText.down = hardBtn.down;
// Don't start the game yet!
return;
}
actuallyStartGame();
function actuallyStartGame() {
currentLetterIdx = 0;
score = 0;
scoreText.setText(score);
feedbackText.alpha = 0;
correctInARow = 0;
lettersPerRound = 4;
// Set game mode defaults
if (typeof selectedGameMode === "undefined") {
selectedGameMode = "endless";
}
// Endless Mode: no timer, endless play, normal bubbles
// Timed Mode: timer, increasing difficulty, normal/zigzag bubbles
// Hard Mode: no timer, all bubbles zigzag/faded, increasing count
if (selectedGameMode === "endless") {
endlessMode = true;
timerActive = false;
timerValue = 0;
timerText.setText('');
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
} else if (selectedGameMode === "timed") {
endlessMode = false;
timerActive = true;
timerValue = TIMER_START;
timerText.setText('Time: ' + timerValue);
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
timerInterval = LK.setInterval(function () {
if (!timerActive) return;
timerValue--;
timerText.setText('Time: ' + timerValue);
if (timerValue <= 0) {
timerValue = 0;
timerText.setText('Time: 0');
endGameTimeout();
}
}, 1000);
} else if (selectedGameMode === "hard") {
endlessMode = false;
timerActive = true;
timerValue = TIMER_START;
timerText.setText('Time: ' + timerValue);
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
timerInterval = LK.setInterval(function () {
if (!timerActive) return;
timerValue--;
timerText.setText('Time: ' + timerValue);
if (timerValue <= 0) {
timerValue = 0;
timerText.setText('Time: 0');
endGameTimeout();
}
}, 1000);
}
// Reload high score from storage for the selected mode
if (selectedGameMode === "endless") {
highScore = typeof storage.letterpop_highscore_endless !== "undefined" ? storage.letterpop_highscore_endless : 0;
} else if (selectedGameMode === "timed") {
highScore = typeof storage.letterpop_highscore_timed !== "undefined" ? storage.letterpop_highscore_timed : 0;
} else if (selectedGameMode === "hard") {
highScore = typeof storage.letterpop_highscore_hard !== "undefined" ? storage.letterpop_highscore_hard : 0;
} else {
highScore = 0;
}
highScoreText.setText('High Score: ' + highScore);
startRound();
}
}
// Start on load
startGame();
// Animate zigzag bubbles (if any) each frame
game.update = function () {
for (var i = 0; i < bubbles.length; i++) {
if (typeof bubbles[i].update === "function") {
bubbles[i].update();
}
}
};
// Touchscreen: No drag/move needed, only tap (down) on bubbles
// Make sure no elements are in top-left 100x100 (all UI is top/center/right);