/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (colorType) {
var self = Container.call(this);
self.colorType = colorType;
self.directionX = 0;
self.directionY = 0;
self.speed = 400;
self.isDestroyed = false;
self.hasProcessedBlocks = false;
self.connectedBlocks = [];
// Create arrow graphics - long white arrow (invisible)
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
alpha: 0
});
self.setDirection = function (targetX, targetY) {
// Calculate normalized direction vector
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
arrowGraphics.rotation = Math.atan2(dy, dx);
}
};
self.update = function () {
if (self.isDestroyed) {
return;
}
// Check if we have enough connected blocks to stop
if (self.connectedBlocks && self.connectedBlocks.length >= 3 && !self.hasProcessedBlocks) {
self.hasProcessedBlocks = true;
// Process connected blocks immediately
clearSelection();
selectedBlocks = self.connectedBlocks;
blastSelectedBlocks();
// Keep arrow visible for a moment before destroying
LK.setTimeout(function () {
self.destroyArrow();
}, 500);
return;
}
// Move in straight line using direction vector only if we haven't processed blocks yet
// Stop moving if we have any connected blocks to keep arrow visible
if (!self.hasProcessedBlocks && (!self.connectedBlocks || self.connectedBlocks.length === 0)) {
self.x += self.directionX * self.speed * (1 / 60);
self.y += self.directionY * self.speed * (1 / 60);
}
// Arrow is invisible, no pulsing effect needed
// Check collision with blocks only if we haven't processed blocks yet
if (!self.hasProcessedBlocks) {
self.checkBlockCollision();
}
// Remove arrow if it goes off screen
if (self.x < -200 || self.x > 2248 || self.y < -200 || self.y > 2932) {
// Only destroy if we haven't processed blocks or if no blocks were connected
if (!self.hasProcessedBlocks) {
// Clear selection for any connected blocks
if (self.connectedBlocks && self.connectedBlocks.length > 0) {
for (var i = 0; i < self.connectedBlocks.length; i++) {
self.connectedBlocks[i].setSelected(false);
}
}
self.destroyArrow();
}
}
};
self.connectAdjacentBlocks = function (centerBlock) {
if (!centerBlock || centerBlock.isDestroyed) {
return;
}
var directions = [[0, -1], [0, 1], [-1, 0], [1, 0]]; // up, down, left, right
for (var i = 0; i < directions.length; i++) {
var newX = centerBlock.gridX + directions[i][0];
var newY = centerBlock.gridY + directions[i][1];
if (newX >= 0 && newX < GRID_WIDTH && newY >= 0 && newY < GRID_HEIGHT) {
var adjacentBlock = grid[newX][newY];
if (adjacentBlock && adjacentBlock.colorType === self.colorType && !adjacentBlock.isDestroyed) {
// Check if we already connected this block
var alreadyConnected = false;
for (var j = 0; j < self.connectedBlocks.length; j++) {
if (self.connectedBlocks[j] === adjacentBlock) {
alreadyConnected = true;
break;
}
}
if (!alreadyConnected) {
self.connectedBlocks.push(adjacentBlock);
adjacentBlock.setSelected(true);
// Recursively connect blocks adjacent to this one
self.connectAdjacentBlocks(adjacentBlock);
}
}
}
}
};
self.checkBlockCollision = function () {
var gridX = Math.floor((self.x - GRID_START_X) / BLOCK_SIZE);
var gridY = Math.floor((self.y - GRID_START_Y) / BLOCK_SIZE);
if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT) {
var hitBlock = grid[gridX][gridY];
if (hitBlock && hitBlock.colorType === self.colorType && !hitBlock.isDestroyed) {
// Mark this block as connected by the arrow
if (!self.connectedBlocks) {
self.connectedBlocks = [];
}
// Check if we already connected this block
var alreadyConnected = false;
for (var i = 0; i < self.connectedBlocks.length; i++) {
if (self.connectedBlocks[i] === hitBlock) {
alreadyConnected = true;
break;
}
}
if (!alreadyConnected) {
self.connectedBlocks.push(hitBlock);
// Visual feedback for connected block
hitBlock.setSelected(true);
// Connect all adjacent blocks of the same color
self.connectAdjacentBlocks(hitBlock);
// If we have less than 3 blocks after connecting, set a timeout to destroy arrow
if (self.connectedBlocks.length < 3) {
LK.setTimeout(function () {
if (!self.isDestroyed && !self.hasProcessedBlocks) {
// Clear selection for connected blocks
for (var i = 0; i < self.connectedBlocks.length; i++) {
self.connectedBlocks[i].setSelected(false);
}
self.destroyArrow();
}
}, 1500); // Give 1.5 seconds to potentially connect more blocks
}
}
}
}
};
self.destroyArrow = function () {
if (!self.isDestroyed) {
self.isDestroyed = true;
self.destroy();
var index = activeArrows.indexOf(self);
if (index !== -1) {
activeArrows.splice(index, 1);
}
}
};
return self;
});
var Block = Container.expand(function (colorType) {
var self = Container.call(this);
self.colorType = colorType;
self.gridX = 0;
self.gridY = 0;
self.isSelected = false;
self.isDragging = false;
self.originalX = 0;
self.originalY = 0;
self.isDestroyed = false;
var blockAssets = ['blockRed', 'blockBlue', 'blockGreen', 'blockYellow', 'blockPurple'];
var blockGraphics = self.attachAsset(blockAssets[colorType], {
anchorX: 0.5,
anchorY: 0.5
});
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected) {
blockGraphics.alpha = 0.8;
blockGraphics.scaleX = 1.05;
blockGraphics.scaleY = 1.05;
tween(blockGraphics, {
tint: 0xFFFFAA
}, {
duration: 200
});
} else {
blockGraphics.alpha = 1.0;
blockGraphics.scaleX = 1.0;
blockGraphics.scaleY = 1.0;
tween(blockGraphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
};
self.blast = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
// No individual point effect - will be handled by blastSelectedBlocks
tween(blockGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var PointEffect = Container.expand(function (points, x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Randomly choose between point effect assets
var effectAssets = ['pointEffect', 'pointEffect2'];
var randomAsset = effectAssets[Math.floor(Math.random() * effectAssets.length)];
// Create point effect graphics - bigger for combo effects
var effectGraphics = self.attachAsset(randomAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
// No text needed - just visual effect
// Appear with bigger effect for combos
var maxScale = Math.min(3.0, 1.0 + points / 500);
tween(effectGraphics, {
scaleX: maxScale,
scaleY: maxScale,
alpha: 1.0
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Move up and fade out
tween(self, {
y: self.y - 150
}, {
duration: 1000,
easing: tween.easeOut
});
tween(effectGraphics, {
alpha: 0,
scaleX: maxScale * 1.5,
scaleY: maxScale * 1.5
}, {
duration: 1000,
easing: tween.easeOut
});
// Destroy effect after animation completes
LK.setTimeout(function () {
self.destroy();
}, 1000);
}
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
var GRID_WIDTH = 8;
var GRID_HEIGHT = 10;
var BLOCK_SIZE = 200;
var GRID_START_X = (2048 - GRID_WIDTH * BLOCK_SIZE) / 2;
var GRID_START_Y = 400;
var grid = [];
var selectedBlocks = [];
var currentLevel = 1;
var currentScore = 0;
var targetScore = 1000;
var gameActive = true;
var activeArrows = [];
var shootingBlock = null;
// Initialize grid array
for (var x = 0; x < GRID_WIDTH; x++) {
grid[x] = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[x][y] = null;
}
}
// Add white background - bigger scale to fill more screen
var background = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1.2,
scaleY: 1.2
});
// UI Elements
// Add background shape for score text
var scoreBackground = game.attachAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 180,
alpha: 0.8
});
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0.5);
scoreText.x = 1024;
scoreText.y = 180;
game.addChild(scoreText);
// Add background shape for target text
var targetBackground = game.attachAsset('targetBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 280,
alpha: 0.8
});
var targetText = new Text2('Target: 1000', {
size: 70,
fill: 0x0066CC
});
targetText.anchor.set(0.5, 0.5);
targetText.x = 1024;
targetText.y = 280;
game.addChild(targetText);
// Add background shape for level text
var levelBackground = game.attachAsset('levelBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 100,
alpha: 0.8
});
var levelText = new Text2('Level 1', {
size: 90,
fill: 0x000000
});
levelText.anchor.set(0.5, 0.5);
levelText.x = 1024;
levelText.y = 100;
game.addChild(levelText);
function createBlock(x, y) {
var colorType = Math.floor(Math.random() * 5);
var block = new Block(colorType);
block.gridX = x;
block.gridY = y;
block.x = GRID_START_X + x * BLOCK_SIZE + BLOCK_SIZE / 2;
block.y = GRID_START_Y + y * BLOCK_SIZE + BLOCK_SIZE / 2;
grid[x][y] = block;
game.addChild(block);
return block;
}
function initializeGrid() {
for (var x = 0; x < GRID_WIDTH; x++) {
for (var y = 0; y < GRID_HEIGHT; y++) {
createBlock(x, y);
}
}
}
function getConnectedBlocks(startX, startY, colorType, visited) {
if (!visited) {
visited = [];
}
var key = startX + ',' + startY;
if (visited.indexOf(key) !== -1) {
return [];
}
if (startX < 0 || startX >= GRID_WIDTH || startY < 0 || startY >= GRID_HEIGHT) {
return [];
}
if (!grid[startX][startY] || grid[startX][startY].colorType !== colorType) {
return [];
}
visited.push(key);
var connected = [grid[startX][startY]];
// Check adjacent blocks (up, down, left, right)
var directions = [[0, -1], [0, 1], [-1, 0], [1, 0]];
for (var i = 0; i < directions.length; i++) {
var newX = startX + directions[i][0];
var newY = startY + directions[i][1];
var adjacentBlocks = getConnectedBlocks(newX, newY, colorType, visited);
connected = connected.concat(adjacentBlocks);
}
return connected;
}
function clearSelection() {
for (var i = 0; i < selectedBlocks.length; i++) {
selectedBlocks[i].setSelected(false);
}
selectedBlocks = [];
}
function selectBlockGroup(block) {
clearSelection();
selectedBlocks = getConnectedBlocks(block.gridX, block.gridY, block.colorType);
if (selectedBlocks.length >= 3) {
for (var i = 0; i < selectedBlocks.length; i++) {
selectedBlocks[i].setSelected(true);
}
return true;
} else {
selectedBlocks = [];
return false;
}
}
function blastSelectedBlocks() {
if (selectedBlocks.length < 3 || !gameActive) {
return;
}
var points = calculateScore(selectedBlocks.length);
currentScore += points;
LK.getSound('blockBlast').play();
LK.effects.flashScreen(0xffffff, 200);
// Calculate center position of all selected blocks for big effect
var centerX = 0;
var centerY = 0;
for (var i = 0; i < selectedBlocks.length; i++) {
centerX += selectedBlocks[i].x;
centerY += selectedBlocks[i].y;
}
centerX /= selectedBlocks.length;
centerY /= selectedBlocks.length;
// Create single big point effect at center
var bigPointEffect = new PointEffect(points, centerX, centerY);
game.addChild(bigPointEffect);
// Remove blocks from grid and blast them
for (var i = 0; i < selectedBlocks.length; i++) {
var block = selectedBlocks[i];
grid[block.gridX][block.gridY] = null;
block.blast();
}
selectedBlocks = [];
// Apply gravity after a short delay
LK.setTimeout(function () {
applyGravity();
updateUI();
checkGameState();
}, 400);
}
function calculateScore(blockCount) {
return blockCount * blockCount * 10;
}
function applyGravity() {
for (var x = 0; x < GRID_WIDTH; x++) {
var writeY = GRID_HEIGHT - 1;
// Move existing blocks down
for (var y = GRID_HEIGHT - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (writeY !== y) {
grid[x][writeY] = grid[x][y];
grid[x][y] = null;
grid[x][writeY].gridY = writeY;
// Animate block falling
var targetY = GRID_START_Y + writeY * BLOCK_SIZE + BLOCK_SIZE / 2;
tween(grid[x][writeY], {
y: targetY
}, {
duration: 300,
easing: tween.easeOut
});
}
writeY--;
}
}
// Fill empty spaces at top with new blocks
for (var y = writeY; y >= 0; y--) {
var newBlock = createBlock(x, y);
newBlock.y = GRID_START_Y - (writeY - y + 1) * BLOCK_SIZE + BLOCK_SIZE / 2;
var targetY = GRID_START_Y + y * BLOCK_SIZE + BLOCK_SIZE / 2;
tween(newBlock, {
y: targetY
}, {
duration: 500,
easing: tween.bounceOut
});
}
}
}
function updateUI() {
scoreText.setText('Score: ' + currentScore);
LK.setScore(currentScore);
}
function checkGameState() {
if (currentScore >= targetScore) {
gameActive = false;
LK.getSound('levelComplete').play();
LK.setTimeout(function () {
nextLevel();
}, 1000);
}
}
function nextLevel() {
currentLevel++;
targetScore = currentScore + 500 * currentLevel;
gameActive = true;
levelText.setText('Level ' + currentLevel);
targetText.setText('Target: ' + targetScore);
updateUI();
if (currentLevel >= 10) {
LK.showYouWin();
}
}
game.down = function (x, y, obj) {
if (!gameActive) {
return;
}
var gridX = Math.floor((x - GRID_START_X) / BLOCK_SIZE);
var gridY = Math.floor((y - GRID_START_Y) / BLOCK_SIZE);
if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT) {
var block = grid[gridX][gridY];
if (block) {
shootingBlock = block;
}
}
};
game.move = function (x, y, obj) {
// No dragging mechanics needed for arrow shooting
};
game.up = function (x, y, obj) {
if (!gameActive || !shootingBlock) {
return;
}
// Shoot arrow in direction of target
var arrow = new Arrow(shootingBlock.colorType);
arrow.x = shootingBlock.x;
arrow.y = shootingBlock.y;
arrow.setDirection(x, y);
activeArrows.push(arrow);
game.addChild(arrow);
// Arrow is invisible, no spawn animation needed
// Clear shooting state
shootingBlock = null;
clearSelection();
};
game.update = function () {
// Update all active arrows
for (var i = activeArrows.length - 1; i >= 0; i--) {
var arrow = activeArrows[i];
if (arrow.update) {
arrow.update();
}
}
};
// Initialize the game
initializeGrid();
updateUI(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (colorType) {
var self = Container.call(this);
self.colorType = colorType;
self.directionX = 0;
self.directionY = 0;
self.speed = 400;
self.isDestroyed = false;
self.hasProcessedBlocks = false;
self.connectedBlocks = [];
// Create arrow graphics - long white arrow (invisible)
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
alpha: 0
});
self.setDirection = function (targetX, targetY) {
// Calculate normalized direction vector
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.directionX = dx / distance;
self.directionY = dy / distance;
arrowGraphics.rotation = Math.atan2(dy, dx);
}
};
self.update = function () {
if (self.isDestroyed) {
return;
}
// Check if we have enough connected blocks to stop
if (self.connectedBlocks && self.connectedBlocks.length >= 3 && !self.hasProcessedBlocks) {
self.hasProcessedBlocks = true;
// Process connected blocks immediately
clearSelection();
selectedBlocks = self.connectedBlocks;
blastSelectedBlocks();
// Keep arrow visible for a moment before destroying
LK.setTimeout(function () {
self.destroyArrow();
}, 500);
return;
}
// Move in straight line using direction vector only if we haven't processed blocks yet
// Stop moving if we have any connected blocks to keep arrow visible
if (!self.hasProcessedBlocks && (!self.connectedBlocks || self.connectedBlocks.length === 0)) {
self.x += self.directionX * self.speed * (1 / 60);
self.y += self.directionY * self.speed * (1 / 60);
}
// Arrow is invisible, no pulsing effect needed
// Check collision with blocks only if we haven't processed blocks yet
if (!self.hasProcessedBlocks) {
self.checkBlockCollision();
}
// Remove arrow if it goes off screen
if (self.x < -200 || self.x > 2248 || self.y < -200 || self.y > 2932) {
// Only destroy if we haven't processed blocks or if no blocks were connected
if (!self.hasProcessedBlocks) {
// Clear selection for any connected blocks
if (self.connectedBlocks && self.connectedBlocks.length > 0) {
for (var i = 0; i < self.connectedBlocks.length; i++) {
self.connectedBlocks[i].setSelected(false);
}
}
self.destroyArrow();
}
}
};
self.connectAdjacentBlocks = function (centerBlock) {
if (!centerBlock || centerBlock.isDestroyed) {
return;
}
var directions = [[0, -1], [0, 1], [-1, 0], [1, 0]]; // up, down, left, right
for (var i = 0; i < directions.length; i++) {
var newX = centerBlock.gridX + directions[i][0];
var newY = centerBlock.gridY + directions[i][1];
if (newX >= 0 && newX < GRID_WIDTH && newY >= 0 && newY < GRID_HEIGHT) {
var adjacentBlock = grid[newX][newY];
if (adjacentBlock && adjacentBlock.colorType === self.colorType && !adjacentBlock.isDestroyed) {
// Check if we already connected this block
var alreadyConnected = false;
for (var j = 0; j < self.connectedBlocks.length; j++) {
if (self.connectedBlocks[j] === adjacentBlock) {
alreadyConnected = true;
break;
}
}
if (!alreadyConnected) {
self.connectedBlocks.push(adjacentBlock);
adjacentBlock.setSelected(true);
// Recursively connect blocks adjacent to this one
self.connectAdjacentBlocks(adjacentBlock);
}
}
}
}
};
self.checkBlockCollision = function () {
var gridX = Math.floor((self.x - GRID_START_X) / BLOCK_SIZE);
var gridY = Math.floor((self.y - GRID_START_Y) / BLOCK_SIZE);
if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT) {
var hitBlock = grid[gridX][gridY];
if (hitBlock && hitBlock.colorType === self.colorType && !hitBlock.isDestroyed) {
// Mark this block as connected by the arrow
if (!self.connectedBlocks) {
self.connectedBlocks = [];
}
// Check if we already connected this block
var alreadyConnected = false;
for (var i = 0; i < self.connectedBlocks.length; i++) {
if (self.connectedBlocks[i] === hitBlock) {
alreadyConnected = true;
break;
}
}
if (!alreadyConnected) {
self.connectedBlocks.push(hitBlock);
// Visual feedback for connected block
hitBlock.setSelected(true);
// Connect all adjacent blocks of the same color
self.connectAdjacentBlocks(hitBlock);
// If we have less than 3 blocks after connecting, set a timeout to destroy arrow
if (self.connectedBlocks.length < 3) {
LK.setTimeout(function () {
if (!self.isDestroyed && !self.hasProcessedBlocks) {
// Clear selection for connected blocks
for (var i = 0; i < self.connectedBlocks.length; i++) {
self.connectedBlocks[i].setSelected(false);
}
self.destroyArrow();
}
}, 1500); // Give 1.5 seconds to potentially connect more blocks
}
}
}
}
};
self.destroyArrow = function () {
if (!self.isDestroyed) {
self.isDestroyed = true;
self.destroy();
var index = activeArrows.indexOf(self);
if (index !== -1) {
activeArrows.splice(index, 1);
}
}
};
return self;
});
var Block = Container.expand(function (colorType) {
var self = Container.call(this);
self.colorType = colorType;
self.gridX = 0;
self.gridY = 0;
self.isSelected = false;
self.isDragging = false;
self.originalX = 0;
self.originalY = 0;
self.isDestroyed = false;
var blockAssets = ['blockRed', 'blockBlue', 'blockGreen', 'blockYellow', 'blockPurple'];
var blockGraphics = self.attachAsset(blockAssets[colorType], {
anchorX: 0.5,
anchorY: 0.5
});
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected) {
blockGraphics.alpha = 0.8;
blockGraphics.scaleX = 1.05;
blockGraphics.scaleY = 1.05;
tween(blockGraphics, {
tint: 0xFFFFAA
}, {
duration: 200
});
} else {
blockGraphics.alpha = 1.0;
blockGraphics.scaleX = 1.0;
blockGraphics.scaleY = 1.0;
tween(blockGraphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
};
self.blast = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
// No individual point effect - will be handled by blastSelectedBlocks
tween(blockGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var PointEffect = Container.expand(function (points, x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Randomly choose between point effect assets
var effectAssets = ['pointEffect', 'pointEffect2'];
var randomAsset = effectAssets[Math.floor(Math.random() * effectAssets.length)];
// Create point effect graphics - bigger for combo effects
var effectGraphics = self.attachAsset(randomAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
// No text needed - just visual effect
// Appear with bigger effect for combos
var maxScale = Math.min(3.0, 1.0 + points / 500);
tween(effectGraphics, {
scaleX: maxScale,
scaleY: maxScale,
alpha: 1.0
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Move up and fade out
tween(self, {
y: self.y - 150
}, {
duration: 1000,
easing: tween.easeOut
});
tween(effectGraphics, {
alpha: 0,
scaleX: maxScale * 1.5,
scaleY: maxScale * 1.5
}, {
duration: 1000,
easing: tween.easeOut
});
// Destroy effect after animation completes
LK.setTimeout(function () {
self.destroy();
}, 1000);
}
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
var GRID_WIDTH = 8;
var GRID_HEIGHT = 10;
var BLOCK_SIZE = 200;
var GRID_START_X = (2048 - GRID_WIDTH * BLOCK_SIZE) / 2;
var GRID_START_Y = 400;
var grid = [];
var selectedBlocks = [];
var currentLevel = 1;
var currentScore = 0;
var targetScore = 1000;
var gameActive = true;
var activeArrows = [];
var shootingBlock = null;
// Initialize grid array
for (var x = 0; x < GRID_WIDTH; x++) {
grid[x] = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[x][y] = null;
}
}
// Add white background - bigger scale to fill more screen
var background = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1.2,
scaleY: 1.2
});
// UI Elements
// Add background shape for score text
var scoreBackground = game.attachAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 180,
alpha: 0.8
});
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0.5);
scoreText.x = 1024;
scoreText.y = 180;
game.addChild(scoreText);
// Add background shape for target text
var targetBackground = game.attachAsset('targetBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 280,
alpha: 0.8
});
var targetText = new Text2('Target: 1000', {
size: 70,
fill: 0x0066CC
});
targetText.anchor.set(0.5, 0.5);
targetText.x = 1024;
targetText.y = 280;
game.addChild(targetText);
// Add background shape for level text
var levelBackground = game.attachAsset('levelBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 100,
alpha: 0.8
});
var levelText = new Text2('Level 1', {
size: 90,
fill: 0x000000
});
levelText.anchor.set(0.5, 0.5);
levelText.x = 1024;
levelText.y = 100;
game.addChild(levelText);
function createBlock(x, y) {
var colorType = Math.floor(Math.random() * 5);
var block = new Block(colorType);
block.gridX = x;
block.gridY = y;
block.x = GRID_START_X + x * BLOCK_SIZE + BLOCK_SIZE / 2;
block.y = GRID_START_Y + y * BLOCK_SIZE + BLOCK_SIZE / 2;
grid[x][y] = block;
game.addChild(block);
return block;
}
function initializeGrid() {
for (var x = 0; x < GRID_WIDTH; x++) {
for (var y = 0; y < GRID_HEIGHT; y++) {
createBlock(x, y);
}
}
}
function getConnectedBlocks(startX, startY, colorType, visited) {
if (!visited) {
visited = [];
}
var key = startX + ',' + startY;
if (visited.indexOf(key) !== -1) {
return [];
}
if (startX < 0 || startX >= GRID_WIDTH || startY < 0 || startY >= GRID_HEIGHT) {
return [];
}
if (!grid[startX][startY] || grid[startX][startY].colorType !== colorType) {
return [];
}
visited.push(key);
var connected = [grid[startX][startY]];
// Check adjacent blocks (up, down, left, right)
var directions = [[0, -1], [0, 1], [-1, 0], [1, 0]];
for (var i = 0; i < directions.length; i++) {
var newX = startX + directions[i][0];
var newY = startY + directions[i][1];
var adjacentBlocks = getConnectedBlocks(newX, newY, colorType, visited);
connected = connected.concat(adjacentBlocks);
}
return connected;
}
function clearSelection() {
for (var i = 0; i < selectedBlocks.length; i++) {
selectedBlocks[i].setSelected(false);
}
selectedBlocks = [];
}
function selectBlockGroup(block) {
clearSelection();
selectedBlocks = getConnectedBlocks(block.gridX, block.gridY, block.colorType);
if (selectedBlocks.length >= 3) {
for (var i = 0; i < selectedBlocks.length; i++) {
selectedBlocks[i].setSelected(true);
}
return true;
} else {
selectedBlocks = [];
return false;
}
}
function blastSelectedBlocks() {
if (selectedBlocks.length < 3 || !gameActive) {
return;
}
var points = calculateScore(selectedBlocks.length);
currentScore += points;
LK.getSound('blockBlast').play();
LK.effects.flashScreen(0xffffff, 200);
// Calculate center position of all selected blocks for big effect
var centerX = 0;
var centerY = 0;
for (var i = 0; i < selectedBlocks.length; i++) {
centerX += selectedBlocks[i].x;
centerY += selectedBlocks[i].y;
}
centerX /= selectedBlocks.length;
centerY /= selectedBlocks.length;
// Create single big point effect at center
var bigPointEffect = new PointEffect(points, centerX, centerY);
game.addChild(bigPointEffect);
// Remove blocks from grid and blast them
for (var i = 0; i < selectedBlocks.length; i++) {
var block = selectedBlocks[i];
grid[block.gridX][block.gridY] = null;
block.blast();
}
selectedBlocks = [];
// Apply gravity after a short delay
LK.setTimeout(function () {
applyGravity();
updateUI();
checkGameState();
}, 400);
}
function calculateScore(blockCount) {
return blockCount * blockCount * 10;
}
function applyGravity() {
for (var x = 0; x < GRID_WIDTH; x++) {
var writeY = GRID_HEIGHT - 1;
// Move existing blocks down
for (var y = GRID_HEIGHT - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (writeY !== y) {
grid[x][writeY] = grid[x][y];
grid[x][y] = null;
grid[x][writeY].gridY = writeY;
// Animate block falling
var targetY = GRID_START_Y + writeY * BLOCK_SIZE + BLOCK_SIZE / 2;
tween(grid[x][writeY], {
y: targetY
}, {
duration: 300,
easing: tween.easeOut
});
}
writeY--;
}
}
// Fill empty spaces at top with new blocks
for (var y = writeY; y >= 0; y--) {
var newBlock = createBlock(x, y);
newBlock.y = GRID_START_Y - (writeY - y + 1) * BLOCK_SIZE + BLOCK_SIZE / 2;
var targetY = GRID_START_Y + y * BLOCK_SIZE + BLOCK_SIZE / 2;
tween(newBlock, {
y: targetY
}, {
duration: 500,
easing: tween.bounceOut
});
}
}
}
function updateUI() {
scoreText.setText('Score: ' + currentScore);
LK.setScore(currentScore);
}
function checkGameState() {
if (currentScore >= targetScore) {
gameActive = false;
LK.getSound('levelComplete').play();
LK.setTimeout(function () {
nextLevel();
}, 1000);
}
}
function nextLevel() {
currentLevel++;
targetScore = currentScore + 500 * currentLevel;
gameActive = true;
levelText.setText('Level ' + currentLevel);
targetText.setText('Target: ' + targetScore);
updateUI();
if (currentLevel >= 10) {
LK.showYouWin();
}
}
game.down = function (x, y, obj) {
if (!gameActive) {
return;
}
var gridX = Math.floor((x - GRID_START_X) / BLOCK_SIZE);
var gridY = Math.floor((y - GRID_START_Y) / BLOCK_SIZE);
if (gridX >= 0 && gridX < GRID_WIDTH && gridY >= 0 && gridY < GRID_HEIGHT) {
var block = grid[gridX][gridY];
if (block) {
shootingBlock = block;
}
}
};
game.move = function (x, y, obj) {
// No dragging mechanics needed for arrow shooting
};
game.up = function (x, y, obj) {
if (!gameActive || !shootingBlock) {
return;
}
// Shoot arrow in direction of target
var arrow = new Arrow(shootingBlock.colorType);
arrow.x = shootingBlock.x;
arrow.y = shootingBlock.y;
arrow.setDirection(x, y);
activeArrows.push(arrow);
game.addChild(arrow);
// Arrow is invisible, no spawn animation needed
// Clear shooting state
shootingBlock = null;
clearSelection();
};
game.update = function () {
// Update all active arrows
for (var i = activeArrows.length - 1; i >= 0; i--) {
var arrow = activeArrows[i];
if (arrow.update) {
arrow.update();
}
}
};
// Initialize the game
initializeGrid();
updateUI();