/**** 
* Classes
****/ 
var Barrier = Container.expand(function () {
	var self = Container.call(this);
	var barrierGraphics = self.attachAsset('barrier', {
		anchorX: .5,
		anchorY: .5
	});
});
var BonusUX = Container.expand(function () {
	var self = Container.call(this);
	//Insert label here
	var barHeight = 50;
	var bonusLabel = self.addChild(new Text2('Streak Bonus', {
		size: 90,
		fill: 0xF4F5FF,
		font: "Impact"
	}));
	var bonusAmountLabel = self.addChild(new Text2('1x', {
		size: 170,
		fill: 0xF4F5FF,
		font: "Impact"
	}));
	bonusLabel.anchor.set(1, 1);
	bonusLabel.y = barHeight / 2;
	var rightMargin = -10;
	bonusLabel.x = rightMargin;
	bonusAmountLabel.anchor.set(.5, .5);
	bonusAmountLabel.x = 100;
	var bonusBarWidth = bonusLabel.width;
	var bonusBarStart = self.attachAsset('bonusend', {
		y: 30,
		x: -bonusBarWidth + rightMargin
	});
	var bonuseBarEnd = self.attachAsset('bonusend', {
		y: 30,
		x: -bonusBarWidth + rightMargin
	});
	var bonusBarMiddle = self.attachAsset('bonusbarmiddle', {
		y: 30,
		x: -bonusBarWidth + rightMargin + barHeight / 2,
		width: 0
	});
	self.x = game.width - 270;
	self.y = game.height - 145;
	var bonusBarStepSize = (bonusBarWidth - barHeight) / 5;
	var targetWidth = 0;
	var currentWidth = 0;
	var jumpToAtEnd = 0;
	self.bonusAmount = 1;
	self.streakCount = 0;
	var maxLevel = 40;
	self.setStreakCount = function (level) {
		self.streakCount = Math.min(level, maxLevel);
		var newBonus = Math.floor(self.streakCount / 5) + 1;
		if (newBonus != self.bonusAmount) {
			for (var a = 0; a < scoreMultipliers.length; a++) {
				scoreMultipliers[a].setMultiplier(newBonus);
			}
		}
		self.bonusAmount = newBonus;
		bonusAmountLabel.setText(self.bonusAmount + 'x');
		var newbarpos = level >= maxLevel ? 5 : level % 5;
		targetWidth = newbarpos * bonusBarStepSize;
		jumpToAtEnd = targetWidth;
		if (newbarpos == 0 && level > 0) {
			targetWidth = 5 * bonusBarStepSize;
			jumpToAtEnd = 0;
		}
	};
	self.update = function () {
		var delta = targetWidth - currentWidth;
		if (delta < 1) {
			targetWidth = currentWidth = jumpToAtEnd;
		} else {
			currentWidth += delta / 8;
		}
		bonuseBarEnd.x = -bonusBarWidth + currentWidth + rightMargin;
		bonusBarMiddle.width = currentWidth;
	};
	//	bonuseBarEnd.x = -bonusLabel.width;
});
var Bubble = Container.expand(function (max_types, isFireBall, type) {
	var self = Container.call(this);
	self.isFireBall = isFireBall;
	var state = 0;
	self.isAttached = true;
	self.isFreeBubble = false;
	var speedX = 0;
	var speedY = 0;
	self.targetX = 0;
	self.targetY = 0;
	self.setPos = function (x, y) {
		self.x = self.targetX = x;
		self.y = self.targetY = y;
	};
	if (type !== undefined) {
		this.type = type;
	} else {
		max_types = max_types || 3;
		if (max_types > 4) {
			self.type = Math.floor(Math.random() * (.8 + Math.random() * .2) * max_types);
		} else {
			self.type = Math.floor(Math.random() * max_types);
		}
	}
	if (isFireBall) {
		var bubbleGraphics = self.attachAsset('fireball', {
			anchorX: 0.5,
			anchorY: 0.5
		});
		bubbleGraphics.width = 150;
		bubbleGraphics.height = 150;
	} else {
		var bubbleGraphics;
		if (self.type === 2) {
			bubbleGraphics = self.attachAsset('Soccerball', {
				anchorX: 0.5,
				anchorY: 0.5
			});
		} else {
			bubbleGraphics = self.attachAsset('bubble' + self.type, {
				anchorX: 0.5,
				anchorY: 0.5
			});
		}
	}
	/*if (!isFireBall && self.type > 1) {
		bubbleGraphics.tint = bubbleColors[self.type];
	}*/ 
	self.detach = function () {
		freeBubbleLayer.addChild(self);
		LK.getSound('detachCircle').play();
		self.y += grid.y;
		self.isAttached = false;
		speedX = Math.random() * 40 - 20;
		speedY = -Math.random() * 30;
		self.down = undefined;
	};
	var spawnMod = 0;
	self.update = function () {
		if (self.isFreeBubble) {
			if (isFireBall) {
				if (++spawnMod % 2 == 0 && self.parent) {
					// Spawn fire particles every 5 ticks
					var angle = Math.random() * Math.PI * 2;
					var fireParticle = self.parent.addChild(new FireParticle(angle));
					fireParticle.x = self.x + Math.cos(angle) * self.width / 4;
					fireParticle.y = self.y + Math.sin(angle) * self.width / 4;
				}
			}
			return;
		}
		if (self.isAttached) {
			if (self.x != self.targetX) {
				self.x += (self.targetX - self.x) / 10;
			}
			if (self.y != self.targetY) {
				self.y += (self.targetY - self.y) / 10;
			}
		} else {
			self.x += speedX;
			self.y += speedY;
			speedY += 1.5;
			if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
				speedX = -speedX;
				LK.getSound('circleBounce').play();
			}
			// Check for collision with barriers
			for (var i = 0; i < barriers.length; i++) {
				var barrier = barriers[i];
				var dx = self.x - barrier.x;
				var dy = self.y - barrier.y;
				var distance = Math.sqrt(dx * dx + dy * dy);
				var minDist = bubbleSize / 2 + barrier.width / 2;
				if (distance < minDist) {
					// Calculate the angle of the collision
					var angle = Math.atan2(dy, dx);
					// Calculate the new speed based on the angle of collision, treating the barrier as a static billiard ball
					var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
					speedX = Math.cos(angle) * newSpeed * .7;
					speedY = Math.sin(angle) * newSpeed * .7;
					// Move the bubble back to the point where it just touches the barrier
					var overlap = minDist - distance;
					self.x += overlap * Math.cos(angle);
					self.y += overlap * Math.sin(angle);
					LK.getSound('circleBounce').play();
				}
			}
			// Remove unattached bubbles that fall below 2732 - 500
			if (self.y > 2732 - 400) {
				self.destroy();
				scoreMultipliers[Math.floor(self.x / (2048 / 5))].applyBubble(self);
				LK.getSound('scoreCollected').play();
			}
		}
	};
});
var BubbleRemoveParticle = Container.expand(function () {
	var self = Container.call(this);
	var particle = self.attachAsset('removebubbleeffect', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	particle.blendMode = 1;
	self.scale.set(.33, .33);
	var cscale = .5;
	self.update = function () {
		cscale += .02;
		self.scale.set(cscale, cscale);
		self.alpha = 1 - (cscale - .5) * 1.5;
		if (self.alpha < 0) {
			self.destroy();
		}
	};
});
var FireBallPowerupOverlay = Container.expand(function () {
	var self = Container.call(this);
	var bubbleGraphics = self.attachAsset('fireball', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.y = game.height - 140;
	self.x = 200;
	var countBG = self.attachAsset('countbg', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	countBG.x = 90;
	countBG.y = 50;
	self.fireballsLeft = 1;
	var label = self.addChild(new Text2(self.fireballsLeft, {
		size: 70,
		fill: 0xFFFFFF,
		font: "Impact",
		stroke: 0x000000,
		strokeThickness: 13
	}));
	label.anchor.set(.5, .5);
	label.x = 90;
	label.y = 50;
	self.down = function () {
		if (self.fireballsLeft > 0 && !launcher.isFireBall()) {
			self.fireballsLeft--;
			label.setText(self.fireballsLeft);
			launcher.triggerFireBall();
			if (self.fireballsLeft == 0) {
				self.alpha = .5;
			}
		}
	};
});
var FireParticle = Container.expand(function (angle) {
	var self = Container.call(this);
	var particleGraphics = self.attachAsset('fireparticle', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	particleGraphics.blendMode = 1;
	var speedX = Math.cos(angle) * 1;
	var speedY = Math.sin(angle) * 1;
	var rotationSpeed = Math.random() * 0.1 - 0.05;
	self.update = function () {
		self.x += speedX * self.alpha;
		self.y += speedY * self.alpha;
		particleGraphics.rotation += rotationSpeed;
		self.alpha -= 0.01;
		if (self.alpha <= 0) {
			self.destroy();
		}
	};
});
var Grid = Container.expand(function () {
	var self = Container.call(this);
	var rows = [];
	self.container = self.addChild(new Container());
	var rowCount = 0;
	function insertRow() {
		var row = [];
		var rowWidth = rowCount % 2 == 0 ? 13 : 12;
		for (var a = 0; a < rowWidth; a++) {
			var bubble = new Bubble(getMaxTypes());
			bubble.setPos((2048 - bubbleSize * rowWidth) / 2 + bubbleSize * a + bubbleSize / 2, -rowCount * (1.7320508076 * bubbleSize) / 2);
			self.container.addChild(bubble);
			row.push(bubble);
			/*bubble.down = function () {
				var bubbles = self.getConnectedBubbles(this);
				self.removeBubbles(bubbles);
				var disconnected = self.getDetachedBubbles();
				self.removeBubbles(disconnected);
			};*/
		}
		rows.push(row);
		rowCount++;
	}
	//Method that removes an array of bubbles from the rows array. 
	self.removeBubbles = function (bubbles) {
		for (var i = 0; i < bubbles.length; i++) {
			var bubble = bubbles[i];
			if (bubble) {
				var bubbleIndex = this.findBubbleIndex(bubble);
				if (bubbleIndex) {
					rows[bubbleIndex.row][bubbleIndex.col] = null;
					bubble.detach();
				}
			}
		}
	};
	self.getConnectedBubbles = function (bubble, ignoreType) {
		var connectedBubbles = [];
		var queue = [bubble];
		var visited = [];
		while (queue.length > 0) {
			var currentBubble = queue.shift();
			if (visited.indexOf(currentBubble) === -1) {
				visited.push(currentBubble);
				connectedBubbles.push(currentBubble);
				var neighbors = self.getNeighbors(currentBubble);
				for (var i = 0; i < neighbors.length; i++) {
					var neighbor = neighbors[i];
					if (neighbor && (neighbor.type === bubble.type || ignoreType)) {
						queue.push(neighbor);
					}
				}
			}
		}
		return connectedBubbles;
	};
	//Get a list of bubbles that are not connected to the top row, or to a chain of bubbles connected to the top row.
	self.getDetachedBubbles = function () {
		var detachedBubbles = [];
		var connectedToTop = [];
		// Mark all bubbles connected to the bottom row
		var lastRowIndex = rows.length - 1;
		for (var i = 0; i < rows[lastRowIndex].length; i++) {
			if (rows[lastRowIndex][i] !== null) {
				var bottomConnected = self.getConnectedBubbles(rows[lastRowIndex][i], true);
				connectedToTop = connectedToTop.concat(bottomConnected);
			}
		}
		// Mark all bubbles as visited or not
		var visited = connectedToTop.filter(function (bubble) {
			return bubble != null;
		});
		// Find all bubbles that are not visited and not connected to the top
		for (var row = 0; row < rows.length - 1; row++) {
			for (var col = 0; col < rows[row].length; col++) {
				var bubble = rows[row][col];
				if (bubble !== null && visited.indexOf(bubble) == -1) {
					detachedBubbles.push(bubble);
				}
			}
		}
		return detachedBubbles;
	};
	self.getNeighbors = function (bubble) {
		var neighbors = [];
		var bubbleIndex = this.findBubbleIndex(bubble);
		if (!bubbleIndex) {
			return [];
		}
		var directions = [[-1, 0], [1, 0],
		// left and right
		[0, -1], [0, 1],
		// above and below
		[-1, -1], [1, -1] // diagonals for even rows
		];
		if (bubbleIndex && rows[bubbleIndex.row] && rows[bubbleIndex.row].length == 12) {
			// Adjust diagonals for odd rows
			directions[4] = [-1, 1];
			directions[5] = [1, 1];
		}
		for (var i = 0; i < directions.length; i++) {
			var dir = directions[i];
			if (bubbleIndex && rows[bubbleIndex.row]) {
				var newRow = bubbleIndex.row + dir[0];
			}
			var newCol = bubbleIndex.col + dir[1];
			if (newRow >= 0 && newRow < rows.length && newCol >= 0 && newCol < rows[newRow].length) {
				neighbors.push(rows[newRow][newCol]);
			}
		}
		return neighbors;
	};
	self.findBubbleIndex = function (bubble) {
		for (var row = 0; row < rows.length; row++) {
			var col = rows[row].indexOf(bubble);
			if (col !== -1) {
				return {
					row: row,
					col: col
				};
			}
		}
		return null;
	};
	self.printRowsToConsole = function () {
		var gridString = '';
		for (var i = rows.length - 1; i >= 0; i--) {
			var rowString = ': ' + (rows[i].length == 13 ? '' : '  ');
			for (var j = 0; j < rows[i].length; j++) {
				var bubble = rows[i][j];
				rowString += bubble ? '[' + bubble.type + ']' : '[_]';
			}
			gridString += rowString + '\n';
		}
		console.log(gridString);
	};
	// Method to calculate path of movement based on angle and starting point
	//TODO: MAKE THIS MUCH FASTER!
	self.bubbleIntersectsGrid = function (nextX, nextY) {
		outer: for (var row = 0; row < rows.length; row++) {
			for (var col = 0; col < rows[row].length; col++) {
				var bubble = rows[row][col];
				if (bubble) {
					var dist = nextY - bubble.y - self.y;
					//Quick exit if we are nowhere near the row
					if (dist > 145 || dist < -145) {
						continue outer;
					}
					var dx = nextX - bubble.x - self.x;
					var dy = nextY - bubble.y - self.y;
					var distance = Math.sqrt(dx * dx + dy * dy);
					if (distance < (bubbleSize - 70) / 2 + bubbleSize / 2) {
						return bubble;
					}
				}
			}
		}
		return false;
	};
	self.calculatePath = function (startPoint, angle) {
		var path = [];
		var currentPoint = {
			x: startPoint.x,
			y: startPoint.y
		};
		var radians = angle;
		var stepSize = 4;
		var hitBubble = false;
		while (currentPoint.y > 0 && !hitBubble) {
			// Calculate next point
			var nextX = currentPoint.x + stepSize * Math.cos(radians);
			var nextY = currentPoint.y + stepSize * Math.sin(radians);
			// Check for wall collisions
			if (nextX < 150 / 2 || nextX > 2048 - 150 / 2) {
				radians = Math.PI - radians; // Reflect angle
				nextX = currentPoint.x + stepSize * Math.cos(radians); // Recalculate nextX after reflection
			}
			hitBubble = self.bubbleIntersectsGrid(nextX, nextY);
			// Add point to path and update currentPoint
			path.push({
				x: nextX,
				y: nextY
			});
			currentPoint.x = nextX;
			currentPoint.y = nextY;
		}
		if (hitBubble) {
			//Only increase avilable bubble type when we have actually pointed as such a bubble
			if (hitBubble.type + 1 > maxSelectableBubble) {
				maxSelectableBubble = hitBubble.type + 1;
			}
			;
		}
		return path;
	};
	var bubblesInFlight = [];
	self.fireBubble = function (bubble, angle) {
		self.addChild(bubble);
		bubble.x = launcher.x;
		bubble.y += launcher.y - self.y;
		bubblesInFlight.push({
			bubble: bubble,
			angle: angle
		});
	};
	self.calculateWarningScoreList = function () {
		var warningScores = [];
		for (var i = 0; i < 13; i++) {
			warningScores.push(0); // Initialize all scores to 0
		}
		// Calculate the distance from the bottom for each bubble and increment the warning score based on proximity
		for (var row = 0; row < rows.length; row++) {
			for (var col = 0; col < rows[row].length; col++) {
				var bubble = rows[row][col];
				if (bubble) {
					var distanceFromBottom = 2732 - (bubble.y + self.y);
					if (distanceFromBottom < 2000) {
						// If a bubble is within 500px from the bottom
						var columnIndex = Math.floor(bubble.x / (2048 / 13));
						warningScores[columnIndex] += (2000 - distanceFromBottom) / 2000; // Increment the warning score for the column
					}
				}
			}
		}
		return warningScores;
	};
	self.update = function () {
		outer: for (var a = 0; a < bubblesInFlight.length; a++) {
			var current = bubblesInFlight[a];
			var bubble = current.bubble;
			var nextX = bubble.x;
			var nextY = bubble.y + gridSpeed;
			var prevX = bubble.x;
			var prevY = bubble.y;
			for (var rep = 0; rep < 25; rep++) {
				prevX = nextX;
				prevY = nextY;
				nextX += Math.cos(current.angle) * 4;
				nextY += Math.sin(current.angle) * 4;
				if (nextX < 150 / 2 || nextX > 2048 - 150 / 2) {
					current.angle = Math.PI - current.angle; // Reflect angle
					nextX = Math.min(Math.max(nextX, 150 / 2), 2048 - 150 / 2);
					LK.getSound('circleBounce').play();
				}
				var intersectedBubble = self.bubbleIntersectsGrid(nextX + self.x, nextY + self.y);
				if (intersectedBubble) {
					gameIsStarted = true;
					if (bubble.isFireBall) {
						self.removeBubbles([intersectedBubble]);
						var disconnected = self.getDetachedBubbles();
						self.removeBubbles(disconnected);
					} else {
						var intersectedBubblePos = self.findBubbleIndex(intersectedBubble);
						var colOffset = rows[intersectedBubblePos.row].length == 13 ? 0 : 1;
						var offsetPositions = [{
							x: intersectedBubble.targetX - bubbleSize / 2,
							y: intersectedBubble.targetY - 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row + 1,
							co: intersectedBubblePos.col - 1 + colOffset
						}, {
							x: intersectedBubble.targetX + bubbleSize / 2,
							y: intersectedBubble.targetY - 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row + 1,
							co: intersectedBubblePos.col + colOffset
						}, {
							x: intersectedBubble.targetX + bubbleSize,
							y: intersectedBubble.targetY,
							ro: intersectedBubblePos.row,
							co: intersectedBubblePos.col + 1
						}, {
							x: intersectedBubble.targetX + bubbleSize / 2,
							y: intersectedBubble.targetY + 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row - 1,
							co: intersectedBubblePos.col + colOffset
						}, {
							x: intersectedBubble.targetX - bubbleSize / 2,
							y: intersectedBubble.targetY + 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row - 1,
							co: intersectedBubblePos.col - 1 + colOffset
						}, {
							x: intersectedBubble.targetX - bubbleSize,
							y: intersectedBubble.targetY,
							ro: intersectedBubblePos.row,
							co: intersectedBubblePos.col - 1
						}];
						var closestPosition = 0;
						var closestDistance = Math.sqrt(Math.pow(offsetPositions[0].x - bubble.x, 2) + Math.pow(offsetPositions[0].y - bubble.y, 2));
						for (var i = 1; i < offsetPositions.length; i++) {
							var currentPosition = offsetPositions[i];
							var currentDistance = Math.sqrt(Math.pow(currentPosition.x - bubble.x, 2) + Math.pow(currentPosition.y - bubble.y, 2));
							if (currentDistance < closestDistance) {
								var row = rows[currentPosition.ro];
								if (currentPosition.co < 0) {
									continue;
								}
								if (row) {
									if (row[currentPosition.co]) {
										continue;
									}
									if (currentPosition.co >= row.length) {
										continue;
									}
								} else {
									var newRowLength = rows[intersectedBubblePos.row].length == 13 ? 12 : 13;
									if (currentPosition.co >= newRowLength) {
										continue;
									}
								}
								closestDistance = currentDistance;
								closestPosition = i;
							}
						}
						// Attach bubble to the closest position
						var currentMatch = offsetPositions[closestPosition];
						bubble.x = prevX;
						bubble.y = prevY;
						bubble.targetX = currentMatch.x;
						bubble.targetY = currentMatch.y;
						bubble.isFreeBubble = false;
						var row = rows[offsetPositions[closestPosition].ro];
						if (!row) {
							if (rows[intersectedBubblePos.row].length == 13) {
								row = [null, null, null, null, null, null, null, null, null, null, null, null];
							} else {
								row = [null, null, null, null, null, null, null, null, null, null, null, null, null];
							}
							rows.unshift(row);
						}
						row[offsetPositions[closestPosition].co] = bubble;
						bubblesInFlight.splice(a--, 1);
						refreshHintLine();
						var bubbles = self.getConnectedBubbles(bubble);
						if (bubbles.length > 2) {
							self.removeBubbles(bubbles);
							var disconnected = self.getDetachedBubbles();
							self.removeBubbles(disconnected);
							bonusUX.setStreakCount(bonusUX.streakCount + 1);
						} else {
							bonusUX.setStreakCount(0);
							LK.getSound('attachCircle').play();
						}
						//Add a grid movement effect when you don't do a match
						var neighbors = self.getNeighbors(bubble);
						var touched = [];
						var neighbors2 = [];
						for (var i = 0; i < neighbors.length; i++) {
							var neighbor = neighbors[i];
							if (neighbor) {
								touched.push(neighbor);
								neighbors2 = neighbors2.concat(self.getNeighbors(neighbor));
								var ox = neighbor.x - bubble.x;
								var oy = neighbor.y - bubble.y;
								var angle = Math.atan2(oy, ox);
								neighbor.x += Math.cos(angle) * 20;
								neighbor.y += Math.sin(angle) * 20;
							}
						}
						//One more layer
						for (var i = 0; i < neighbors2.length; i++) {
							var neighbor = neighbors2[i];
							if (neighbor && touched.indexOf(neighbor) == -1) {
								touched.push(neighbor);
								var ox = neighbor.x - bubble.x;
								var oy = neighbor.y - bubble.y;
								var angle = Math.atan2(oy, ox);
								neighbor.x += Math.cos(angle) * 10;
								neighbor.y += Math.sin(angle) * 10;
							}
						}
						//self.printRowsToConsole();
						continue outer;
					}
				}
			}
			bubble.x = nextX;
			bubble.y = nextY;
			if (bubble.y + self.y < -1000) {
				//Destory bubbles that somehow manages to escape at the top
				bubblesInFlight.splice(a--, 1);
				bubble.destroy();
			}
		}
		if (gameIsStarted) {
			self.y += gridSpeed;
		}
		var zeroRow = rows[rows.length - 1];
		if (zeroRow) {
			for (var a = 0; a < zeroRow.length; a++) {
				var bubble = zeroRow[a];
				if (bubble) {
					if (bubble.y + self.y > 0) {
						insertRow();
					}
					break;
				}
			}
		} else {
			insertRow();
		}
		for (var row = rows.length - 1; row >= 0; row--) {
			if (rows[row].every(function (bubble) {
				return !bubble;
			})) {
				rows.splice(row, 1);
			}
		}
		var lastRow = rows[0];
		/*if(LK.ticks % 10 == 0){
			self.printRowsToConsole()
		}*/ 
		if (lastRow) {
			for (var a = 0; a < zeroRow.length; a++) {
				var bubble = lastRow[a];
				if (bubble) {
					if (bubble.y + self.y > 2200) {
						LK.effects.flashScreen(0xff0000, 3000);
						LK.getSound('gameOverJingle').play();
						LK.showGameOver();
					}
					if (gameIsStarted) {
						var targetSpeed = Math.pow(Math.pow((2200 - (bubble.y + self.y)) / 2200, 2), 2) * 4 + 0.5;
						if (bubble.y + self.y > 2000) {
							targetSpeed = .2;
						}
						gridSpeed += (targetSpeed - gridSpeed) / 20;
						if (LK.ticks % 10 == 0) {
							//console.log(gridSpeed)
						}
					}
					break;
				}
			}
		}
	};
	for (var a = 0; a < 8; a++) {
		insertRow();
	}
});
var HintBubble = Container.expand(function () {
	var self = Container.call(this);
	var bubble = self.attachAsset('hintbubble', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.setTint = function (tint) {
		bubble.tint = tint;
	};
	self.getTint = function (tint) {
		return bubble.tint;
	};
});
var Launcher = Container.expand(function () {
	var self = Container.call(this);
	var bubble = self.addChild(new Bubble(getMaxTypes(), false));
	bubble.isFreeBubble = true;
	var previewBubble;
	var lastTypes = [undefined, bubble.type];
	function createPreviewBubble() {
		var nextType;
		do {
			nextType = Math.floor(Math.random() * maxSelectableBubble);
		} while (nextType == lastTypes[0] && nextType == lastTypes[1]);
		lastTypes.shift();
		lastTypes.push(nextType);
		previewBubble = self.addChildAt(new Bubble(maxSelectableBubble, false, nextType), 0);
		previewBubble.scale.set(.7, .7);
		previewBubble.x = -90;
		previewBubble.y = 20;
		previewBubble.isFreeBubble = true;
	}
	createPreviewBubble();
	self.fire = function () {
		bulletsFired++;
		LK.getSound('fireBubble').play(); // Play sound when the ball is fired
		grid.fireBubble(bubble, self.angle);
		bubble = previewBubble;
		previewBubble.x = previewBubble.y = 0;
		previewBubble.scale.set(1, 1);
		createPreviewBubble();
	};
	self.angle = -Math.PI / 2;
	self.getBubble = function () {
		return bubble;
	};
	self.isFireBall = function () {
		return bubble.isFireBall;
	};
	self.triggerFireBall = function () {
		bubble.destroy();
		bubble = self.addChild(new Bubble(getMaxTypes(), true));
		bubble.isFreeBubble = true;
	};
});
var ScoreIndicatorLabel = Container.expand(function (score, type) {
	var self = Container.call(this);
	var label = new Text2(score, {
		size: 100,
		fill: "#" + bubbleColors[type].toString(16).padStart(6, '0'),
		font: "Impact"
	});
	label.anchor.set(0.5, 0);
	self.addChild(label);
	self.update = function () {
		self.y -= 7;
		self.alpha -= .05;
		if (self.alpha <= 0) {
			self.destroy();
			increaseScore(score);
		}
	};
});
var ScoreMultipliers = Container.expand(function (baseValue) {
	var self = Container.call(this);
	// Create a score label text string for ScoreMultipliers
	var scoreMultiplierLabel = new Text2(baseValue, {
		size: 100,
		fill: 0x3954FF,
		font: "Impact"
	});
	scoreMultiplierLabel.anchor.set(0.5, 0);
	self.addChild(scoreMultiplierLabel);
	var currentMultiplier = 1;
	self.applyBubble = function (bubble) {
		var scoreIndicator = game.addChild(new ScoreIndicatorLabel(baseValue * currentMultiplier, bubble.type));
		scoreIndicator.x = self.x;
		scoreIndicator.y = self.y;
		var particle = particlesLayer.addChild(new BubbleRemoveParticle());
		particle.x = bubble.x;
		particle.y = self.y + 150;
	};
	self.setMultiplier = function (multiplier) {
		currentMultiplier = multiplier;
		scoreMultiplierLabel.setText(baseValue * currentMultiplier);
	};
});
var WarningLine = Container.expand(function () {
	var self = Container.call(this);
	var warning = self.attachAsset('warningstripe', {
		anchorX: .5,
		anchorY: .5
	});
	var warningOffset = Math.random() * 100;
	var speed = Math.random() * 1 + 1;
	self.update = function () {
		warningOffset += speed;
		warning.alpha = (Math.cos(warningOffset / 50) + 1) / 2 * 0.3 + .7;
	};
	warning.blendMode = 1;
	warning.rotation = .79;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x0c0d25
});
/**** 
* Game Code
****/ 
var gridSpeed = .5;
function increaseScore(amount) {
	var currentScore = LK.getScore();
	var newScore = currentScore + amount;
	LK.setScore(newScore); // Update the game score using LK method
	scoreLabel.setText(newScore.toString()); // Update the score label with the new score
}
//Game size 2048x2732
/* 
Todo: 
[X] Make sure we GC nodes that drop of screen
[ ] Make preview line fade out at the end
*/
var bulletsFired = 0; //3*30+1
var bubbleSize = 150;
var gameIsStarted = false;
var bubbleColors = [0xff2853, 0xcb2bff, 0x5252ff, 0xcb2bff, 0x28f2f0, 0xffc411];
var barriers = [];
var maxSelectableBubble = 3;
var warningLines = [];
for (var a = 0; a < 13; a++) {
	var wl = game.addChild(new WarningLine());
	wl.x = 2048 / 13 * (a + .5);
	wl.y = 2200;
	wl.alpha = 0;
	warningLines.push(wl);
	wl.scale.set(16, 40);
}
var warningOverlay = game.attachAsset('dangeroverlay', {});
warningOverlay.y = 2280;
var uxoverlay = game.attachAsset('uxoverlay', {});
uxoverlay.y = 2440;
var uxoverlay2 = game.attachAsset('uxoverlay2', {});
uxoverlay2.y = 2460;
for (var a = 0; a < 4; a++) {
	for (var b = 0; b < 3; b++) {
		var barrier = game.addChild(new Barrier());
		barrier.y = 2732 - 450 + b * 70;
		barrier.x = 2048 / 5 * a + 2048 / 5;
		barriers.push(barrier);
	}
	var barrierBlock = game.attachAsset('barrierblock', {});
	barrierBlock.x = 2048 / 5 * a + 2048 / 5;
	barrierBlock.y = 2732 - 450;
	barrierBlock.anchor.x = .5;
}
// Create a score label
var scoreLabel = new Text2('0', {
	size: 120,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 15,
	font: "Impact"
});
scoreLabel.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreLabel);
var scoreMultipliers = [];
var baseScores = [100, 250, 500, 250, 100];
for (var a = 0; a < 5; a++) {
	var sm = new ScoreMultipliers(baseScores[a]);
	sm.x = 2048 / 5 * a + 2048 / 10;
	sm.y = 2300;
	scoreMultipliers.push(sm);
	game.addChild(sm);
}
var bonusUX = game.addChild(new BonusUX());
var fireBallPowerupOverlay = game.addChild(new FireBallPowerupOverlay());
var particlesLayer = game.addChild(new Container());
var grid = game.addChild(new Grid());
grid.y = 1000;
var freeBubbleLayer = game.addChild(new Container());
var hintBubblePlayer = game.addChild(new Container());
var launcher = game.addChild(new Launcher());
launcher.x = game.width / 2;
launcher.y = game.height - 138;
var hintBubbleCache = [];
var hintBubbles = [];
var isValid = false;
var path = [];
var bubbleAlpha = 1;
var hintTargetX = game.width / 2;
var hintTargetY = 0;
game.move = function (x, y, obj) {
	hintTargetX = x;
	hintTargetY = y;
	refreshHintLine();
	//	}
};
game.down = game.move;
function getMaxTypes() {
	if (bulletsFired > 30 * 3 * 3) {
		return 6;
	} else if (bulletsFired > 30 * 3) {
		return 5;
	} else if (bulletsFired > 30) {
		return 4;
	}
	return 3;
}
function refreshHintLine() {
	var ox = hintTargetX - launcher.x;
	var oy = hintTargetY - launcher.y;
	var angle = Math.atan2(oy, ox);
	launcher.angle = angle;
	isValid = angle < -.2 && angle > -Math.PI + .2;
	if (isValid) {
		path = grid.calculatePath(launcher, angle);
		//This allows updated faster than 60fps, making everyting feel better.
	}
	renderHintBubbels();
}
var hintOffset = 0;
var distanceBetweenHintbubbles = 100;
function renderHintBubbels() {
	if (isValid) {
		hintOffset = hintOffset % distanceBetweenHintbubbles;
		var distanceSinceLastDot = -hintOffset + 100;
		var hintBubbleOffset = 0;
		var lastPoint = path[0];
		var bubble = launcher.getBubble();
		var tint = bubble.isFireBall ? 0xff9c00 : bubbleColors[bubble.type];
		var updateTint = true;
		for (var a = 1; a < path.length; a++) {
			var p2 = path[a];
			var ox = p2.x - lastPoint.x;
			var oy = p2.y - lastPoint.y;
			var dist = Math.sqrt(ox * ox + oy * oy);
			distanceSinceLastDot += dist;
			if (distanceSinceLastDot >= distanceBetweenHintbubbles) {
				var amountOver = distanceSinceLastDot - distanceBetweenHintbubbles;
				var angle = Math.atan2(oy, ox);
				var currentBubble = hintBubbles[hintBubbleOffset];
				if (!currentBubble) {
					currentBubble = hintBubbles[hintBubbleOffset] = new HintBubble();
					hintBubblePlayer.addChild(currentBubble);
				}
				currentBubble.alpha = bubbleAlpha;
				currentBubble.visible = true;
				var currentTint = currentBubble.getTint();
				if (hintBubbleOffset == 0) {
					currentBubble.setTint(tint);
				} else if (updateTint && currentTint != tint || currentTint == 0xffffff) {
					currentBubble.setTint(tint);
					updateTint = false;
				}
				currentBubble.x = lastPoint.x - Math.cos(angle) * amountOver;
				currentBubble.y = lastPoint.y - Math.sin(angle) * amountOver;
				hintBubbleOffset++;
				distanceSinceLastDot = 0;
				lastPoint = currentBubble;
			} else {
				lastPoint = p2;
			}
		}
		for (var a = hintBubbleOffset; a < hintBubbles.length; a++) {
			hintBubbles[a].visible = false;
		}
	} else {
		for (var a = 0; a < hintBubbles.length; a++) {
			hintBubbles[a].alpha = bubbleAlpha;
		}
	}
}
game.update = function () {
	hintOffset += 5;
	if (isValid) {
		bubbleAlpha = Math.min(bubbleAlpha + .05, 1);
	} else {
		bubbleAlpha = Math.max(bubbleAlpha - .05, 0);
	}
	refreshHintLine();
	var alphaList = grid.calculateWarningScoreList();
	for (var a = 0; a < warningLines.length; a++) {
		var value = alphaList[a] / 3;
		warningLines[a].alpha += (Math.min(value, .6) - warningLines[a].alpha) / 100;
		warningLines[a].scale.y += (value * 60 - warningLines[a].scale.y) / 100;
	}
};
game.up = function () {
	if (isValid) {
		launcher.fire();
	}
};
;
;
;
;
;
;
;
;
;
; /**** 
* Classes
****/ 
var Barrier = Container.expand(function () {
	var self = Container.call(this);
	var barrierGraphics = self.attachAsset('barrier', {
		anchorX: .5,
		anchorY: .5
	});
});
var BonusUX = Container.expand(function () {
	var self = Container.call(this);
	//Insert label here
	var barHeight = 50;
	var bonusLabel = self.addChild(new Text2('Streak Bonus', {
		size: 90,
		fill: 0xF4F5FF,
		font: "Impact"
	}));
	var bonusAmountLabel = self.addChild(new Text2('1x', {
		size: 170,
		fill: 0xF4F5FF,
		font: "Impact"
	}));
	bonusLabel.anchor.set(1, 1);
	bonusLabel.y = barHeight / 2;
	var rightMargin = -10;
	bonusLabel.x = rightMargin;
	bonusAmountLabel.anchor.set(.5, .5);
	bonusAmountLabel.x = 100;
	var bonusBarWidth = bonusLabel.width;
	var bonusBarStart = self.attachAsset('bonusend', {
		y: 30,
		x: -bonusBarWidth + rightMargin
	});
	var bonuseBarEnd = self.attachAsset('bonusend', {
		y: 30,
		x: -bonusBarWidth + rightMargin
	});
	var bonusBarMiddle = self.attachAsset('bonusbarmiddle', {
		y: 30,
		x: -bonusBarWidth + rightMargin + barHeight / 2,
		width: 0
	});
	self.x = game.width - 270;
	self.y = game.height - 145;
	var bonusBarStepSize = (bonusBarWidth - barHeight) / 5;
	var targetWidth = 0;
	var currentWidth = 0;
	var jumpToAtEnd = 0;
	self.bonusAmount = 1;
	self.streakCount = 0;
	var maxLevel = 40;
	self.setStreakCount = function (level) {
		self.streakCount = Math.min(level, maxLevel);
		var newBonus = Math.floor(self.streakCount / 5) + 1;
		if (newBonus != self.bonusAmount) {
			for (var a = 0; a < scoreMultipliers.length; a++) {
				scoreMultipliers[a].setMultiplier(newBonus);
			}
		}
		self.bonusAmount = newBonus;
		bonusAmountLabel.setText(self.bonusAmount + 'x');
		var newbarpos = level >= maxLevel ? 5 : level % 5;
		targetWidth = newbarpos * bonusBarStepSize;
		jumpToAtEnd = targetWidth;
		if (newbarpos == 0 && level > 0) {
			targetWidth = 5 * bonusBarStepSize;
			jumpToAtEnd = 0;
		}
	};
	self.update = function () {
		var delta = targetWidth - currentWidth;
		if (delta < 1) {
			targetWidth = currentWidth = jumpToAtEnd;
		} else {
			currentWidth += delta / 8;
		}
		bonuseBarEnd.x = -bonusBarWidth + currentWidth + rightMargin;
		bonusBarMiddle.width = currentWidth;
	};
	//	bonuseBarEnd.x = -bonusLabel.width;
});
var Bubble = Container.expand(function (max_types, isFireBall, type) {
	var self = Container.call(this);
	self.isFireBall = isFireBall;
	var state = 0;
	self.isAttached = true;
	self.isFreeBubble = false;
	var speedX = 0;
	var speedY = 0;
	self.targetX = 0;
	self.targetY = 0;
	self.setPos = function (x, y) {
		self.x = self.targetX = x;
		self.y = self.targetY = y;
	};
	if (type !== undefined) {
		this.type = type;
	} else {
		max_types = max_types || 3;
		if (max_types > 4) {
			self.type = Math.floor(Math.random() * (.8 + Math.random() * .2) * max_types);
		} else {
			self.type = Math.floor(Math.random() * max_types);
		}
	}
	if (isFireBall) {
		var bubbleGraphics = self.attachAsset('fireball', {
			anchorX: 0.5,
			anchorY: 0.5
		});
		bubbleGraphics.width = 150;
		bubbleGraphics.height = 150;
	} else {
		var bubbleGraphics;
		if (self.type === 2) {
			bubbleGraphics = self.attachAsset('Soccerball', {
				anchorX: 0.5,
				anchorY: 0.5
			});
		} else {
			bubbleGraphics = self.attachAsset('bubble' + self.type, {
				anchorX: 0.5,
				anchorY: 0.5
			});
		}
	}
	/*if (!isFireBall && self.type > 1) {
		bubbleGraphics.tint = bubbleColors[self.type];
	}*/ 
	self.detach = function () {
		freeBubbleLayer.addChild(self);
		LK.getSound('detachCircle').play();
		self.y += grid.y;
		self.isAttached = false;
		speedX = Math.random() * 40 - 20;
		speedY = -Math.random() * 30;
		self.down = undefined;
	};
	var spawnMod = 0;
	self.update = function () {
		if (self.isFreeBubble) {
			if (isFireBall) {
				if (++spawnMod % 2 == 0 && self.parent) {
					// Spawn fire particles every 5 ticks
					var angle = Math.random() * Math.PI * 2;
					var fireParticle = self.parent.addChild(new FireParticle(angle));
					fireParticle.x = self.x + Math.cos(angle) * self.width / 4;
					fireParticle.y = self.y + Math.sin(angle) * self.width / 4;
				}
			}
			return;
		}
		if (self.isAttached) {
			if (self.x != self.targetX) {
				self.x += (self.targetX - self.x) / 10;
			}
			if (self.y != self.targetY) {
				self.y += (self.targetY - self.y) / 10;
			}
		} else {
			self.x += speedX;
			self.y += speedY;
			speedY += 1.5;
			if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
				speedX = -speedX;
				LK.getSound('circleBounce').play();
			}
			// Check for collision with barriers
			for (var i = 0; i < barriers.length; i++) {
				var barrier = barriers[i];
				var dx = self.x - barrier.x;
				var dy = self.y - barrier.y;
				var distance = Math.sqrt(dx * dx + dy * dy);
				var minDist = bubbleSize / 2 + barrier.width / 2;
				if (distance < minDist) {
					// Calculate the angle of the collision
					var angle = Math.atan2(dy, dx);
					// Calculate the new speed based on the angle of collision, treating the barrier as a static billiard ball
					var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
					speedX = Math.cos(angle) * newSpeed * .7;
					speedY = Math.sin(angle) * newSpeed * .7;
					// Move the bubble back to the point where it just touches the barrier
					var overlap = minDist - distance;
					self.x += overlap * Math.cos(angle);
					self.y += overlap * Math.sin(angle);
					LK.getSound('circleBounce').play();
				}
			}
			// Remove unattached bubbles that fall below 2732 - 500
			if (self.y > 2732 - 400) {
				self.destroy();
				scoreMultipliers[Math.floor(self.x / (2048 / 5))].applyBubble(self);
				LK.getSound('scoreCollected').play();
			}
		}
	};
});
var BubbleRemoveParticle = Container.expand(function () {
	var self = Container.call(this);
	var particle = self.attachAsset('removebubbleeffect', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	particle.blendMode = 1;
	self.scale.set(.33, .33);
	var cscale = .5;
	self.update = function () {
		cscale += .02;
		self.scale.set(cscale, cscale);
		self.alpha = 1 - (cscale - .5) * 1.5;
		if (self.alpha < 0) {
			self.destroy();
		}
	};
});
var FireBallPowerupOverlay = Container.expand(function () {
	var self = Container.call(this);
	var bubbleGraphics = self.attachAsset('fireball', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.y = game.height - 140;
	self.x = 200;
	var countBG = self.attachAsset('countbg', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	countBG.x = 90;
	countBG.y = 50;
	self.fireballsLeft = 1;
	var label = self.addChild(new Text2(self.fireballsLeft, {
		size: 70,
		fill: 0xFFFFFF,
		font: "Impact",
		stroke: 0x000000,
		strokeThickness: 13
	}));
	label.anchor.set(.5, .5);
	label.x = 90;
	label.y = 50;
	self.down = function () {
		if (self.fireballsLeft > 0 && !launcher.isFireBall()) {
			self.fireballsLeft--;
			label.setText(self.fireballsLeft);
			launcher.triggerFireBall();
			if (self.fireballsLeft == 0) {
				self.alpha = .5;
			}
		}
	};
});
var FireParticle = Container.expand(function (angle) {
	var self = Container.call(this);
	var particleGraphics = self.attachAsset('fireparticle', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	particleGraphics.blendMode = 1;
	var speedX = Math.cos(angle) * 1;
	var speedY = Math.sin(angle) * 1;
	var rotationSpeed = Math.random() * 0.1 - 0.05;
	self.update = function () {
		self.x += speedX * self.alpha;
		self.y += speedY * self.alpha;
		particleGraphics.rotation += rotationSpeed;
		self.alpha -= 0.01;
		if (self.alpha <= 0) {
			self.destroy();
		}
	};
});
var Grid = Container.expand(function () {
	var self = Container.call(this);
	var rows = [];
	self.container = self.addChild(new Container());
	var rowCount = 0;
	function insertRow() {
		var row = [];
		var rowWidth = rowCount % 2 == 0 ? 13 : 12;
		for (var a = 0; a < rowWidth; a++) {
			var bubble = new Bubble(getMaxTypes());
			bubble.setPos((2048 - bubbleSize * rowWidth) / 2 + bubbleSize * a + bubbleSize / 2, -rowCount * (1.7320508076 * bubbleSize) / 2);
			self.container.addChild(bubble);
			row.push(bubble);
			/*bubble.down = function () {
				var bubbles = self.getConnectedBubbles(this);
				self.removeBubbles(bubbles);
				var disconnected = self.getDetachedBubbles();
				self.removeBubbles(disconnected);
			};*/
		}
		rows.push(row);
		rowCount++;
	}
	//Method that removes an array of bubbles from the rows array. 
	self.removeBubbles = function (bubbles) {
		for (var i = 0; i < bubbles.length; i++) {
			var bubble = bubbles[i];
			if (bubble) {
				var bubbleIndex = this.findBubbleIndex(bubble);
				if (bubbleIndex) {
					rows[bubbleIndex.row][bubbleIndex.col] = null;
					bubble.detach();
				}
			}
		}
	};
	self.getConnectedBubbles = function (bubble, ignoreType) {
		var connectedBubbles = [];
		var queue = [bubble];
		var visited = [];
		while (queue.length > 0) {
			var currentBubble = queue.shift();
			if (visited.indexOf(currentBubble) === -1) {
				visited.push(currentBubble);
				connectedBubbles.push(currentBubble);
				var neighbors = self.getNeighbors(currentBubble);
				for (var i = 0; i < neighbors.length; i++) {
					var neighbor = neighbors[i];
					if (neighbor && (neighbor.type === bubble.type || ignoreType)) {
						queue.push(neighbor);
					}
				}
			}
		}
		return connectedBubbles;
	};
	//Get a list of bubbles that are not connected to the top row, or to a chain of bubbles connected to the top row.
	self.getDetachedBubbles = function () {
		var detachedBubbles = [];
		var connectedToTop = [];
		// Mark all bubbles connected to the bottom row
		var lastRowIndex = rows.length - 1;
		for (var i = 0; i < rows[lastRowIndex].length; i++) {
			if (rows[lastRowIndex][i] !== null) {
				var bottomConnected = self.getConnectedBubbles(rows[lastRowIndex][i], true);
				connectedToTop = connectedToTop.concat(bottomConnected);
			}
		}
		// Mark all bubbles as visited or not
		var visited = connectedToTop.filter(function (bubble) {
			return bubble != null;
		});
		// Find all bubbles that are not visited and not connected to the top
		for (var row = 0; row < rows.length - 1; row++) {
			for (var col = 0; col < rows[row].length; col++) {
				var bubble = rows[row][col];
				if (bubble !== null && visited.indexOf(bubble) == -1) {
					detachedBubbles.push(bubble);
				}
			}
		}
		return detachedBubbles;
	};
	self.getNeighbors = function (bubble) {
		var neighbors = [];
		var bubbleIndex = this.findBubbleIndex(bubble);
		if (!bubbleIndex) {
			return [];
		}
		var directions = [[-1, 0], [1, 0],
		// left and right
		[0, -1], [0, 1],
		// above and below
		[-1, -1], [1, -1] // diagonals for even rows
		];
		if (bubbleIndex && rows[bubbleIndex.row] && rows[bubbleIndex.row].length == 12) {
			// Adjust diagonals for odd rows
			directions[4] = [-1, 1];
			directions[5] = [1, 1];
		}
		for (var i = 0; i < directions.length; i++) {
			var dir = directions[i];
			if (bubbleIndex && rows[bubbleIndex.row]) {
				var newRow = bubbleIndex.row + dir[0];
			}
			var newCol = bubbleIndex.col + dir[1];
			if (newRow >= 0 && newRow < rows.length && newCol >= 0 && newCol < rows[newRow].length) {
				neighbors.push(rows[newRow][newCol]);
			}
		}
		return neighbors;
	};
	self.findBubbleIndex = function (bubble) {
		for (var row = 0; row < rows.length; row++) {
			var col = rows[row].indexOf(bubble);
			if (col !== -1) {
				return {
					row: row,
					col: col
				};
			}
		}
		return null;
	};
	self.printRowsToConsole = function () {
		var gridString = '';
		for (var i = rows.length - 1; i >= 0; i--) {
			var rowString = ': ' + (rows[i].length == 13 ? '' : '  ');
			for (var j = 0; j < rows[i].length; j++) {
				var bubble = rows[i][j];
				rowString += bubble ? '[' + bubble.type + ']' : '[_]';
			}
			gridString += rowString + '\n';
		}
		console.log(gridString);
	};
	// Method to calculate path of movement based on angle and starting point
	//TODO: MAKE THIS MUCH FASTER!
	self.bubbleIntersectsGrid = function (nextX, nextY) {
		outer: for (var row = 0; row < rows.length; row++) {
			for (var col = 0; col < rows[row].length; col++) {
				var bubble = rows[row][col];
				if (bubble) {
					var dist = nextY - bubble.y - self.y;
					//Quick exit if we are nowhere near the row
					if (dist > 145 || dist < -145) {
						continue outer;
					}
					var dx = nextX - bubble.x - self.x;
					var dy = nextY - bubble.y - self.y;
					var distance = Math.sqrt(dx * dx + dy * dy);
					if (distance < (bubbleSize - 70) / 2 + bubbleSize / 2) {
						return bubble;
					}
				}
			}
		}
		return false;
	};
	self.calculatePath = function (startPoint, angle) {
		var path = [];
		var currentPoint = {
			x: startPoint.x,
			y: startPoint.y
		};
		var radians = angle;
		var stepSize = 4;
		var hitBubble = false;
		while (currentPoint.y > 0 && !hitBubble) {
			// Calculate next point
			var nextX = currentPoint.x + stepSize * Math.cos(radians);
			var nextY = currentPoint.y + stepSize * Math.sin(radians);
			// Check for wall collisions
			if (nextX < 150 / 2 || nextX > 2048 - 150 / 2) {
				radians = Math.PI - radians; // Reflect angle
				nextX = currentPoint.x + stepSize * Math.cos(radians); // Recalculate nextX after reflection
			}
			hitBubble = self.bubbleIntersectsGrid(nextX, nextY);
			// Add point to path and update currentPoint
			path.push({
				x: nextX,
				y: nextY
			});
			currentPoint.x = nextX;
			currentPoint.y = nextY;
		}
		if (hitBubble) {
			//Only increase avilable bubble type when we have actually pointed as such a bubble
			if (hitBubble.type + 1 > maxSelectableBubble) {
				maxSelectableBubble = hitBubble.type + 1;
			}
			;
		}
		return path;
	};
	var bubblesInFlight = [];
	self.fireBubble = function (bubble, angle) {
		self.addChild(bubble);
		bubble.x = launcher.x;
		bubble.y += launcher.y - self.y;
		bubblesInFlight.push({
			bubble: bubble,
			angle: angle
		});
	};
	self.calculateWarningScoreList = function () {
		var warningScores = [];
		for (var i = 0; i < 13; i++) {
			warningScores.push(0); // Initialize all scores to 0
		}
		// Calculate the distance from the bottom for each bubble and increment the warning score based on proximity
		for (var row = 0; row < rows.length; row++) {
			for (var col = 0; col < rows[row].length; col++) {
				var bubble = rows[row][col];
				if (bubble) {
					var distanceFromBottom = 2732 - (bubble.y + self.y);
					if (distanceFromBottom < 2000) {
						// If a bubble is within 500px from the bottom
						var columnIndex = Math.floor(bubble.x / (2048 / 13));
						warningScores[columnIndex] += (2000 - distanceFromBottom) / 2000; // Increment the warning score for the column
					}
				}
			}
		}
		return warningScores;
	};
	self.update = function () {
		outer: for (var a = 0; a < bubblesInFlight.length; a++) {
			var current = bubblesInFlight[a];
			var bubble = current.bubble;
			var nextX = bubble.x;
			var nextY = bubble.y + gridSpeed;
			var prevX = bubble.x;
			var prevY = bubble.y;
			for (var rep = 0; rep < 25; rep++) {
				prevX = nextX;
				prevY = nextY;
				nextX += Math.cos(current.angle) * 4;
				nextY += Math.sin(current.angle) * 4;
				if (nextX < 150 / 2 || nextX > 2048 - 150 / 2) {
					current.angle = Math.PI - current.angle; // Reflect angle
					nextX = Math.min(Math.max(nextX, 150 / 2), 2048 - 150 / 2);
					LK.getSound('circleBounce').play();
				}
				var intersectedBubble = self.bubbleIntersectsGrid(nextX + self.x, nextY + self.y);
				if (intersectedBubble) {
					gameIsStarted = true;
					if (bubble.isFireBall) {
						self.removeBubbles([intersectedBubble]);
						var disconnected = self.getDetachedBubbles();
						self.removeBubbles(disconnected);
					} else {
						var intersectedBubblePos = self.findBubbleIndex(intersectedBubble);
						var colOffset = rows[intersectedBubblePos.row].length == 13 ? 0 : 1;
						var offsetPositions = [{
							x: intersectedBubble.targetX - bubbleSize / 2,
							y: intersectedBubble.targetY - 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row + 1,
							co: intersectedBubblePos.col - 1 + colOffset
						}, {
							x: intersectedBubble.targetX + bubbleSize / 2,
							y: intersectedBubble.targetY - 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row + 1,
							co: intersectedBubblePos.col + colOffset
						}, {
							x: intersectedBubble.targetX + bubbleSize,
							y: intersectedBubble.targetY,
							ro: intersectedBubblePos.row,
							co: intersectedBubblePos.col + 1
						}, {
							x: intersectedBubble.targetX + bubbleSize / 2,
							y: intersectedBubble.targetY + 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row - 1,
							co: intersectedBubblePos.col + colOffset
						}, {
							x: intersectedBubble.targetX - bubbleSize / 2,
							y: intersectedBubble.targetY + 1.7320508076 * bubbleSize / 2,
							ro: intersectedBubblePos.row - 1,
							co: intersectedBubblePos.col - 1 + colOffset
						}, {
							x: intersectedBubble.targetX - bubbleSize,
							y: intersectedBubble.targetY,
							ro: intersectedBubblePos.row,
							co: intersectedBubblePos.col - 1
						}];
						var closestPosition = 0;
						var closestDistance = Math.sqrt(Math.pow(offsetPositions[0].x - bubble.x, 2) + Math.pow(offsetPositions[0].y - bubble.y, 2));
						for (var i = 1; i < offsetPositions.length; i++) {
							var currentPosition = offsetPositions[i];
							var currentDistance = Math.sqrt(Math.pow(currentPosition.x - bubble.x, 2) + Math.pow(currentPosition.y - bubble.y, 2));
							if (currentDistance < closestDistance) {
								var row = rows[currentPosition.ro];
								if (currentPosition.co < 0) {
									continue;
								}
								if (row) {
									if (row[currentPosition.co]) {
										continue;
									}
									if (currentPosition.co >= row.length) {
										continue;
									}
								} else {
									var newRowLength = rows[intersectedBubblePos.row].length == 13 ? 12 : 13;
									if (currentPosition.co >= newRowLength) {
										continue;
									}
								}
								closestDistance = currentDistance;
								closestPosition = i;
							}
						}
						// Attach bubble to the closest position
						var currentMatch = offsetPositions[closestPosition];
						bubble.x = prevX;
						bubble.y = prevY;
						bubble.targetX = currentMatch.x;
						bubble.targetY = currentMatch.y;
						bubble.isFreeBubble = false;
						var row = rows[offsetPositions[closestPosition].ro];
						if (!row) {
							if (rows[intersectedBubblePos.row].length == 13) {
								row = [null, null, null, null, null, null, null, null, null, null, null, null];
							} else {
								row = [null, null, null, null, null, null, null, null, null, null, null, null, null];
							}
							rows.unshift(row);
						}
						row[offsetPositions[closestPosition].co] = bubble;
						bubblesInFlight.splice(a--, 1);
						refreshHintLine();
						var bubbles = self.getConnectedBubbles(bubble);
						if (bubbles.length > 2) {
							self.removeBubbles(bubbles);
							var disconnected = self.getDetachedBubbles();
							self.removeBubbles(disconnected);
							bonusUX.setStreakCount(bonusUX.streakCount + 1);
						} else {
							bonusUX.setStreakCount(0);
							LK.getSound('attachCircle').play();
						}
						//Add a grid movement effect when you don't do a match
						var neighbors = self.getNeighbors(bubble);
						var touched = [];
						var neighbors2 = [];
						for (var i = 0; i < neighbors.length; i++) {
							var neighbor = neighbors[i];
							if (neighbor) {
								touched.push(neighbor);
								neighbors2 = neighbors2.concat(self.getNeighbors(neighbor));
								var ox = neighbor.x - bubble.x;
								var oy = neighbor.y - bubble.y;
								var angle = Math.atan2(oy, ox);
								neighbor.x += Math.cos(angle) * 20;
								neighbor.y += Math.sin(angle) * 20;
							}
						}
						//One more layer
						for (var i = 0; i < neighbors2.length; i++) {
							var neighbor = neighbors2[i];
							if (neighbor && touched.indexOf(neighbor) == -1) {
								touched.push(neighbor);
								var ox = neighbor.x - bubble.x;
								var oy = neighbor.y - bubble.y;
								var angle = Math.atan2(oy, ox);
								neighbor.x += Math.cos(angle) * 10;
								neighbor.y += Math.sin(angle) * 10;
							}
						}
						//self.printRowsToConsole();
						continue outer;
					}
				}
			}
			bubble.x = nextX;
			bubble.y = nextY;
			if (bubble.y + self.y < -1000) {
				//Destory bubbles that somehow manages to escape at the top
				bubblesInFlight.splice(a--, 1);
				bubble.destroy();
			}
		}
		if (gameIsStarted) {
			self.y += gridSpeed;
		}
		var zeroRow = rows[rows.length - 1];
		if (zeroRow) {
			for (var a = 0; a < zeroRow.length; a++) {
				var bubble = zeroRow[a];
				if (bubble) {
					if (bubble.y + self.y > 0) {
						insertRow();
					}
					break;
				}
			}
		} else {
			insertRow();
		}
		for (var row = rows.length - 1; row >= 0; row--) {
			if (rows[row].every(function (bubble) {
				return !bubble;
			})) {
				rows.splice(row, 1);
			}
		}
		var lastRow = rows[0];
		/*if(LK.ticks % 10 == 0){
			self.printRowsToConsole()
		}*/ 
		if (lastRow) {
			for (var a = 0; a < zeroRow.length; a++) {
				var bubble = lastRow[a];
				if (bubble) {
					if (bubble.y + self.y > 2200) {
						LK.effects.flashScreen(0xff0000, 3000);
						LK.getSound('gameOverJingle').play();
						LK.showGameOver();
					}
					if (gameIsStarted) {
						var targetSpeed = Math.pow(Math.pow((2200 - (bubble.y + self.y)) / 2200, 2), 2) * 4 + 0.5;
						if (bubble.y + self.y > 2000) {
							targetSpeed = .2;
						}
						gridSpeed += (targetSpeed - gridSpeed) / 20;
						if (LK.ticks % 10 == 0) {
							//console.log(gridSpeed)
						}
					}
					break;
				}
			}
		}
	};
	for (var a = 0; a < 8; a++) {
		insertRow();
	}
});
var HintBubble = Container.expand(function () {
	var self = Container.call(this);
	var bubble = self.attachAsset('hintbubble', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.setTint = function (tint) {
		bubble.tint = tint;
	};
	self.getTint = function (tint) {
		return bubble.tint;
	};
});
var Launcher = Container.expand(function () {
	var self = Container.call(this);
	var bubble = self.addChild(new Bubble(getMaxTypes(), false));
	bubble.isFreeBubble = true;
	var previewBubble;
	var lastTypes = [undefined, bubble.type];
	function createPreviewBubble() {
		var nextType;
		do {
			nextType = Math.floor(Math.random() * maxSelectableBubble);
		} while (nextType == lastTypes[0] && nextType == lastTypes[1]);
		lastTypes.shift();
		lastTypes.push(nextType);
		previewBubble = self.addChildAt(new Bubble(maxSelectableBubble, false, nextType), 0);
		previewBubble.scale.set(.7, .7);
		previewBubble.x = -90;
		previewBubble.y = 20;
		previewBubble.isFreeBubble = true;
	}
	createPreviewBubble();
	self.fire = function () {
		bulletsFired++;
		LK.getSound('fireBubble').play(); // Play sound when the ball is fired
		grid.fireBubble(bubble, self.angle);
		bubble = previewBubble;
		previewBubble.x = previewBubble.y = 0;
		previewBubble.scale.set(1, 1);
		createPreviewBubble();
	};
	self.angle = -Math.PI / 2;
	self.getBubble = function () {
		return bubble;
	};
	self.isFireBall = function () {
		return bubble.isFireBall;
	};
	self.triggerFireBall = function () {
		bubble.destroy();
		bubble = self.addChild(new Bubble(getMaxTypes(), true));
		bubble.isFreeBubble = true;
	};
});
var ScoreIndicatorLabel = Container.expand(function (score, type) {
	var self = Container.call(this);
	var label = new Text2(score, {
		size: 100,
		fill: "#" + bubbleColors[type].toString(16).padStart(6, '0'),
		font: "Impact"
	});
	label.anchor.set(0.5, 0);
	self.addChild(label);
	self.update = function () {
		self.y -= 7;
		self.alpha -= .05;
		if (self.alpha <= 0) {
			self.destroy();
			increaseScore(score);
		}
	};
});
var ScoreMultipliers = Container.expand(function (baseValue) {
	var self = Container.call(this);
	// Create a score label text string for ScoreMultipliers
	var scoreMultiplierLabel = new Text2(baseValue, {
		size: 100,
		fill: 0x3954FF,
		font: "Impact"
	});
	scoreMultiplierLabel.anchor.set(0.5, 0);
	self.addChild(scoreMultiplierLabel);
	var currentMultiplier = 1;
	self.applyBubble = function (bubble) {
		var scoreIndicator = game.addChild(new ScoreIndicatorLabel(baseValue * currentMultiplier, bubble.type));
		scoreIndicator.x = self.x;
		scoreIndicator.y = self.y;
		var particle = particlesLayer.addChild(new BubbleRemoveParticle());
		particle.x = bubble.x;
		particle.y = self.y + 150;
	};
	self.setMultiplier = function (multiplier) {
		currentMultiplier = multiplier;
		scoreMultiplierLabel.setText(baseValue * currentMultiplier);
	};
});
var WarningLine = Container.expand(function () {
	var self = Container.call(this);
	var warning = self.attachAsset('warningstripe', {
		anchorX: .5,
		anchorY: .5
	});
	var warningOffset = Math.random() * 100;
	var speed = Math.random() * 1 + 1;
	self.update = function () {
		warningOffset += speed;
		warning.alpha = (Math.cos(warningOffset / 50) + 1) / 2 * 0.3 + .7;
	};
	warning.blendMode = 1;
	warning.rotation = .79;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x0c0d25
});
/**** 
* Game Code
****/ 
var gridSpeed = .5;
function increaseScore(amount) {
	var currentScore = LK.getScore();
	var newScore = currentScore + amount;
	LK.setScore(newScore); // Update the game score using LK method
	scoreLabel.setText(newScore.toString()); // Update the score label with the new score
}
//Game size 2048x2732
/* 
Todo: 
[X] Make sure we GC nodes that drop of screen
[ ] Make preview line fade out at the end
*/
var bulletsFired = 0; //3*30+1
var bubbleSize = 150;
var gameIsStarted = false;
var bubbleColors = [0xff2853, 0xcb2bff, 0x5252ff, 0xcb2bff, 0x28f2f0, 0xffc411];
var barriers = [];
var maxSelectableBubble = 3;
var warningLines = [];
for (var a = 0; a < 13; a++) {
	var wl = game.addChild(new WarningLine());
	wl.x = 2048 / 13 * (a + .5);
	wl.y = 2200;
	wl.alpha = 0;
	warningLines.push(wl);
	wl.scale.set(16, 40);
}
var warningOverlay = game.attachAsset('dangeroverlay', {});
warningOverlay.y = 2280;
var uxoverlay = game.attachAsset('uxoverlay', {});
uxoverlay.y = 2440;
var uxoverlay2 = game.attachAsset('uxoverlay2', {});
uxoverlay2.y = 2460;
for (var a = 0; a < 4; a++) {
	for (var b = 0; b < 3; b++) {
		var barrier = game.addChild(new Barrier());
		barrier.y = 2732 - 450 + b * 70;
		barrier.x = 2048 / 5 * a + 2048 / 5;
		barriers.push(barrier);
	}
	var barrierBlock = game.attachAsset('barrierblock', {});
	barrierBlock.x = 2048 / 5 * a + 2048 / 5;
	barrierBlock.y = 2732 - 450;
	barrierBlock.anchor.x = .5;
}
// Create a score label
var scoreLabel = new Text2('0', {
	size: 120,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 15,
	font: "Impact"
});
scoreLabel.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreLabel);
var scoreMultipliers = [];
var baseScores = [100, 250, 500, 250, 100];
for (var a = 0; a < 5; a++) {
	var sm = new ScoreMultipliers(baseScores[a]);
	sm.x = 2048 / 5 * a + 2048 / 10;
	sm.y = 2300;
	scoreMultipliers.push(sm);
	game.addChild(sm);
}
var bonusUX = game.addChild(new BonusUX());
var fireBallPowerupOverlay = game.addChild(new FireBallPowerupOverlay());
var particlesLayer = game.addChild(new Container());
var grid = game.addChild(new Grid());
grid.y = 1000;
var freeBubbleLayer = game.addChild(new Container());
var hintBubblePlayer = game.addChild(new Container());
var launcher = game.addChild(new Launcher());
launcher.x = game.width / 2;
launcher.y = game.height - 138;
var hintBubbleCache = [];
var hintBubbles = [];
var isValid = false;
var path = [];
var bubbleAlpha = 1;
var hintTargetX = game.width / 2;
var hintTargetY = 0;
game.move = function (x, y, obj) {
	hintTargetX = x;
	hintTargetY = y;
	refreshHintLine();
	//	}
};
game.down = game.move;
function getMaxTypes() {
	if (bulletsFired > 30 * 3 * 3) {
		return 6;
	} else if (bulletsFired > 30 * 3) {
		return 5;
	} else if (bulletsFired > 30) {
		return 4;
	}
	return 3;
}
function refreshHintLine() {
	var ox = hintTargetX - launcher.x;
	var oy = hintTargetY - launcher.y;
	var angle = Math.atan2(oy, ox);
	launcher.angle = angle;
	isValid = angle < -.2 && angle > -Math.PI + .2;
	if (isValid) {
		path = grid.calculatePath(launcher, angle);
		//This allows updated faster than 60fps, making everyting feel better.
	}
	renderHintBubbels();
}
var hintOffset = 0;
var distanceBetweenHintbubbles = 100;
function renderHintBubbels() {
	if (isValid) {
		hintOffset = hintOffset % distanceBetweenHintbubbles;
		var distanceSinceLastDot = -hintOffset + 100;
		var hintBubbleOffset = 0;
		var lastPoint = path[0];
		var bubble = launcher.getBubble();
		var tint = bubble.isFireBall ? 0xff9c00 : bubbleColors[bubble.type];
		var updateTint = true;
		for (var a = 1; a < path.length; a++) {
			var p2 = path[a];
			var ox = p2.x - lastPoint.x;
			var oy = p2.y - lastPoint.y;
			var dist = Math.sqrt(ox * ox + oy * oy);
			distanceSinceLastDot += dist;
			if (distanceSinceLastDot >= distanceBetweenHintbubbles) {
				var amountOver = distanceSinceLastDot - distanceBetweenHintbubbles;
				var angle = Math.atan2(oy, ox);
				var currentBubble = hintBubbles[hintBubbleOffset];
				if (!currentBubble) {
					currentBubble = hintBubbles[hintBubbleOffset] = new HintBubble();
					hintBubblePlayer.addChild(currentBubble);
				}
				currentBubble.alpha = bubbleAlpha;
				currentBubble.visible = true;
				var currentTint = currentBubble.getTint();
				if (hintBubbleOffset == 0) {
					currentBubble.setTint(tint);
				} else if (updateTint && currentTint != tint || currentTint == 0xffffff) {
					currentBubble.setTint(tint);
					updateTint = false;
				}
				currentBubble.x = lastPoint.x - Math.cos(angle) * amountOver;
				currentBubble.y = lastPoint.y - Math.sin(angle) * amountOver;
				hintBubbleOffset++;
				distanceSinceLastDot = 0;
				lastPoint = currentBubble;
			} else {
				lastPoint = p2;
			}
		}
		for (var a = hintBubbleOffset; a < hintBubbles.length; a++) {
			hintBubbles[a].visible = false;
		}
	} else {
		for (var a = 0; a < hintBubbles.length; a++) {
			hintBubbles[a].alpha = bubbleAlpha;
		}
	}
}
game.update = function () {
	hintOffset += 5;
	if (isValid) {
		bubbleAlpha = Math.min(bubbleAlpha + .05, 1);
	} else {
		bubbleAlpha = Math.max(bubbleAlpha - .05, 0);
	}
	refreshHintLine();
	var alphaList = grid.calculateWarningScoreList();
	for (var a = 0; a < warningLines.length; a++) {
		var value = alphaList[a] / 3;
		warningLines[a].alpha += (Math.min(value, .6) - warningLines[a].alpha) / 100;
		warningLines[a].scale.y += (value * 60 - warningLines[a].scale.y) / 100;
	}
};
game.up = function () {
	if (isValid) {
		launcher.fire();
	}
};
;
;
;
;
;
;
;
;
;
;
:quality(85)/https://cdn.frvr.ai/66217070f46305276d9e4c4a.png%3F3) 
 Circular white gradient circle on black background. Gradient from white on the center to black on the outer edge all around.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/662250c7eff7d73bc2d1d329.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6623af081d31755ac75386fa.png%3F3) 
 Soft straight Long red paint on black background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/662621666b90388bb0195f83.png%3F3) 
 Fire ball. Bubble shooter game. Thin black outline.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/66262e026b90388bb01960c1.png%3F3) 
 green notification bubble. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
:quality(85)/https://cdn.frvr.ai/666ae3d78d9daa19ed5a534a.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/666ae3d78d9daa19ed5a5349.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/666ae3d73daf3ace2d07bde8.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/666ae3d73daf3ace2d07bde9.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/666ae3d73daf3ace2d07bdea.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/67523363fe26e16bc6ae9b8c.png%3F3) 
 Can you create a soccer ball?. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.