/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Balloon class
var Balloon = Container.expand(function () {
var self = Container.call(this);
// Store color for logic
self.color = self.color || 0xff0000; // Default red, will be overwritten on creation
// Store grid position
self.gridX = 0;
self.gridY = 0;
// Is this balloon popped?
self.popped = false;
// Attach balloon asset (ellipse shape) - will be replaced in buildGrid with correct color
var balloonGraphics = self.attachAsset('balloon_red', {
anchorX: 0.5,
anchorY: 0.5
});
// Pop animation and logic
self.pop = function () {
if (self.popped) return;
self.popped = true;
// Animate scale down and fade out
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
// Down event (touch/click)
self.down = function (x, y, obj) {
if (self.popped) return;
// Only allow popping if this is the current color
// Always compare as integer values to avoid color type mismatch
if (parseInt(self.color, 10) !== parseInt(currentColor, 10)) return;
// Find all connected balloons of the same color (side-by-side or vertical)
var group = findConnectedBalloons(self.gridX, self.gridY, self.color);
// Only pop if at least 2 connected (including self)
if (group.length < 2) return;
// Push current state to undo stack before popping
undoStack.push(cloneGameState());
for (var i = 0; i < group.length; i++) {
if (!group[i].popped) {
group[i].pop();
}
}
// Update score (100 points per balloon)
LK.setScore(LK.getScore() + group.length * 100);
scoreTxt.setText(LK.getScore());
// After popping, let balloons above fall down to fill empty spaces
LK.setTimeout(function () {
for (var x = 0; x < GRID_COLS; x++) {
// For each column, go from bottom to top
for (var y = GRID_ROWS - 1; y >= 0; y--) {
if (!balloons[x][y] || balloons[x][y].popped) {
// Find the nearest non-popped balloon above
for (var aboveY = y - 1; aboveY >= 0; aboveY--) {
if (balloons[x][aboveY] && !balloons[x][aboveY].popped) {
// Move this balloon down
var b = balloons[x][aboveY];
// Update grid
balloons[x][y] = b;
balloons[x][aboveY] = null;
b.gridY = y;
// Animate drop
var targetY = balloons[x][y].y = Math.floor((2732 - GRID_ROWS * balloonSize) / 2) + balloonSize / 2 + y * (balloonSize + balloonSpacing);
tween(b, {
y: targetY
}, {
duration: 200,
easing: tween.easeIn
});
break;
}
}
}
}
}
// After all drops, fill empty spots at the top with null (no new balloons)
for (var x = 0; x < GRID_COLS; x++) {
for (var y = 0; y < GRID_ROWS; y++) {
if (!balloons[x][y] || balloons[x][y] && balloons[x][y].popped) {
balloons[x][y] = null;
}
}
}
}, 260); // Wait for pop animation to finish
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffffff
});
/****
* Game Code
****/
// Balloon grid settings
var GRID_COLS = 10;
var GRID_ROWS = 12;
var BALLOON_COLORS = [0xff4d4d,
// red
0x4db8ff,
// blue
0x4dff4d,
// green
0xffe14d,
// yellow
0xff4df7 // pink
];
var BALLOON_COLOR_NAMES = ['red', 'blue', 'green', 'yellow', 'pink'];
// Balloon size and spacing
var balloonSize = Math.floor(2048 / GRID_COLS);
var balloonSpacing = 0; // No gap
// Store balloons in a 2D array
var balloons = [];
// Store all balloon objects for easy iteration
var allBalloons = [];
// Current color to pop
var currentColor = null;
var currentColorIdx = 0;
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x222222
});
// Score text at top left (leave 110px margin for menu icon)
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 110;
scoreTxt.y = 40;
LK.gui.topLeft.addChild(scoreTxt);
// Timer text at top center (always visible, in LK.gui.top)
var timerTxt = new Text2('Time: 0s', {
size: 90,
fill: 0x222222
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 2048 / 2;
timerTxt.y = 40;
LK.gui.top.addChild(timerTxt);
// Color indicator balloon and text (always visible, in LK.gui.topRight)
var colorIndicatorBalloon = null;
var colorIndicatorText = null;
// Add poppable color name text at bottom right (English, always visible)
var colorNameTopRightText = new Text2('', {
size: 90,
fill: 0x222222,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
// Anchor at right bottom
colorNameTopRightText.anchor.set(1, 1);
// Position at bottom right, 40px from the bottom edge and 40px from the right edge
colorNameTopRightText.x = 2048 - 40;
colorNameTopRightText.y = 2732 - 40;
LK.gui.bottom.addChild(colorNameTopRightText);
// (Removed popColorMainText at top center)
// Background pulse tween state
var bgPulseTween = null;
var bgPulseState = {
t: 0
}; // t: 0 (white), t: 1 (color)
function setColorIndicator(color) {
// Remove previous indicator if exists
if (colorIndicatorBalloon) {
colorIndicatorBalloon.destroy();
colorIndicatorBalloon = null;
}
// Remove previous color name text if exists
if (colorIndicatorText) {
colorIndicatorText.destroy();
colorIndicatorText = null;
}
// Find color index and name
var idx = -1;
for (var i = 0; i < BALLOON_COLORS.length; i++) {
if (parseInt(BALLOON_COLORS[i], 10) === parseInt(color, 10)) {
idx = i;
break;
}
}
if (idx === -1) return;
var name = BALLOON_COLOR_NAMES[idx];
// Create indicator balloon
colorIndicatorBalloon = LK.getAsset('balloon_' + name, {
anchorX: 1,
anchorY: 0,
x: 2048 - 40,
y: 40,
scaleX: 1.2,
scaleY: 1.2
});
// Add to GUI top right (not game, so it stays on top)
LK.gui.topRight.addChild(colorIndicatorBalloon);
// Create and add color name text
colorIndicatorText = new Text2(name.toUpperCase(), {
size: 90,
fill: color,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
colorIndicatorText.anchor.set(1, 0);
// Position text at top right, aligned with the balloon
colorIndicatorText.x = 2048 - 40;
colorIndicatorText.y = 40;
colorIndicatorText.setStyle({
fill: color
}); // Ensure text is in the actual color
LK.gui.topRight.addChild(colorIndicatorText);
// (Removed update of popColorMainText at top center)
// Update the bottom left color name text (English, capitalized) and set its color to the actual color
if (typeof colorNameTopRightText !== "undefined" && colorNameTopRightText) {
colorNameTopRightText.setText(name.charAt(0).toUpperCase() + name.slice(1));
colorNameTopRightText.setStyle({
fill: color
});
}
// Animate pulse (fade in/out forever)
function pulseIndicator() {
if (!colorIndicatorBalloon) return;
colorIndicatorBalloon.alpha = 1;
tween(colorIndicatorBalloon, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!colorIndicatorBalloon) return;
tween(colorIndicatorBalloon, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
pulseIndicator();
}
});
}
});
}
pulseIndicator();
// Update all frame sides to match the poppable color
updateFrameColor(color);
// No background color pulsing; keep background static
}
// Helper: find all connected balloons of the same color (DFS, only side-by-side or vertical)
function findConnectedBalloons(x, y, color) {
var visited = {};
var group = [];
function dfs(cx, cy) {
var key = cx + ',' + cy;
if (visited[key]) return;
if (cx < 0 || cy < 0 || cx >= GRID_COLS || cy >= GRID_ROWS) return;
var b = balloons[cx][cy];
if (!b || b.popped || parseInt(b.color, 10) !== parseInt(color, 10)) return;
visited[key] = true;
group.push(b);
// Check neighbors (up, down, left, right) - no diagonal
dfs(cx - 1, cy); // left
dfs(cx + 1, cy); // right
dfs(cx, cy - 1); // up
dfs(cx, cy + 1); // down
}
dfs(x, y);
return group;
}
// Helper: pick a new color (random, but must exist on board)
function pickNewColor() {
// Find all colors still present, and count how many of each
var present = {};
for (var i = 0; i < allBalloons.length; i++) {
var b = allBalloons[i];
if (!b.popped) {
if (!present[b.color]) present[b.color] = 0;
present[b.color]++;
}
}
var presentColors = [];
for (var i = 0; i < BALLOON_COLORS.length; i++) {
if (present[BALLOON_COLORS[i]] && present[BALLOON_COLORS[i]] > 0) presentColors.push(BALLOON_COLORS[i]);
}
if (presentColors.length === 0) {
// Win condition: all balloons popped
LK.showYouWin();
return;
}
// Pick random color
var idx = Math.floor(Math.random() * presentColors.length);
currentColor = presentColors[idx];
setColorIndicator(currentColor);
}
// Build the balloon grid
function buildGrid() {
var offsetX = Math.floor((2048 - GRID_COLS * balloonSize) / 2) + balloonSize / 2;
var offsetY = Math.floor((2732 - GRID_ROWS * balloonSize) / 2) + balloonSize / 2;
balloons = [];
allBalloons = [];
for (var x = 0; x < GRID_COLS; x++) {
balloons[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
// Pick random color
var colorIdx = Math.floor(Math.random() * BALLOON_COLORS.length);
var color = BALLOON_COLORS[colorIdx];
var name = BALLOON_COLOR_NAMES[colorIdx];
var balloon = new Balloon();
balloon.color = color;
balloon.gridX = x;
balloon.gridY = y;
// Set asset to correct color
balloon.removeChildren();
balloon.attachAsset('balloon_' + name, {
anchorX: 0.5,
anchorY: 0.5
});
balloon.x = offsetX + x * (balloonSize + balloonSpacing);
balloon.y = offsetY + y * (balloonSize + balloonSpacing);
balloons[x][y] = balloon;
allBalloons.push(balloon);
game.addChild(balloon);
}
}
}
// Reset game state
function resetGame() {
LK.setScore(0);
scoreTxt.setText('0');
// Remove all balloons
for (var i = 0; i < allBalloons.length; i++) {
allBalloons[i].destroy();
}
buildGrid();
pickNewColor();
startElapsedTimer();
}
// Timer for color change
var colorTimer = null;
function startColorTimer() {
if (colorTimer) LK.clearInterval(colorTimer);
colorTimer = LK.setInterval(function () {
pickNewColor();
}, 3000);
}
// Timer for elapsed seconds
var elapsedSeconds = 0;
var elapsedTimer = null;
var timerTxt = new Text2('Time: 0s', {
size: 90,
fill: 0x222222
});
// timerTxt is now positioned in gui.top in the correct place above
function startElapsedTimer() {
if (elapsedTimer) LK.clearInterval(elapsedTimer);
elapsedSeconds = 0;
timerTxt.setText('Time: 0s');
elapsedTimer = LK.setInterval(function () {
elapsedSeconds++;
timerTxt.setText('Time: ' + elapsedSeconds + 's');
}, 1000);
}
function stopElapsedTimer() {
if (elapsedTimer) {
LK.clearInterval(elapsedTimer);
elapsedTimer = null;
}
}
// --- Draw four static rectangle frame sides around the game area ---
// Frame settings
var frameThickness = 24;
var frameColor = 0x222222;
// Top frame
var frameTop = LK.getAsset('frame_top', {
width: 2048,
height: frameThickness,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameTop.x = 0;
frameTop.y = 0;
game.addChild(frameTop);
// Bottom frame
var frameBottom = LK.getAsset('frame_bottom', {
width: 2048,
height: frameThickness,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameBottom.x = 0;
frameBottom.y = 2732 - frameThickness;
game.addChild(frameBottom);
// Left frame
var frameLeft = LK.getAsset('frame_left', {
width: frameThickness,
height: 2732,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameLeft.x = 0;
frameLeft.y = 0;
game.addChild(frameLeft);
// Right frame
var frameRight = LK.getAsset('frame_right', {
width: frameThickness,
height: 2732,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameRight.x = 2048 - frameThickness;
frameRight.y = 0;
game.addChild(frameRight);
// Helper to update frame color (all four sides)
function updateFrameColor(color) {
if (frameTop && frameTop.setStyle) frameTop.setStyle({
fill: color
});
if (frameBottom && frameBottom.setStyle) frameBottom.setStyle({
fill: color
});
if (frameLeft && frameLeft.setStyle) frameLeft.setStyle({
fill: color
});
if (frameRight && frameRight.setStyle) frameRight.setStyle({
fill: color
});
if (frameTop && frameTop.tint !== undefined) frameTop.tint = color;
if (frameBottom && frameBottom.tint !== undefined) frameBottom.tint = color;
if (frameLeft && frameLeft.tint !== undefined) frameLeft.tint = color;
if (frameRight && frameRight.tint !== undefined) frameRight.tint = color;
if (frameTop) frameTop.color = color;
if (frameBottom) frameBottom.color = color;
if (frameLeft) frameLeft.color = color;
if (frameRight) frameRight.color = color;
}
// Start game
resetGame();
startColorTimer();
// Game update (not much needed)
game.update = function () {
// Check win condition
var remaining = 0;
for (var i = 0; i < allBalloons.length; i++) {
if (!allBalloons[i].popped) remaining++;
}
if (remaining === 0) {
stopElapsedTimer();
// Calculate score per second (100 points per balloon)
var totalScore = LK.getScore();
var seconds = elapsedSeconds > 0 ? elapsedSeconds : 1;
var scorePerSecond = Math.round(totalScore / seconds * 100) / 100;
LK.showYouWin('Score: ' + totalScore + '\nTime: ' + seconds + 's\nScore/s: ' + scorePerSecond);
return;
}
// Check for at least one pair of adjacent same-color balloons (horizontal or vertical)
var foundPair = false;
for (var x = 0; x < GRID_COLS; x++) {
for (var y = 0; y < GRID_ROWS; y++) {
var b = balloons[x][y];
if (!b || b.popped) continue;
// Check right neighbor
if (x < GRID_COLS - 1) {
var bRight = balloons[x + 1][y];
if (bRight && !bRight.popped && parseInt(b.color, 10) === parseInt(bRight.color, 10)) {
foundPair = true;
break;
}
}
// Check down neighbor
if (y < GRID_ROWS - 1) {
var bDown = balloons[x][y + 1];
if (bDown && !bDown.popped && parseInt(b.color, 10) === parseInt(bDown.color, 10)) {
foundPair = true;
break;
}
}
}
if (foundPair) break;
}
// If no pair found, game over
if (!foundPair) {
stopElapsedTimer();
// Calculate score per second (100 points per balloon)
var totalScore = LK.getScore();
var seconds = elapsedSeconds > 0 ? elapsedSeconds : 1;
var scorePerSecond = Math.round(totalScore / seconds * 100) / 100;
LK.showGameOver('Score: ' + totalScore + '\nTime: ' + seconds + 's\nScore/s: ' + scorePerSecond);
}
};
// Undo stack for game state
var undoStack = [];
function cloneGameState() {
// Deep clone balloons and allBalloons arrays, and score
var balloonsClone = [];
for (var x = 0; x < GRID_COLS; x++) {
balloonsClone[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
var b = balloons[x][y];
if (b) {
balloonsClone[x][y] = {
color: b.color,
gridX: b.gridX,
gridY: b.gridY,
popped: b.popped
};
} else {
balloonsClone[x][y] = null;
}
}
}
var allBalloonsClone = [];
for (var i = 0; i < allBalloons.length; i++) {
var b = allBalloons[i];
allBalloonsClone.push({
color: b.color,
gridX: b.gridX,
gridY: b.gridY,
popped: b.popped
});
}
return {
balloons: balloonsClone,
allBalloons: allBalloonsClone,
score: LK.getScore()
};
}
function restoreGameState(state) {
// Remove all balloons
for (var i = 0; i < allBalloons.length; i++) {
allBalloons[i].destroy();
}
// Restore balloons and allBalloons
balloons = [];
allBalloons = [];
var offsetX = Math.floor((2048 - GRID_COLS * balloonSize) / 2) + balloonSize / 2;
var offsetY = Math.floor((2732 - GRID_ROWS * balloonSize) / 2) + balloonSize / 2;
for (var x = 0; x < GRID_COLS; x++) {
balloons[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
var bData = state.balloons[x][y];
if (bData) {
var colorIdx = BALLOON_COLORS.indexOf(bData.color);
var name = BALLOON_COLOR_NAMES[colorIdx];
var balloon = new Balloon();
balloon.color = bData.color;
balloon.gridX = bData.gridX;
balloon.gridY = bData.gridY;
balloon.popped = bData.popped;
balloon.removeChildren();
balloon.attachAsset('balloon_' + name, {
anchorX: 0.5,
anchorY: 0.5
});
balloon.x = offsetX + x * (balloonSize + balloonSpacing);
balloon.y = offsetY + y * (balloonSize + balloonSpacing);
if (!balloon.popped) {
game.addChild(balloon);
}
balloons[x][y] = balloon;
allBalloons.push(balloon);
} else {
balloons[x][y] = null;
}
}
}
LK.setScore(state.score);
scoreTxt.setText(state.score);
}
function undoLastAction() {
if (undoStack.length > 0) {
var prevState = undoStack.pop();
restoreGameState(prevState);
}
}
// Undo button at top center
var undoBtn = new Text2('Undo', {
size: 90,
fill: 0x222222,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
undoBtn.anchor.set(0.5, 0);
undoBtn.x = 2048 / 2;
undoBtn.y = 140;
undoBtn.interactive = true;
undoBtn.buttonMode = true;
undoBtn.down = function () {
undoLastAction();
};
LK.gui.top.addChild(undoBtn);
// On game over or win, reset game
game.onGameOver = function () {
resetGame();
startColorTimer();
startElapsedTimer();
};
game.onYouWin = function () {
resetGame();
startColorTimer();
startElapsedTimer();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Balloon class
var Balloon = Container.expand(function () {
var self = Container.call(this);
// Store color for logic
self.color = self.color || 0xff0000; // Default red, will be overwritten on creation
// Store grid position
self.gridX = 0;
self.gridY = 0;
// Is this balloon popped?
self.popped = false;
// Attach balloon asset (ellipse shape) - will be replaced in buildGrid with correct color
var balloonGraphics = self.attachAsset('balloon_red', {
anchorX: 0.5,
anchorY: 0.5
});
// Pop animation and logic
self.pop = function () {
if (self.popped) return;
self.popped = true;
// Animate scale down and fade out
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
// Down event (touch/click)
self.down = function (x, y, obj) {
if (self.popped) return;
// Only allow popping if this is the current color
// Always compare as integer values to avoid color type mismatch
if (parseInt(self.color, 10) !== parseInt(currentColor, 10)) return;
// Find all connected balloons of the same color (side-by-side or vertical)
var group = findConnectedBalloons(self.gridX, self.gridY, self.color);
// Only pop if at least 2 connected (including self)
if (group.length < 2) return;
// Push current state to undo stack before popping
undoStack.push(cloneGameState());
for (var i = 0; i < group.length; i++) {
if (!group[i].popped) {
group[i].pop();
}
}
// Update score (100 points per balloon)
LK.setScore(LK.getScore() + group.length * 100);
scoreTxt.setText(LK.getScore());
// After popping, let balloons above fall down to fill empty spaces
LK.setTimeout(function () {
for (var x = 0; x < GRID_COLS; x++) {
// For each column, go from bottom to top
for (var y = GRID_ROWS - 1; y >= 0; y--) {
if (!balloons[x][y] || balloons[x][y].popped) {
// Find the nearest non-popped balloon above
for (var aboveY = y - 1; aboveY >= 0; aboveY--) {
if (balloons[x][aboveY] && !balloons[x][aboveY].popped) {
// Move this balloon down
var b = balloons[x][aboveY];
// Update grid
balloons[x][y] = b;
balloons[x][aboveY] = null;
b.gridY = y;
// Animate drop
var targetY = balloons[x][y].y = Math.floor((2732 - GRID_ROWS * balloonSize) / 2) + balloonSize / 2 + y * (balloonSize + balloonSpacing);
tween(b, {
y: targetY
}, {
duration: 200,
easing: tween.easeIn
});
break;
}
}
}
}
}
// After all drops, fill empty spots at the top with null (no new balloons)
for (var x = 0; x < GRID_COLS; x++) {
for (var y = 0; y < GRID_ROWS; y++) {
if (!balloons[x][y] || balloons[x][y] && balloons[x][y].popped) {
balloons[x][y] = null;
}
}
}
}, 260); // Wait for pop animation to finish
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffffff
});
/****
* Game Code
****/
// Balloon grid settings
var GRID_COLS = 10;
var GRID_ROWS = 12;
var BALLOON_COLORS = [0xff4d4d,
// red
0x4db8ff,
// blue
0x4dff4d,
// green
0xffe14d,
// yellow
0xff4df7 // pink
];
var BALLOON_COLOR_NAMES = ['red', 'blue', 'green', 'yellow', 'pink'];
// Balloon size and spacing
var balloonSize = Math.floor(2048 / GRID_COLS);
var balloonSpacing = 0; // No gap
// Store balloons in a 2D array
var balloons = [];
// Store all balloon objects for easy iteration
var allBalloons = [];
// Current color to pop
var currentColor = null;
var currentColorIdx = 0;
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x222222
});
// Score text at top left (leave 110px margin for menu icon)
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 110;
scoreTxt.y = 40;
LK.gui.topLeft.addChild(scoreTxt);
// Timer text at top center (always visible, in LK.gui.top)
var timerTxt = new Text2('Time: 0s', {
size: 90,
fill: 0x222222
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 2048 / 2;
timerTxt.y = 40;
LK.gui.top.addChild(timerTxt);
// Color indicator balloon and text (always visible, in LK.gui.topRight)
var colorIndicatorBalloon = null;
var colorIndicatorText = null;
// Add poppable color name text at bottom right (English, always visible)
var colorNameTopRightText = new Text2('', {
size: 90,
fill: 0x222222,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
// Anchor at right bottom
colorNameTopRightText.anchor.set(1, 1);
// Position at bottom right, 40px from the bottom edge and 40px from the right edge
colorNameTopRightText.x = 2048 - 40;
colorNameTopRightText.y = 2732 - 40;
LK.gui.bottom.addChild(colorNameTopRightText);
// (Removed popColorMainText at top center)
// Background pulse tween state
var bgPulseTween = null;
var bgPulseState = {
t: 0
}; // t: 0 (white), t: 1 (color)
function setColorIndicator(color) {
// Remove previous indicator if exists
if (colorIndicatorBalloon) {
colorIndicatorBalloon.destroy();
colorIndicatorBalloon = null;
}
// Remove previous color name text if exists
if (colorIndicatorText) {
colorIndicatorText.destroy();
colorIndicatorText = null;
}
// Find color index and name
var idx = -1;
for (var i = 0; i < BALLOON_COLORS.length; i++) {
if (parseInt(BALLOON_COLORS[i], 10) === parseInt(color, 10)) {
idx = i;
break;
}
}
if (idx === -1) return;
var name = BALLOON_COLOR_NAMES[idx];
// Create indicator balloon
colorIndicatorBalloon = LK.getAsset('balloon_' + name, {
anchorX: 1,
anchorY: 0,
x: 2048 - 40,
y: 40,
scaleX: 1.2,
scaleY: 1.2
});
// Add to GUI top right (not game, so it stays on top)
LK.gui.topRight.addChild(colorIndicatorBalloon);
// Create and add color name text
colorIndicatorText = new Text2(name.toUpperCase(), {
size: 90,
fill: color,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
colorIndicatorText.anchor.set(1, 0);
// Position text at top right, aligned with the balloon
colorIndicatorText.x = 2048 - 40;
colorIndicatorText.y = 40;
colorIndicatorText.setStyle({
fill: color
}); // Ensure text is in the actual color
LK.gui.topRight.addChild(colorIndicatorText);
// (Removed update of popColorMainText at top center)
// Update the bottom left color name text (English, capitalized) and set its color to the actual color
if (typeof colorNameTopRightText !== "undefined" && colorNameTopRightText) {
colorNameTopRightText.setText(name.charAt(0).toUpperCase() + name.slice(1));
colorNameTopRightText.setStyle({
fill: color
});
}
// Animate pulse (fade in/out forever)
function pulseIndicator() {
if (!colorIndicatorBalloon) return;
colorIndicatorBalloon.alpha = 1;
tween(colorIndicatorBalloon, {
alpha: 0.3
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!colorIndicatorBalloon) return;
tween(colorIndicatorBalloon, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
pulseIndicator();
}
});
}
});
}
pulseIndicator();
// Update all frame sides to match the poppable color
updateFrameColor(color);
// No background color pulsing; keep background static
}
// Helper: find all connected balloons of the same color (DFS, only side-by-side or vertical)
function findConnectedBalloons(x, y, color) {
var visited = {};
var group = [];
function dfs(cx, cy) {
var key = cx + ',' + cy;
if (visited[key]) return;
if (cx < 0 || cy < 0 || cx >= GRID_COLS || cy >= GRID_ROWS) return;
var b = balloons[cx][cy];
if (!b || b.popped || parseInt(b.color, 10) !== parseInt(color, 10)) return;
visited[key] = true;
group.push(b);
// Check neighbors (up, down, left, right) - no diagonal
dfs(cx - 1, cy); // left
dfs(cx + 1, cy); // right
dfs(cx, cy - 1); // up
dfs(cx, cy + 1); // down
}
dfs(x, y);
return group;
}
// Helper: pick a new color (random, but must exist on board)
function pickNewColor() {
// Find all colors still present, and count how many of each
var present = {};
for (var i = 0; i < allBalloons.length; i++) {
var b = allBalloons[i];
if (!b.popped) {
if (!present[b.color]) present[b.color] = 0;
present[b.color]++;
}
}
var presentColors = [];
for (var i = 0; i < BALLOON_COLORS.length; i++) {
if (present[BALLOON_COLORS[i]] && present[BALLOON_COLORS[i]] > 0) presentColors.push(BALLOON_COLORS[i]);
}
if (presentColors.length === 0) {
// Win condition: all balloons popped
LK.showYouWin();
return;
}
// Pick random color
var idx = Math.floor(Math.random() * presentColors.length);
currentColor = presentColors[idx];
setColorIndicator(currentColor);
}
// Build the balloon grid
function buildGrid() {
var offsetX = Math.floor((2048 - GRID_COLS * balloonSize) / 2) + balloonSize / 2;
var offsetY = Math.floor((2732 - GRID_ROWS * balloonSize) / 2) + balloonSize / 2;
balloons = [];
allBalloons = [];
for (var x = 0; x < GRID_COLS; x++) {
balloons[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
// Pick random color
var colorIdx = Math.floor(Math.random() * BALLOON_COLORS.length);
var color = BALLOON_COLORS[colorIdx];
var name = BALLOON_COLOR_NAMES[colorIdx];
var balloon = new Balloon();
balloon.color = color;
balloon.gridX = x;
balloon.gridY = y;
// Set asset to correct color
balloon.removeChildren();
balloon.attachAsset('balloon_' + name, {
anchorX: 0.5,
anchorY: 0.5
});
balloon.x = offsetX + x * (balloonSize + balloonSpacing);
balloon.y = offsetY + y * (balloonSize + balloonSpacing);
balloons[x][y] = balloon;
allBalloons.push(balloon);
game.addChild(balloon);
}
}
}
// Reset game state
function resetGame() {
LK.setScore(0);
scoreTxt.setText('0');
// Remove all balloons
for (var i = 0; i < allBalloons.length; i++) {
allBalloons[i].destroy();
}
buildGrid();
pickNewColor();
startElapsedTimer();
}
// Timer for color change
var colorTimer = null;
function startColorTimer() {
if (colorTimer) LK.clearInterval(colorTimer);
colorTimer = LK.setInterval(function () {
pickNewColor();
}, 3000);
}
// Timer for elapsed seconds
var elapsedSeconds = 0;
var elapsedTimer = null;
var timerTxt = new Text2('Time: 0s', {
size: 90,
fill: 0x222222
});
// timerTxt is now positioned in gui.top in the correct place above
function startElapsedTimer() {
if (elapsedTimer) LK.clearInterval(elapsedTimer);
elapsedSeconds = 0;
timerTxt.setText('Time: 0s');
elapsedTimer = LK.setInterval(function () {
elapsedSeconds++;
timerTxt.setText('Time: ' + elapsedSeconds + 's');
}, 1000);
}
function stopElapsedTimer() {
if (elapsedTimer) {
LK.clearInterval(elapsedTimer);
elapsedTimer = null;
}
}
// --- Draw four static rectangle frame sides around the game area ---
// Frame settings
var frameThickness = 24;
var frameColor = 0x222222;
// Top frame
var frameTop = LK.getAsset('frame_top', {
width: 2048,
height: frameThickness,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameTop.x = 0;
frameTop.y = 0;
game.addChild(frameTop);
// Bottom frame
var frameBottom = LK.getAsset('frame_bottom', {
width: 2048,
height: frameThickness,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameBottom.x = 0;
frameBottom.y = 2732 - frameThickness;
game.addChild(frameBottom);
// Left frame
var frameLeft = LK.getAsset('frame_left', {
width: frameThickness,
height: 2732,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameLeft.x = 0;
frameLeft.y = 0;
game.addChild(frameLeft);
// Right frame
var frameRight = LK.getAsset('frame_right', {
width: frameThickness,
height: 2732,
color: frameColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
frameRight.x = 2048 - frameThickness;
frameRight.y = 0;
game.addChild(frameRight);
// Helper to update frame color (all four sides)
function updateFrameColor(color) {
if (frameTop && frameTop.setStyle) frameTop.setStyle({
fill: color
});
if (frameBottom && frameBottom.setStyle) frameBottom.setStyle({
fill: color
});
if (frameLeft && frameLeft.setStyle) frameLeft.setStyle({
fill: color
});
if (frameRight && frameRight.setStyle) frameRight.setStyle({
fill: color
});
if (frameTop && frameTop.tint !== undefined) frameTop.tint = color;
if (frameBottom && frameBottom.tint !== undefined) frameBottom.tint = color;
if (frameLeft && frameLeft.tint !== undefined) frameLeft.tint = color;
if (frameRight && frameRight.tint !== undefined) frameRight.tint = color;
if (frameTop) frameTop.color = color;
if (frameBottom) frameBottom.color = color;
if (frameLeft) frameLeft.color = color;
if (frameRight) frameRight.color = color;
}
// Start game
resetGame();
startColorTimer();
// Game update (not much needed)
game.update = function () {
// Check win condition
var remaining = 0;
for (var i = 0; i < allBalloons.length; i++) {
if (!allBalloons[i].popped) remaining++;
}
if (remaining === 0) {
stopElapsedTimer();
// Calculate score per second (100 points per balloon)
var totalScore = LK.getScore();
var seconds = elapsedSeconds > 0 ? elapsedSeconds : 1;
var scorePerSecond = Math.round(totalScore / seconds * 100) / 100;
LK.showYouWin('Score: ' + totalScore + '\nTime: ' + seconds + 's\nScore/s: ' + scorePerSecond);
return;
}
// Check for at least one pair of adjacent same-color balloons (horizontal or vertical)
var foundPair = false;
for (var x = 0; x < GRID_COLS; x++) {
for (var y = 0; y < GRID_ROWS; y++) {
var b = balloons[x][y];
if (!b || b.popped) continue;
// Check right neighbor
if (x < GRID_COLS - 1) {
var bRight = balloons[x + 1][y];
if (bRight && !bRight.popped && parseInt(b.color, 10) === parseInt(bRight.color, 10)) {
foundPair = true;
break;
}
}
// Check down neighbor
if (y < GRID_ROWS - 1) {
var bDown = balloons[x][y + 1];
if (bDown && !bDown.popped && parseInt(b.color, 10) === parseInt(bDown.color, 10)) {
foundPair = true;
break;
}
}
}
if (foundPair) break;
}
// If no pair found, game over
if (!foundPair) {
stopElapsedTimer();
// Calculate score per second (100 points per balloon)
var totalScore = LK.getScore();
var seconds = elapsedSeconds > 0 ? elapsedSeconds : 1;
var scorePerSecond = Math.round(totalScore / seconds * 100) / 100;
LK.showGameOver('Score: ' + totalScore + '\nTime: ' + seconds + 's\nScore/s: ' + scorePerSecond);
}
};
// Undo stack for game state
var undoStack = [];
function cloneGameState() {
// Deep clone balloons and allBalloons arrays, and score
var balloonsClone = [];
for (var x = 0; x < GRID_COLS; x++) {
balloonsClone[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
var b = balloons[x][y];
if (b) {
balloonsClone[x][y] = {
color: b.color,
gridX: b.gridX,
gridY: b.gridY,
popped: b.popped
};
} else {
balloonsClone[x][y] = null;
}
}
}
var allBalloonsClone = [];
for (var i = 0; i < allBalloons.length; i++) {
var b = allBalloons[i];
allBalloonsClone.push({
color: b.color,
gridX: b.gridX,
gridY: b.gridY,
popped: b.popped
});
}
return {
balloons: balloonsClone,
allBalloons: allBalloonsClone,
score: LK.getScore()
};
}
function restoreGameState(state) {
// Remove all balloons
for (var i = 0; i < allBalloons.length; i++) {
allBalloons[i].destroy();
}
// Restore balloons and allBalloons
balloons = [];
allBalloons = [];
var offsetX = Math.floor((2048 - GRID_COLS * balloonSize) / 2) + balloonSize / 2;
var offsetY = Math.floor((2732 - GRID_ROWS * balloonSize) / 2) + balloonSize / 2;
for (var x = 0; x < GRID_COLS; x++) {
balloons[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
var bData = state.balloons[x][y];
if (bData) {
var colorIdx = BALLOON_COLORS.indexOf(bData.color);
var name = BALLOON_COLOR_NAMES[colorIdx];
var balloon = new Balloon();
balloon.color = bData.color;
balloon.gridX = bData.gridX;
balloon.gridY = bData.gridY;
balloon.popped = bData.popped;
balloon.removeChildren();
balloon.attachAsset('balloon_' + name, {
anchorX: 0.5,
anchorY: 0.5
});
balloon.x = offsetX + x * (balloonSize + balloonSpacing);
balloon.y = offsetY + y * (balloonSize + balloonSpacing);
if (!balloon.popped) {
game.addChild(balloon);
}
balloons[x][y] = balloon;
allBalloons.push(balloon);
} else {
balloons[x][y] = null;
}
}
}
LK.setScore(state.score);
scoreTxt.setText(state.score);
}
function undoLastAction() {
if (undoStack.length > 0) {
var prevState = undoStack.pop();
restoreGameState(prevState);
}
}
// Undo button at top center
var undoBtn = new Text2('Undo', {
size: 90,
fill: 0x222222,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
undoBtn.anchor.set(0.5, 0);
undoBtn.x = 2048 / 2;
undoBtn.y = 140;
undoBtn.interactive = true;
undoBtn.buttonMode = true;
undoBtn.down = function () {
undoLastAction();
};
LK.gui.top.addChild(undoBtn);
// On game over or win, reset game
game.onGameOver = function () {
resetGame();
startColorTimer();
startElapsedTimer();
};
game.onYouWin = function () {
resetGame();
startColorTimer();
startElapsedTimer();
};