/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Bird class for background animation var Bird = Container.expand(function () { var self = Container.call(this); // Randomly pick one of the two bird assets var birdType = Math.random() < 0.5 ? 'bird1' : 'bird2'; var birdAsset = self.attachAsset(birdType, { anchorX: 0.5, anchorY: 0.5 }); // Set initial scale (can be randomized for depth effect) var scale = 0.8 + Math.random() * 0.6; birdAsset.scaleX = scale; birdAsset.scaleY = scale; self.width = birdAsset.width * scale; self.height = birdAsset.height * scale; // Set random vertical position (avoid top 200px and bottom 400px) self.y = 150 + Math.random() * (1800 - 150); // Start just off the right edge self.x = 2048 + self.width; // Set speed (pixels per frame) self.speed = 3 + Math.random() * 2; // For possible future: flip bird horizontally if needed birdAsset.scaleX = -scale; // Face left // No interaction self.interactive = false; // Track lastX for event logic if needed self.lastX = self.x; // Update method for movement self.update = function () { self.lastX = self.x; self.x -= self.speed; // If off the left edge, destroy if (self.x < -self.width) { if (self.parent) self.parent.removeChild(self); self.destroy(); } }; return self; }); // GoldenStitch class: moves evasively, hard to catch, player wins if caught var GoldenStitch = Container.expand(function () { var self = Container.call(this); // Use the unique goldenStitch asset var stitchAsset = self.attachAsset('goldenStitch', { anchorX: 0.5, anchorY: 0.5 }); stitchAsset.width = 120; stitchAsset.height = 120; // Evasive movement parameters self.x = 2048 / 2; self.y = 600 + Math.random() * 1000; self.lastX = self.x; self.lastY = self.y; self.targetX = self.x; self.targetY = self.y; self.moveTimer = 0; self.moveInterval = 30 + Math.floor(Math.random() * 40); // frames between moves self.speed = 18 + Math.random() * 8; // pixels per frame, fast! self.evasionRadius = 400; // how far it jumps per move self.interactive = true; // so it can be caught // Make it hard to tap: only allow catching if tap is very close to center self.down = function (x, y, obj) { // Only allow catching if tap is within 60px of center var dx = x - self.x; var dy = y - self.y; if (dx * dx + dy * dy < 60 * 60) { // Player caught the stitch, win! if (typeof timer !== "undefined" && timer) { LK.clearInterval(timer); timer = null; } LK.showYouWin(); } }; // Evasive movement: picks a new random target, tweens there, repeats self.update = function () { self.lastX = self.x; self.lastY = self.y; self.moveTimer++; // If close to target or time to pick new target, pick a new one var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 20 || self.moveTimer > self.moveInterval) { // Pick a new target far from current, but inside screen var angle = Math.random() * Math.PI * 2; var radius = self.evasionRadius * (0.5 + Math.random() * 0.7); var tx = self.x + Math.cos(angle) * radius; var ty = self.y + Math.sin(angle) * radius; // Clamp to screen bounds (avoid top 200, bottom 400) tx = Math.max(100, Math.min(2048 - 100, tx)); ty = Math.max(200, Math.min(2732 - 400, ty)); self.targetX = tx; self.targetY = ty; self.moveTimer = 0; self.moveInterval = 24 + Math.floor(Math.random() * 32); // Tween to new target tween(self, { x: self.targetX, y: self.targetY }, { duration: 400 + Math.random() * 200, easing: tween.cubicInOut }); } }; return self; }); // Rocket class for background animation var Rocket = Container.expand(function () { var self = Container.call(this); var rocketAsset = self.attachAsset('rocket', { anchorX: 0.5, anchorY: 1 // Anchor at the bottom of the rocket }); // Set scale (can be randomized) var scale = 0.6 + Math.random() * 0.4; rocketAsset.scaleX = scale; rocketAsset.scaleY = scale; self.width = rocketAsset.width * scale; self.height = rocketAsset.height * scale; // Start just off the bottom edge self.y = 2732 + self.height; // Set random horizontal position (avoid edges) self.x = 100 + Math.random() * (2048 - 200); // Set speed (pixels per frame) self.speed = 5 + Math.random() * 3; // No interaction self.interactive = false; // Track lastY for event logic if needed self.lastY = self.y; // Update method for movement self.update = function () { self.lastY = self.y; self.y -= self.speed; // If off the top edge, destroy if (self.y < -self.height) { if (self.parent) self.parent.removeChild(self); self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Bird assets for background animation (use two different images) // --- Game Variables --- // Import tween plugin for animations var currentQuestion = null; // Holds the current math question object var answerDigits = []; // Array of selected digit values (as strings) var digitCubes = []; // Array of digit cube objects (0-9) var answerBox = null; // The answer drop zone var questionText = null; // The question display var timerText = null; // Timer display var timer = null; // Timer interval id var timeLeft = 60; // Total seconds for the game var timerBar = null; // Visual timer bar var timerBarBg = null; // Timer bar background var timerBarWidth = 1200; // Width of the timer bar var timerBarHeight = 60; // Height of the timer bar var timerBarX = 2048 / 2 - timerBarWidth / 2; var timerBarY = 240; var timerBarColor = 0xFF3333; var timerBarBgColor = 0x222222; var timerBarPadding = 8; // Removed hero and drag logic; input is now by clicking digit cubes var score = 0; // Player score // --- Bird Animation Variables --- var birds = []; // Array to hold active bird objects var birdSpawnTimer = 0; // Timer for spawning birds var birdSpawnInterval = 120; // Frames between bird spawns (2 seconds at 60fps) // --- Rocket Animation Variables --- var rockets = []; // Array to hold active rocket objects var rocketSpawnTimer = 0; // Timer for spawning rockets var rocketSpawnInterval = 240; // Frames between rocket spawns (4 seconds at 60fps) // --- Background Image --- var bgImage = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChildAt(bgImage, 0); // Add as background // --- Joker State --- var jokers = [{ used: false, label: "First Digit", color: 0x00cc00 }, { used: false, label: "Remove Wrong", color: 0x00cc00 }, { used: false, label: "Skip", color: 0x00cc00 }]; var jokerButtons = []; // --- Utility Functions --- function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // Generates a random math question object: {text, answer} function generateQuestion() { var ops = ['+', '-', '×', '÷']; var op = ops[randomInt(0, ops.length - 1)]; var a, b, text, answer; if (op === '+') { a = randomInt(1, 99); b = randomInt(1, 99); answer = a + b; } else if (op === '-') { a = randomInt(10, 99); b = randomInt(1, a); answer = a - b; } else if (op === '×') { a = randomInt(2, 12); b = randomInt(2, 12); answer = a * b; } else { // ÷ b = randomInt(2, 12); answer = randomInt(2, 12); a = b * answer; } text = a + " " + op + " " + b + " = ?"; return { text: text, answer: answer }; } // Resets the answer input and updates the answer box display function resetAnswer() { answerDigits = []; updateAnswerBox(); } // Updates the answer box text to show current input function updateAnswerBox() { if (answerBox && answerBox.textObj) { answerBox.textObj.setText(answerDigits.length ? answerDigits.join('') : ""); } } // Starts a new question function startNewQuestion() { if (typeof updateStatsText === "function") updateStatsText(); if (timer) { LK.clearInterval(timer); } currentQuestion = generateQuestion(); questionText.setText(currentQuestion.text); resetAnswer(); // Reset digit cubes visibility for new question for (var d = 0; d < digitCubes.length; d++) { digitCubes[d].visible = true; } // Do not reset timeLeft here; timer is for the whole game // Only set timer if not already running if (!timer) { timerText.setText("⏰ " + timeLeft); timerBar.width = timerBarWidth; timer = LK.setInterval(function () { timeLeft--; timerText.setText("⏰ " + timeLeft); // Update timer bar width timerBar.width = Math.max(0, timerBarWidth * (timeLeft / 60)); // --- Auto-drop two non-answer digits if timer < 20s --- if (timeLeft === 20) { // Find digits not in the answer var ansStr = currentQuestion.answer + ""; var digitsInAnswer = {}; for (var k = 0; k < ansStr.length; k++) { digitsInAnswer[ansStr[k]] = true; } var toDrop = []; for (var d = 0; d < digitCubes.length; d++) { if (!digitCubes[d].visible) continue; if (!digitsInAnswer.hasOwnProperty(digitCubes[d].digit + "")) { toDrop.push(digitCubes[d]); if (toDrop.length === 2) break; } } // Animate and hide the cubes for (var i = 0; i < toDrop.length; i++) { (function (cube) { tween(cube, { y: 2900 }, { duration: 1600, // Slower drop effect (was 700) easing: tween.cubicIn, onFinish: function onFinish() { cube.visible = false; } }); })(toDrop[i]); } } if (timeLeft <= 0) { LK.clearInterval(timer); timer = null; timerBar.width = 0; if (typeof updateStatsText === "function") updateStatsText(); LK.showGameOver(); } }, 1000); } } // Checks the answer and proceeds accordingly function checkAnswer() { var guess = parseInt(answerDigits.join('')); if (guess === currentQuestion.answer) { score++; // Show 'Tebrikler' popup var congratsPopup = new Container(); var popupBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5 }); popupBg.width = 800; popupBg.height = 400; popupBg.tint = 0x8720fe; congratsPopup.addChild(popupBg); var popupText = new Text2("Congratulations!", { size: 140, fill: 0xffffff }); popupText.anchor.set(0.5, 0.5); popupText.x = 0; popupText.y = -40; congratsPopup.addChild(popupText); var popupSubText = new Text2("Tap to continue", { size: 60, fill: 0xffffff }); popupSubText.anchor.set(0.5, 0.5); popupSubText.x = 0; popupSubText.y = 90; congratsPopup.addChild(popupSubText); congratsPopup.x = 2048 / 2; congratsPopup.y = 1200; // Block input to game while popup is up congratsPopup.interactive = true; congratsPopup.down = function () { // Remove popup if (congratsPopup.parent) congratsPopup.parent.removeChild(congratsPopup); timeLeft = 60; // Reset timer to 60 seconds if (timer) { LK.clearInterval(timer); timer = null; } startNewQuestion(); }; game.addChild(congratsPopup); } else { LK.clearInterval(timer); timer = null; if (timerBar) timerBar.width = 0; // Show correct answer in the answer box before game over if (answerBox && answerBox.textObj) { answerBox.textObj.setText("Correct: " + currentQuestion.answer); } LK.setTimeout(function () { if (typeof updateStatsText === "function") updateStatsText(); LK.showGameOver(); }, 1200); } } // --- UI Setup --- // --- Question Box (purple) centered between timer and answer box --- var questionBox = new Container(); var questionBoxAsset = LK.getAsset('questionBox', { anchorX: 0.5, anchorY: 0.5 }); questionBoxAsset.width = 600; questionBoxAsset.height = 180; questionBox.addChild(questionBoxAsset); // Place questionBox between timerText (y=20, height~160) and answerBox (y=900, height=200) // Let's center it at y = (timerBarY + answerBox.y) / 2, but visually, a bit above answerBox questionBox.x = 2048 / 2; questionBox.y = (timerBarY + 900) / 2 - 60; // visually balanced questionText = new Text2("", { size: 120, fill: 0xFFFFFF }); questionText.anchor.set(0.5, 0.5); questionText.x = 0; questionText.y = 0; questionBox.addChild(questionText); game.addChild(questionBox); // Timer at top center, larger and more visible timerText = new Text2("⏰ 60", { size: 160, fill: 0xFF3333, fontWeight: "bold" }); timerText.anchor.set(0.5, 0); timerText.x = 2048 / 2; // Move timerText higher to avoid overlap with questionText timerText.y = 20; game.addChild(timerText); // Timer bar background timerBarBg = LK.getAsset('timerBarBg', { anchorX: 0, anchorY: 0 }); timerBarBg.width = timerBarWidth + timerBarPadding * 2; timerBarBg.height = timerBarHeight + timerBarPadding * 2; timerBarBg.x = timerBarX - timerBarPadding; timerBarBg.y = timerBarY - timerBarPadding; game.addChild(timerBarBg); // Timer bar (foreground) timerBar = LK.getAsset('timerBar', { anchorX: 0, anchorY: 0 }); timerBar.width = timerBarWidth; timerBar.height = timerBarHeight; timerBar.x = timerBarX; timerBar.y = timerBarY; game.addChild(timerBar); // --- Hard Mode: Always enabled, no toggle button --- // Joker buttons (right side, vertically spaced) var jokerStartY = 400; var jokerSpacing = 220; for (var j = 0; j < 3; j++) { var jokerBtn = new Container(); var jokerAssetName = j === 0 ? 'joker1' : j === 1 ? 'joker2' : 'joker3'; var btnAsset = LK.getAsset(jokerAssetName, { anchorX: 0.5, anchorY: 0.5 }); btnAsset.width = 180; btnAsset.height = 180; btnAsset.tint = jokers[j].color; jokerBtn.addChild(btnAsset); var btnText = new Text2(jokers[j].label, { size: 38, fill: 0xffffff }); btnText.anchor.set(0.5, 0.5); btnText.x = 0; btnText.y = 0; jokerBtn.addChild(btnText); jokerBtn.x = 2048 - 180; jokerBtn.y = jokerStartY + j * jokerSpacing; jokerBtn.jokerIndex = j; // Joker button logic jokerBtn.down = function (idx) { return function (x, y, obj) { if (jokers[idx].used) return; jokers[idx].used = true; // Change color to red jokerButtons[idx].children[0].tint = 0xcc0000; // Joker 1: Fill first digit if (idx === 0) { var ansStr = currentQuestion.answer + ""; if (answerDigits.length < ansStr.length) { answerDigits = [ansStr[0]]; updateAnswerBox(); } } // Joker 2: Remove two incorrect digits from cubes else if (idx === 1) { var ansStr2 = currentQuestion.answer + ""; var digitsInAnswer = {}; for (var k = 0; k < ansStr2.length; k++) { digitsInAnswer[ansStr2[k]] = true; } var removed = 0; for (var d = 0; d < digitCubes.length; d++) { if (!digitsInAnswer.hasOwnProperty(digitCubes[d].digit + "") && removed < 2) { digitCubes[d].visible = false; removed++; } } } // Joker 3: Skip to next question else if (idx === 2) { startNewQuestion(); } }; }(j); jokerButtons.push(jokerBtn); game.addChild(jokerBtn); } // Answer box in center answerBox = new Container(); var boxAsset = LK.getAsset('answerBox', { anchorX: 0.5, anchorY: 0.5 }); boxAsset.width = 400; boxAsset.height = 200; answerBox.addChild(boxAsset); answerBox.x = 2048 / 2; answerBox.y = 900; answerBox.textObj = new Text2("", { size: 120, fill: 0x000000 }); answerBox.textObj.anchor.set(0.5, 0.5); answerBox.textObj.x = 0; answerBox.textObj.y = 0; answerBox.addChild(answerBox.textObj); game.addChild(answerBox); // Digit cubes (0-9) at bottom, spaced evenly, now clickable var cubeSpacing = 180; var startX = (2048 - (cubeSpacing * 10 - 40)) / 2; for (var i = 0; i < 10; i++) { var cube = new Container(); var asset = LK.getAsset('balloon', { anchorX: 0.5, anchorY: 0.5 }); asset.width = 220; asset.height = 220; cube.addChild(asset); var txt = new Text2(i + "", { size: 140, fill: 0x000000 }); txt.anchor.set(0.5, 0.5); txt.x = 0; txt.y = 0; cube.addChild(txt); cube.x = startX + i * cubeSpacing; cube.y = 2400; cube.digit = i; // Add click/tap handler cube.down = function (digit) { return function (x, y, obj) { // Only allow input if we haven't reached the required number of digits if (answerDigits.length < (currentQuestion.answer + "").length) { answerDigits.push(digit + ""); updateAnswerBox(); // If we've reached the required number of digits, check the answer if (answerDigits.length === (currentQuestion.answer + "").length) { checkAnswer(); } } // If already at max digits, ignore further input }; }(i); digitCubes.push(cube); game.addChild(cube); } // --- Delete Button (bottom left) --- var deleteBtn = new Container(); var deleteAsset = LK.getAsset('deleteBtn', { anchorX: 0.5, anchorY: 0.5 }); deleteAsset.width = 180; deleteAsset.height = 180; deleteBtn.addChild(deleteAsset); // Optional: Add a "←" icon/text on top of the button for clarity var delTxt = new Text2("←", { size: 120, fill: 0xffffff }); delTxt.anchor.set(0.5, 0.5); delTxt.x = 0; delTxt.y = 0; deleteBtn.addChild(delTxt); // Place at bottom left, but not in the top left 100x100 reserved area deleteBtn.x = 120; deleteBtn.y = 2550; deleteBtn.interactive = true; deleteBtn.down = function (x, y, obj) { if (answerDigits.length > 0) { answerDigits.pop(); updateAnswerBox(); } }; game.addChild(deleteBtn); // Hero character and drag logic removed; input is now by clicking digit cubes // --- Start Game --- startNewQuestion(); // --- Golden Stitch setup --- var goldenStitch = new GoldenStitch(); goldenStitch.x = 2048 / 2; goldenStitch.y = 900; game.addChildAt(goldenStitch, 2); // Above birds, below main UI // --- Stats Tracking --- var seriesCount = 0; // How many questions answered (right or wrong) var goldenSnitchCaught = 0; // Total golden snitch caught // --- Stats Display (bottom right) --- // --- Stats Display (bottom right) with background --- var statsBg = LK.getAsset('timerBarBg', { anchorX: 1, anchorY: 1 }); statsBg.width = 520; // widened for more left coverage statsBg.height = 180; statsBg.tint = 0x222222; statsBg.alpha = 0.85; statsBg.x = 2048 - 20; statsBg.y = 2732 - 20; game.addChild(statsBg); var statsText = new Text2("", { size: 60, fill: 0xFFFFFF }); statsText.anchor.set(1, 1); // bottom right statsText.x = 2048 - 40; statsText.y = 2732 - 40; game.addChild(statsText); function updateStatsText() { if (typeof statsText !== "undefined" && statsText && typeof statsText.setText === "function") { statsText.setText("Streak: " + seriesCount + "\nGolden Snitch: " + goldenSnitchCaught); } } updateStatsText(); // --- Golden Snitch Win Handler Patch --- var _origGoldenStitchDown = goldenStitch.down; goldenStitch.down = function (x, y, obj) { var dx = x - goldenStitch.x; var dy = y - goldenStitch.y; if (dx * dx + dy * dy < 60 * 60) { goldenSnitchCaught++; updateStatsText(); if (typeof timer !== "undefined" && timer) { LK.clearInterval(timer); timer = null; } LK.showYouWin(); } }; // --- Patch checkAnswer to increment seriesCount only --- var _origCheckAnswer = checkAnswer; checkAnswer = function checkAnswer() { seriesCount++; updateStatsText(); _origCheckAnswer(); }; // --- Patch game over to reset seriesCount only --- var _origShowGameOver = LK.showGameOver; LK.showGameOver = function () { updateStatsText(); seriesCount = 0; _origShowGameOver(); }; // --- Bird background animation logic --- game.update = function () { // Bird spawn logic birdSpawnTimer++; if (birdSpawnTimer >= birdSpawnInterval) { birdSpawnTimer = 0; // Spawn a new bird var bird = new Bird(); // Place behind all main game elements, but above background game.addChildAt(bird, 1); birds.push(bird); // Randomize next spawn interval (between 1.5s and 3.5s) birdSpawnInterval = 90 + Math.floor(Math.random() * 120); } // Update all birds for (var i = birds.length - 1; i >= 0; i--) { if (birds[i].update) birds[i].update(); // Remove from array if destroyed if (!birds[i].parent) { birds.splice(i, 1); } } // Rocket spawn logic rocketSpawnTimer++; if (rocketSpawnTimer >= rocketSpawnInterval) { rocketSpawnTimer = 0; // Spawn a new rocket var rocket = new Rocket(); // Place behind all main game elements, but above background game.addChildAt(rocket, 1); rockets.push(rocket); // Randomize next spawn interval (between 3s and 6s) rocketSpawnInterval = 180 + Math.floor(Math.random() * 180); } // Update all rockets for (var i = rockets.length - 1; i >= 0; i--) { if (rockets[i].update) rockets[i].update(); // Remove from array if destroyed if (!rockets[i].parent) { rockets.splice(i, 1); } } // Update golden stitch movement if (goldenStitch && goldenStitch.update) { goldenStitch.update(); } }; // --- Hard Mode Digit Cube Movement: Always enabled, slower movement --- var hardModeMoveTimer = null; function updateHardModeMovement() { // Movement speed: slow at start, fast at end // At 60s: 1200ms, at 0s: 350ms var minDuration = 350; var maxDuration = 1200; var t = Math.max(0, Math.min(1, timeLeft / 60)); // 1 at start, 0 at end var moveDuration = Math.round(minDuration + (maxDuration - minDuration) * t); // Helper to check overlap between two cubes function cubesOverlap(cubeA, xA, yA, cubeB, xB, yB) { var rA = 110; // half of 220 (cube size) var rB = 110; var dx = xA - xB; var dy = yA - yB; var distSq = dx * dx + dy * dy; var minDist = rA + rB + 10; // 10px margin return distSq < minDist * minDist; } var placedPositions = []; // Helper to check overlap with answer box function cubeOverlapsAnswerBox(x, y) { var rCube = 110; var rBox = Math.max(answerBox.width, answerBox.height) / 2; var dx = x - answerBox.x; var dy = y - answerBox.y; var distSq = dx * dx + dy * dy; var minDist = rCube + rBox + 10; return distSq < minDist * minDist; } for (var i = 0; i < digitCubes.length; i++) { // Only move visible cubes if (!digitCubes[i].visible) continue; var minX = 100; var maxX = 2048 - 100; var minY = 2200; var maxY = answerBox.y - 120; var tryCount = 0; var targetX, targetY, overlap, boxOverlap; do { targetX = randomInt(minX, maxX); targetY = randomInt(minY, maxY); overlap = false; boxOverlap = cubeOverlapsAnswerBox(targetX, targetY); for (var j = 0; j < placedPositions.length; j++) { if (cubesOverlap(digitCubes[i], targetX, targetY, placedPositions[j].cube, placedPositions[j].x, placedPositions[j].y)) { overlap = true; break; } } tryCount++; } while ((overlap || boxOverlap) && tryCount < 30); placedPositions.push({ cube: digitCubes[i], x: targetX, y: targetY }); tween(digitCubes[i], { x: targetX, y: targetY }, { duration: moveDuration, easing: tween.quadraticInOut }); } // Bounce logic: if cubes overlap after tween, reverse their direction for (var i = 0; i < digitCubes.length; i++) { if (!digitCubes[i].visible) continue; for (var j = i + 1; j < digitCubes.length; j++) { if (!digitCubes[j].visible) continue; var dx = digitCubes[i].x - digitCubes[j].x; var dy = digitCubes[i].y - digitCubes[j].y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 220) { // Bounce: move both cubes away from each other var angle = Math.atan2(dy, dx); var moveDist = 30; var nx = Math.cos(angle) * moveDist; var ny = Math.sin(angle) * moveDist; digitCubes[i].x += nx; digitCubes[i].y += ny; digitCubes[j].x -= nx; digitCubes[j].y -= ny; } } // Bounce off answer box var dxBox = digitCubes[i].x - answerBox.x; var dyBox = digitCubes[i].y - answerBox.y; var distBox = Math.sqrt(dxBox * dxBox + dyBox * dyBox); var minDistBox = 110 + Math.max(answerBox.width, answerBox.height) / 2 + 10; if (distBox < minDistBox) { // Move cube away from answer box var angleBox = Math.atan2(dyBox, dxBox); var moveDistBox = minDistBox - distBox + 10; digitCubes[i].x = answerBox.x + Math.cos(angleBox) * (minDistBox + 10); digitCubes[i].y = answerBox.y + Math.sin(angleBox) * (minDistBox + 10); } } } // Set up interval for hard mode movement (interval will be dynamically updated) var hardModeMoveTimer = null; function scheduleHardModeMove() { // Movement speed: slow at start, fast at end var minInterval = 350; var maxInterval = 1200; var t = Math.max(0, Math.min(1, timeLeft / 60)); var moveInterval = Math.round(minInterval + (maxInterval - minInterval) * t); if (hardModeMoveTimer) LK.clearTimeout(hardModeMoveTimer); updateHardModeMovement(); hardModeMoveTimer = LK.setTimeout(scheduleHardModeMove, moveInterval); } scheduleHardModeMove(); // When starting a new question, do not reset cube positions (always hard mode) var _origStartNewQuestion = startNewQuestion; startNewQuestion = function startNewQuestion() { _origStartNewQuestion(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class for background animation
var Bird = Container.expand(function () {
var self = Container.call(this);
// Randomly pick one of the two bird assets
var birdType = Math.random() < 0.5 ? 'bird1' : 'bird2';
var birdAsset = self.attachAsset(birdType, {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial scale (can be randomized for depth effect)
var scale = 0.8 + Math.random() * 0.6;
birdAsset.scaleX = scale;
birdAsset.scaleY = scale;
self.width = birdAsset.width * scale;
self.height = birdAsset.height * scale;
// Set random vertical position (avoid top 200px and bottom 400px)
self.y = 150 + Math.random() * (1800 - 150);
// Start just off the right edge
self.x = 2048 + self.width;
// Set speed (pixels per frame)
self.speed = 3 + Math.random() * 2;
// For possible future: flip bird horizontally if needed
birdAsset.scaleX = -scale; // Face left
// No interaction
self.interactive = false;
// Track lastX for event logic if needed
self.lastX = self.x;
// Update method for movement
self.update = function () {
self.lastX = self.x;
self.x -= self.speed;
// If off the left edge, destroy
if (self.x < -self.width) {
if (self.parent) self.parent.removeChild(self);
self.destroy();
}
};
return self;
});
// GoldenStitch class: moves evasively, hard to catch, player wins if caught
var GoldenStitch = Container.expand(function () {
var self = Container.call(this);
// Use the unique goldenStitch asset
var stitchAsset = self.attachAsset('goldenStitch', {
anchorX: 0.5,
anchorY: 0.5
});
stitchAsset.width = 120;
stitchAsset.height = 120;
// Evasive movement parameters
self.x = 2048 / 2;
self.y = 600 + Math.random() * 1000;
self.lastX = self.x;
self.lastY = self.y;
self.targetX = self.x;
self.targetY = self.y;
self.moveTimer = 0;
self.moveInterval = 30 + Math.floor(Math.random() * 40); // frames between moves
self.speed = 18 + Math.random() * 8; // pixels per frame, fast!
self.evasionRadius = 400; // how far it jumps per move
self.interactive = true; // so it can be caught
// Make it hard to tap: only allow catching if tap is very close to center
self.down = function (x, y, obj) {
// Only allow catching if tap is within 60px of center
var dx = x - self.x;
var dy = y - self.y;
if (dx * dx + dy * dy < 60 * 60) {
// Player caught the stitch, win!
if (typeof timer !== "undefined" && timer) {
LK.clearInterval(timer);
timer = null;
}
LK.showYouWin();
}
};
// Evasive movement: picks a new random target, tweens there, repeats
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.moveTimer++;
// If close to target or time to pick new target, pick a new one
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 20 || self.moveTimer > self.moveInterval) {
// Pick a new target far from current, but inside screen
var angle = Math.random() * Math.PI * 2;
var radius = self.evasionRadius * (0.5 + Math.random() * 0.7);
var tx = self.x + Math.cos(angle) * radius;
var ty = self.y + Math.sin(angle) * radius;
// Clamp to screen bounds (avoid top 200, bottom 400)
tx = Math.max(100, Math.min(2048 - 100, tx));
ty = Math.max(200, Math.min(2732 - 400, ty));
self.targetX = tx;
self.targetY = ty;
self.moveTimer = 0;
self.moveInterval = 24 + Math.floor(Math.random() * 32);
// Tween to new target
tween(self, {
x: self.targetX,
y: self.targetY
}, {
duration: 400 + Math.random() * 200,
easing: tween.cubicInOut
});
}
};
return self;
});
// Rocket class for background animation
var Rocket = Container.expand(function () {
var self = Container.call(this);
var rocketAsset = self.attachAsset('rocket', {
anchorX: 0.5,
anchorY: 1 // Anchor at the bottom of the rocket
});
// Set scale (can be randomized)
var scale = 0.6 + Math.random() * 0.4;
rocketAsset.scaleX = scale;
rocketAsset.scaleY = scale;
self.width = rocketAsset.width * scale;
self.height = rocketAsset.height * scale;
// Start just off the bottom edge
self.y = 2732 + self.height;
// Set random horizontal position (avoid edges)
self.x = 100 + Math.random() * (2048 - 200);
// Set speed (pixels per frame)
self.speed = 5 + Math.random() * 3;
// No interaction
self.interactive = false;
// Track lastY for event logic if needed
self.lastY = self.y;
// Update method for movement
self.update = function () {
self.lastY = self.y;
self.y -= self.speed;
// If off the top edge, destroy
if (self.y < -self.height) {
if (self.parent) self.parent.removeChild(self);
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Bird assets for background animation (use two different images)
// --- Game Variables ---
// Import tween plugin for animations
var currentQuestion = null; // Holds the current math question object
var answerDigits = []; // Array of selected digit values (as strings)
var digitCubes = []; // Array of digit cube objects (0-9)
var answerBox = null; // The answer drop zone
var questionText = null; // The question display
var timerText = null; // Timer display
var timer = null; // Timer interval id
var timeLeft = 60; // Total seconds for the game
var timerBar = null; // Visual timer bar
var timerBarBg = null; // Timer bar background
var timerBarWidth = 1200; // Width of the timer bar
var timerBarHeight = 60; // Height of the timer bar
var timerBarX = 2048 / 2 - timerBarWidth / 2;
var timerBarY = 240;
var timerBarColor = 0xFF3333;
var timerBarBgColor = 0x222222;
var timerBarPadding = 8;
// Removed hero and drag logic; input is now by clicking digit cubes
var score = 0; // Player score
// --- Bird Animation Variables ---
var birds = []; // Array to hold active bird objects
var birdSpawnTimer = 0; // Timer for spawning birds
var birdSpawnInterval = 120; // Frames between bird spawns (2 seconds at 60fps)
// --- Rocket Animation Variables ---
var rockets = []; // Array to hold active rocket objects
var rocketSpawnTimer = 0; // Timer for spawning rockets
var rocketSpawnInterval = 240; // Frames between rocket spawns (4 seconds at 60fps)
// --- Background Image ---
var bgImage = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChildAt(bgImage, 0); // Add as background
// --- Joker State ---
var jokers = [{
used: false,
label: "First Digit",
color: 0x00cc00
}, {
used: false,
label: "Remove Wrong",
color: 0x00cc00
}, {
used: false,
label: "Skip",
color: 0x00cc00
}];
var jokerButtons = [];
// --- Utility Functions ---
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Generates a random math question object: {text, answer}
function generateQuestion() {
var ops = ['+', '-', '×', '÷'];
var op = ops[randomInt(0, ops.length - 1)];
var a, b, text, answer;
if (op === '+') {
a = randomInt(1, 99);
b = randomInt(1, 99);
answer = a + b;
} else if (op === '-') {
a = randomInt(10, 99);
b = randomInt(1, a);
answer = a - b;
} else if (op === '×') {
a = randomInt(2, 12);
b = randomInt(2, 12);
answer = a * b;
} else {
// ÷
b = randomInt(2, 12);
answer = randomInt(2, 12);
a = b * answer;
}
text = a + " " + op + " " + b + " = ?";
return {
text: text,
answer: answer
};
}
// Resets the answer input and updates the answer box display
function resetAnswer() {
answerDigits = [];
updateAnswerBox();
}
// Updates the answer box text to show current input
function updateAnswerBox() {
if (answerBox && answerBox.textObj) {
answerBox.textObj.setText(answerDigits.length ? answerDigits.join('') : "");
}
}
// Starts a new question
function startNewQuestion() {
if (typeof updateStatsText === "function") updateStatsText();
if (timer) {
LK.clearInterval(timer);
}
currentQuestion = generateQuestion();
questionText.setText(currentQuestion.text);
resetAnswer();
// Reset digit cubes visibility for new question
for (var d = 0; d < digitCubes.length; d++) {
digitCubes[d].visible = true;
}
// Do not reset timeLeft here; timer is for the whole game
// Only set timer if not already running
if (!timer) {
timerText.setText("⏰ " + timeLeft);
timerBar.width = timerBarWidth;
timer = LK.setInterval(function () {
timeLeft--;
timerText.setText("⏰ " + timeLeft);
// Update timer bar width
timerBar.width = Math.max(0, timerBarWidth * (timeLeft / 60));
// --- Auto-drop two non-answer digits if timer < 20s ---
if (timeLeft === 20) {
// Find digits not in the answer
var ansStr = currentQuestion.answer + "";
var digitsInAnswer = {};
for (var k = 0; k < ansStr.length; k++) {
digitsInAnswer[ansStr[k]] = true;
}
var toDrop = [];
for (var d = 0; d < digitCubes.length; d++) {
if (!digitCubes[d].visible) continue;
if (!digitsInAnswer.hasOwnProperty(digitCubes[d].digit + "")) {
toDrop.push(digitCubes[d]);
if (toDrop.length === 2) break;
}
}
// Animate and hide the cubes
for (var i = 0; i < toDrop.length; i++) {
(function (cube) {
tween(cube, {
y: 2900
}, {
duration: 1600,
// Slower drop effect (was 700)
easing: tween.cubicIn,
onFinish: function onFinish() {
cube.visible = false;
}
});
})(toDrop[i]);
}
}
if (timeLeft <= 0) {
LK.clearInterval(timer);
timer = null;
timerBar.width = 0;
if (typeof updateStatsText === "function") updateStatsText();
LK.showGameOver();
}
}, 1000);
}
}
// Checks the answer and proceeds accordingly
function checkAnswer() {
var guess = parseInt(answerDigits.join(''));
if (guess === currentQuestion.answer) {
score++;
// Show 'Tebrikler' popup
var congratsPopup = new Container();
var popupBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5
});
popupBg.width = 800;
popupBg.height = 400;
popupBg.tint = 0x8720fe;
congratsPopup.addChild(popupBg);
var popupText = new Text2("Congratulations!", {
size: 140,
fill: 0xffffff
});
popupText.anchor.set(0.5, 0.5);
popupText.x = 0;
popupText.y = -40;
congratsPopup.addChild(popupText);
var popupSubText = new Text2("Tap to continue", {
size: 60,
fill: 0xffffff
});
popupSubText.anchor.set(0.5, 0.5);
popupSubText.x = 0;
popupSubText.y = 90;
congratsPopup.addChild(popupSubText);
congratsPopup.x = 2048 / 2;
congratsPopup.y = 1200;
// Block input to game while popup is up
congratsPopup.interactive = true;
congratsPopup.down = function () {
// Remove popup
if (congratsPopup.parent) congratsPopup.parent.removeChild(congratsPopup);
timeLeft = 60; // Reset timer to 60 seconds
if (timer) {
LK.clearInterval(timer);
timer = null;
}
startNewQuestion();
};
game.addChild(congratsPopup);
} else {
LK.clearInterval(timer);
timer = null;
if (timerBar) timerBar.width = 0;
// Show correct answer in the answer box before game over
if (answerBox && answerBox.textObj) {
answerBox.textObj.setText("Correct: " + currentQuestion.answer);
}
LK.setTimeout(function () {
if (typeof updateStatsText === "function") updateStatsText();
LK.showGameOver();
}, 1200);
}
}
// --- UI Setup ---
// --- Question Box (purple) centered between timer and answer box ---
var questionBox = new Container();
var questionBoxAsset = LK.getAsset('questionBox', {
anchorX: 0.5,
anchorY: 0.5
});
questionBoxAsset.width = 600;
questionBoxAsset.height = 180;
questionBox.addChild(questionBoxAsset);
// Place questionBox between timerText (y=20, height~160) and answerBox (y=900, height=200)
// Let's center it at y = (timerBarY + answerBox.y) / 2, but visually, a bit above answerBox
questionBox.x = 2048 / 2;
questionBox.y = (timerBarY + 900) / 2 - 60; // visually balanced
questionText = new Text2("", {
size: 120,
fill: 0xFFFFFF
});
questionText.anchor.set(0.5, 0.5);
questionText.x = 0;
questionText.y = 0;
questionBox.addChild(questionText);
game.addChild(questionBox);
// Timer at top center, larger and more visible
timerText = new Text2("⏰ 60", {
size: 160,
fill: 0xFF3333,
fontWeight: "bold"
});
timerText.anchor.set(0.5, 0);
timerText.x = 2048 / 2;
// Move timerText higher to avoid overlap with questionText
timerText.y = 20;
game.addChild(timerText);
// Timer bar background
timerBarBg = LK.getAsset('timerBarBg', {
anchorX: 0,
anchorY: 0
});
timerBarBg.width = timerBarWidth + timerBarPadding * 2;
timerBarBg.height = timerBarHeight + timerBarPadding * 2;
timerBarBg.x = timerBarX - timerBarPadding;
timerBarBg.y = timerBarY - timerBarPadding;
game.addChild(timerBarBg);
// Timer bar (foreground)
timerBar = LK.getAsset('timerBar', {
anchorX: 0,
anchorY: 0
});
timerBar.width = timerBarWidth;
timerBar.height = timerBarHeight;
timerBar.x = timerBarX;
timerBar.y = timerBarY;
game.addChild(timerBar);
// --- Hard Mode: Always enabled, no toggle button ---
// Joker buttons (right side, vertically spaced)
var jokerStartY = 400;
var jokerSpacing = 220;
for (var j = 0; j < 3; j++) {
var jokerBtn = new Container();
var jokerAssetName = j === 0 ? 'joker1' : j === 1 ? 'joker2' : 'joker3';
var btnAsset = LK.getAsset(jokerAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
btnAsset.width = 180;
btnAsset.height = 180;
btnAsset.tint = jokers[j].color;
jokerBtn.addChild(btnAsset);
var btnText = new Text2(jokers[j].label, {
size: 38,
fill: 0xffffff
});
btnText.anchor.set(0.5, 0.5);
btnText.x = 0;
btnText.y = 0;
jokerBtn.addChild(btnText);
jokerBtn.x = 2048 - 180;
jokerBtn.y = jokerStartY + j * jokerSpacing;
jokerBtn.jokerIndex = j;
// Joker button logic
jokerBtn.down = function (idx) {
return function (x, y, obj) {
if (jokers[idx].used) return;
jokers[idx].used = true;
// Change color to red
jokerButtons[idx].children[0].tint = 0xcc0000;
// Joker 1: Fill first digit
if (idx === 0) {
var ansStr = currentQuestion.answer + "";
if (answerDigits.length < ansStr.length) {
answerDigits = [ansStr[0]];
updateAnswerBox();
}
}
// Joker 2: Remove two incorrect digits from cubes
else if (idx === 1) {
var ansStr2 = currentQuestion.answer + "";
var digitsInAnswer = {};
for (var k = 0; k < ansStr2.length; k++) {
digitsInAnswer[ansStr2[k]] = true;
}
var removed = 0;
for (var d = 0; d < digitCubes.length; d++) {
if (!digitsInAnswer.hasOwnProperty(digitCubes[d].digit + "") && removed < 2) {
digitCubes[d].visible = false;
removed++;
}
}
}
// Joker 3: Skip to next question
else if (idx === 2) {
startNewQuestion();
}
};
}(j);
jokerButtons.push(jokerBtn);
game.addChild(jokerBtn);
}
// Answer box in center
answerBox = new Container();
var boxAsset = LK.getAsset('answerBox', {
anchorX: 0.5,
anchorY: 0.5
});
boxAsset.width = 400;
boxAsset.height = 200;
answerBox.addChild(boxAsset);
answerBox.x = 2048 / 2;
answerBox.y = 900;
answerBox.textObj = new Text2("", {
size: 120,
fill: 0x000000
});
answerBox.textObj.anchor.set(0.5, 0.5);
answerBox.textObj.x = 0;
answerBox.textObj.y = 0;
answerBox.addChild(answerBox.textObj);
game.addChild(answerBox);
// Digit cubes (0-9) at bottom, spaced evenly, now clickable
var cubeSpacing = 180;
var startX = (2048 - (cubeSpacing * 10 - 40)) / 2;
for (var i = 0; i < 10; i++) {
var cube = new Container();
var asset = LK.getAsset('balloon', {
anchorX: 0.5,
anchorY: 0.5
});
asset.width = 220;
asset.height = 220;
cube.addChild(asset);
var txt = new Text2(i + "", {
size: 140,
fill: 0x000000
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = 0;
cube.addChild(txt);
cube.x = startX + i * cubeSpacing;
cube.y = 2400;
cube.digit = i;
// Add click/tap handler
cube.down = function (digit) {
return function (x, y, obj) {
// Only allow input if we haven't reached the required number of digits
if (answerDigits.length < (currentQuestion.answer + "").length) {
answerDigits.push(digit + "");
updateAnswerBox();
// If we've reached the required number of digits, check the answer
if (answerDigits.length === (currentQuestion.answer + "").length) {
checkAnswer();
}
}
// If already at max digits, ignore further input
};
}(i);
digitCubes.push(cube);
game.addChild(cube);
}
// --- Delete Button (bottom left) ---
var deleteBtn = new Container();
var deleteAsset = LK.getAsset('deleteBtn', {
anchorX: 0.5,
anchorY: 0.5
});
deleteAsset.width = 180;
deleteAsset.height = 180;
deleteBtn.addChild(deleteAsset);
// Optional: Add a "←" icon/text on top of the button for clarity
var delTxt = new Text2("←", {
size: 120,
fill: 0xffffff
});
delTxt.anchor.set(0.5, 0.5);
delTxt.x = 0;
delTxt.y = 0;
deleteBtn.addChild(delTxt);
// Place at bottom left, but not in the top left 100x100 reserved area
deleteBtn.x = 120;
deleteBtn.y = 2550;
deleteBtn.interactive = true;
deleteBtn.down = function (x, y, obj) {
if (answerDigits.length > 0) {
answerDigits.pop();
updateAnswerBox();
}
};
game.addChild(deleteBtn);
// Hero character and drag logic removed; input is now by clicking digit cubes
// --- Start Game ---
startNewQuestion();
// --- Golden Stitch setup ---
var goldenStitch = new GoldenStitch();
goldenStitch.x = 2048 / 2;
goldenStitch.y = 900;
game.addChildAt(goldenStitch, 2); // Above birds, below main UI
// --- Stats Tracking ---
var seriesCount = 0; // How many questions answered (right or wrong)
var goldenSnitchCaught = 0; // Total golden snitch caught
// --- Stats Display (bottom right) ---
// --- Stats Display (bottom right) with background ---
var statsBg = LK.getAsset('timerBarBg', {
anchorX: 1,
anchorY: 1
});
statsBg.width = 520; // widened for more left coverage
statsBg.height = 180;
statsBg.tint = 0x222222;
statsBg.alpha = 0.85;
statsBg.x = 2048 - 20;
statsBg.y = 2732 - 20;
game.addChild(statsBg);
var statsText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
statsText.anchor.set(1, 1); // bottom right
statsText.x = 2048 - 40;
statsText.y = 2732 - 40;
game.addChild(statsText);
function updateStatsText() {
if (typeof statsText !== "undefined" && statsText && typeof statsText.setText === "function") {
statsText.setText("Streak: " + seriesCount + "\nGolden Snitch: " + goldenSnitchCaught);
}
}
updateStatsText();
// --- Golden Snitch Win Handler Patch ---
var _origGoldenStitchDown = goldenStitch.down;
goldenStitch.down = function (x, y, obj) {
var dx = x - goldenStitch.x;
var dy = y - goldenStitch.y;
if (dx * dx + dy * dy < 60 * 60) {
goldenSnitchCaught++;
updateStatsText();
if (typeof timer !== "undefined" && timer) {
LK.clearInterval(timer);
timer = null;
}
LK.showYouWin();
}
};
// --- Patch checkAnswer to increment seriesCount only ---
var _origCheckAnswer = checkAnswer;
checkAnswer = function checkAnswer() {
seriesCount++;
updateStatsText();
_origCheckAnswer();
};
// --- Patch game over to reset seriesCount only ---
var _origShowGameOver = LK.showGameOver;
LK.showGameOver = function () {
updateStatsText();
seriesCount = 0;
_origShowGameOver();
};
// --- Bird background animation logic ---
game.update = function () {
// Bird spawn logic
birdSpawnTimer++;
if (birdSpawnTimer >= birdSpawnInterval) {
birdSpawnTimer = 0;
// Spawn a new bird
var bird = new Bird();
// Place behind all main game elements, but above background
game.addChildAt(bird, 1);
birds.push(bird);
// Randomize next spawn interval (between 1.5s and 3.5s)
birdSpawnInterval = 90 + Math.floor(Math.random() * 120);
}
// Update all birds
for (var i = birds.length - 1; i >= 0; i--) {
if (birds[i].update) birds[i].update();
// Remove from array if destroyed
if (!birds[i].parent) {
birds.splice(i, 1);
}
}
// Rocket spawn logic
rocketSpawnTimer++;
if (rocketSpawnTimer >= rocketSpawnInterval) {
rocketSpawnTimer = 0;
// Spawn a new rocket
var rocket = new Rocket();
// Place behind all main game elements, but above background
game.addChildAt(rocket, 1);
rockets.push(rocket);
// Randomize next spawn interval (between 3s and 6s)
rocketSpawnInterval = 180 + Math.floor(Math.random() * 180);
}
// Update all rockets
for (var i = rockets.length - 1; i >= 0; i--) {
if (rockets[i].update) rockets[i].update();
// Remove from array if destroyed
if (!rockets[i].parent) {
rockets.splice(i, 1);
}
}
// Update golden stitch movement
if (goldenStitch && goldenStitch.update) {
goldenStitch.update();
}
};
// --- Hard Mode Digit Cube Movement: Always enabled, slower movement ---
var hardModeMoveTimer = null;
function updateHardModeMovement() {
// Movement speed: slow at start, fast at end
// At 60s: 1200ms, at 0s: 350ms
var minDuration = 350;
var maxDuration = 1200;
var t = Math.max(0, Math.min(1, timeLeft / 60)); // 1 at start, 0 at end
var moveDuration = Math.round(minDuration + (maxDuration - minDuration) * t);
// Helper to check overlap between two cubes
function cubesOverlap(cubeA, xA, yA, cubeB, xB, yB) {
var rA = 110; // half of 220 (cube size)
var rB = 110;
var dx = xA - xB;
var dy = yA - yB;
var distSq = dx * dx + dy * dy;
var minDist = rA + rB + 10; // 10px margin
return distSq < minDist * minDist;
}
var placedPositions = [];
// Helper to check overlap with answer box
function cubeOverlapsAnswerBox(x, y) {
var rCube = 110;
var rBox = Math.max(answerBox.width, answerBox.height) / 2;
var dx = x - answerBox.x;
var dy = y - answerBox.y;
var distSq = dx * dx + dy * dy;
var minDist = rCube + rBox + 10;
return distSq < minDist * minDist;
}
for (var i = 0; i < digitCubes.length; i++) {
// Only move visible cubes
if (!digitCubes[i].visible) continue;
var minX = 100;
var maxX = 2048 - 100;
var minY = 2200;
var maxY = answerBox.y - 120;
var tryCount = 0;
var targetX, targetY, overlap, boxOverlap;
do {
targetX = randomInt(minX, maxX);
targetY = randomInt(minY, maxY);
overlap = false;
boxOverlap = cubeOverlapsAnswerBox(targetX, targetY);
for (var j = 0; j < placedPositions.length; j++) {
if (cubesOverlap(digitCubes[i], targetX, targetY, placedPositions[j].cube, placedPositions[j].x, placedPositions[j].y)) {
overlap = true;
break;
}
}
tryCount++;
} while ((overlap || boxOverlap) && tryCount < 30);
placedPositions.push({
cube: digitCubes[i],
x: targetX,
y: targetY
});
tween(digitCubes[i], {
x: targetX,
y: targetY
}, {
duration: moveDuration,
easing: tween.quadraticInOut
});
}
// Bounce logic: if cubes overlap after tween, reverse their direction
for (var i = 0; i < digitCubes.length; i++) {
if (!digitCubes[i].visible) continue;
for (var j = i + 1; j < digitCubes.length; j++) {
if (!digitCubes[j].visible) continue;
var dx = digitCubes[i].x - digitCubes[j].x;
var dy = digitCubes[i].y - digitCubes[j].y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 220) {
// Bounce: move both cubes away from each other
var angle = Math.atan2(dy, dx);
var moveDist = 30;
var nx = Math.cos(angle) * moveDist;
var ny = Math.sin(angle) * moveDist;
digitCubes[i].x += nx;
digitCubes[i].y += ny;
digitCubes[j].x -= nx;
digitCubes[j].y -= ny;
}
}
// Bounce off answer box
var dxBox = digitCubes[i].x - answerBox.x;
var dyBox = digitCubes[i].y - answerBox.y;
var distBox = Math.sqrt(dxBox * dxBox + dyBox * dyBox);
var minDistBox = 110 + Math.max(answerBox.width, answerBox.height) / 2 + 10;
if (distBox < minDistBox) {
// Move cube away from answer box
var angleBox = Math.atan2(dyBox, dxBox);
var moveDistBox = minDistBox - distBox + 10;
digitCubes[i].x = answerBox.x + Math.cos(angleBox) * (minDistBox + 10);
digitCubes[i].y = answerBox.y + Math.sin(angleBox) * (minDistBox + 10);
}
}
}
// Set up interval for hard mode movement (interval will be dynamically updated)
var hardModeMoveTimer = null;
function scheduleHardModeMove() {
// Movement speed: slow at start, fast at end
var minInterval = 350;
var maxInterval = 1200;
var t = Math.max(0, Math.min(1, timeLeft / 60));
var moveInterval = Math.round(minInterval + (maxInterval - minInterval) * t);
if (hardModeMoveTimer) LK.clearTimeout(hardModeMoveTimer);
updateHardModeMovement();
hardModeMoveTimer = LK.setTimeout(scheduleHardModeMove, moveInterval);
}
scheduleHardModeMove();
// When starting a new question, do not reset cube positions (always hard mode)
var _origStartNewQuestion = startNewQuestion;
startNewQuestion = function startNewQuestion() {
_origStartNewQuestion();
};
sky. In-Game asset. 2d. High contrast. No shadows
cloud. In-Game asset. 2d. High contrast. No shadows
grey cloud. In-Game asset. 2d. High contrast. No shadows
angry bird. In-Game asset. 2d. High contrast. No shadows
yellow angry bird. In-Game asset. 2d. High contrast. No shadows
golden snitch. In-Game asset. 2d. High contrast. No shadows
trashcan. In-Game asset. 2d. High contrast. No shadows
Rocketship. In-Game asset. 2d. High contrast. No shadows
A sign with oval corners. Light blue. In-Game asset. 2d. High contrast. No shadows