Code edit (4 edits merged)
Please save this source code
User prompt
use same anchors for brokenvials as for vials
User prompt
adjust the broken vials y to match vials y
User prompt
in explodeAnim, add as many brokenVials asset as vials on the same place as vials after the flash. remove them before calling initRound (so make a global container for them)
User prompt
make var brokenVials global
User prompt
in explodeAnim, add brokenVials asset on the center of the screen after the flash. remove it before calling initRound
Code edit (1 edits merged)
Please save this source code
User prompt
add a global flag to track if explosionAnim is ended
User prompt
restore all vials alpha to 1 in initround
User prompt
finally, instead of ``` var liquidGraphic = vial.children.find(function (child) { return child.tint === colorList[liquid.color]; }); if (liquidGraphic) { liquidGraphic.alpha = 0; // Hide the corresponding liquid } ``` just hide the whole vial
Code edit (1 edits merged)
Please save this source code
User prompt
in slideInInterval/slideOutInterval take into acount witchBlack height
User prompt
after slideInInterval play tryAgain sound
User prompt
at explosion end, add the witchBlack asset and make it slide in from the bottom then slide out from bottom
Code edit (5 edits merged)
Please save this source code
User prompt
instead of `vial.liquids.splice(index, 1); // Hide the corresponding liquid` search for the liquid graphic and set its alpha to 0
Code edit (1 edits merged)
Please save this source code
User prompt
play boom sound in explosion anim
Code edit (22 edits merged)
Please save this source code
User prompt
in particleInterval, take into account explosionContainer position for end condition `if (particle.x < 0 || particle.x > 2048 || particle.y < 0 || particle.y > 2732)`
Code edit (1 edits merged)
Please save this source code
User prompt
change condition for particleInterval, check if out of screen instead of alpha <=0
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: obj.event is undefined' in or related to this line: 'obj.event.stopPropagation();' Line Number: 486
User prompt
in handlePostExplosion, stop Propagation of down event
/**** 
* 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 = 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) {
		self.height = 0;
		self.x = x;
		self.y = y + 380;
		liquidGraphics.tint = colorList[color];
		liquidGraphics.alpha = 1;
	};
	self.stop = function () {
		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.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) {
			log("Already trying to pour...");
			return; // Ignore taps while pouring
		}
		if (selectedTube && selectedTube === self) {
			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
		} else if (selectedTube) {
			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;
			}
		} else {
			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.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
****/ 
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) {
	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.5 + 0.75,
			scaleY: Math.random() * 0.5 + 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.1;
				vy *= 1.1;
				particle.scale.x *= 1.02; // Exponential growth
				particle.scale.y *= 1.02; // Exponential growth
				log("particle:", particle.x, particle.y);
				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 slideInInterval = LK.setInterval(function () {
					witchBlack.y -= 20; // Adjust the speed of sliding in
					if (witchBlack.y <= (2732 - LK.getAsset('witchBlack', {}).height) / 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 += 20; // Adjust the speed of sliding out
								if (witchBlack.y >= 2732 + LK.getAsset('witchBlack', {}).height) {
									LK.clearInterval(slideOutInterval);
									game.removeChild(witchBlack);
								}
							}, 16); // 60 FPS
						}, 2000); // Wait for 2 seconds before starting the slide out
					}
				}, 16); // 60 FPS
				//createFlashEffect(color);
			}
		});
	}
	game.addChild(explosionContainer);
	/*LK.setTimeout(function () {
		game.removeChild(explosionContainer);
	}, 2000);
	*/ 
	// 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) {
			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 (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 (currentLevel === 1) {
			self.vials[0].addLiquid(self.liquids[0], 0.25);
			self.vials[1].addLiquid(self.liquids[0], 0.75);
		} else if (currentLevel === 2) {
			self.vials[0].addLiquid(self.liquids[0], 0.5);
			self.vials[1].addLiquid(self.liquids[1], 0.5);
			self.vials[2].addLiquid(self.liquids[0], 0.5);
			self.vials[2].addLiquid(self.liquids[1], 0.5);
		} 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 = 1; // TEMP DEBUG 1;
var maxPerLine = 5;
var score = 0;
var preStarted = false;
var showedRules = false;
var isPlaying = false;
var selectedTube;
var roundDuration = 5; //60; // 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 isPouring = false;
var liquidStream;
var background;
var scoreTxt;
var timerTxt;
var debugTxt;
var hourglass;
var restartButton;
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
};
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) {
		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) {
				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
				}
				if (puzzleManager && puzzleManager.vials) {
					puzzleManager.vials.forEach(function (vial) {
						vial.liquids.forEach(function (liquid, index) {
							explodeAnim(vial.x, vial.y, colorList[liquid.color]);
							var liquidGraphic = vial.children.find(function (child) {
								return child.tint === colorList[liquid.color];
							});
							if (liquidGraphic) {
								liquidGraphic.alpha = 0; // Hide the corresponding liquid
							}
						});
					});
				}
				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() {
	// 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('60', {
		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: 1770,
		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);
	levelTxt = new Text2('Level: ' + currentLevel, {
		size: 100,
		fill: "#ffffff"
	});
	levelTxt.anchor.set(1, 1);
	LK.gui.bottomRight.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();
		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();
		}
	}
	if (reset && lastDistributionConfig) {
		log("Reloading last distribution configuration:", lastDistributionConfig);
		puzzleManager.reloadDistribution();
	} else {
		if (typeof puzzleManager === 'undefined') {
			puzzleManager = new PuzzleManager();
		}
		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
		});
	}
	timeLeft = roundDuration; // Initialize the timer with the global round duration
	updateTimer(timeLeft);
	if (hourglassAnimationInterval) {
		LK.clearInterval(hourglassAnimationInterval);
		hourglass.width = initialHourglassWidth;
		hourglass.height = initialHourglassHeight;
	}
	roundLost = false;
	particlesCompleted = 0;
	gameWin = false;
	log("initRound completed");
}
// Function to initialize the game
function initializeGame() {
	score = 0; // Initialize the score with 0
	initUI();
	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(); ===================================================================
--- original.js
+++ change.js
@@ -440,22 +440,22 @@
 				var witchBlack = LK.getAsset('witchBlack', {
 					anchorX: 0.5,
 					anchorY: 0.5,
 					x: 2048 / 2,
-					y: 2732 + 100 // Start off-screen at the bottom
+					y: 2732 + LK.getAsset('witchBlack', {}).height // Start off-screen at the bottom
 				});
 				game.addChild(witchBlack);
 				// Slide in from the bottom
 				var slideInInterval = LK.setInterval(function () {
 					witchBlack.y -= 20; // Adjust the speed of sliding in
-					if (witchBlack.y <= 2732 / 2) {
+					if (witchBlack.y <= (2732 - LK.getAsset('witchBlack', {}).height) / 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 += 20; // Adjust the speed of sliding out
-								if (witchBlack.y >= 2732 + 100) {
+								if (witchBlack.y >= 2732 + LK.getAsset('witchBlack', {}).height) {
 									LK.clearInterval(slideOutInterval);
 									game.removeChild(witchBlack);
 								}
 							}, 16); // 60 FPS
:quality(85)/https://cdn.frvr.ai/669f929c5b1c2decfdc35cd7.png%3F3) 
 Basic white Restart icon (rounded arrow). UI
:quality(85)/https://cdn.frvr.ai/66a005742d9b858094241e7f.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a13f492d9b858094241fa0.png%3F3) 
 Une classe dβune Γ©cole de sorciΓ¨re sans les Γ©lΓ¨ves.
:quality(85)/https://cdn.frvr.ai/66a38b90e5d25718d4992a83.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a40318e5d25718d4992bef.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a5c92d14615af1a60bd01f.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a611ca14615af1a60bd079.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a612bd14615af1a60bd0a8.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a6130a14615af1a60bd0b7.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a61f6714615af1a60bd19c.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a6206f14615af1a60bd1c7.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a6227c14615af1a60bd1df.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a6294d14615af1a60bd27d.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a62c3714615af1a60bd2e5.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a62e2e14615af1a60bd2f0.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a62f8214615af1a60bd2fa.png%3F3) 
 un sablier de sorcière.
:quality(85)/https://cdn.frvr.ai/66a63fbf14615af1a60bd346.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66a67c0e14615af1a60bd3a1.png%3F3) 
 a bubble.
:quality(85)/https://cdn.frvr.ai/66aaa483f88bbc6bb41e13ad.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66ab9b34f88bbc6bb41e144d.png%3F3) 
 exploded broken glass
:quality(85)/https://cdn.frvr.ai/66af6177e9c5efee3850d107.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66afbe1be9c5efee3850d613.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66afc4ede9c5efee3850d63c.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66afd465e9c5efee3850d657.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66b11cb1e9c5efee3850d72c.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66b3048fb97cf4096a70e850.png%3F3) 
 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.
:quality(85)/https://cdn.frvr.ai/66b45864d23a7fddab9a62cd.png%3F3) 
 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