Code edit (1 edits merged)
Please save this source code
User prompt
in step 4, make a white screen flash and make confettis spread from the tube (use bubble asset)
Code edit (1 edits merged)
Please save this source code
User prompt
use something else than `utils.rgb2hex([tintFactor, tintFactor, 1 - tintFactor]);` as it's not allowed
User prompt
in animateRainbowLiquid add also a sinus tint change. restore tint at the end of anim
Code edit (4 edits merged)
Please save this source code
User prompt
in animateRainbowLiquid add also a sinusoidal scale change
User prompt
in step3LiquidAnim, now repeat 10 times instead of 3, and make anim speed increase (+=10+2*repeatCount instead of +=10)
User prompt
in step3LiquidAnim repeat the animation 3 times
Code edit (6 edits merged)
Please save this source code
User prompt
add a white screen flash at step2RainbowLiquid start
Code edit (2 edits merged)
Please save this source code
User prompt
in step1 ``` vial.liquids.forEach(function (liquid) { liquid.alpha = 0; }); ``` is wrong, that's the alpha of the graphic that should change
Code edit (1 edits merged)
Please save this source code
User prompt
in step1JoinTubes, after the vial reach the target, hide liquids of the vial
User prompt
in step1JoinTubes, after the vial reach the target, hide it except the 1st in the puzzleManager.vials
Code edit (4 edits merged)
Please save this source code
User prompt
add a function step2RainbowLiquid()
User prompt
Ok now we gonna prepare the final animation... create a function step1JoinTubes() that animates all vials moving to the center of the screen. call it in playFinalAnim()
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: l is not defined' in or related to this line: 'l.log("playFinalAnim");' Line Number: 672
Code edit (4 edits merged)
Please save this source code
User prompt
When Progressively start boiling all vials based on timer, add a random delay for each vial boil start to avoid uniformity
Code edit (4 edits merged)
Please save this source code
User prompt
in initUI, shuffle colorList
/**** * Classes ****/ var LiquidBase = Container.expand(function (color) { var self = Container.call(this); self.color = color; var liquidGraphics = self.attachAsset('liquidBase', { anchorX: 0.5, anchorY: 0.0 }); liquidGraphics.tint = colorList[color]; return self; }); var LiquidStream = Container.expand(function (color) { var self = Container.call(this); self.color = colorList[color]; var liquidGraphics = self.attachAsset('liquidBase', { width: 10, anchorX: 0.5, anchorY: 0, alpha: 0 }); liquidGraphics.tint = colorList[color]; self.init = function (x, y, color) { log("LiquidStream init at ", x, y, color); self.height = 0; self.x = x; self.y = y + 380; liquidGraphics.tint = color; liquidGraphics.alpha = 1; log("LiquidStream init ok:", self); }; self.stop = function () { log("LiquidStream stoping:"); liquidGraphics.alpha = 0; }; return self; }); var Vial = Container.expand(function (chalk) { var self = Container.call(this); // Attach graphical representation var tubeGraphics = self.attachAsset(chalk ? 'tubeChalk' : 'tube', { anchorX: 0.5, anchorY: 0.0, alpha: chalk ? 1 : 0.5 }); self.fillHeight = tubeGraphics.height - 25 - 100; self.baseX = 0; // Initialize baseX to store the base x-coordinate self.baseY = 0; // Initialize baseY to store the base y-coordinate self.baseLiquidY = tubeGraphics.height - 25; self.addChild(tubeGraphics); self.topOfLiquidsY = self.baseLiquidY; self.liquids = []; // Initialize functional representation self.addLiquid = function (liquid, ratio) { var totalRatio = self.liquids.reduce(function (sum, liquid) { return sum + liquid.ratio; }, 0); if (totalRatio + ratio > 1) { ratio = 1 - totalRatio; // Adjust ratio to fit within the tube's capacity } // Fix floating-point precision issue ratio = Math.round(ratio / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO; ratio = parseFloat(ratio.toFixed(2)); if (self.liquids.length > 0 && self.liquids[self.liquids.length - 1].color === liquid.color) { self.liquids[self.liquids.length - 1].ratio = Math.round((self.liquids[self.liquids.length - 1].ratio + ratio) / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO; } else { if (ratio > 0) { var newLiquid = { color: liquid.color, index: self.liquids.length, ratio: ratio }; self.liquids.push(newLiquid); } } // Ensure the total ratio does not exceed 1 var totalRatio = self.liquids.reduce(function (sum, liquid) { return sum + liquid.ratio; }, 0); if (totalRatio > 1) { var excessRatio = totalRatio - 1; self.liquids[self.liquids.length - 1].ratio -= excessRatio; if (self.liquids[self.liquids.length - 1].ratio <= 0) { self.liquids.pop(); } } self.topOfLiquidsY = self.baseLiquidY; for (var i = 0; i < self.liquids.length; i++) { self.topOfLiquidsY -= self.fillHeight * self.liquids[i].ratio; } }; self.removeLiquid = function (ratio) { if (self.liquids.length > 0) { var topLiquid = self.liquids[self.liquids.length - 1]; if (ratio >= topLiquid.ratio) { var removedLiquid = self.liquids.pop(); if (self.liquids.length > 0 && self.liquids[self.liquids.length - 1].color === removedLiquid.color) { self.liquids[self.liquids.length - 1].ratio += removedLiquid.ratio; } self.topOfLiquidsY = self.baseLiquidY; for (var i = 0; i < self.liquids.length; i++) { self.topOfLiquidsY -= self.fillHeight * self.liquids[i].ratio; } return removedLiquid; } else { topLiquid.ratio = Math.round((topLiquid.ratio - ratio) / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO; // Fix floating-point precision issue var removedRatio = Math.round(ratio / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO; removedRatio = parseFloat(removedRatio.toFixed(2)); if (topLiquid.ratio <= 0) { self.liquids.pop(); } self.topOfLiquidsY = self.baseLiquidY; for (var i = 0; i < self.liquids.length; i++) { self.topOfLiquidsY -= self.fillHeight * self.liquids[i].ratio; } return { color: topLiquid.color, ratio: removedRatio }; } } return null; }; self.containsPoint = function (point) { var bounds = self.getBounds(); return point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y + bounds.height; }; self.canPour = function (destinationTube) { if (self.liquids.length === 0) { return 0; // No liquid to pour } if (destinationTube.liquids.length === 0) { return 1; // Destination tube is empty, can pour all } var topLiquid = self.liquids[self.liquids.length - 1]; var destinationTopLiquid = destinationTube.liquids[destinationTube.liquids.length - 1]; if (topLiquid.color !== destinationTopLiquid.color) { return 0; // Different colors, cannot pour } var sameColorCount = 0; for (var i = self.liquids.length - 1; i >= 0; i--) { if (self.liquids[i].color === topLiquid.color) { sameColorCount++; } else { break; } } var totalRatio = destinationTube.liquids.reduce(function (sum, liquid) { return sum + liquid.ratio; }, 0); var availableSpace = 1 - totalRatio; return Math.min(sameColorCount, availableSpace) / sameColorCount; }; self.renderLiquids = function () { // Remove all existing liquid graphics for (var i = self.children.length - 1; i >= 0; i--) { if (self.children[i] !== tubeGraphics) { self.removeChild(self.children[i]); } } // Add new liquid graphics based on the current state of self.liquids var cumulativeHeight = 0; for (var j = 0; j < self.liquids.length; j++) { var liquid = self.liquids[j]; var liquidGraphics = self.attachAsset('liquidBase', { anchorX: 0.5, anchorY: 1.0 }); liquidGraphics.tint = colorList[liquid.color]; liquidGraphics.y = self.baseLiquidY - cumulativeHeight; // Adjust the offset to prevent liquids from appearing out of the vial bottom liquidGraphics.height = self.fillHeight * liquid.ratio; cumulativeHeight += liquidGraphics.height; self.addChildAt(liquidGraphics, 0); // Add debug text for liquidGraphics.y for index 0 and tubeGraphics.height if (j === 0) { updateDebugText('.y for index 0: ' + liquidGraphics.y + ' | .height: ' + tubeGraphics.height); } } }; self.handleTapOnSelectedTube = function () { self.scale.set(1, 1); // Reset the scale of the selected tube self.y += selectionYOffset; // Restore the tube to its initial position LK.getSound('drop').play(); selectedTube = null; // Unselect the tube }; self.handleTapOnAnotherTube = function () { var pouringColor = selectedTube.liquids.length > 0 ? selectedTube.liquids[selectedTube.liquids.length - 1].color : null; if (!pouringColor) { return; // Exit if there is no liquid to pour } var sourceMaxRatio = 0; if (selectedTube.liquids.length) { sourceMaxRatio = selectedTube.liquids[selectedTube.liquids.length - 1].ratio; } var destinationMaxRatio = selectedTube.canPour(self); var pourRatio = Math.min(sourceMaxRatio, destinationMaxRatio); log("sourceMaxRatio/destinationMaxRatio/pourRatio/color:", sourceMaxRatio, destinationMaxRatio, pourRatio, pouringColor); if (pourRatio > 0) { isTryingToPour = true; // Set the flag to prevent new selections moveMade = true; // Set the moveMade flag to true when the first tube is selected restartButton.visible = true; // Show the restartButton after moveMade // Phase 1: Move the selected vial near the destination vial, then rotate it var originalX = selectedTube.x; var originalY = selectedTube.y; var targetX = selectedTube.x < self.x ? self.x - 50 : self.x + 50; // Adjust the target position based on the side of the destination tube var targetY = self.y - 200; // Adjust the target position as needed var rotationDirection = selectedTube.x < self.x ? Math.PI / 4 : -Math.PI / 4; // Determine rotation direction var moveAndRotate = function moveAndRotate() { selectedTube.x = targetX; selectedTube.y = targetY; selectedTube.rotation = rotationDirection; // Rotate the selected tube // Initialize a liquidStream at the top of the selected tube if (selectedTube && selectedTube.liquids.length > 0) { isPouring = true; if (!liquidStream) { liquidStream = new LiquidStream(pouringColor); game.addChildAt(liquidStream, 0); // Ensure liquidStream is behind vials } var xDelta = selectedTube.x < self.x ? 50 : -50; liquidStream.init(selectedTube.x + xDelta, selectedTube.y - selectedTube.height / 2, colorList[pouringColor]); } // Make the LiquidStream height grow to simulate pouring LK.getSound('pouring').play(); // Find source Top liquid graphic var sourceTopLiquid = selectedTube.children.find(function (child) { return child.tint === colorList[pouringColor]; }); // Create a fakeSourceLiquid with pouringColor, size, coordinates, and rotation of source Liquid var fakeSourceLiquid = new LiquidBase(pouringColor); fakeSourceLiquid.children[0].height = sourceTopLiquid.height; fakeSourceLiquid.children[0].width = sourceTopLiquid.width; fakeSourceLiquid.children[0].x = sourceTopLiquid.x; fakeSourceLiquid.children[0].y = sourceTopLiquid.y; fakeSourceLiquid.children[0].rotation = -sourceTopLiquid.rotation + Math.PI; log("sourceTopLiquid :", sourceTopLiquid); log("fakeSourceLiquid :", fakeSourceLiquid); selectedTube.addChildAt(fakeSourceLiquid, 0); // Set source top liquid alpha to 0 sourceTopLiquid.alpha = 0; // Find destination Top liquid var destinationTopLiquid = self.liquids.length > 0 ? self.liquids[self.liquids.length - 1] : null; // Create a fakeDestinationLiquid with pouringColor, and place it on top of destination Top liquid with height 0 var fakeDestinationLiquid = new LiquidBase(pouringColor); fakeDestinationLiquid.height = 0; fakeDestinationLiquid.rotation = Math.PI; // Rotate by 180 degrees fakeDestinationLiquid.width = sourceTopLiquid.width; fakeDestinationLiquid.x = 0; fakeDestinationLiquid.y = self.baseLiquidY - (destinationTopLiquid ? destinationTopLiquid.ratio * self.fillHeight : 0); if (destinationTopLiquid) { log("fakeDestinationLiquid :", destinationTopLiquid, self.baseLiquidY, self.y); } else { log("fakeDestinationLiquid : destinationTopLiquid is null", self.baseLiquidY, self.y); } //fakeDestinationLiquid.y += 700; //pourRatio * destinationTopLiquid.fillHeight; self.addChildAt(fakeDestinationLiquid, 0); // Animate fakeSourceLiquid.height diminution to 0 // Animate fakeDestinationLiquid.height augmentation to pourRatio*fillHeight var pourInterval = LK.setInterval(function () { if (selectedTube && selectedTube.liquids.length > 0 && liquidStream.height < selectedTube.height) { liquidStream.height += 20; // Adjust the growth rate as needed log("Grow liquidStream :", liquidStream.height, liquidStream.x, liquidStream.y); fakeSourceLiquid.children[0].height = Math.max(fakeSourceLiquid.children[0].height - 20, self.fillHeight * (selectedTube.liquids[selectedTube.liquids.length - 1].ratio - pourRatio)); fakeDestinationLiquid.height = Math.min(fakeDestinationLiquid.height + 20, self.fillHeight * pourRatio); log("pourRatio:", pourRatio); } else { LK.clearInterval(pourInterval); liquidStream.stop(); // Call stop function at the end of pouring isPouring = false; LK.getSound('pouring').stop(); // Stop pouring sound if (selectedTube) { selectedTube.x = originalX; selectedTube.y = originalY; selectedTube.rotation = 0; // Reset rotation selectedTube.removeChild(fakeSourceLiquid); } if (self) { self.removeChild(fakeDestinationLiquid); } } }, 16); // 60 FPS // Proceed to the next phase after a short delay LK.setTimeout(function () { if (selectedTube && selectedTube.liquids.length > 0) { var liquid = selectedTube.removeLiquid(Math.min(pourRatio, selectedTube.liquids[selectedTube.liquids.length - 1].ratio)); if (liquid) { self.addLiquid(liquid, liquid.ratio); } selectedTube.x = originalX; selectedTube.y = originalY; selectedTube.rotation = 0; // Reset rotation selectedTube.y += selectionYOffset; // Restore the selected tube to its initial position selectedTube.scale.set(1, 1); // Reset the scale of the previously selected tube selectedTube.renderLiquids(); self.renderLiquids(); selectedTube = null; } if (puzzleManager.checkWinCondition()) { gameWin = true; LK.getSound('goodJob').play(); // Play 'goodJob' sound // Level up and initialize a new round currentLevel++; levelTxt.setText('Level ' + currentLevel); LK.setTimeout(function () { initRound(); }, 2000); } else { if (isPlaying && self.liquids[self.liquids.length - 1].ratio === 1) { LK.getSound('yes').play(); } else { LK.getSound('drop').play(); } } log("Pour end 1"); LK.setTimeout(function () { log("Pour end 2"); isTryingToPour = false; }, 300); }, 700 * pourRatio); // Adjust the delay as needed }; // Move the selected tube to the target position moveInterval = LK.setInterval(function () { if (selectedTube && Math.abs(selectedTube.x - targetX) < 5 && Math.abs(selectedTube.y - targetY) < 5) { LK.clearInterval(moveInterval); moveAndRotate(); } else if (selectedTube) { log("Move end 3"); selectedTube.x += (targetX - selectedTube.x) * 0.1; selectedTube.y += (targetY - selectedTube.y) * 0.1; selectedTube.rotation += (rotationDirection - selectedTube.rotation) * 0.1; // Rotate the tube while moving } }, 16); // 60 FPS } else { log("Wrong tube..."); if (selectedTube) { selectedTube.scale.set(1, 1); // Reset the scale if selection is wrong selectedTube.y += selectionYOffset; // Restore the previously selected tube to its initial position LK.getSound('wrong').play(); // Play wrong sound } selectedTube = null; } }; self.handleTapOnFirstTube = function () { LK.getSound('tap').play(); selectedTube = self; log("Selecting 1st tube..."); self.y -= selectionYOffset; // Move the selected tube up by selectionYOffset if (self.parent === game) { game.setChildIndex(self, game.children.length - 1); // Bring the selected vial to the front } }; self.down = function (x, y, obj) { log("Down on tube at", x, y); var liquidInfo = self.liquids.map(function (liquid) { return liquid.color + ": " + liquid.ratio; }).join(", "); updateDebugText("Vial : " + liquidInfo); log("down. Status : roundLost || gameWin || isTryingToPour ", roundLost, gameWin, isTryingToPour); if (roundLost || gameWin || isTryingToPour || !isPlaying) { log("Already trying to pour..."); return; // Ignore taps while pouring } if ((self.x !== self.baseX || self.y !== self.baseY) && selectedTube !== self) { return; // Ignore taps if the vial is not at its initial position and it's not the selected tube } else if (selectedTube && selectedTube === self) { self.handleTapOnSelectedTube(); } else if (selectedTube) { self.handleTapOnAnotherTube(); } else { self.handleTapOnFirstTube(); } }; self.boil = function () { self.bubbleInterval = createBubbleInterval(self); // Update bubble spawn interval dynamically based on remaining time var updateBubbleInterval = LK.setInterval(function () { if (self.bubbleInterval) { LK.clearInterval(self.bubbleInterval); self.bubbleInterval = createBubbleInterval(self); } }, 1000); // Update interval every second }; self.stopBoil = function () { if (self.bubbleInterval) { LK.clearInterval(self.bubbleInterval); self.bubbleInterval = null; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 //Init game with black background }); /**** * Game Code ****/ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) { return r; } } var gameWin = false; function createFlashEffect(color) { // Use LK flash screen effect var startDelay = Math.random() * 300; // Random start delay between 0ms and 500ms var duration = 1500; // Fixed duration of 500ms LK.setTimeout(function () { LK.effects.flashScreen(color, duration); // Flash screen with the given color for the fixed duration }, startDelay); } function explodeAnim(x, y, color) { explosionAnimEnded = false; log("explodeAnim called with parameters:", x, y, color); var explosionContainer = new Container(); explosionContainer.x = x; explosionContainer.y = y; var nbParticles = 10; log("Creating explosion particles"); LK.getSound('boom').play(); createFlashEffect(color); for (var i = 0; i < nbParticles; i++) { var particle = LK.getAsset('bubble', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: Math.random() * 0.75 + 0.75, scaleY: Math.random() * 0.75 + 0.75, rotation: Math.random() * Math.PI * 2, alpha: 1 }); particle.tint = color; explosionContainer.addChild(particle); var speed = Math.random() * 5 + 10; var angle = -1 * Math.PI * 0.75 + 0.5 * Math.PI * Math.random(); var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; (function (particle, vx, vy, onComplete) { var particleInterval = LK.setInterval(function () { particle.x += vx; particle.y += vy; vx *= 1.05; vy *= 1.05; particle.scale.x *= 1.03; // Exponential growth particle.scale.y *= 1.03; // Exponential growth //log("particle:", particle.x + explosionContainer.x, particle.y + explosionContainer.y, particle.y + explosionContainer.y < 0); if (particle.x + explosionContainer.x < 0 || particle.x + explosionContainer.x > 2048 || particle.y + explosionContainer.y < 0 || particle.y + explosionContainer.y > 2732) { explosionContainer.removeChild(particle); LK.clearInterval(particleInterval); if (onComplete) { onComplete(); } } }, 16); })(particle, vx, vy, function () { particlesCompleted++; if (particlesCompleted === nbParticles) { log("Explosion end"); if (explosionContainer) { game.removeChild(explosionContainer); } // Add witchBlack asset and make it slide in from the bottom then slide out from bottom var witchBlack = LK.getAsset('witchBlack', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 + LK.getAsset('witchBlack', {}).height // Start off-screen at the bottom }); game.addChild(witchBlack); // Slide in from the bottom var slideSpeed = 30; var slideInInterval = LK.setInterval(function () { witchBlack.y -= slideSpeed; // Adjust the speed of sliding in if (witchBlack.y <= 2732 / 2) { LK.clearInterval(slideInInterval); LK.getSound('tryAgain').play(); // Slide out to the bottom after a delay LK.setTimeout(function () { var slideOutInterval = LK.setInterval(function () { witchBlack.y += slideSpeed; // Adjust the speed of sliding out if (witchBlack.y >= 2732 + LK.getAsset('witchBlack', {}).height) { LK.clearInterval(slideOutInterval); game.removeChild(witchBlack); // Animate broken vials fade out var fadeOutInterval = LK.setInterval(function () { for (var i = 0; i < brokenVialsContainer.children.length; i++) { var brokenVial = brokenVialsContainer.children[i]; brokenVial.alpha -= 0.05; // Adjust the fade-out speed as needed if (brokenVial.alpha <= 0) { brokenVialsContainer.removeChild(brokenVial); i--; // Adjust index after removal } } if (brokenVialsContainer.children.length === 0) { LK.clearInterval(fadeOutInterval); explosionAnimEnded = true; } }, 16); // 60 FPS } }, 16); // 60 FPS }, 1500); // Wait for 2 seconds before starting the slide out } }, 16); // 60 FPS } }); } game.addChild(explosionContainer); // Stop all boiling vials if (puzzleManager && puzzleManager.vials) { log("Stopping all boiling vials"); puzzleManager.vials.forEach(function (vial) { vial.stopBoil(); }); } } function handlePostExplosion() { // Wait for user tap to call initRound(true) log("Waiting for user tap to call initRound(true)"); game.down = function (x, y, obj) { if (roundLost && explosionAnimEnded) { initRound(true); } }; } function createBubbleInterval(vial) { vial.topOfLiquidsY = vial.baseLiquidY; for (var i = 0; i < vial.liquids.length; i++) { vial.topOfLiquidsY -= vial.fillHeight * vial.liquids[i].ratio; } if (vial.liquids.length === 0 || vial.liquids.length === 1 && vial.liquids[0].ratio === 1) { return; // No liquids or full with one liquid, no boil } var delay = Math.random() * 100; return LK.setInterval(function () { var rand = Math.random(); var scale = rand + 0.75; var alpha = 0.5 + rand * 0.5; var color = 0xFFFFFF; // Random tint color var x = 5 + rand * 150 - 80; var r = rand * Math.PI; var bubble = vial.attachAsset('bubble', { anchorX: 0.5, anchorY: 1.0, x: x, y: vial.height - 20, alpha: alpha, scaleX: scale, scaleY: scale, rotation: r }); vial.addChild(bubble); var bubbleRiseInterval = LK.setInterval(function () { bubble.y -= 10 * (1 + (roundDuration - timeLeft) / roundDuration); // Increase speed over time bubble.scale.x += 0.05 * (roundDuration - timeLeft) / roundDuration; // Increase size over time bubble.scale.y += 0.05 * (roundDuration - timeLeft) / roundDuration; // Increase size over time var topLiquidY = vial.topOfLiquidsY + 15; // offset to prevent bubble outside if (bubble.y <= topLiquidY - 10) { vial.removeChild(bubble); LK.clearInterval(bubbleRiseInterval); } }, 16); // 60 FPS }, Math.max(5, delay + 1000 * (timeLeft / roundDuration))); // Initial interval } function deepCopyVials(vials) { return (vials || []).map(function (vial) { var newVial = new Vial(); vial.liquids.forEach(function (liquid) { newVial.addLiquid(new LiquidBase(liquid.color), liquid.ratio); }); return newVial; }); } // Function to update debug text function updateDebugText(info) { if (isDebug && debugTxt) { debugTxt.setText(info); } } // Create an instance of the PuzzleManager var PuzzleManager = function PuzzleManager() { var self = this; self.liquids = []; // Public property to store liquids self.initVials = function () { var positions = []; var nbVials = Math.min(currentLevel + 1, 10); log("initVials : nbVials =", nbVials); if (nbVials > maxPerLine) { log("Bigger board..."); board.y = 2732 / 2 + 100; board.height = 2000; } self.vials = []; // Add liquidStream to the game before vials to ensure it is always behind if (!liquidStream) { liquidStream = new LiquidStream('blue'); // Default color, will be updated later game.addChild(liquidStream); } for (var i = 0; i < nbVials; i++) { self.vials.push(new Vial()); } var spacingX = 2048 / (Math.min(nbVials, maxPerLine) + 1); var tubeHeight = LK.getAsset('tube', {}).height; var spacingY = nbVials <= maxPerLine ? (2732 - tubeHeight) / 2 : 2732 / Math.ceil(nbVials / maxPerLine); for (var i = 0; i < nbVials; i++) { positions.push({ x: spacingX * (i % maxPerLine + 1), y: nbVials <= maxPerLine ? (2732 - tubeHeight) / 2 : spacingY * Math.floor(i / maxPerLine) + 500 }); } for (var i = 0; i < self.vials.length; i++) { self.vials[i].x = positions[i].x; self.vials[i].y = positions[i].y; // Adjust if (self.vials.length > maxPerLine) { if (i < maxPerLine) { self.vials[i].y += 380; // Move the first row up by 300 } else { self.vials[i].y -= 280; // Move the second row up by 500 } } self.vials[i].baseX = self.vials[i].x; // Store base x-coordinate self.vials[i].baseY = self.vials[i].y; // Store base y-coordinate game.addChild(self.vials[i]); } }; // Initialize liquids self.initLiquids = function (level) { // Create and initialize liquid objects based on level if (level === 1) { self.liquids = [new LiquidBase('green')]; } else { if (level === 2) { self.liquids = [new LiquidBase('blue'), new LiquidBase('green')]; } else { self.liquids = []; for (var i = 0; i < level; i++) { var colorName = Object.keys(colorList)[i % Object.keys(colorList).length]; self.liquids.push(new LiquidBase(colorName)); } } } }; self.distributeLiquids = function () { // Helper function to shuffle an array function shuffle(array) { for (var i = array.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = array[i]; array[i] = array[j]; array[j] = temp; } } // Distribute initialized liquids into the provided vials if (self.vials.length === 0) { return; } if (shortFormatDistributions[currentLevel]) { var decodedDistribution = decodeShortFormat(shortFormatDistributions[currentLevel]); if (decodedDistribution) { decodedDistribution.forEach(function (distribution) { self.vials[distribution.vial].addLiquid(self.liquids[distribution.liquid], distribution.ratio); }); } else { console.error("Decoded distribution for level " + currentLevel + " is undefined."); } } else { var totalPortions = (self.vials.length - 1) * (1 / BASE_LIQUID_RATIO); var portionsPerColor = Math.floor(totalPortions / self.liquids.length); var portions = self.liquids.flatMap(function (liquid) { return Array(portionsPerColor).fill(liquid.color); }); shuffle(portions); var attempts = 0; var maxAttempts = 1000; while (attempts < maxAttempts) { var portionIndex = 0; for (var i = 0; i < self.vials.length - 1; i++) { self.vials[i].liquids = []; var vialPortions = totalPortions / (self.vials.length - 1); for (var j = 0; j < vialPortions; j++) { var color = portions[portionIndex++]; var liquid = self.liquids.find(function (l) { return l.color === color; }); if (liquid) { self.vials[i].addLiquid(liquid, BASE_LIQUID_RATIO); } } } if (self.isSolvable()) { break; } attempts++; shuffle(portions); } lastDistributionConfig = deepCopyVials(self.vials); } }; self.isSolvable = function () { var visitedStates = []; var stateQueue = []; function serializeState(vials) { return vials.map(function (vial) { return vial.liquids.map(function (liquid) { return liquid.color + ":" + liquid.ratio; }).join(","); }).join("|"); } function isSolved(vials) { return vials.every(function (vial) { return vial.liquids.length === 0 || vial.liquids.every(function (liquid) { return liquid.color === vial.liquids[0].color; }); }); } function getNextStates(vials) { var nextStates = []; for (var i = 0; i < vials.length; i++) { for (var j = 0; j < vials.length; j++) { if (i !== j && vials[i].canPour(vials[j]) > 0) { var newVials = deepCopyVials(vials); var liquid = newVials[i].removeLiquid(newVials[i].canPour(newVials[j])); newVials[j].addLiquid(liquid, liquid.ratio); nextStates.push(newVials); } } } return nextStates; } stateQueue.push(deepCopyVials(self.vials)); visitedStates.push(serializeState(self.vials)); var maxDepth = 1000; var heuristicLimit = 100; var heuristicChecks = 0; var depth = 0; while (stateQueue.length > 0 && depth < maxDepth) { var currentState = stateQueue.shift(); if (isSolved(currentState)) { return true; } var nextStates = getNextStates(currentState); heuristicChecks++; if (heuristicChecks > heuristicLimit) { return false; } for (var k = 0; k < nextStates.length; k++) { var nextState = nextStates[k]; var serializedState = serializeState(nextState); if (visitedStates.indexOf(serializedState) === -1) { visitedStates.push(serializedState); stateQueue.push(nextState); } } depth++; } return false; }; self.reloadDistribution = function () { var savedVials = lastDistributionConfig; if (self.vials && Array.isArray(self.vials) && self.vials.length > 0) { self.vials.forEach(function (vial, i) { if (savedVials[i]) { vial.liquids = savedVials[i].liquids.map(function (liquid) { return { color: liquid.color, ratio: liquid.ratio }; }); vial.renderLiquids(); } }); } }; // Check for win conditions self.checkWinCondition = function () { // Implement logic to check if the puzzle is solved var colorArray = []; if (!self.vials || self.vials.length === 0) { return false; // No vials to check } for (var i = 0; i < self.vials.length; i++) { if (self.vials[i].liquids.length > 0) { var firstColor = self.vials[i].liquids[0].color; if (colorArray.indexOf(firstColor) !== -1) { return false; // Color already found in another vial } colorArray.push(firstColor); for (var j = 1; j < self.vials[i].liquids.length; j++) { if (self.vials[i].liquids[j].color !== firstColor) { return false; // Different colors in the same vial } } } } return true; }; // Initialize the puzzle manager self.init = function (level) { self.initVials(); self.initLiquids(level); self.distributeLiquids(); for (var i = 0; i < self.vials.length; i++) { self.vials[i].renderLiquids(); } updateDebugText('Puzzle Solvable: ' + self.isSolvable()); }; return self; }; var BASE_LIQUID_RATIO = 0.25; var puzzleManager; var rulesContainer; var currentLevel = 10; // TEMP DEBUG 1; var maxPerLine = 5; var score = 0; var preStarted = false; var showedRules = false; var isPlaying = false; var selectedTube; var roundDuration = 240; // Global variable for round duration var timeLeft = roundDuration; // Initialize timeLeft with roundDuration var roundLost = false; var selectionYOffset = 300; var distributionConfigurations = []; var lastDistributionConfig = null; var pourInterval; var moveInterval; var hourglassAnimationInterval; var initialHourglassWidth = 100; var initialHourglassHeight = 186.51; var particlesCompleted = 0; var isTryingToPour = false; var moveMade = false; // Flag to indicate that a move was made (1st tube selected in the round) var explosionAnimEnded = false; var isPouring = false; var liquidStream; var background; var scoreTxt; var timerTxt; var debugTxt; var hourglass; var restartButton; var brokenVialsContainer; var isDebug = true; var board; var colorList = { blue: 0x0000FF, // DodgerBlue green: 0x00FF00, // LimeGreen red: 0xFF0000, // OrangeRed white: 0xFFFFFF, // White yellow: 0xFFFF00, // Yellow purple: 0x800080, // Purple cyan: 0x00FFFF, // Cyan magenta: 0xFF00FF, // Magenta orange: 0xFFA500, // Orange pink: 0xFFC0CB // Pink }; var levelDistributions = {}; var shortFormatDistributions = { 1: "0,1|0,3", 2: "0,2|0,2;1,2|1,2", 3: "0,1;1,2;2,1|0,3;2,1|1,2;2,2", 4: "1,1;2,1;3,2|0,2;1,1;3,1|1,2;2,2|0,2;2,1;3,1", 5: "0,1;1,2;4,1|2,2;3,2|0,3;1,1|1,1;2,2;4,1|3,2;4,2", 6: "1,1;2,2;4,1|0,2;1,1;2,1|3,2;4,1;5,1|2,1;3,1;5,2|0,2;4,2|1,2;3,1;5,1", 7: "2,1;4,1;5,2|1,2;2,1;4,1|5,1;6,3|0,2;2,1;4,1|1,1;3,1;4,1;6,1|3,3;5,1|0,2;1,1;2,1", 8: "0,3;1,1|3,1;5,1;6,1;7,1|1,2;3,2|0,1;1,1;4,1;7,1|2,1;3,1;4,1;5,1|2,3;5,1|4,1;6,2;7,1|4,1;5,1;6,1;7,1", 9: "1,1;4,1;5,1;7,1|0,2;1,1;6,1|3,2;4,1;5,1|0,2;1,1;5,1|2,2;3,1;4,1|4,1;5,1;6,1;7,1|1,1;6,1;7,2|2,2;3,1;6,1", 10: "2,1;3,1;8,2|3,1;4,1;5,1;6,1|1,1;2,2;3,1|0,2;1,1;4,1|4,2;6,1;8,1|0,2;6,1;7,1|2,1;3,1;5,1;7,1|5,2;7,1;8,1|1,2;6,1;7,1" }; var boardByLevel = { 1: 'board1', 2: 'board1', 3: 'board2', 4: 'board2', 5: 'board2', 6: 'board3', 7: 'board3', 8: 'board4', 9: 'board4', 10: 'board' }; var timerByLevel = { 1: 30, 2: 60, 3: 90, 4: 120, 5: 180, 6: 200, 7: 260, 8: 280, 9: 260, 10: 300 }; function decodeShortFormat(shortFormat) { var longFormat = {}; var cleanReg = /['"]/g; shortFormat = shortFormat.replaceAll(cleanReg, ''); // Replace all matched characters with an empty string log("decodeShortFormat:", shortFormat); var vials = shortFormat.split('|'); log("vials raw:", vials); var level = vials.length; var distribution = []; vials.forEach(function (vial, vialIndex) { var liquids = vial.split(';'); log("vial #" + vialIndex + " liquids:", liquids); liquids.forEach(function (liquid) { var _liquid$split$map = liquid.split(',').map(Number), _liquid$split$map2 = _slicedToArray(_liquid$split$map, 2), liquidIndex = _liquid$split$map2[0], multiplier = _liquid$split$map2[1]; log("liquid #" + liquidIndex + " => ", multiplier); distribution.push({ vial: vialIndex, liquid: liquidIndex, ratio: multiplier * BASE_LIQUID_RATIO }); }); }); log("distribution :", distribution); return distribution; } function log() { if (isDebug) { var _console; (_console = console).log.apply(_console, arguments); } } // Function to update score function updateScore(newScore) { score = newScore; if (scoreTxt) { if (newScore) { scoreTxt.setText(newScore.toString()); } else { scoreTxt.setText('0'); } } } // Function to update timer function updateTimer(newTime) { if (newTime === roundDuration && hourglass) { // Animate a rotation of PI degrees for the hourglass var initialRotation = hourglass.rotation; var targetRotation = initialRotation + Math.PI; var rotationStep = (targetRotation - initialRotation) / 60; // 60 frames for 1 second animation var rotateInterval = LK.setInterval(function () { if (Math.abs(hourglass.rotation - targetRotation) < Math.abs(rotationStep)) { hourglass.rotation = targetRotation; LK.clearInterval(rotateInterval); } else { hourglass.rotation += rotationStep; } }, 16); // 60 FPS } if (timerTxt) { timerTxt.setText(timeLeft.toString()); if (newTime <= 5) { timerTxt.tint = 0xFF0000; // Tint to red } else { timerTxt.tint = 0xFFFFFF; // Reset to white } } if (timeLeft === 10) { animateHourglass(); LK.getSound('hurryUp').play(); } else if (timeLeft === 1) { restartButton.visible = false; LK.clearInterval(hourglassAnimationInterval); hourglass.width = initialHourglassWidth; hourglass.height = initialHourglassHeight; } } function animateHourglass() { initialHourglassWidth = hourglass.width; initialHourglassHeight = hourglass.height; var scaleFactor = 0.1; // Adjust the scale factor as needed var animationSpeed = 0.05; // Adjust the speed of the animation var animate = function animate() { var scale = 1 + scaleFactor * Math.sin(LK.ticks * animationSpeed); hourglass.width = initialHourglassWidth * scale; hourglass.height = initialHourglassHeight * scale; }; hourglassAnimationInterval = LK.setInterval(animate, 16); // 60 FPS } // Game update function game.update = function () { // Update timer if (showedRules && preStarted && !gameWin && LK.ticks % 60 == 0) { // Decrease time every second timeLeft -= 1; if (timeLeft <= 0) { if (!roundLost) { isPlaying = false; roundLost = true; updateTimer(0); if (selectedTube) { selectedTube.scale.set(1, 1); // Reset the scale of the selected tube selectedTube.y += selectionYOffset; // Restore the tube to its initial position selectedTube = null; // Unselect the tube } levelTxt.visible = false; // Set the LevelText invisible when the level is lost if (puzzleManager && puzzleManager.vials) { puzzleManager.vials.forEach(function (vial) { vial.liquids.forEach(function (liquid, index) { explodeAnim(vial.x, vial.y, colorList[liquid.color]); var brokenVial = LK.getAsset('brokenVials', { anchorX: 0.5, anchorY: 0, x: vial.x, y: vial.baseY, // Adjust y position to match the vial's base y position rotation: Math.random() * Math.PI * 2 // Add random rotation }); brokenVialsContainer.addChild(brokenVial); vial.alpha = 0; // Hide the whole vial }); }); } handlePostExplosion(); } } else { updateTimer(timeLeft - 1); // Progressively start boiling all vials based on timer if (puzzleManager && puzzleManager.vials) { puzzleManager.vials.forEach(function (vial) { if (timeLeft <= roundDuration * 0.75 && !vial.bubbleInterval) { vial.boil(); } }); } } } }; function initUI() { // Shuffle colorList var colorKeys = Object.keys(colorList); for (var i = colorKeys.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = colorList[colorKeys[i]]; colorList[colorKeys[i]] = colorList[colorKeys[j]]; colorList[colorKeys[j]] = temp; } // Add header asset at the top of the screen var header = LK.getAsset('header', { anchorX: 0.5, anchorY: 0.0, x: 2048 / 2, y: 0, alpha: 0.75 }); game.addChild(header); // Add board asset board = LK.getAsset('board', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.22, // Increase the size of the board scaleY: 1.2, // Increase the size of the board x: 2048 / 2, y: 2732 / 2 + 100 }); board.height = 2000; game.addChild(board); scoreTxt = new Text2('0', { size: 140, fill: "#ffffff" }); scoreTxt.anchor.set(0.5, 0); scoreTxt.visible = false; // Disabled for now LK.gui.top.addChild(scoreTxt); timerTxt = new Text2('240', { size: 140, fill: "#ffffff" }); timerTxt.anchor.set(1, 0); LK.gui.topRight.addChild(timerTxt); // Add hourglass asset at the top right hourglass = LK.getAsset('hourglass', { anchorX: 0.5, anchorY: 0.5, x: 1640, y: 123 }); game.addChild(hourglass); // Create debug text debugTxt = new Text2('Debug Info', { size: 50, fill: "#FFFFFF" }); debugTxt.anchor.set(0, 1); LK.gui.bottomLeft.addChild(debugTxt); debugTxt.visible = isDebug; levelTxt = new Text2('Level ' + currentLevel, { size: 60, fill: "#ffffff" }); levelTxt.anchor.set(0.5, 1.75); LK.gui.bottom.addChild(levelTxt); restartButton = LK.getAsset('restartRound', { anchorX: 0.5, anchorY: 0.5, x: 612, y: 130 }); game.addChild(restartButton); restartButton.down = function () { LK.getSound('reset').play(); LK.effects.flashScreen(0xFFFFFF, 500); // Flash screen white for 500ms initRound(true); }; } function initRound(reset) { log("initRound started with reset:", reset); // Remove previous vials if (isPouring) { log("Removing previous vials"); if (pourInterval) { LK.clearInterval(pourInterval); } isPouring = false; liquidStream.stop(); } if (moveInterval) { LK.clearInterval(moveInterval); } if (typeof selectedTube !== 'undefined' && selectedTube) { selectedTube.x = selectedTube.baseX; selectedTube.y = selectedTube.baseY; selectedTube.rotation = 0; selectedTube = null; } isTryingToPour = false; if (puzzleManager.vials) { for (var i = 0; i < puzzleManager.vials.length; i++) { puzzleManager.vials[i].stopBoil(); puzzleManager.vials[i].alpha = 1; // Restore the alpha of each vial to 1 } } brokenVialsContainer.removeChildren(); if (reset && lastDistributionConfig) { log("Reloading last distribution configuration:", lastDistributionConfig); puzzleManager.reloadDistribution(); if (timeLeft <= 0) { roundDuration = timerByLevel[currentLevel] || 240; // Set roundDuration based on the current level or default to 240 timeLeft = roundDuration; // Initialize the timer with the dynamic round duration updateTimer(timeLeft); if (hourglassAnimationInterval) { LK.clearInterval(hourglassAnimationInterval); hourglass.width = initialHourglassWidth; hourglass.height = initialHourglassHeight; } } } else { if (typeof puzzleManager === 'undefined') { puzzleManager = new PuzzleManager(); } roundDuration = timerByLevel[currentLevel] || 240; // Set roundDuration based on the current level or default to 240 timeLeft = roundDuration; // Initialize the timer with the dynamic round duration updateTimer(timeLeft); if (hourglassAnimationInterval) { LK.clearInterval(hourglassAnimationInterval); hourglass.width = initialHourglassWidth; hourglass.height = initialHourglassHeight; } // Change the board asset depending on the current level using boardByLevel if (boardByLevel[currentLevel]) { game.removeChild(board); board = LK.getAsset(boardByLevel[currentLevel], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.22, scaleY: 1.2, x: 2048 / 2, y: 2732 / 2 + 100 }); board.height = 2000; game.addChildAt(board, 1); // Ensure the board is just in front of the background } if (puzzleManager.vials) { for (var i = 0; i < puzzleManager.vials.length; i++) { game.removeChild(puzzleManager.vials[i]); } } log("Initializing puzzle manager for level:", currentLevel); puzzleManager.init(currentLevel); lastDistributionConfig = deepCopyVials(puzzleManager.vials); log("Level configuration saved:", lastDistributionConfig); } if (puzzleManager.vials && puzzleManager.vials.length > 0) { log("Restoring vial properties"); puzzleManager.vials.forEach(function (vial) { vial.scale.set(1, 1); // Reset the scale of each vial vial.x = vial.baseX; // Restore base x-coordinate vial.y = vial.baseY; // Restore base y-coordinate vial.rotation = 0; // Reset rotation }); } roundLost = false; particlesCompleted = 0; gameWin = false; moveMade = false; // Reset the moveMade flag at round initialization restartButton.visible = false; // Hide restartButton if !moveMade levelTxt.visible = true; // Restore the visibility of LevelText in initRound log("initRound completed"); LK.setTimeout(function () { isPlaying = true; }, 600); } // Function to initialize the game function initializeGame() { score = 0; // Initialize the score with 0 initUI(); brokenVialsContainer = new Container(); game.addChild(brokenVialsContainer); updateScore(score); puzzleManager = new PuzzleManager(); initRound(); isPlaying = true; } function initRulesVials() { // Demo Vials to explain the rules // Liquids var greenChalk1 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 325, y: 1480, width: 140, height: 140 }); rulesContainer.addChild(greenChalk1); var greenChalk2 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 603, y: 1480, width: 140, height: 140 }); rulesContainer.addChild(greenChalk2); var greenChalk3 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 885, y: 1480, width: 140, height: 140 }); rulesContainer.addChild(greenChalk3); var greenChalk4 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 1437, y: 1480, width: 140, height: 140 }); rulesContainer.addChild(greenChalk4); var blueChalk5 = LK.getAsset('blueChalk', { anchorX: 0.5, anchorY: 0.5, x: 1715, y: 1480, width: 140, height: 140 }); rulesContainer.addChild(blueChalk5); // Liquid Row 2 - Goal var greenChalk6 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 475, y: 2310, width: 140, height: 120 }); rulesContainer.addChild(greenChalk6); var greenChalk7 = LK.getAsset('blueChalk', { anchorX: 0.5, anchorY: 0.5, x: 475, y: 2155, width: 140, height: 200 }); rulesContainer.addChild(greenChalk7); var greenChalk8 = LK.getAsset('blueChalk', { anchorX: 0.5, anchorY: 0.5, x: 740, y: 2310, width: 140, height: 120 }); rulesContainer.addChild(greenChalk8); var greenChalk9 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 740, y: 2150, width: 140, height: 200 }); rulesContainer.addChild(greenChalk9); var goal = LK.getAsset('goal', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1850 }); rulesContainer.addChild(goal); var greenChalk10 = LK.getAsset('greenChalk', { anchorX: 0.5, anchorY: 0.5, x: 1300, y: 2205, width: 140, height: 320 }); rulesContainer.addChild(greenChalk10); var greenChalk11 = LK.getAsset('blueChalk', { anchorX: 0.5, anchorY: 0.5, x: 1575, y: 2205, width: 140, height: 320 }); rulesContainer.addChild(greenChalk11); // Vials var row1Vials = 6; var row2Vials = 5; var spacingX = (2048 - 100) / (row1Vials + 1); var spacingY = 2732 / 3; for (var i = 0; i < row1Vials; i++) { var vial = new Vial(true); vial.x = 50 + spacingX * (i + 1); vial.y = spacingY + 150; vial.width = 160; vial.height = 512; rulesContainer.addChild(vial); } for (var j = 0; j < row2Vials; j++) { if (j == 2) { continue; } var vial = new Vial(true); vial.x = 200 + (2048 - 400) / (row2Vials + 1) * (j + 1); vial.y = spacingY * 2 + 50; vial.width = 160; vial.height = 512; rulesContainer.addChild(vial); } // Arrows var arrow1 = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, x: 470, y: 1010 }); rulesContainer.addChild(arrow1); var arrow2 = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, x: 1020, y: 1010 }); rulesContainer.addChild(arrow2); var arrow3 = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, x: 1580, y: 1010 }); rulesContainer.addChild(arrow3); // Checks var check1 = LK.getAsset('check', { anchorX: 0.5, anchorY: 0.5, x: 560, y: 940 }); rulesContainer.addChild(check1); var check2 = LK.getAsset('check', { anchorX: 0.5, anchorY: 0.5, x: 1110, y: 940 }); rulesContainer.addChild(check2); var cross1 = LK.getAsset('cross', { anchorX: 0.5, anchorY: 0.5, x: 1690, y: 940 }); rulesContainer.addChild(cross1); // Separators var separator1 = LK.getAsset('separator', { anchorX: 0.5, anchorY: 0.5, x: 740, y: 1280 }); rulesContainer.addChild(separator1); var separator2 = LK.getAsset('separator', { anchorX: 0.5, anchorY: 0.5, x: 1300, y: 1280 }); rulesContainer.addChild(separator2); } // Function to display rules and wait for tap function showRules() { // Add background image background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(background, 0); // Ensure background is behind all other elements rulesContainer = new Container(); game.addChild(rulesContainer); var rulesBoard = LK.getAsset('rulesBoard', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, x: 2048 / 2, y: 2732 / 2 }); rulesBoard.y = 2732 / 2 + 100; rulesBoard.height = 2000; rulesContainer.addChild(rulesBoard); initRulesVials(); var witch = LK.getAsset('witch', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, // Start off-screen to the left y: 2732 / 2 }); rulesContainer.addChild(witch); LK.setTimeout(function () { var slideOutInterval = LK.setInterval(function () { witch.x += 20; // Adjust the speed of sliding in if (witch.x >= 4096) { LK.clearInterval(slideOutInterval); } }, 16); // 60 FPS game.down = function (x, y, obj) { if (showedRules) { return; } showedRules = true; /*game.removeChild(rulesBoard); game.removeChild(witch); */ game.removeChild(rulesContainer); LK.getSound('letsgo').play(); LK.setTimeout(function () { initializeGame(); }, 600); // Wait for 2 seconds before starting the slide out }; }, isDebug ? 100 : 1800); // Wait for 2 seconds before starting the slide out LK.getSound('rememberTheRules').play(); } // Function to display start screen and wait for tap function preStart() { var startScreen = LK.getAsset('startScreen', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(startScreen); var startScreen2 = LK.getAsset('startScreen2', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 34, y: 2732 / 2 + 34, scaleX: 1.05, scaleY: 1.05, alpha: 1 }); game.addChild(startScreen2); var animateAlpha = function animateAlpha() { var increasing = true; var alphaInterval = LK.setInterval(function () { if (increasing) { startScreen2.alpha += 0.01; if (startScreen2.alpha >= 1) { startScreen2.alpha = 1; increasing = false; } } else { startScreen2.alpha -= 0.01; if (startScreen2.alpha <= 0) { startScreen2.alpha = 0; increasing = true; } } }, 60); // 30ms interval for smooth animation }; animateAlpha(); game.down = function (x, y, obj) { if (preStarted) { return; } preStarted = true; LK.getSound('welcome').play(); var fadeOutInterval = LK.setInterval(function () { startScreen.alpha -= 0.01; startScreen.alpha -= isDebug ? 0.02 : 0; if (startScreen.alpha <= 0) { LK.clearInterval(fadeOutInterval); game.removeChild(startScreen); game.removeChild(startScreen2); showRules(); } }, 30); // 30ms interval for 100 steps over 3 seconds }; } preStart(); function handleTapOnSelectedTube() { selectedTube.scale.set(1, 1); // Reset the scale of the selected tube selectedTube.y += selectionYOffset; // Restore the tube to its initial position LK.getSound('drop').play(); selectedTube = null; // Unselect the tube isTryingToPour = false; // Reset the flag to allow new selections } function handleTapOnAnotherTube() { var pouringColor = selectedTube.liquids.length > 0 ? selectedTube.liquids[selectedTube.liquids.length - 1].color : null; if (!pouringColor) { return; // Exit if there is no liquid to pour } var sourceMaxRatio = 0; if (selectedTube.liquids.length) { sourceMaxRatio = selectedTube.liquids[selectedTube.liquids.length - 1].ratio; } var destinationMaxRatio = selectedTube.canPour(self); var pourRatio = Math.min(sourceMaxRatio, destinationMaxRatio); log("sourceMaxRatio/destinationMaxRatio/pourRatio:", sourceMaxRatio, destinationMaxRatio, pourRatio); if (pourRatio > 0) { isTryingToPour = true; // Phase 1: Move the selected vial near the destination vial, then rotate it var originalX = selectedTube.x; var originalY = selectedTube.y; var targetX = selectedTube.x < self.x ? self.x - 50 : self.x + 50; // Adjust the target position based on the side of the destination tube var targetY = self.y - 200; // Adjust the target position as needed var rotationDirection = selectedTube.x < self.x ? Math.PI / 4 : -Math.PI / 4; // Determine rotation direction var moveAndRotate = function moveAndRotate() { selectedTube.x = targetX; selectedTube.y = targetY; selectedTube.rotation = rotationDirection; // Rotate the selected tube // Initialize a liquidStream at the top of the selected tube if (selectedTube && selectedTube.liquids.length > 0) { isPouring = true; if (!liquidStream) { liquidStream = new LiquidStream(pouringColor); game.addChildAt(liquidStream, 0); // Ensure liquidStream is behind vials } var xDelta = selectedTube.x < self.x ? 50 : -50; liquidStream.init(selectedTube.x + xDelta, selectedTube.y - selectedTube.height / 2, pouringColor); } // Make the LiquidStream height grow to simulate pouring LK.getSound('pouring').play(); // Find source Top liquid graphic var sourceTopLiquid = selectedTube.children.find(function (child) { return child.tint === colorList[pouringColor]; }); // Create a fakeSourceLiquid with pouringColor, size, coordinates, and rotation of source Liquid var fakeSourceLiquid = new LiquidBase(pouringColor); fakeSourceLiquid.children[0].height = sourceTopLiquid.height; fakeSourceLiquid.children[0].width = sourceTopLiquid.width; fakeSourceLiquid.children[0].x = sourceTopLiquid.x; fakeSourceLiquid.children[0].y = sourceTopLiquid.y; fakeSourceLiquid.children[0].rotation = -sourceTopLiquid.rotation + Math.PI; log("source Liquid :", selectedTube.liquids[self.liquids.length - 1]); log("fakeSourceLiquid :", fakeSourceLiquid); selectedTube.addChildAt(fakeSourceLiquid, 0); // Set source top liquid alpha to 0 sourceTopLiquid.alpha = 0; // Find destination Top liquid var destinationTopLiquid = self.liquids.length > 0 ? self.liquids[self.liquids.length - 1] : null; // Create a fakeDestinationLiquid with pouringColor, and place it on top of destination Top liquid with height 0 var fakeDestinationLiquid = new LiquidBase(pouringColor); fakeDestinationLiquid.height = 0; fakeDestinationLiquid.rotation = Math.PI; // Rotate by 180 degrees fakeDestinationLiquid.width = sourceTopLiquid.width; fakeDestinationLiquid.x = 0; fakeDestinationLiquid.y = self.baseLiquidY - (destinationTopLiquid ? destinationTopLiquid.ratio * self.fillHeight : 0); if (destinationTopLiquid) { log("fakeDestinationLiquid :", destinationTopLiquid, self.baseLiquidY, self.y); } else { log("fakeDestinationLiquid : destinationTopLiquid is null", self.baseLiquidY, self.y); } //fakeDestinationLiquid.y += 700; //pourRatio * destinationTopLiquid.fillHeight; self.addChildAt(fakeDestinationLiquid, 0); // Animate fakeSourceLiquid.height diminution to 0 // Animate fakeDestinationLiquid.height augmentation to pourRatio*fillHeight var pourInterval = LK.setInterval(function () { if (selectedTube && selectedTube.liquids.length > 0 && liquidStream.height < selectedTube.height) { liquidStream.height += 20; // Adjust the growth rate as needed fakeSourceLiquid.children[0].height = Math.max(fakeSourceLiquid.children[0].height - 20, 0); fakeDestinationLiquid.height = Math.min(fakeDestinationLiquid.height + 20, self.fillHeight * pourRatio); log("pourRatio:", pourRatio); } else { LK.clearInterval(pourInterval); liquidStream.stop(); // Call stop function at the end of pouring isPouring = false; LK.getSound('pouring').stop(); // Stop pouring sound if (selectedTube) { selectedTube.x = originalX; selectedTube.y = originalY; selectedTube.rotation = 0; // Reset rotation selectedTube.removeChild(fakeSourceLiquid); } if (self) { self.removeChild(fakeDestinationLiquid); } } }, 16); // 60 FPS // Proceed to the next phase after a short delay LK.setTimeout(function () { if (selectedTube && selectedTube.liquids.length > 0) { var liquid = selectedTube.removeLiquid(Math.min(pourRatio, selectedTube.liquids[selectedTube.liquids.length - 1].ratio)); if (liquid) { self.addLiquid(liquid, liquid.ratio); } selectedTube.x = originalX; selectedTube.y = originalY; selectedTube.rotation = 0; // Reset rotation selectedTube.y += selectionYOffset; // Restore the selected tube to its initial position selectedTube.scale.set(1, 1); // Reset the scale of the previously selected tube selectedTube.renderLiquids(); self.renderLiquids(); selectedTube = null; } if (puzzleManager.checkWinCondition()) { gameWin = true; LK.getSound('goodJob').play(); // Play 'goodJob' sound // Level up and initialize a new round currentLevel++; levelTxt.setText('Level: ' + currentLevel); LK.setTimeout(function () { initRound(); }, 2000); } else { if (isPlaying && self.liquids[self.liquids.length - 1].ratio === 1) { LK.getSound('yes').play(); } else { LK.getSound('drop').play(); } } isTryingToPour = false; }, 700 * pourRatio); // Adjust the delay as needed }; // Move the selected tube to the target position moveInterval = LK.setInterval(function () { if (selectedTube && Math.abs(selectedTube.x - targetX) < 5 && Math.abs(selectedTube.y - targetY) < 5) { LK.clearInterval(moveInterval); moveAndRotate(); } else if (selectedTube) { selectedTube.x += (targetX - selectedTube.x) * 0.1; selectedTube.y += (targetY - selectedTube.y) * 0.1; selectedTube.rotation += (rotationDirection - selectedTube.rotation) * 0.1; // Rotate the tube while moving } }, 16); // 60 FPS } else { log("Wrong tube..."); if (selectedTube) { selectedTube.scale.set(1, 1); // Reset the scale if selection is wrong selectedTube.y += selectionYOffset; // Restore the previously selected tube to its initial position LK.getSound('wrong').play(); // Play wrong sound } selectedTube = null; } } function handleTapOnFirstTube() { LK.getSound('tap').play(); selectedTube = self; log("Selecting 1st tube..."); this.y -= selectionYOffset; // Move the selected tube up by selectionYOffset if (self.parent === game) { game.setChildIndex(self, game.children.length - 1); // Bring the selected vial to the front } isTryingToPour = true; // Set the flag to prevent new selections }
/****
* Classes
****/
var LiquidBase = Container.expand(function (color) {
var self = Container.call(this);
self.color = color;
var liquidGraphics = self.attachAsset('liquidBase', {
anchorX: 0.5,
anchorY: 0.0
});
liquidGraphics.tint = colorList[color];
return self;
});
var LiquidStream = Container.expand(function (color) {
var self = Container.call(this);
self.color = colorList[color];
var liquidGraphics = self.attachAsset('liquidBase', {
width: 10,
anchorX: 0.5,
anchorY: 0,
alpha: 0
});
liquidGraphics.tint = colorList[color];
self.init = function (x, y, color) {
log("LiquidStream init at ", x, y, color);
self.height = 0;
self.x = x;
self.y = y + 380;
liquidGraphics.tint = color;
liquidGraphics.alpha = 1;
log("LiquidStream init ok:", self);
};
self.stop = function () {
log("LiquidStream stoping:");
liquidGraphics.alpha = 0;
};
return self;
});
var Vial = Container.expand(function (chalk) {
var self = Container.call(this);
// Attach graphical representation
var tubeGraphics = self.attachAsset(chalk ? 'tubeChalk' : 'tube', {
anchorX: 0.5,
anchorY: 0.0,
alpha: chalk ? 1 : 0.5
});
self.fillHeight = tubeGraphics.height - 25 - 100;
self.baseX = 0; // Initialize baseX to store the base x-coordinate
self.baseY = 0; // Initialize baseY to store the base y-coordinate
self.baseLiquidY = tubeGraphics.height - 25;
self.addChild(tubeGraphics);
self.topOfLiquidsY = self.baseLiquidY;
self.liquids = []; // Initialize functional representation
self.addLiquid = function (liquid, ratio) {
var totalRatio = self.liquids.reduce(function (sum, liquid) {
return sum + liquid.ratio;
}, 0);
if (totalRatio + ratio > 1) {
ratio = 1 - totalRatio; // Adjust ratio to fit within the tube's capacity
}
// Fix floating-point precision issue
ratio = Math.round(ratio / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO;
ratio = parseFloat(ratio.toFixed(2));
if (self.liquids.length > 0 && self.liquids[self.liquids.length - 1].color === liquid.color) {
self.liquids[self.liquids.length - 1].ratio = Math.round((self.liquids[self.liquids.length - 1].ratio + ratio) / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO;
} else {
if (ratio > 0) {
var newLiquid = {
color: liquid.color,
index: self.liquids.length,
ratio: ratio
};
self.liquids.push(newLiquid);
}
}
// Ensure the total ratio does not exceed 1
var totalRatio = self.liquids.reduce(function (sum, liquid) {
return sum + liquid.ratio;
}, 0);
if (totalRatio > 1) {
var excessRatio = totalRatio - 1;
self.liquids[self.liquids.length - 1].ratio -= excessRatio;
if (self.liquids[self.liquids.length - 1].ratio <= 0) {
self.liquids.pop();
}
}
self.topOfLiquidsY = self.baseLiquidY;
for (var i = 0; i < self.liquids.length; i++) {
self.topOfLiquidsY -= self.fillHeight * self.liquids[i].ratio;
}
};
self.removeLiquid = function (ratio) {
if (self.liquids.length > 0) {
var topLiquid = self.liquids[self.liquids.length - 1];
if (ratio >= topLiquid.ratio) {
var removedLiquid = self.liquids.pop();
if (self.liquids.length > 0 && self.liquids[self.liquids.length - 1].color === removedLiquid.color) {
self.liquids[self.liquids.length - 1].ratio += removedLiquid.ratio;
}
self.topOfLiquidsY = self.baseLiquidY;
for (var i = 0; i < self.liquids.length; i++) {
self.topOfLiquidsY -= self.fillHeight * self.liquids[i].ratio;
}
return removedLiquid;
} else {
topLiquid.ratio = Math.round((topLiquid.ratio - ratio) / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO;
// Fix floating-point precision issue
var removedRatio = Math.round(ratio / BASE_LIQUID_RATIO) * BASE_LIQUID_RATIO;
removedRatio = parseFloat(removedRatio.toFixed(2));
if (topLiquid.ratio <= 0) {
self.liquids.pop();
}
self.topOfLiquidsY = self.baseLiquidY;
for (var i = 0; i < self.liquids.length; i++) {
self.topOfLiquidsY -= self.fillHeight * self.liquids[i].ratio;
}
return {
color: topLiquid.color,
ratio: removedRatio
};
}
}
return null;
};
self.containsPoint = function (point) {
var bounds = self.getBounds();
return point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y + bounds.height;
};
self.canPour = function (destinationTube) {
if (self.liquids.length === 0) {
return 0; // No liquid to pour
}
if (destinationTube.liquids.length === 0) {
return 1; // Destination tube is empty, can pour all
}
var topLiquid = self.liquids[self.liquids.length - 1];
var destinationTopLiquid = destinationTube.liquids[destinationTube.liquids.length - 1];
if (topLiquid.color !== destinationTopLiquid.color) {
return 0; // Different colors, cannot pour
}
var sameColorCount = 0;
for (var i = self.liquids.length - 1; i >= 0; i--) {
if (self.liquids[i].color === topLiquid.color) {
sameColorCount++;
} else {
break;
}
}
var totalRatio = destinationTube.liquids.reduce(function (sum, liquid) {
return sum + liquid.ratio;
}, 0);
var availableSpace = 1 - totalRatio;
return Math.min(sameColorCount, availableSpace) / sameColorCount;
};
self.renderLiquids = function () {
// Remove all existing liquid graphics
for (var i = self.children.length - 1; i >= 0; i--) {
if (self.children[i] !== tubeGraphics) {
self.removeChild(self.children[i]);
}
}
// Add new liquid graphics based on the current state of self.liquids
var cumulativeHeight = 0;
for (var j = 0; j < self.liquids.length; j++) {
var liquid = self.liquids[j];
var liquidGraphics = self.attachAsset('liquidBase', {
anchorX: 0.5,
anchorY: 1.0
});
liquidGraphics.tint = colorList[liquid.color];
liquidGraphics.y = self.baseLiquidY - cumulativeHeight; // Adjust the offset to prevent liquids from appearing out of the vial bottom
liquidGraphics.height = self.fillHeight * liquid.ratio;
cumulativeHeight += liquidGraphics.height;
self.addChildAt(liquidGraphics, 0);
// Add debug text for liquidGraphics.y for index 0 and tubeGraphics.height
if (j === 0) {
updateDebugText('.y for index 0: ' + liquidGraphics.y + ' | .height: ' + tubeGraphics.height);
}
}
};
self.handleTapOnSelectedTube = function () {
self.scale.set(1, 1); // Reset the scale of the selected tube
self.y += selectionYOffset; // Restore the tube to its initial position
LK.getSound('drop').play();
selectedTube = null; // Unselect the tube
};
self.handleTapOnAnotherTube = function () {
var pouringColor = selectedTube.liquids.length > 0 ? selectedTube.liquids[selectedTube.liquids.length - 1].color : null;
if (!pouringColor) {
return; // Exit if there is no liquid to pour
}
var sourceMaxRatio = 0;
if (selectedTube.liquids.length) {
sourceMaxRatio = selectedTube.liquids[selectedTube.liquids.length - 1].ratio;
}
var destinationMaxRatio = selectedTube.canPour(self);
var pourRatio = Math.min(sourceMaxRatio, destinationMaxRatio);
log("sourceMaxRatio/destinationMaxRatio/pourRatio/color:", sourceMaxRatio, destinationMaxRatio, pourRatio, pouringColor);
if (pourRatio > 0) {
isTryingToPour = true; // Set the flag to prevent new selections
moveMade = true; // Set the moveMade flag to true when the first tube is selected
restartButton.visible = true; // Show the restartButton after moveMade
// Phase 1: Move the selected vial near the destination vial, then rotate it
var originalX = selectedTube.x;
var originalY = selectedTube.y;
var targetX = selectedTube.x < self.x ? self.x - 50 : self.x + 50; // Adjust the target position based on the side of the destination tube
var targetY = self.y - 200; // Adjust the target position as needed
var rotationDirection = selectedTube.x < self.x ? Math.PI / 4 : -Math.PI / 4; // Determine rotation direction
var moveAndRotate = function moveAndRotate() {
selectedTube.x = targetX;
selectedTube.y = targetY;
selectedTube.rotation = rotationDirection; // Rotate the selected tube
// Initialize a liquidStream at the top of the selected tube
if (selectedTube && selectedTube.liquids.length > 0) {
isPouring = true;
if (!liquidStream) {
liquidStream = new LiquidStream(pouringColor);
game.addChildAt(liquidStream, 0); // Ensure liquidStream is behind vials
}
var xDelta = selectedTube.x < self.x ? 50 : -50;
liquidStream.init(selectedTube.x + xDelta, selectedTube.y - selectedTube.height / 2, colorList[pouringColor]);
}
// Make the LiquidStream height grow to simulate pouring
LK.getSound('pouring').play();
// Find source Top liquid graphic
var sourceTopLiquid = selectedTube.children.find(function (child) {
return child.tint === colorList[pouringColor];
});
// Create a fakeSourceLiquid with pouringColor, size, coordinates, and rotation of source Liquid
var fakeSourceLiquid = new LiquidBase(pouringColor);
fakeSourceLiquid.children[0].height = sourceTopLiquid.height;
fakeSourceLiquid.children[0].width = sourceTopLiquid.width;
fakeSourceLiquid.children[0].x = sourceTopLiquid.x;
fakeSourceLiquid.children[0].y = sourceTopLiquid.y;
fakeSourceLiquid.children[0].rotation = -sourceTopLiquid.rotation + Math.PI;
log("sourceTopLiquid :", sourceTopLiquid);
log("fakeSourceLiquid :", fakeSourceLiquid);
selectedTube.addChildAt(fakeSourceLiquid, 0);
// Set source top liquid alpha to 0
sourceTopLiquid.alpha = 0;
// Find destination Top liquid
var destinationTopLiquid = self.liquids.length > 0 ? self.liquids[self.liquids.length - 1] : null;
// Create a fakeDestinationLiquid with pouringColor, and place it on top of destination Top liquid with height 0
var fakeDestinationLiquid = new LiquidBase(pouringColor);
fakeDestinationLiquid.height = 0;
fakeDestinationLiquid.rotation = Math.PI; // Rotate by 180 degrees
fakeDestinationLiquid.width = sourceTopLiquid.width;
fakeDestinationLiquid.x = 0;
fakeDestinationLiquid.y = self.baseLiquidY - (destinationTopLiquid ? destinationTopLiquid.ratio * self.fillHeight : 0);
if (destinationTopLiquid) {
log("fakeDestinationLiquid :", destinationTopLiquid, self.baseLiquidY, self.y);
} else {
log("fakeDestinationLiquid : destinationTopLiquid is null", self.baseLiquidY, self.y);
}
//fakeDestinationLiquid.y += 700; //pourRatio * destinationTopLiquid.fillHeight;
self.addChildAt(fakeDestinationLiquid, 0);
// Animate fakeSourceLiquid.height diminution to 0
// Animate fakeDestinationLiquid.height augmentation to pourRatio*fillHeight
var pourInterval = LK.setInterval(function () {
if (selectedTube && selectedTube.liquids.length > 0 && liquidStream.height < selectedTube.height) {
liquidStream.height += 20; // Adjust the growth rate as needed
log("Grow liquidStream :", liquidStream.height, liquidStream.x, liquidStream.y);
fakeSourceLiquid.children[0].height = Math.max(fakeSourceLiquid.children[0].height - 20, self.fillHeight * (selectedTube.liquids[selectedTube.liquids.length - 1].ratio - pourRatio));
fakeDestinationLiquid.height = Math.min(fakeDestinationLiquid.height + 20, self.fillHeight * pourRatio);
log("pourRatio:", pourRatio);
} else {
LK.clearInterval(pourInterval);
liquidStream.stop(); // Call stop function at the end of pouring
isPouring = false;
LK.getSound('pouring').stop(); // Stop pouring sound
if (selectedTube) {
selectedTube.x = originalX;
selectedTube.y = originalY;
selectedTube.rotation = 0; // Reset rotation
selectedTube.removeChild(fakeSourceLiquid);
}
if (self) {
self.removeChild(fakeDestinationLiquid);
}
}
}, 16); // 60 FPS
// Proceed to the next phase after a short delay
LK.setTimeout(function () {
if (selectedTube && selectedTube.liquids.length > 0) {
var liquid = selectedTube.removeLiquid(Math.min(pourRatio, selectedTube.liquids[selectedTube.liquids.length - 1].ratio));
if (liquid) {
self.addLiquid(liquid, liquid.ratio);
}
selectedTube.x = originalX;
selectedTube.y = originalY;
selectedTube.rotation = 0; // Reset rotation
selectedTube.y += selectionYOffset; // Restore the selected tube to its initial position
selectedTube.scale.set(1, 1); // Reset the scale of the previously selected tube
selectedTube.renderLiquids();
self.renderLiquids();
selectedTube = null;
}
if (puzzleManager.checkWinCondition()) {
gameWin = true;
LK.getSound('goodJob').play(); // Play 'goodJob' sound
// Level up and initialize a new round
currentLevel++;
levelTxt.setText('Level ' + currentLevel);
LK.setTimeout(function () {
initRound();
}, 2000);
} else {
if (isPlaying && self.liquids[self.liquids.length - 1].ratio === 1) {
LK.getSound('yes').play();
} else {
LK.getSound('drop').play();
}
}
log("Pour end 1");
LK.setTimeout(function () {
log("Pour end 2");
isTryingToPour = false;
}, 300);
}, 700 * pourRatio); // Adjust the delay as needed
};
// Move the selected tube to the target position
moveInterval = LK.setInterval(function () {
if (selectedTube && Math.abs(selectedTube.x - targetX) < 5 && Math.abs(selectedTube.y - targetY) < 5) {
LK.clearInterval(moveInterval);
moveAndRotate();
} else if (selectedTube) {
log("Move end 3");
selectedTube.x += (targetX - selectedTube.x) * 0.1;
selectedTube.y += (targetY - selectedTube.y) * 0.1;
selectedTube.rotation += (rotationDirection - selectedTube.rotation) * 0.1; // Rotate the tube while moving
}
}, 16); // 60 FPS
} else {
log("Wrong tube...");
if (selectedTube) {
selectedTube.scale.set(1, 1); // Reset the scale if selection is wrong
selectedTube.y += selectionYOffset; // Restore the previously selected tube to its initial position
LK.getSound('wrong').play(); // Play wrong sound
}
selectedTube = null;
}
};
self.handleTapOnFirstTube = function () {
LK.getSound('tap').play();
selectedTube = self;
log("Selecting 1st tube...");
self.y -= selectionYOffset; // Move the selected tube up by selectionYOffset
if (self.parent === game) {
game.setChildIndex(self, game.children.length - 1); // Bring the selected vial to the front
}
};
self.down = function (x, y, obj) {
log("Down on tube at", x, y);
var liquidInfo = self.liquids.map(function (liquid) {
return liquid.color + ": " + liquid.ratio;
}).join(", ");
updateDebugText("Vial : " + liquidInfo);
log("down. Status : roundLost || gameWin || isTryingToPour ", roundLost, gameWin, isTryingToPour);
if (roundLost || gameWin || isTryingToPour || !isPlaying) {
log("Already trying to pour...");
return; // Ignore taps while pouring
}
if ((self.x !== self.baseX || self.y !== self.baseY) && selectedTube !== self) {
return; // Ignore taps if the vial is not at its initial position and it's not the selected tube
} else if (selectedTube && selectedTube === self) {
self.handleTapOnSelectedTube();
} else if (selectedTube) {
self.handleTapOnAnotherTube();
} else {
self.handleTapOnFirstTube();
}
};
self.boil = function () {
self.bubbleInterval = createBubbleInterval(self);
// Update bubble spawn interval dynamically based on remaining time
var updateBubbleInterval = LK.setInterval(function () {
if (self.bubbleInterval) {
LK.clearInterval(self.bubbleInterval);
self.bubbleInterval = createBubbleInterval(self);
}
}, 1000); // Update interval every second
};
self.stopBoil = function () {
if (self.bubbleInterval) {
LK.clearInterval(self.bubbleInterval);
self.bubbleInterval = null;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 //Init game with black background
});
/****
* Game Code
****/
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) {
return _arrayLikeToArray(r, a);
}
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) {
n[e] = r[e];
}
return n;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) {
return;
}
f = !1;
} else {
for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) {
;
}
}
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) {
return;
}
} finally {
if (o) {
throw n;
}
}
}
return a;
}
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) {
return r;
}
}
var gameWin = false;
function createFlashEffect(color) {
// Use LK flash screen effect
var startDelay = Math.random() * 300; // Random start delay between 0ms and 500ms
var duration = 1500; // Fixed duration of 500ms
LK.setTimeout(function () {
LK.effects.flashScreen(color, duration); // Flash screen with the given color for the fixed duration
}, startDelay);
}
function explodeAnim(x, y, color) {
explosionAnimEnded = false;
log("explodeAnim called with parameters:", x, y, color);
var explosionContainer = new Container();
explosionContainer.x = x;
explosionContainer.y = y;
var nbParticles = 10;
log("Creating explosion particles");
LK.getSound('boom').play();
createFlashEffect(color);
for (var i = 0; i < nbParticles; i++) {
var particle = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: Math.random() * 0.75 + 0.75,
scaleY: Math.random() * 0.75 + 0.75,
rotation: Math.random() * Math.PI * 2,
alpha: 1
});
particle.tint = color;
explosionContainer.addChild(particle);
var speed = Math.random() * 5 + 10;
var angle = -1 * Math.PI * 0.75 + 0.5 * Math.PI * Math.random();
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
(function (particle, vx, vy, onComplete) {
var particleInterval = LK.setInterval(function () {
particle.x += vx;
particle.y += vy;
vx *= 1.05;
vy *= 1.05;
particle.scale.x *= 1.03; // Exponential growth
particle.scale.y *= 1.03; // Exponential growth
//log("particle:", particle.x + explosionContainer.x, particle.y + explosionContainer.y, particle.y + explosionContainer.y < 0);
if (particle.x + explosionContainer.x < 0 || particle.x + explosionContainer.x > 2048 || particle.y + explosionContainer.y < 0 || particle.y + explosionContainer.y > 2732) {
explosionContainer.removeChild(particle);
LK.clearInterval(particleInterval);
if (onComplete) {
onComplete();
}
}
}, 16);
})(particle, vx, vy, function () {
particlesCompleted++;
if (particlesCompleted === nbParticles) {
log("Explosion end");
if (explosionContainer) {
game.removeChild(explosionContainer);
}
// Add witchBlack asset and make it slide in from the bottom then slide out from bottom
var witchBlack = LK.getAsset('witchBlack', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 + LK.getAsset('witchBlack', {}).height // Start off-screen at the bottom
});
game.addChild(witchBlack);
// Slide in from the bottom
var slideSpeed = 30;
var slideInInterval = LK.setInterval(function () {
witchBlack.y -= slideSpeed; // Adjust the speed of sliding in
if (witchBlack.y <= 2732 / 2) {
LK.clearInterval(slideInInterval);
LK.getSound('tryAgain').play();
// Slide out to the bottom after a delay
LK.setTimeout(function () {
var slideOutInterval = LK.setInterval(function () {
witchBlack.y += slideSpeed; // Adjust the speed of sliding out
if (witchBlack.y >= 2732 + LK.getAsset('witchBlack', {}).height) {
LK.clearInterval(slideOutInterval);
game.removeChild(witchBlack);
// Animate broken vials fade out
var fadeOutInterval = LK.setInterval(function () {
for (var i = 0; i < brokenVialsContainer.children.length; i++) {
var brokenVial = brokenVialsContainer.children[i];
brokenVial.alpha -= 0.05; // Adjust the fade-out speed as needed
if (brokenVial.alpha <= 0) {
brokenVialsContainer.removeChild(brokenVial);
i--; // Adjust index after removal
}
}
if (brokenVialsContainer.children.length === 0) {
LK.clearInterval(fadeOutInterval);
explosionAnimEnded = true;
}
}, 16); // 60 FPS
}
}, 16); // 60 FPS
}, 1500); // Wait for 2 seconds before starting the slide out
}
}, 16); // 60 FPS
}
});
}
game.addChild(explosionContainer);
// Stop all boiling vials
if (puzzleManager && puzzleManager.vials) {
log("Stopping all boiling vials");
puzzleManager.vials.forEach(function (vial) {
vial.stopBoil();
});
}
}
function handlePostExplosion() {
// Wait for user tap to call initRound(true)
log("Waiting for user tap to call initRound(true)");
game.down = function (x, y, obj) {
if (roundLost && explosionAnimEnded) {
initRound(true);
}
};
}
function createBubbleInterval(vial) {
vial.topOfLiquidsY = vial.baseLiquidY;
for (var i = 0; i < vial.liquids.length; i++) {
vial.topOfLiquidsY -= vial.fillHeight * vial.liquids[i].ratio;
}
if (vial.liquids.length === 0 || vial.liquids.length === 1 && vial.liquids[0].ratio === 1) {
return; // No liquids or full with one liquid, no boil
}
var delay = Math.random() * 100;
return LK.setInterval(function () {
var rand = Math.random();
var scale = rand + 0.75;
var alpha = 0.5 + rand * 0.5;
var color = 0xFFFFFF; // Random tint color
var x = 5 + rand * 150 - 80;
var r = rand * Math.PI;
var bubble = vial.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 1.0,
x: x,
y: vial.height - 20,
alpha: alpha,
scaleX: scale,
scaleY: scale,
rotation: r
});
vial.addChild(bubble);
var bubbleRiseInterval = LK.setInterval(function () {
bubble.y -= 10 * (1 + (roundDuration - timeLeft) / roundDuration); // Increase speed over time
bubble.scale.x += 0.05 * (roundDuration - timeLeft) / roundDuration; // Increase size over time
bubble.scale.y += 0.05 * (roundDuration - timeLeft) / roundDuration; // Increase size over time
var topLiquidY = vial.topOfLiquidsY + 15; // offset to prevent bubble outside
if (bubble.y <= topLiquidY - 10) {
vial.removeChild(bubble);
LK.clearInterval(bubbleRiseInterval);
}
}, 16); // 60 FPS
}, Math.max(5, delay + 1000 * (timeLeft / roundDuration))); // Initial interval
}
function deepCopyVials(vials) {
return (vials || []).map(function (vial) {
var newVial = new Vial();
vial.liquids.forEach(function (liquid) {
newVial.addLiquid(new LiquidBase(liquid.color), liquid.ratio);
});
return newVial;
});
}
// Function to update debug text
function updateDebugText(info) {
if (isDebug && debugTxt) {
debugTxt.setText(info);
}
}
// Create an instance of the PuzzleManager
var PuzzleManager = function PuzzleManager() {
var self = this;
self.liquids = []; // Public property to store liquids
self.initVials = function () {
var positions = [];
var nbVials = Math.min(currentLevel + 1, 10);
log("initVials : nbVials =", nbVials);
if (nbVials > maxPerLine) {
log("Bigger board...");
board.y = 2732 / 2 + 100;
board.height = 2000;
}
self.vials = [];
// Add liquidStream to the game before vials to ensure it is always behind
if (!liquidStream) {
liquidStream = new LiquidStream('blue'); // Default color, will be updated later
game.addChild(liquidStream);
}
for (var i = 0; i < nbVials; i++) {
self.vials.push(new Vial());
}
var spacingX = 2048 / (Math.min(nbVials, maxPerLine) + 1);
var tubeHeight = LK.getAsset('tube', {}).height;
var spacingY = nbVials <= maxPerLine ? (2732 - tubeHeight) / 2 : 2732 / Math.ceil(nbVials / maxPerLine);
for (var i = 0; i < nbVials; i++) {
positions.push({
x: spacingX * (i % maxPerLine + 1),
y: nbVials <= maxPerLine ? (2732 - tubeHeight) / 2 : spacingY * Math.floor(i / maxPerLine) + 500
});
}
for (var i = 0; i < self.vials.length; i++) {
self.vials[i].x = positions[i].x;
self.vials[i].y = positions[i].y;
// Adjust
if (self.vials.length > maxPerLine) {
if (i < maxPerLine) {
self.vials[i].y += 380; // Move the first row up by 300
} else {
self.vials[i].y -= 280; // Move the second row up by 500
}
}
self.vials[i].baseX = self.vials[i].x; // Store base x-coordinate
self.vials[i].baseY = self.vials[i].y; // Store base y-coordinate
game.addChild(self.vials[i]);
}
};
// Initialize liquids
self.initLiquids = function (level) {
// Create and initialize liquid objects based on level
if (level === 1) {
self.liquids = [new LiquidBase('green')];
} else {
if (level === 2) {
self.liquids = [new LiquidBase('blue'), new LiquidBase('green')];
} else {
self.liquids = [];
for (var i = 0; i < level; i++) {
var colorName = Object.keys(colorList)[i % Object.keys(colorList).length];
self.liquids.push(new LiquidBase(colorName));
}
}
}
};
self.distributeLiquids = function () {
// Helper function to shuffle an array
function shuffle(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// Distribute initialized liquids into the provided vials
if (self.vials.length === 0) {
return;
}
if (shortFormatDistributions[currentLevel]) {
var decodedDistribution = decodeShortFormat(shortFormatDistributions[currentLevel]);
if (decodedDistribution) {
decodedDistribution.forEach(function (distribution) {
self.vials[distribution.vial].addLiquid(self.liquids[distribution.liquid], distribution.ratio);
});
} else {
console.error("Decoded distribution for level " + currentLevel + " is undefined.");
}
} else {
var totalPortions = (self.vials.length - 1) * (1 / BASE_LIQUID_RATIO);
var portionsPerColor = Math.floor(totalPortions / self.liquids.length);
var portions = self.liquids.flatMap(function (liquid) {
return Array(portionsPerColor).fill(liquid.color);
});
shuffle(portions);
var attempts = 0;
var maxAttempts = 1000;
while (attempts < maxAttempts) {
var portionIndex = 0;
for (var i = 0; i < self.vials.length - 1; i++) {
self.vials[i].liquids = [];
var vialPortions = totalPortions / (self.vials.length - 1);
for (var j = 0; j < vialPortions; j++) {
var color = portions[portionIndex++];
var liquid = self.liquids.find(function (l) {
return l.color === color;
});
if (liquid) {
self.vials[i].addLiquid(liquid, BASE_LIQUID_RATIO);
}
}
}
if (self.isSolvable()) {
break;
}
attempts++;
shuffle(portions);
}
lastDistributionConfig = deepCopyVials(self.vials);
}
};
self.isSolvable = function () {
var visitedStates = [];
var stateQueue = [];
function serializeState(vials) {
return vials.map(function (vial) {
return vial.liquids.map(function (liquid) {
return liquid.color + ":" + liquid.ratio;
}).join(",");
}).join("|");
}
function isSolved(vials) {
return vials.every(function (vial) {
return vial.liquids.length === 0 || vial.liquids.every(function (liquid) {
return liquid.color === vial.liquids[0].color;
});
});
}
function getNextStates(vials) {
var nextStates = [];
for (var i = 0; i < vials.length; i++) {
for (var j = 0; j < vials.length; j++) {
if (i !== j && vials[i].canPour(vials[j]) > 0) {
var newVials = deepCopyVials(vials);
var liquid = newVials[i].removeLiquid(newVials[i].canPour(newVials[j]));
newVials[j].addLiquid(liquid, liquid.ratio);
nextStates.push(newVials);
}
}
}
return nextStates;
}
stateQueue.push(deepCopyVials(self.vials));
visitedStates.push(serializeState(self.vials));
var maxDepth = 1000;
var heuristicLimit = 100;
var heuristicChecks = 0;
var depth = 0;
while (stateQueue.length > 0 && depth < maxDepth) {
var currentState = stateQueue.shift();
if (isSolved(currentState)) {
return true;
}
var nextStates = getNextStates(currentState);
heuristicChecks++;
if (heuristicChecks > heuristicLimit) {
return false;
}
for (var k = 0; k < nextStates.length; k++) {
var nextState = nextStates[k];
var serializedState = serializeState(nextState);
if (visitedStates.indexOf(serializedState) === -1) {
visitedStates.push(serializedState);
stateQueue.push(nextState);
}
}
depth++;
}
return false;
};
self.reloadDistribution = function () {
var savedVials = lastDistributionConfig;
if (self.vials && Array.isArray(self.vials) && self.vials.length > 0) {
self.vials.forEach(function (vial, i) {
if (savedVials[i]) {
vial.liquids = savedVials[i].liquids.map(function (liquid) {
return {
color: liquid.color,
ratio: liquid.ratio
};
});
vial.renderLiquids();
}
});
}
};
// Check for win conditions
self.checkWinCondition = function () {
// Implement logic to check if the puzzle is solved
var colorArray = [];
if (!self.vials || self.vials.length === 0) {
return false; // No vials to check
}
for (var i = 0; i < self.vials.length; i++) {
if (self.vials[i].liquids.length > 0) {
var firstColor = self.vials[i].liquids[0].color;
if (colorArray.indexOf(firstColor) !== -1) {
return false; // Color already found in another vial
}
colorArray.push(firstColor);
for (var j = 1; j < self.vials[i].liquids.length; j++) {
if (self.vials[i].liquids[j].color !== firstColor) {
return false; // Different colors in the same vial
}
}
}
}
return true;
};
// Initialize the puzzle manager
self.init = function (level) {
self.initVials();
self.initLiquids(level);
self.distributeLiquids();
for (var i = 0; i < self.vials.length; i++) {
self.vials[i].renderLiquids();
}
updateDebugText('Puzzle Solvable: ' + self.isSolvable());
};
return self;
};
var BASE_LIQUID_RATIO = 0.25;
var puzzleManager;
var rulesContainer;
var currentLevel = 10; // TEMP DEBUG 1;
var maxPerLine = 5;
var score = 0;
var preStarted = false;
var showedRules = false;
var isPlaying = false;
var selectedTube;
var roundDuration = 240; // Global variable for round duration
var timeLeft = roundDuration; // Initialize timeLeft with roundDuration
var roundLost = false;
var selectionYOffset = 300;
var distributionConfigurations = [];
var lastDistributionConfig = null;
var pourInterval;
var moveInterval;
var hourglassAnimationInterval;
var initialHourglassWidth = 100;
var initialHourglassHeight = 186.51;
var particlesCompleted = 0;
var isTryingToPour = false;
var moveMade = false; // Flag to indicate that a move was made (1st tube selected in the round)
var explosionAnimEnded = false;
var isPouring = false;
var liquidStream;
var background;
var scoreTxt;
var timerTxt;
var debugTxt;
var hourglass;
var restartButton;
var brokenVialsContainer;
var isDebug = true;
var board;
var colorList = {
blue: 0x0000FF,
// DodgerBlue
green: 0x00FF00,
// LimeGreen
red: 0xFF0000,
// OrangeRed
white: 0xFFFFFF,
// White
yellow: 0xFFFF00,
// Yellow
purple: 0x800080,
// Purple
cyan: 0x00FFFF,
// Cyan
magenta: 0xFF00FF,
// Magenta
orange: 0xFFA500,
// Orange
pink: 0xFFC0CB // Pink
};
var levelDistributions = {};
var shortFormatDistributions = {
1: "0,1|0,3",
2: "0,2|0,2;1,2|1,2",
3: "0,1;1,2;2,1|0,3;2,1|1,2;2,2",
4: "1,1;2,1;3,2|0,2;1,1;3,1|1,2;2,2|0,2;2,1;3,1",
5: "0,1;1,2;4,1|2,2;3,2|0,3;1,1|1,1;2,2;4,1|3,2;4,2",
6: "1,1;2,2;4,1|0,2;1,1;2,1|3,2;4,1;5,1|2,1;3,1;5,2|0,2;4,2|1,2;3,1;5,1",
7: "2,1;4,1;5,2|1,2;2,1;4,1|5,1;6,3|0,2;2,1;4,1|1,1;3,1;4,1;6,1|3,3;5,1|0,2;1,1;2,1",
8: "0,3;1,1|3,1;5,1;6,1;7,1|1,2;3,2|0,1;1,1;4,1;7,1|2,1;3,1;4,1;5,1|2,3;5,1|4,1;6,2;7,1|4,1;5,1;6,1;7,1",
9: "1,1;4,1;5,1;7,1|0,2;1,1;6,1|3,2;4,1;5,1|0,2;1,1;5,1|2,2;3,1;4,1|4,1;5,1;6,1;7,1|1,1;6,1;7,2|2,2;3,1;6,1",
10: "2,1;3,1;8,2|3,1;4,1;5,1;6,1|1,1;2,2;3,1|0,2;1,1;4,1|4,2;6,1;8,1|0,2;6,1;7,1|2,1;3,1;5,1;7,1|5,2;7,1;8,1|1,2;6,1;7,1"
};
var boardByLevel = {
1: 'board1',
2: 'board1',
3: 'board2',
4: 'board2',
5: 'board2',
6: 'board3',
7: 'board3',
8: 'board4',
9: 'board4',
10: 'board'
};
var timerByLevel = {
1: 30,
2: 60,
3: 90,
4: 120,
5: 180,
6: 200,
7: 260,
8: 280,
9: 260,
10: 300
};
function decodeShortFormat(shortFormat) {
var longFormat = {};
var cleanReg = /['"]/g;
shortFormat = shortFormat.replaceAll(cleanReg, ''); // Replace all matched characters with an empty string
log("decodeShortFormat:", shortFormat);
var vials = shortFormat.split('|');
log("vials raw:", vials);
var level = vials.length;
var distribution = [];
vials.forEach(function (vial, vialIndex) {
var liquids = vial.split(';');
log("vial #" + vialIndex + " liquids:", liquids);
liquids.forEach(function (liquid) {
var _liquid$split$map = liquid.split(',').map(Number),
_liquid$split$map2 = _slicedToArray(_liquid$split$map, 2),
liquidIndex = _liquid$split$map2[0],
multiplier = _liquid$split$map2[1];
log("liquid #" + liquidIndex + " => ", multiplier);
distribution.push({
vial: vialIndex,
liquid: liquidIndex,
ratio: multiplier * BASE_LIQUID_RATIO
});
});
});
log("distribution :", distribution);
return distribution;
}
function log() {
if (isDebug) {
var _console;
(_console = console).log.apply(_console, arguments);
}
}
// Function to update score
function updateScore(newScore) {
score = newScore;
if (scoreTxt) {
if (newScore) {
scoreTxt.setText(newScore.toString());
} else {
scoreTxt.setText('0');
}
}
}
// Function to update timer
function updateTimer(newTime) {
if (newTime === roundDuration && hourglass) {
// Animate a rotation of PI degrees for the hourglass
var initialRotation = hourglass.rotation;
var targetRotation = initialRotation + Math.PI;
var rotationStep = (targetRotation - initialRotation) / 60; // 60 frames for 1 second animation
var rotateInterval = LK.setInterval(function () {
if (Math.abs(hourglass.rotation - targetRotation) < Math.abs(rotationStep)) {
hourglass.rotation = targetRotation;
LK.clearInterval(rotateInterval);
} else {
hourglass.rotation += rotationStep;
}
}, 16); // 60 FPS
}
if (timerTxt) {
timerTxt.setText(timeLeft.toString());
if (newTime <= 5) {
timerTxt.tint = 0xFF0000; // Tint to red
} else {
timerTxt.tint = 0xFFFFFF; // Reset to white
}
}
if (timeLeft === 10) {
animateHourglass();
LK.getSound('hurryUp').play();
} else if (timeLeft === 1) {
restartButton.visible = false;
LK.clearInterval(hourglassAnimationInterval);
hourglass.width = initialHourglassWidth;
hourglass.height = initialHourglassHeight;
}
}
function animateHourglass() {
initialHourglassWidth = hourglass.width;
initialHourglassHeight = hourglass.height;
var scaleFactor = 0.1; // Adjust the scale factor as needed
var animationSpeed = 0.05; // Adjust the speed of the animation
var animate = function animate() {
var scale = 1 + scaleFactor * Math.sin(LK.ticks * animationSpeed);
hourglass.width = initialHourglassWidth * scale;
hourglass.height = initialHourglassHeight * scale;
};
hourglassAnimationInterval = LK.setInterval(animate, 16); // 60 FPS
}
// Game update function
game.update = function () {
// Update timer
if (showedRules && preStarted && !gameWin && LK.ticks % 60 == 0) {
// Decrease time every second
timeLeft -= 1;
if (timeLeft <= 0) {
if (!roundLost) {
isPlaying = false;
roundLost = true;
updateTimer(0);
if (selectedTube) {
selectedTube.scale.set(1, 1); // Reset the scale of the selected tube
selectedTube.y += selectionYOffset; // Restore the tube to its initial position
selectedTube = null; // Unselect the tube
}
levelTxt.visible = false; // Set the LevelText invisible when the level is lost
if (puzzleManager && puzzleManager.vials) {
puzzleManager.vials.forEach(function (vial) {
vial.liquids.forEach(function (liquid, index) {
explodeAnim(vial.x, vial.y, colorList[liquid.color]);
var brokenVial = LK.getAsset('brokenVials', {
anchorX: 0.5,
anchorY: 0,
x: vial.x,
y: vial.baseY,
// Adjust y position to match the vial's base y position
rotation: Math.random() * Math.PI * 2 // Add random rotation
});
brokenVialsContainer.addChild(brokenVial);
vial.alpha = 0; // Hide the whole vial
});
});
}
handlePostExplosion();
}
} else {
updateTimer(timeLeft - 1);
// Progressively start boiling all vials based on timer
if (puzzleManager && puzzleManager.vials) {
puzzleManager.vials.forEach(function (vial) {
if (timeLeft <= roundDuration * 0.75 && !vial.bubbleInterval) {
vial.boil();
}
});
}
}
}
};
function initUI() {
// Shuffle colorList
var colorKeys = Object.keys(colorList);
for (var i = colorKeys.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = colorList[colorKeys[i]];
colorList[colorKeys[i]] = colorList[colorKeys[j]];
colorList[colorKeys[j]] = temp;
}
// Add header asset at the top of the screen
var header = LK.getAsset('header', {
anchorX: 0.5,
anchorY: 0.0,
x: 2048 / 2,
y: 0,
alpha: 0.75
});
game.addChild(header);
// Add board asset
board = LK.getAsset('board', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.22,
// Increase the size of the board
scaleY: 1.2,
// Increase the size of the board
x: 2048 / 2,
y: 2732 / 2 + 100
});
board.height = 2000;
game.addChild(board);
scoreTxt = new Text2('0', {
size: 140,
fill: "#ffffff"
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false; // Disabled for now
LK.gui.top.addChild(scoreTxt);
timerTxt = new Text2('240', {
size: 140,
fill: "#ffffff"
});
timerTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(timerTxt);
// Add hourglass asset at the top right
hourglass = LK.getAsset('hourglass', {
anchorX: 0.5,
anchorY: 0.5,
x: 1640,
y: 123
});
game.addChild(hourglass);
// Create debug text
debugTxt = new Text2('Debug Info', {
size: 50,
fill: "#FFFFFF"
});
debugTxt.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(debugTxt);
debugTxt.visible = isDebug;
levelTxt = new Text2('Level ' + currentLevel, {
size: 60,
fill: "#ffffff"
});
levelTxt.anchor.set(0.5, 1.75);
LK.gui.bottom.addChild(levelTxt);
restartButton = LK.getAsset('restartRound', {
anchorX: 0.5,
anchorY: 0.5,
x: 612,
y: 130
});
game.addChild(restartButton);
restartButton.down = function () {
LK.getSound('reset').play();
LK.effects.flashScreen(0xFFFFFF, 500); // Flash screen white for 500ms
initRound(true);
};
}
function initRound(reset) {
log("initRound started with reset:", reset);
// Remove previous vials
if (isPouring) {
log("Removing previous vials");
if (pourInterval) {
LK.clearInterval(pourInterval);
}
isPouring = false;
liquidStream.stop();
}
if (moveInterval) {
LK.clearInterval(moveInterval);
}
if (typeof selectedTube !== 'undefined' && selectedTube) {
selectedTube.x = selectedTube.baseX;
selectedTube.y = selectedTube.baseY;
selectedTube.rotation = 0;
selectedTube = null;
}
isTryingToPour = false;
if (puzzleManager.vials) {
for (var i = 0; i < puzzleManager.vials.length; i++) {
puzzleManager.vials[i].stopBoil();
puzzleManager.vials[i].alpha = 1; // Restore the alpha of each vial to 1
}
}
brokenVialsContainer.removeChildren();
if (reset && lastDistributionConfig) {
log("Reloading last distribution configuration:", lastDistributionConfig);
puzzleManager.reloadDistribution();
if (timeLeft <= 0) {
roundDuration = timerByLevel[currentLevel] || 240; // Set roundDuration based on the current level or default to 240
timeLeft = roundDuration; // Initialize the timer with the dynamic round duration
updateTimer(timeLeft);
if (hourglassAnimationInterval) {
LK.clearInterval(hourglassAnimationInterval);
hourglass.width = initialHourglassWidth;
hourglass.height = initialHourglassHeight;
}
}
} else {
if (typeof puzzleManager === 'undefined') {
puzzleManager = new PuzzleManager();
}
roundDuration = timerByLevel[currentLevel] || 240; // Set roundDuration based on the current level or default to 240
timeLeft = roundDuration; // Initialize the timer with the dynamic round duration
updateTimer(timeLeft);
if (hourglassAnimationInterval) {
LK.clearInterval(hourglassAnimationInterval);
hourglass.width = initialHourglassWidth;
hourglass.height = initialHourglassHeight;
}
// Change the board asset depending on the current level using boardByLevel
if (boardByLevel[currentLevel]) {
game.removeChild(board);
board = LK.getAsset(boardByLevel[currentLevel], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.22,
scaleY: 1.2,
x: 2048 / 2,
y: 2732 / 2 + 100
});
board.height = 2000;
game.addChildAt(board, 1); // Ensure the board is just in front of the background
}
if (puzzleManager.vials) {
for (var i = 0; i < puzzleManager.vials.length; i++) {
game.removeChild(puzzleManager.vials[i]);
}
}
log("Initializing puzzle manager for level:", currentLevel);
puzzleManager.init(currentLevel);
lastDistributionConfig = deepCopyVials(puzzleManager.vials);
log("Level configuration saved:", lastDistributionConfig);
}
if (puzzleManager.vials && puzzleManager.vials.length > 0) {
log("Restoring vial properties");
puzzleManager.vials.forEach(function (vial) {
vial.scale.set(1, 1); // Reset the scale of each vial
vial.x = vial.baseX; // Restore base x-coordinate
vial.y = vial.baseY; // Restore base y-coordinate
vial.rotation = 0; // Reset rotation
});
}
roundLost = false;
particlesCompleted = 0;
gameWin = false;
moveMade = false; // Reset the moveMade flag at round initialization
restartButton.visible = false; // Hide restartButton if !moveMade
levelTxt.visible = true; // Restore the visibility of LevelText in initRound
log("initRound completed");
LK.setTimeout(function () {
isPlaying = true;
}, 600);
}
// Function to initialize the game
function initializeGame() {
score = 0; // Initialize the score with 0
initUI();
brokenVialsContainer = new Container();
game.addChild(brokenVialsContainer);
updateScore(score);
puzzleManager = new PuzzleManager();
initRound();
isPlaying = true;
}
function initRulesVials() {
// Demo Vials to explain the rules
// Liquids
var greenChalk1 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 325,
y: 1480,
width: 140,
height: 140
});
rulesContainer.addChild(greenChalk1);
var greenChalk2 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 603,
y: 1480,
width: 140,
height: 140
});
rulesContainer.addChild(greenChalk2);
var greenChalk3 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 885,
y: 1480,
width: 140,
height: 140
});
rulesContainer.addChild(greenChalk3);
var greenChalk4 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 1437,
y: 1480,
width: 140,
height: 140
});
rulesContainer.addChild(greenChalk4);
var blueChalk5 = LK.getAsset('blueChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 1715,
y: 1480,
width: 140,
height: 140
});
rulesContainer.addChild(blueChalk5);
// Liquid Row 2 - Goal
var greenChalk6 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 475,
y: 2310,
width: 140,
height: 120
});
rulesContainer.addChild(greenChalk6);
var greenChalk7 = LK.getAsset('blueChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 475,
y: 2155,
width: 140,
height: 200
});
rulesContainer.addChild(greenChalk7);
var greenChalk8 = LK.getAsset('blueChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 740,
y: 2310,
width: 140,
height: 120
});
rulesContainer.addChild(greenChalk8);
var greenChalk9 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 740,
y: 2150,
width: 140,
height: 200
});
rulesContainer.addChild(greenChalk9);
var goal = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1850
});
rulesContainer.addChild(goal);
var greenChalk10 = LK.getAsset('greenChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 1300,
y: 2205,
width: 140,
height: 320
});
rulesContainer.addChild(greenChalk10);
var greenChalk11 = LK.getAsset('blueChalk', {
anchorX: 0.5,
anchorY: 0.5,
x: 1575,
y: 2205,
width: 140,
height: 320
});
rulesContainer.addChild(greenChalk11);
// Vials
var row1Vials = 6;
var row2Vials = 5;
var spacingX = (2048 - 100) / (row1Vials + 1);
var spacingY = 2732 / 3;
for (var i = 0; i < row1Vials; i++) {
var vial = new Vial(true);
vial.x = 50 + spacingX * (i + 1);
vial.y = spacingY + 150;
vial.width = 160;
vial.height = 512;
rulesContainer.addChild(vial);
}
for (var j = 0; j < row2Vials; j++) {
if (j == 2) {
continue;
}
var vial = new Vial(true);
vial.x = 200 + (2048 - 400) / (row2Vials + 1) * (j + 1);
vial.y = spacingY * 2 + 50;
vial.width = 160;
vial.height = 512;
rulesContainer.addChild(vial);
}
// Arrows
var arrow1 = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
x: 470,
y: 1010
});
rulesContainer.addChild(arrow1);
var arrow2 = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
x: 1020,
y: 1010
});
rulesContainer.addChild(arrow2);
var arrow3 = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
x: 1580,
y: 1010
});
rulesContainer.addChild(arrow3);
// Checks
var check1 = LK.getAsset('check', {
anchorX: 0.5,
anchorY: 0.5,
x: 560,
y: 940
});
rulesContainer.addChild(check1);
var check2 = LK.getAsset('check', {
anchorX: 0.5,
anchorY: 0.5,
x: 1110,
y: 940
});
rulesContainer.addChild(check2);
var cross1 = LK.getAsset('cross', {
anchorX: 0.5,
anchorY: 0.5,
x: 1690,
y: 940
});
rulesContainer.addChild(cross1);
// Separators
var separator1 = LK.getAsset('separator', {
anchorX: 0.5,
anchorY: 0.5,
x: 740,
y: 1280
});
rulesContainer.addChild(separator1);
var separator2 = LK.getAsset('separator', {
anchorX: 0.5,
anchorY: 0.5,
x: 1300,
y: 1280
});
rulesContainer.addChild(separator2);
}
// Function to display rules and wait for tap
function showRules() {
// Add background image
background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(background, 0); // Ensure background is behind all other elements
rulesContainer = new Container();
game.addChild(rulesContainer);
var rulesBoard = LK.getAsset('rulesBoard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
x: 2048 / 2,
y: 2732 / 2
});
rulesBoard.y = 2732 / 2 + 100;
rulesBoard.height = 2000;
rulesContainer.addChild(rulesBoard);
initRulesVials();
var witch = LK.getAsset('witch', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
// Start off-screen to the left
y: 2732 / 2
});
rulesContainer.addChild(witch);
LK.setTimeout(function () {
var slideOutInterval = LK.setInterval(function () {
witch.x += 20; // Adjust the speed of sliding in
if (witch.x >= 4096) {
LK.clearInterval(slideOutInterval);
}
}, 16); // 60 FPS
game.down = function (x, y, obj) {
if (showedRules) {
return;
}
showedRules = true;
/*game.removeChild(rulesBoard);
game.removeChild(witch);
*/
game.removeChild(rulesContainer);
LK.getSound('letsgo').play();
LK.setTimeout(function () {
initializeGame();
}, 600); // Wait for 2 seconds before starting the slide out
};
}, isDebug ? 100 : 1800); // Wait for 2 seconds before starting the slide out
LK.getSound('rememberTheRules').play();
}
// Function to display start screen and wait for tap
function preStart() {
var startScreen = LK.getAsset('startScreen', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(startScreen);
var startScreen2 = LK.getAsset('startScreen2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 34,
y: 2732 / 2 + 34,
scaleX: 1.05,
scaleY: 1.05,
alpha: 1
});
game.addChild(startScreen2);
var animateAlpha = function animateAlpha() {
var increasing = true;
var alphaInterval = LK.setInterval(function () {
if (increasing) {
startScreen2.alpha += 0.01;
if (startScreen2.alpha >= 1) {
startScreen2.alpha = 1;
increasing = false;
}
} else {
startScreen2.alpha -= 0.01;
if (startScreen2.alpha <= 0) {
startScreen2.alpha = 0;
increasing = true;
}
}
}, 60); // 30ms interval for smooth animation
};
animateAlpha();
game.down = function (x, y, obj) {
if (preStarted) {
return;
}
preStarted = true;
LK.getSound('welcome').play();
var fadeOutInterval = LK.setInterval(function () {
startScreen.alpha -= 0.01;
startScreen.alpha -= isDebug ? 0.02 : 0;
if (startScreen.alpha <= 0) {
LK.clearInterval(fadeOutInterval);
game.removeChild(startScreen);
game.removeChild(startScreen2);
showRules();
}
}, 30); // 30ms interval for 100 steps over 3 seconds
};
}
preStart();
function handleTapOnSelectedTube() {
selectedTube.scale.set(1, 1); // Reset the scale of the selected tube
selectedTube.y += selectionYOffset; // Restore the tube to its initial position
LK.getSound('drop').play();
selectedTube = null; // Unselect the tube
isTryingToPour = false; // Reset the flag to allow new selections
}
function handleTapOnAnotherTube() {
var pouringColor = selectedTube.liquids.length > 0 ? selectedTube.liquids[selectedTube.liquids.length - 1].color : null;
if (!pouringColor) {
return; // Exit if there is no liquid to pour
}
var sourceMaxRatio = 0;
if (selectedTube.liquids.length) {
sourceMaxRatio = selectedTube.liquids[selectedTube.liquids.length - 1].ratio;
}
var destinationMaxRatio = selectedTube.canPour(self);
var pourRatio = Math.min(sourceMaxRatio, destinationMaxRatio);
log("sourceMaxRatio/destinationMaxRatio/pourRatio:", sourceMaxRatio, destinationMaxRatio, pourRatio);
if (pourRatio > 0) {
isTryingToPour = true;
// Phase 1: Move the selected vial near the destination vial, then rotate it
var originalX = selectedTube.x;
var originalY = selectedTube.y;
var targetX = selectedTube.x < self.x ? self.x - 50 : self.x + 50; // Adjust the target position based on the side of the destination tube
var targetY = self.y - 200; // Adjust the target position as needed
var rotationDirection = selectedTube.x < self.x ? Math.PI / 4 : -Math.PI / 4; // Determine rotation direction
var moveAndRotate = function moveAndRotate() {
selectedTube.x = targetX;
selectedTube.y = targetY;
selectedTube.rotation = rotationDirection; // Rotate the selected tube
// Initialize a liquidStream at the top of the selected tube
if (selectedTube && selectedTube.liquids.length > 0) {
isPouring = true;
if (!liquidStream) {
liquidStream = new LiquidStream(pouringColor);
game.addChildAt(liquidStream, 0); // Ensure liquidStream is behind vials
}
var xDelta = selectedTube.x < self.x ? 50 : -50;
liquidStream.init(selectedTube.x + xDelta, selectedTube.y - selectedTube.height / 2, pouringColor);
}
// Make the LiquidStream height grow to simulate pouring
LK.getSound('pouring').play();
// Find source Top liquid graphic
var sourceTopLiquid = selectedTube.children.find(function (child) {
return child.tint === colorList[pouringColor];
});
// Create a fakeSourceLiquid with pouringColor, size, coordinates, and rotation of source Liquid
var fakeSourceLiquid = new LiquidBase(pouringColor);
fakeSourceLiquid.children[0].height = sourceTopLiquid.height;
fakeSourceLiquid.children[0].width = sourceTopLiquid.width;
fakeSourceLiquid.children[0].x = sourceTopLiquid.x;
fakeSourceLiquid.children[0].y = sourceTopLiquid.y;
fakeSourceLiquid.children[0].rotation = -sourceTopLiquid.rotation + Math.PI;
log("source Liquid :", selectedTube.liquids[self.liquids.length - 1]);
log("fakeSourceLiquid :", fakeSourceLiquid);
selectedTube.addChildAt(fakeSourceLiquid, 0);
// Set source top liquid alpha to 0
sourceTopLiquid.alpha = 0;
// Find destination Top liquid
var destinationTopLiquid = self.liquids.length > 0 ? self.liquids[self.liquids.length - 1] : null;
// Create a fakeDestinationLiquid with pouringColor, and place it on top of destination Top liquid with height 0
var fakeDestinationLiquid = new LiquidBase(pouringColor);
fakeDestinationLiquid.height = 0;
fakeDestinationLiquid.rotation = Math.PI; // Rotate by 180 degrees
fakeDestinationLiquid.width = sourceTopLiquid.width;
fakeDestinationLiquid.x = 0;
fakeDestinationLiquid.y = self.baseLiquidY - (destinationTopLiquid ? destinationTopLiquid.ratio * self.fillHeight : 0);
if (destinationTopLiquid) {
log("fakeDestinationLiquid :", destinationTopLiquid, self.baseLiquidY, self.y);
} else {
log("fakeDestinationLiquid : destinationTopLiquid is null", self.baseLiquidY, self.y);
}
//fakeDestinationLiquid.y += 700; //pourRatio * destinationTopLiquid.fillHeight;
self.addChildAt(fakeDestinationLiquid, 0);
// Animate fakeSourceLiquid.height diminution to 0
// Animate fakeDestinationLiquid.height augmentation to pourRatio*fillHeight
var pourInterval = LK.setInterval(function () {
if (selectedTube && selectedTube.liquids.length > 0 && liquidStream.height < selectedTube.height) {
liquidStream.height += 20; // Adjust the growth rate as needed
fakeSourceLiquid.children[0].height = Math.max(fakeSourceLiquid.children[0].height - 20, 0);
fakeDestinationLiquid.height = Math.min(fakeDestinationLiquid.height + 20, self.fillHeight * pourRatio);
log("pourRatio:", pourRatio);
} else {
LK.clearInterval(pourInterval);
liquidStream.stop(); // Call stop function at the end of pouring
isPouring = false;
LK.getSound('pouring').stop(); // Stop pouring sound
if (selectedTube) {
selectedTube.x = originalX;
selectedTube.y = originalY;
selectedTube.rotation = 0; // Reset rotation
selectedTube.removeChild(fakeSourceLiquid);
}
if (self) {
self.removeChild(fakeDestinationLiquid);
}
}
}, 16); // 60 FPS
// Proceed to the next phase after a short delay
LK.setTimeout(function () {
if (selectedTube && selectedTube.liquids.length > 0) {
var liquid = selectedTube.removeLiquid(Math.min(pourRatio, selectedTube.liquids[selectedTube.liquids.length - 1].ratio));
if (liquid) {
self.addLiquid(liquid, liquid.ratio);
}
selectedTube.x = originalX;
selectedTube.y = originalY;
selectedTube.rotation = 0; // Reset rotation
selectedTube.y += selectionYOffset; // Restore the selected tube to its initial position
selectedTube.scale.set(1, 1); // Reset the scale of the previously selected tube
selectedTube.renderLiquids();
self.renderLiquids();
selectedTube = null;
}
if (puzzleManager.checkWinCondition()) {
gameWin = true;
LK.getSound('goodJob').play(); // Play 'goodJob' sound
// Level up and initialize a new round
currentLevel++;
levelTxt.setText('Level: ' + currentLevel);
LK.setTimeout(function () {
initRound();
}, 2000);
} else {
if (isPlaying && self.liquids[self.liquids.length - 1].ratio === 1) {
LK.getSound('yes').play();
} else {
LK.getSound('drop').play();
}
}
isTryingToPour = false;
}, 700 * pourRatio); // Adjust the delay as needed
};
// Move the selected tube to the target position
moveInterval = LK.setInterval(function () {
if (selectedTube && Math.abs(selectedTube.x - targetX) < 5 && Math.abs(selectedTube.y - targetY) < 5) {
LK.clearInterval(moveInterval);
moveAndRotate();
} else if (selectedTube) {
selectedTube.x += (targetX - selectedTube.x) * 0.1;
selectedTube.y += (targetY - selectedTube.y) * 0.1;
selectedTube.rotation += (rotationDirection - selectedTube.rotation) * 0.1; // Rotate the tube while moving
}
}, 16); // 60 FPS
} else {
log("Wrong tube...");
if (selectedTube) {
selectedTube.scale.set(1, 1); // Reset the scale if selection is wrong
selectedTube.y += selectionYOffset; // Restore the previously selected tube to its initial position
LK.getSound('wrong').play(); // Play wrong sound
}
selectedTube = null;
}
}
function handleTapOnFirstTube() {
LK.getSound('tap').play();
selectedTube = self;
log("Selecting 1st tube...");
this.y -= selectionYOffset; // Move the selected tube up by selectionYOffset
if (self.parent === game) {
game.setChildIndex(self, game.children.length - 1); // Bring the selected vial to the front
}
isTryingToPour = true; // Set the flag to prevent new selections
}
Basic white Restart icon (rounded arrow). UI
Une classe d’une école de sorcière sans les élèves.
un sablier de sorcière.
a bubble.
exploded broken glass
Yound generously beautifull teacher witch smiling, with glasses, a black witch hat, holding a little brown book in her hands and looking at the camera. wearing light black clothes. Torso head and hat should appear.
tap
Sound effect
drop
Sound effect
reset
Sound effect
wrong
Sound effect
yes
Sound effect
goodJob
Sound effect
pouring
Sound effect
welcome
Sound effect
rememberTheRules
Sound effect
letsgo
Sound effect
hurryUp
Sound effect
boom
Sound effect
tryAgain
Sound effect
rainbowBoom
Sound effect
youDidIt
Sound effect
letmetry
Sound effect
rainbowMix
Sound effect
thankYou1
Sound effect
thankYou2
Sound effect
thankYou3
Sound effect
thankYou4
Sound effect
bonusTime
Sound effect