/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Column = Container.expand(function (gemTypes) { var self = Container.call(this); self.gems = []; self.isActive = false; // Create the three gems that form the column for (var i = 0; i < 3; i++) { var type = gemTypes && gemTypes[i] ? gemTypes[i] : null; var gem = new Gem(type); gem.y = -i * CELL_SIZE; self.gems.push(gem); self.addChild(gem); } self.rotate = function () { if (!self.isActive) { return; } // Move the last gem to a temporary position for animation var lastGem = self.gems.pop(); var firstGem = self.gems[0]; var middleGem = self.gems[1]; // Animate the rotation LK.getSound('rotate').play(); // Update the gems array with the new order self.gems = [lastGem, firstGem, middleGem]; // Update positions to match new order tween(lastGem, { y: firstGem.y }, { duration: 150 }); tween(firstGem, { y: middleGem.y }, { duration: 150 }); tween(middleGem, { y: lastGem.y + 2 * CELL_SIZE }, { duration: 150, onFinish: function onFinish() { // Reset positions after animation for (var i = 0; i < 3; i++) { self.gems[i].y = -i * CELL_SIZE; } } }); }; self.getTypes = function () { return self.gems.map(function (gem) { return gem.type; }); }; return self; }); var GameBoard = Container.expand(function () { var self = Container.call(this); self.grid = []; self.cellContainer = self.addChild(new Container()); self.gemContainer = self.addChild(new Container()); // Create background var boardBg = self.cellContainer.attachAsset('board_bg', { anchorX: 0.5, // Center horizontally anchorY: 0.5 // Center vertically }); boardBg.x = COLS * CELL_SIZE / 2; // Center the background on the board boardBg.y = ROWS * CELL_SIZE / 2; // Center the background on the board // Create grid cells for (var row = 0; row < ROWS; row++) { self.grid[row] = []; for (var col = 0; col < COLS; col++) { // Create cell background var cell = LK.getAsset('board_cell', { anchorX: 0.5, anchorY: 0.5, x: col * CELL_SIZE + CELL_SIZE / 2, y: row * CELL_SIZE + CELL_SIZE / 2, alpha: 0 }); self.cellContainer.addChild(cell); self.grid[row][col] = null; } } self.addGemToGrid = function (gem, row, col) { if (row < 0 || row >= ROWS || col < 0 || col >= COLS) { return false; } if (self.grid[row][col] !== null) { return false; } gem.x = col * CELL_SIZE + CELL_SIZE / 2; gem.y = row * CELL_SIZE + CELL_SIZE / 2; self.gemContainer.addChild(gem); self.grid[row][col] = gem; return true; }; self.removeGemFromGrid = function (row, col) { if (row < 0 || row >= ROWS || col < 0 || col >= COLS) { return null; } var gem = self.grid[row][col]; if (gem) { self.grid[row][col] = null; delete frozenGems[row + ',' + col]; // Remove gem from the dictionary return gem; } return null; }; self.isColumnFull = function (col) { return self.grid[0][col] !== null; }; self.canPlaceColumn = function (col) { // Check if any of the top 3 rows in the column are occupied for (var row = 0; row < 3; row++) { if (col < 0 || col >= COLS) { return false; } if (row < ROWS && self.grid[row][col] !== null) { return false; } } return true; }; self.findFirstEmptyRow = function (col) { for (var row = ROWS - 1; row >= 0; row--) { if (self.grid[row][col] === null) { return row; } } return -1; // Column is full }; self.placeColumn = function (column, col, callback) { var gems = column.gems; var placedGems = []; var firstEmptyRow = self.findFirstEmptyRow(col); if (firstEmptyRow < 0 || firstEmptyRow < gems.length - 1) { if (callback) { callback(false); } return; } // Place gems from bottom to top for (var i = 0; i < gems.length; i++) { var row = firstEmptyRow - i; if (row >= 0) { // Use the existing gem instead of creating a new one var gem = gems[i]; placedGems.push({ gem: gem, row: row, col: col }); // Update the gem's position gem.x = col * CELL_SIZE + CELL_SIZE / 2; gem.y = row * CELL_SIZE + CELL_SIZE / 2; // Add the gem to the grid self.gemContainer.addChild(gem); self.grid[row][col] = gem; frozenGems[row + ',' + col] = gem; // Store gem in the dictionary // If it's the last gem, notify callback if (i === gems.length - 1) { LK.getSound('drop').play(); if (callback) { callback(true, placedGems); } } } } }; self.checkMatches = function () { var matchedCells = []; // Check horizontal matches for (var row = 0; row < ROWS; row++) { var count = 1; var type = null; for (var col = 0; col < COLS; col++) { var gem = self.grid[row][col]; if (gem && type === gem.type) { count++; } else { if (count >= 3) { for (var i = 0; i < count; i++) { matchedCells.push({ row: row, col: col - 1 - i }); } } count = 1; type = gem ? gem.type : null; } } if (count >= 3) { for (var i = 0; i < count; i++) { matchedCells.push({ row: row, col: COLS - 1 - i }); } } } // Check vertical matches for (var col = 0; col < COLS; col++) { var count = 1; var type = null; for (var row = 0; row < ROWS; row++) { var gem = self.grid[row][col]; if (gem && type === gem.type) { count++; } else { if (count >= 3) { for (var i = 0; i < count; i++) { matchedCells.push({ row: row - 1 - i, col: col }); } } count = 1; type = gem ? gem.type : null; } } if (count >= 3) { for (var i = 0; i < count; i++) { matchedCells.push({ row: ROWS - 1 - i, col: col }); } } } // Remove duplicates from matchedCells var uniqueMatches = []; var matchMap = {}; for (var i = 0; i < matchedCells.length; i++) { var cell = matchedCells[i]; var key = cell.row + "," + cell.col; if (!matchMap[key]) { matchMap[key] = true; uniqueMatches.push(cell); } } return uniqueMatches; }; self.removeMatches = function (matches, callback) { if (matches.length === 0) { if (callback) { callback(0); } return; } var remainingAnimations = matches.length; var points = matches.length * 10; // Always play match sound and flash effect when matches are found // Force sound to play even if it's already playing LK.getSound('match').stop(); LK.getSound('match').play(); // Flash for any match (3 or more gems) LK.effects.flashScreen(0xFFFFFF, 300); matches.forEach(function (match) { var gem = self.grid[match.row][match.col]; if (gem) { self.grid[match.row][match.col] = null; delete frozenGems[match.row + ',' + match.col]; // Remove gem from the dictionary gem.highlight(); LK.setTimeout(function () { gem.animateMatch(function () { self.gemContainer.removeChild(gem); remainingAnimations--; if (remainingAnimations === 0) { if (callback) { callback(points); } } }); }, 100); } else { remainingAnimations--; if (remainingAnimations === 0 && callback) { callback(points); } } }); }; self.applyGravity = function (callback) { var movedGems = false; var animationsRunning = 0; // Iterate bottom to top, right to left for (var col = 0; col < COLS; col++) { for (var row = ROWS - 1; row > 0; row--) { if (self.grid[row][col] === null) { // Find the first non-null gem above this position for (var checkRow = row - 1; checkRow >= 0; checkRow--) { if (self.grid[checkRow][col] !== null) { var gem = self.grid[checkRow][col]; self.grid[checkRow][col] = null; self.grid[row][col] = gem; // Update the frozenGems dictionary delete frozenGems[checkRow + ',' + col]; frozenGems[row + ',' + col] = gem; movedGems = true; animationsRunning++; gem.animateDrop(row * CELL_SIZE + CELL_SIZE / 2, 0, function () { animationsRunning--; if (animationsRunning === 0 && callback) { callback(movedGems); } }); break; } } } } } if (animationsRunning === 0 && callback) { callback(movedGems); } }; return self; }); var Gem = Container.expand(function (type) { var self = Container.call(this); self.type = type || getRandomGemType(); var gemSprite = self.attachAsset('gem_' + self.type, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.95, scaleY: 0.95 }); self.setType = function (newType) { self.type = newType; self.removeChildren(); gemSprite = self.attachAsset('gem_' + newType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.95, scaleY: 0.95 }); }; self.highlight = function () { tween(gemSprite, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut }); }; self.unhighlight = function () { tween(gemSprite, { scaleX: 0.95, scaleY: 0.95 }, { duration: 200, easing: tween.easeOut }); }; self.animateMatch = function (callback) { tween(gemSprite, { alpha: 0, scaleX: 0.2, scaleY: 0.2 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { if (callback) { callback(); } } }); }; self.animateDrop = function (targetY, delay, callback) { tween(self, { y: targetY }, { duration: 300 + delay, easing: tween.bounceOut, onFinish: function onFinish() { if (callback) { callback(); } } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111122 }); /**** * Game Code ****/ // Constants var CELL_SIZE = 120; var COLS = 25; var ROWS = Math.floor(2732 / CELL_SIZE); //var ROWS = 25; var GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple']; var DROP_INTERVAL_START = 40; // ms var DROP_INTERVAL_MIN = 40; // ms var LEVEL_THRESHOLD = 50; // Points needed to level up // Game state var board; var activeColumn; var nextColumn; var dropInterval; var dropTimer = 0; var fallSpeed = DROP_INTERVAL_START; var gameActive = false; var score = 0; var level = 1; var nextColumnPreview; var columnPositionX = 0; // Dictionary to track frozen gems var frozenGems = {}; // UI elements var scoreText; var levelText; var highScoreText; function getRandomGemType() { var index = Math.floor(Math.random() * GEM_TYPES.length); return GEM_TYPES[index]; } function setupUI() { // Score text scoreText = new Text2('SCORE: 0', { size: 50, fill: 0x000000 // Changed to black }); scoreText.anchor.set(0.5, 0); scoreText.y = 50; LK.gui.top.addChild(scoreText); // Level text levelText = new Text2('LEVEL: 1', { size: 50, fill: 0x000000 // Changed to black }); levelText.anchor.set(0.5, 0); levelText.y = 100; LK.gui.top.addChild(levelText); // High score text var highScore = storage.highScore || 0; highScoreText = new Text2('BEST: ' + highScore, { size: 40, fill: 0x800080 // Changed to purple }); highScoreText.anchor.set(0.5, 0); highScoreText.y = 150; LK.gui.top.addChild(highScoreText); } function updateScore(points) { score += points; scoreText.setText('SCORE: ' + score); // Check if player leveled up var newLevel = Math.floor(score / LEVEL_THRESHOLD) + 1; if (newLevel > level) { level = newLevel; levelText.setText('LEVEL: ' + level); // Increase game speed fallSpeed = Math.max(DROP_INTERVAL_MIN, DROP_INTERVAL_START - (level - 1) * 10); // Adjust fallSpeed based on the current level // Play level up sound LK.getSound('levelup').play(); // Flash effect for level up LK.effects.flashScreen(0x00FFFF, 500); } // Update high score if needed var highScore = storage.highScore || 0; if (score > highScore) { storage.highScore = score; highScoreText.setText('BEST: ' + score); } } function createBoard() { board = new GameBoard(); board.x = (2048 - COLS * CELL_SIZE) / 2; board.y = (2732 - ROWS * CELL_SIZE) / 2; game.addChild(board); } function createNextColumnPreview() { nextColumnPreview = new Container(); nextColumnPreview.x = 2048 - 200; nextColumnPreview.y = 400; var previewLabel = new Text2('NEXT', { size: 40, fill: 0xFFFFFF }); previewLabel.anchor.set(0.5, 0); nextColumnPreview.addChild(previewLabel); game.addChild(nextColumnPreview); } function updateNextColumnPreview() { // Clear previous preview for (var i = nextColumnPreview.children.length - 1; i > 0; i--) { nextColumnPreview.removeChild(nextColumnPreview.children[i]); } // Add new gems to preview in the correct order if (nextColumn) { // Loop through gems in reverse order to match the visual appearance in the game for (var i = nextColumn.gems.length - 1; i >= 0; i--) { var previewGem = new Gem(nextColumn.gems[i].type); previewGem.x = 0; // Position from top to bottom in the preview previewGem.y = 80 + (nextColumn.gems.length - 1 - i) * CELL_SIZE; previewGem.scale.set(0.8); nextColumnPreview.addChild(previewGem); } } } function createNewColumn() { var column = new Column(); column.isActive = true; return column; } function startGame() { // Reset game state score = 0; level = 1; fallSpeed = DROP_INTERVAL_START; // Initialize fallSpeed with the starting drop interval gameActive = true; dropTimer = 0; // Clear frozen gems dictionary frozenGems = {}; // Initialize the board if (board) { game.removeChild(board); } // Create the preview for the next column if (nextColumnPreview) { game.removeChild(nextColumnPreview); } createNextColumnPreview(); // Set up UI setupUI(); // Create initial columns activeColumn = createNewColumn(); nextColumn = createNewColumn(); updateNextColumnPreview(); // Position active column columnPositionX = Math.floor(COLS / 2); activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2; activeColumn.y = 0; game.addChild(activeColumn); // Start music LK.playMusic('bgmusic', { fade: { start: 0, end: 0.4, duration: 1000 } }); } function processMatches(callback) { var matches = board.checkMatches(); if (matches.length > 0) { board.removeMatches(matches, function (points) { updateScore(points); board.applyGravity(function (movedGems) { // Check for chain reactions if (movedGems) { processMatches(callback); } else { if (callback) { callback(); } } }); }); } else { if (callback) { callback(); } } } function spawnNewColumn() { activeColumn = nextColumn; nextColumn = createNewColumn(); updateNextColumnPreview(); columnPositionX = Math.floor(COLS / 2); activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2; activeColumn.y = 0; game.addChild(activeColumn); // Check if game over if (!board.canPlaceColumn(columnPositionX)) { gameActive = false; LK.showGameOver(); } else { activeColumn.isActive = true; // Reactivate the new column } } function moveColumnLeft() { if (!gameActive || !activeColumn || !activeColumn.isActive) { return; } if (columnPositionX > 0) { // Check if there's a collision in the new position var canMove = true; var currentRow = Math.floor(activeColumn.y / CELL_SIZE); for (var i = 0; i < activeColumn.gems.length; i++) { var gemRow = currentRow - i; if (gemRow >= 0 && gemRow < ROWS) { var gemKey = gemRow + ',' + (columnPositionX - 1); if (frozenGems[gemKey]) { canMove = false; break; } } } if (canMove) { columnPositionX--; activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2; } } } function moveColumnRight() { if (!gameActive || !activeColumn || !activeColumn.isActive) { return; } if (columnPositionX < COLS - 1) { // Check if there's a collision in the new position var canMove = true; var currentRow = Math.floor(activeColumn.y / CELL_SIZE); for (var i = 0; i < activeColumn.gems.length; i++) { var gemRow = currentRow - i; if (gemRow >= 0 && gemRow < ROWS) { var gemKey = gemRow + ',' + (columnPositionX + 1); if (frozenGems[gemKey]) { canMove = false; break; } } } if (canMove) { columnPositionX++; activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2; } } } function rotateColumn() { if (!gameActive || !activeColumn) { return; } activeColumn.rotate(); } function dropColumn() { if (!gameActive || !activeColumn || !activeColumn.isActive) { return; } activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2; activeColumn.isActive = false; board.placeColumn(activeColumn, columnPositionX, function (success, placedGems) { if (success) { // Store the positions of the frozen gems placedGems.forEach(function (placedGem) { frozenGems[placedGem.row + ',' + placedGem.col] = placedGem.gem; }); game.removeChild(activeColumn); processMatches(function () { spawnNewColumn(); // Spawn a new column after processing matches }); } else { // Failed to place column, game over gameActive = false; LK.showGameOver(); } }); } // Input handlers game.down = function (x, y, obj) { if (!gameActive) { startGame(); return; } // Determine which action to take based on touch position var boardX = x - board.x; // If touch is on the board if (boardX >= 0 && boardX < COLS * CELL_SIZE) { var touchCol = Math.floor(boardX / CELL_SIZE); // If touch is on the left side of the active column, move left if (touchCol < columnPositionX) { moveColumnLeft(); } // If touch is on the right side of the active column, move right else if (touchCol > columnPositionX) { moveColumnRight(); } // If touch is on the active column, rotate it else { rotateColumn(); } } }; game.move = function (x, y, obj) { // Optional: handle drag for more fluid control }; game.up = function (x, y, obj) { // Optional: implement swipe detection for quick drop if (gameActive && y > game.down.y + 100) { dropColumn(); } }; game.update = function () { if (!gameActive || !activeColumn || !activeColumn.isActive) { return; } // Update column position using rows var currentRow = Math.floor(activeColumn.y / CELL_SIZE); // Calculate the next row position based on the current fall speed dropTimer += fallSpeed; // Increment dropTimer by fallSpeed if (dropTimer >= 1000) { // Check if dropTimer has reached the threshold for a drop // 1000ms is the base interval for a row drop dropTimer = 0; // Check if the column can move down var canMoveDown = true; var nextRow = currentRow + 1; // Check if any gem in the column would collide with a frozen gem for (var i = 0; i < activeColumn.gems.length; i++) { var gemRow = nextRow - i; if (gemRow >= 0 && gemRow < ROWS) { var gemKey = gemRow + ',' + columnPositionX; if (frozenGems[gemKey]) { canMoveDown = false; break; } } } // Check if column reached the bottom // The bottom-most gem in the column is at position nextRow // So we need to check if this position is beyond the board if (nextRow >= ROWS) { canMoveDown = false; } if (canMoveDown) { activeColumn.y = nextRow * CELL_SIZE; // Move down } else { // Column has hit something or reached the bottom dropColumn(); return; } } // Check for immediate collisions (for fast drops or manual movements) var checkRow = Math.floor(activeColumn.y / CELL_SIZE); for (var i = 0; i < activeColumn.gems.length; i++) { var gemRow = checkRow - i; if (gemRow >= 0 && gemRow < ROWS) { var gemKey = gemRow + ',' + columnPositionX; if (frozenGems[gemKey]) { // Move column back up to avoid overlap activeColumn.y = (gemRow + i) * CELL_SIZE; dropColumn(); return; } } } // Check if column reached the bottom // Only freeze the column if the bottom-most gem has reached the bottom row if (checkRow >= ROWS) { activeColumn.y = (ROWS - 1) * CELL_SIZE; dropColumn(); return; } }; // Initialize the board if (board) { game.removeChild(board); } createBoard(); // Create and display the title image in the center of the screen var titleImage = LK.getAsset('title', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); var blackBg = LK.getAsset('blackBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0.75 }); game.addChild(blackBg); game.addChild(titleImage); startGameText = new Text2('Press any button to start...', { size: 150, fill: 0xFFFFFF // Changed to black, }); startGameText2 = new Text2('Press any button to start...', { size: 150, fill: 0xffffff // Changed to black, }); startGameText.y = 2732 / 2 + 500; startGameText.x = 2048 / 2 - 800; startGameText.addChild(startGameText2); game.addChild(startGameText); // Function to start the game // Start the game when the title image is clicked titleImage.down = function (x, y, obj) { startGame(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Column = Container.expand(function (gemTypes) {
var self = Container.call(this);
self.gems = [];
self.isActive = false;
// Create the three gems that form the column
for (var i = 0; i < 3; i++) {
var type = gemTypes && gemTypes[i] ? gemTypes[i] : null;
var gem = new Gem(type);
gem.y = -i * CELL_SIZE;
self.gems.push(gem);
self.addChild(gem);
}
self.rotate = function () {
if (!self.isActive) {
return;
}
// Move the last gem to a temporary position for animation
var lastGem = self.gems.pop();
var firstGem = self.gems[0];
var middleGem = self.gems[1];
// Animate the rotation
LK.getSound('rotate').play();
// Update the gems array with the new order
self.gems = [lastGem, firstGem, middleGem];
// Update positions to match new order
tween(lastGem, {
y: firstGem.y
}, {
duration: 150
});
tween(firstGem, {
y: middleGem.y
}, {
duration: 150
});
tween(middleGem, {
y: lastGem.y + 2 * CELL_SIZE
}, {
duration: 150,
onFinish: function onFinish() {
// Reset positions after animation
for (var i = 0; i < 3; i++) {
self.gems[i].y = -i * CELL_SIZE;
}
}
});
};
self.getTypes = function () {
return self.gems.map(function (gem) {
return gem.type;
});
};
return self;
});
var GameBoard = Container.expand(function () {
var self = Container.call(this);
self.grid = [];
self.cellContainer = self.addChild(new Container());
self.gemContainer = self.addChild(new Container());
// Create background
var boardBg = self.cellContainer.attachAsset('board_bg', {
anchorX: 0.5,
// Center horizontally
anchorY: 0.5 // Center vertically
});
boardBg.x = COLS * CELL_SIZE / 2; // Center the background on the board
boardBg.y = ROWS * CELL_SIZE / 2; // Center the background on the board
// Create grid cells
for (var row = 0; row < ROWS; row++) {
self.grid[row] = [];
for (var col = 0; col < COLS; col++) {
// Create cell background
var cell = LK.getAsset('board_cell', {
anchorX: 0.5,
anchorY: 0.5,
x: col * CELL_SIZE + CELL_SIZE / 2,
y: row * CELL_SIZE + CELL_SIZE / 2,
alpha: 0
});
self.cellContainer.addChild(cell);
self.grid[row][col] = null;
}
}
self.addGemToGrid = function (gem, row, col) {
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) {
return false;
}
if (self.grid[row][col] !== null) {
return false;
}
gem.x = col * CELL_SIZE + CELL_SIZE / 2;
gem.y = row * CELL_SIZE + CELL_SIZE / 2;
self.gemContainer.addChild(gem);
self.grid[row][col] = gem;
return true;
};
self.removeGemFromGrid = function (row, col) {
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) {
return null;
}
var gem = self.grid[row][col];
if (gem) {
self.grid[row][col] = null;
delete frozenGems[row + ',' + col]; // Remove gem from the dictionary
return gem;
}
return null;
};
self.isColumnFull = function (col) {
return self.grid[0][col] !== null;
};
self.canPlaceColumn = function (col) {
// Check if any of the top 3 rows in the column are occupied
for (var row = 0; row < 3; row++) {
if (col < 0 || col >= COLS) {
return false;
}
if (row < ROWS && self.grid[row][col] !== null) {
return false;
}
}
return true;
};
self.findFirstEmptyRow = function (col) {
for (var row = ROWS - 1; row >= 0; row--) {
if (self.grid[row][col] === null) {
return row;
}
}
return -1; // Column is full
};
self.placeColumn = function (column, col, callback) {
var gems = column.gems;
var placedGems = [];
var firstEmptyRow = self.findFirstEmptyRow(col);
if (firstEmptyRow < 0 || firstEmptyRow < gems.length - 1) {
if (callback) {
callback(false);
}
return;
}
// Place gems from bottom to top
for (var i = 0; i < gems.length; i++) {
var row = firstEmptyRow - i;
if (row >= 0) {
// Use the existing gem instead of creating a new one
var gem = gems[i];
placedGems.push({
gem: gem,
row: row,
col: col
});
// Update the gem's position
gem.x = col * CELL_SIZE + CELL_SIZE / 2;
gem.y = row * CELL_SIZE + CELL_SIZE / 2;
// Add the gem to the grid
self.gemContainer.addChild(gem);
self.grid[row][col] = gem;
frozenGems[row + ',' + col] = gem; // Store gem in the dictionary
// If it's the last gem, notify callback
if (i === gems.length - 1) {
LK.getSound('drop').play();
if (callback) {
callback(true, placedGems);
}
}
}
}
};
self.checkMatches = function () {
var matchedCells = [];
// Check horizontal matches
for (var row = 0; row < ROWS; row++) {
var count = 1;
var type = null;
for (var col = 0; col < COLS; col++) {
var gem = self.grid[row][col];
if (gem && type === gem.type) {
count++;
} else {
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: row,
col: col - 1 - i
});
}
}
count = 1;
type = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: row,
col: COLS - 1 - i
});
}
}
}
// Check vertical matches
for (var col = 0; col < COLS; col++) {
var count = 1;
var type = null;
for (var row = 0; row < ROWS; row++) {
var gem = self.grid[row][col];
if (gem && type === gem.type) {
count++;
} else {
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: row - 1 - i,
col: col
});
}
}
count = 1;
type = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: ROWS - 1 - i,
col: col
});
}
}
}
// Remove duplicates from matchedCells
var uniqueMatches = [];
var matchMap = {};
for (var i = 0; i < matchedCells.length; i++) {
var cell = matchedCells[i];
var key = cell.row + "," + cell.col;
if (!matchMap[key]) {
matchMap[key] = true;
uniqueMatches.push(cell);
}
}
return uniqueMatches;
};
self.removeMatches = function (matches, callback) {
if (matches.length === 0) {
if (callback) {
callback(0);
}
return;
}
var remainingAnimations = matches.length;
var points = matches.length * 10;
// Always play match sound and flash effect when matches are found
// Force sound to play even if it's already playing
LK.getSound('match').stop();
LK.getSound('match').play();
// Flash for any match (3 or more gems)
LK.effects.flashScreen(0xFFFFFF, 300);
matches.forEach(function (match) {
var gem = self.grid[match.row][match.col];
if (gem) {
self.grid[match.row][match.col] = null;
delete frozenGems[match.row + ',' + match.col]; // Remove gem from the dictionary
gem.highlight();
LK.setTimeout(function () {
gem.animateMatch(function () {
self.gemContainer.removeChild(gem);
remainingAnimations--;
if (remainingAnimations === 0) {
if (callback) {
callback(points);
}
}
});
}, 100);
} else {
remainingAnimations--;
if (remainingAnimations === 0 && callback) {
callback(points);
}
}
});
};
self.applyGravity = function (callback) {
var movedGems = false;
var animationsRunning = 0;
// Iterate bottom to top, right to left
for (var col = 0; col < COLS; col++) {
for (var row = ROWS - 1; row > 0; row--) {
if (self.grid[row][col] === null) {
// Find the first non-null gem above this position
for (var checkRow = row - 1; checkRow >= 0; checkRow--) {
if (self.grid[checkRow][col] !== null) {
var gem = self.grid[checkRow][col];
self.grid[checkRow][col] = null;
self.grid[row][col] = gem;
// Update the frozenGems dictionary
delete frozenGems[checkRow + ',' + col];
frozenGems[row + ',' + col] = gem;
movedGems = true;
animationsRunning++;
gem.animateDrop(row * CELL_SIZE + CELL_SIZE / 2, 0, function () {
animationsRunning--;
if (animationsRunning === 0 && callback) {
callback(movedGems);
}
});
break;
}
}
}
}
}
if (animationsRunning === 0 && callback) {
callback(movedGems);
}
};
return self;
});
var Gem = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || getRandomGemType();
var gemSprite = self.attachAsset('gem_' + self.type, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.95
});
self.setType = function (newType) {
self.type = newType;
self.removeChildren();
gemSprite = self.attachAsset('gem_' + newType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.95
});
};
self.highlight = function () {
tween(gemSprite, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
};
self.unhighlight = function () {
tween(gemSprite, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 200,
easing: tween.easeOut
});
};
self.animateMatch = function (callback) {
tween(gemSprite, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
self.animateDrop = function (targetY, delay, callback) {
tween(self, {
y: targetY
}, {
duration: 300 + delay,
easing: tween.bounceOut,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111122
});
/****
* Game Code
****/
// Constants
var CELL_SIZE = 120;
var COLS = 25;
var ROWS = Math.floor(2732 / CELL_SIZE);
//var ROWS = 25;
var GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple'];
var DROP_INTERVAL_START = 40; // ms
var DROP_INTERVAL_MIN = 40; // ms
var LEVEL_THRESHOLD = 50; // Points needed to level up
// Game state
var board;
var activeColumn;
var nextColumn;
var dropInterval;
var dropTimer = 0;
var fallSpeed = DROP_INTERVAL_START;
var gameActive = false;
var score = 0;
var level = 1;
var nextColumnPreview;
var columnPositionX = 0;
// Dictionary to track frozen gems
var frozenGems = {};
// UI elements
var scoreText;
var levelText;
var highScoreText;
function getRandomGemType() {
var index = Math.floor(Math.random() * GEM_TYPES.length);
return GEM_TYPES[index];
}
function setupUI() {
// Score text
scoreText = new Text2('SCORE: 0', {
size: 50,
fill: 0x000000 // Changed to black
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 50;
LK.gui.top.addChild(scoreText);
// Level text
levelText = new Text2('LEVEL: 1', {
size: 50,
fill: 0x000000 // Changed to black
});
levelText.anchor.set(0.5, 0);
levelText.y = 100;
LK.gui.top.addChild(levelText);
// High score text
var highScore = storage.highScore || 0;
highScoreText = new Text2('BEST: ' + highScore, {
size: 40,
fill: 0x800080 // Changed to purple
});
highScoreText.anchor.set(0.5, 0);
highScoreText.y = 150;
LK.gui.top.addChild(highScoreText);
}
function updateScore(points) {
score += points;
scoreText.setText('SCORE: ' + score);
// Check if player leveled up
var newLevel = Math.floor(score / LEVEL_THRESHOLD) + 1;
if (newLevel > level) {
level = newLevel;
levelText.setText('LEVEL: ' + level);
// Increase game speed
fallSpeed = Math.max(DROP_INTERVAL_MIN, DROP_INTERVAL_START - (level - 1) * 10); // Adjust fallSpeed based on the current level
// Play level up sound
LK.getSound('levelup').play();
// Flash effect for level up
LK.effects.flashScreen(0x00FFFF, 500);
}
// Update high score if needed
var highScore = storage.highScore || 0;
if (score > highScore) {
storage.highScore = score;
highScoreText.setText('BEST: ' + score);
}
}
function createBoard() {
board = new GameBoard();
board.x = (2048 - COLS * CELL_SIZE) / 2;
board.y = (2732 - ROWS * CELL_SIZE) / 2;
game.addChild(board);
}
function createNextColumnPreview() {
nextColumnPreview = new Container();
nextColumnPreview.x = 2048 - 200;
nextColumnPreview.y = 400;
var previewLabel = new Text2('NEXT', {
size: 40,
fill: 0xFFFFFF
});
previewLabel.anchor.set(0.5, 0);
nextColumnPreview.addChild(previewLabel);
game.addChild(nextColumnPreview);
}
function updateNextColumnPreview() {
// Clear previous preview
for (var i = nextColumnPreview.children.length - 1; i > 0; i--) {
nextColumnPreview.removeChild(nextColumnPreview.children[i]);
}
// Add new gems to preview in the correct order
if (nextColumn) {
// Loop through gems in reverse order to match the visual appearance in the game
for (var i = nextColumn.gems.length - 1; i >= 0; i--) {
var previewGem = new Gem(nextColumn.gems[i].type);
previewGem.x = 0;
// Position from top to bottom in the preview
previewGem.y = 80 + (nextColumn.gems.length - 1 - i) * CELL_SIZE;
previewGem.scale.set(0.8);
nextColumnPreview.addChild(previewGem);
}
}
}
function createNewColumn() {
var column = new Column();
column.isActive = true;
return column;
}
function startGame() {
// Reset game state
score = 0;
level = 1;
fallSpeed = DROP_INTERVAL_START; // Initialize fallSpeed with the starting drop interval
gameActive = true;
dropTimer = 0;
// Clear frozen gems dictionary
frozenGems = {};
// Initialize the board
if (board) {
game.removeChild(board);
}
// Create the preview for the next column
if (nextColumnPreview) {
game.removeChild(nextColumnPreview);
}
createNextColumnPreview();
// Set up UI
setupUI();
// Create initial columns
activeColumn = createNewColumn();
nextColumn = createNewColumn();
updateNextColumnPreview();
// Position active column
columnPositionX = Math.floor(COLS / 2);
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
activeColumn.y = 0;
game.addChild(activeColumn);
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
function processMatches(callback) {
var matches = board.checkMatches();
if (matches.length > 0) {
board.removeMatches(matches, function (points) {
updateScore(points);
board.applyGravity(function (movedGems) {
// Check for chain reactions
if (movedGems) {
processMatches(callback);
} else {
if (callback) {
callback();
}
}
});
});
} else {
if (callback) {
callback();
}
}
}
function spawnNewColumn() {
activeColumn = nextColumn;
nextColumn = createNewColumn();
updateNextColumnPreview();
columnPositionX = Math.floor(COLS / 2);
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
activeColumn.y = 0;
game.addChild(activeColumn);
// Check if game over
if (!board.canPlaceColumn(columnPositionX)) {
gameActive = false;
LK.showGameOver();
} else {
activeColumn.isActive = true; // Reactivate the new column
}
}
function moveColumnLeft() {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
if (columnPositionX > 0) {
// Check if there's a collision in the new position
var canMove = true;
var currentRow = Math.floor(activeColumn.y / CELL_SIZE);
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = currentRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + (columnPositionX - 1);
if (frozenGems[gemKey]) {
canMove = false;
break;
}
}
}
if (canMove) {
columnPositionX--;
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
}
}
}
function moveColumnRight() {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
if (columnPositionX < COLS - 1) {
// Check if there's a collision in the new position
var canMove = true;
var currentRow = Math.floor(activeColumn.y / CELL_SIZE);
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = currentRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + (columnPositionX + 1);
if (frozenGems[gemKey]) {
canMove = false;
break;
}
}
}
if (canMove) {
columnPositionX++;
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
}
}
}
function rotateColumn() {
if (!gameActive || !activeColumn) {
return;
}
activeColumn.rotate();
}
function dropColumn() {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
activeColumn.isActive = false;
board.placeColumn(activeColumn, columnPositionX, function (success, placedGems) {
if (success) {
// Store the positions of the frozen gems
placedGems.forEach(function (placedGem) {
frozenGems[placedGem.row + ',' + placedGem.col] = placedGem.gem;
});
game.removeChild(activeColumn);
processMatches(function () {
spawnNewColumn(); // Spawn a new column after processing matches
});
} else {
// Failed to place column, game over
gameActive = false;
LK.showGameOver();
}
});
}
// Input handlers
game.down = function (x, y, obj) {
if (!gameActive) {
startGame();
return;
}
// Determine which action to take based on touch position
var boardX = x - board.x;
// If touch is on the board
if (boardX >= 0 && boardX < COLS * CELL_SIZE) {
var touchCol = Math.floor(boardX / CELL_SIZE);
// If touch is on the left side of the active column, move left
if (touchCol < columnPositionX) {
moveColumnLeft();
}
// If touch is on the right side of the active column, move right
else if (touchCol > columnPositionX) {
moveColumnRight();
}
// If touch is on the active column, rotate it
else {
rotateColumn();
}
}
};
game.move = function (x, y, obj) {
// Optional: handle drag for more fluid control
};
game.up = function (x, y, obj) {
// Optional: implement swipe detection for quick drop
if (gameActive && y > game.down.y + 100) {
dropColumn();
}
};
game.update = function () {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
// Update column position using rows
var currentRow = Math.floor(activeColumn.y / CELL_SIZE);
// Calculate the next row position based on the current fall speed
dropTimer += fallSpeed; // Increment dropTimer by fallSpeed
if (dropTimer >= 1000) {
// Check if dropTimer has reached the threshold for a drop
// 1000ms is the base interval for a row drop
dropTimer = 0;
// Check if the column can move down
var canMoveDown = true;
var nextRow = currentRow + 1;
// Check if any gem in the column would collide with a frozen gem
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = nextRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + columnPositionX;
if (frozenGems[gemKey]) {
canMoveDown = false;
break;
}
}
}
// Check if column reached the bottom
// The bottom-most gem in the column is at position nextRow
// So we need to check if this position is beyond the board
if (nextRow >= ROWS) {
canMoveDown = false;
}
if (canMoveDown) {
activeColumn.y = nextRow * CELL_SIZE; // Move down
} else {
// Column has hit something or reached the bottom
dropColumn();
return;
}
}
// Check for immediate collisions (for fast drops or manual movements)
var checkRow = Math.floor(activeColumn.y / CELL_SIZE);
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = checkRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + columnPositionX;
if (frozenGems[gemKey]) {
// Move column back up to avoid overlap
activeColumn.y = (gemRow + i) * CELL_SIZE;
dropColumn();
return;
}
}
}
// Check if column reached the bottom
// Only freeze the column if the bottom-most gem has reached the bottom row
if (checkRow >= ROWS) {
activeColumn.y = (ROWS - 1) * CELL_SIZE;
dropColumn();
return;
}
};
// Initialize the board
if (board) {
game.removeChild(board);
}
createBoard();
// Create and display the title image in the center of the screen
var titleImage = LK.getAsset('title', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
var blackBg = LK.getAsset('blackBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.75
});
game.addChild(blackBg);
game.addChild(titleImage);
startGameText = new Text2('Press any button to start...', {
size: 150,
fill: 0xFFFFFF // Changed to black,
});
startGameText2 = new Text2('Press any button to start...', {
size: 150,
fill: 0xffffff // Changed to black,
});
startGameText.y = 2732 / 2 + 500;
startGameText.x = 2048 / 2 - 800;
startGameText.addChild(startGameText2);
game.addChild(startGameText);
// Function to start the game
// Start the game when the title image is clicked
titleImage.down = function (x, y, obj) {
startGame();
};
Ancient greece background pixel style ruins. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A blue gem. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A green gem. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A purple gem. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A red gem. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A yellow gem. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows