/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, leaderboard: [] }); /**** * Classes ****/ var MenuButton = Container.expand(function (text, width, height) { var self = Container.call(this); // Create background var background = self.attachAsset('tile', { width: width || 300, height: height || 100, tint: 0x8f7a66, anchorX: 0.5, anchorY: 0.5 }); // Add text var buttonText = new Text2(text, { size: 70, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); // Make interactive self.interactive = true; return self; }); // -------------- Particle ----------------- var Particle = Container.expand(function (color) { var self = Container.call(this); var size = Math.random() * 35 + 20; // Make particles bigger // Create particle var particle = self.attachAsset('tile', { width: size, height: size, tint: color || 0xEDC22E, anchorX: 0.5, anchorY: 0.5 }); // Random velocity self.vx = (Math.random() - 0.5) * 10; self.vy = (Math.random() - 0.5) * 10; // Update particle movement self.update = function () { self.x += self.vx; self.y += self.vy; self.alpha -= 0.02; // Remove when faded out if (self.alpha <= 0 && self.parent) { self.parent.removeChild(self); } }; return self; }); // -------------- Popup ----------------- var Popup = Container.expand(function (title, content) { var self = Container.call(this); // Ensure the whole popup container itself is centered self.x = 2048 / 2; self.y = 2732 / 2; // Background overlay var overlay = self.attachAsset('tile', { width: 2048, height: 2732, tint: 0x000000, anchorX: 0.5, anchorY: 0.5 }); overlay.alpha = 0.7; // Popup panel var popup = self.attachAsset('tile', { width: 1600, height: 1600, tint: 0xFAF8EF, anchorX: 0.5, anchorY: 0.5 }); // Title var titleText = new Text2(title, { size: 80, fill: 0x776E65 }); titleText.anchor.set(0.5, 0); titleText.y = -700; popup.addChild(titleText); // Content var contentText = new Text2(content, { size: 50, fill: 0x776E65 }); contentText.anchor.set(0.5, 0.5); popup.addChild(contentText); // Close button var closeButton = new MenuButton("Close", 200, 80); closeButton.y = 700; closeButton.down = function () { LK.getSound('click').play(); if (self.parent) { self.parent.removeChild(self); } }; popup.addChild(closeButton); return self; }); // -------------- Tile ----------------- var Tile = Container.expand(function (value) { var self = Container.call(this); self.value = value || 0; // Background tile – uses global CELL_SIZE var background = self.attachAsset('tile', { anchorX: 0.5, anchorY: 0.5, width: CELL_SIZE, height: CELL_SIZE }); // Text self.valueText = new Text2(self.value > 0 ? self.value.toString() : '', { size: 60, fill: 0x776E65 }); self.valueText.anchor.set(0.5, 0.5); self.addChild(self.valueText); // Update appearance helper self.updateAppearance = function () { var colors = { 0: 0xCDC1B4, 2: 0xEEE4DA, 4: 0xEDE0C8, 8: 0xF2B179, 16: 0xF59563, 32: 0xF67C5F, 64: 0xF65E3B, 128: 0xEDCF72, 256: 0xEDCC61, 512: 0xEDC850, 1024: 0xEDC53F, 2048: 0xEDC22E }; background.tint = colors[self.value] || 0xCDC1B4; self.valueText.setText(self.value > 0 ? self.value.toString() : ''); var fontSize = 60; if (self.value >= 1000) { fontSize = 40; } else if (self.value >= 100) { fontSize = 50; } if (self.valueText && self.valueText.style) { self.valueText.style.size = fontSize; self.valueText.style.fill = self.value <= 4 ? "#776E65" : "#FFFFFF"; } }; self.setValue = function (newValue) { self.value = newValue; self.updateAppearance(); }; self.updateAppearance(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x333333 }); /**** * Game Code ****/ // Game constants var GRID_SIZE = 4; var CELL_SIZE = 250; // ← Bigger tiles var CELL_SPACING = 15; var GRID_PADDING = 20; var START_SCORE = 1000000; var INITIAL_DEDUCT_RATE = 100; var MAX_DEDUCT_RATE = 5000; var DEDUCT_INCREASE_TIME = 10000; // Game state variables var grid = []; var tiles = []; var score = START_SCORE; var gameActive = false; var lastUpdateTime = 0; var currentDeductRate = INITIAL_DEDUCT_RATE; var gameWon = false; var movesInProgress = 0; // UI elements var boardBackground; var scoreText; var timerText; var instructionsText; var restartButton; // NEW // ------------------------------------------------------------ // Board / UI setup // ------------------------------------------------------------ function initializeBoard() { var boardWidth = GRID_SIZE * CELL_SIZE + (GRID_SIZE + 1) * CELL_SPACING + 2 * GRID_PADDING; var boardHeight = boardWidth; boardBackground = LK.getAsset('tile', { width: boardWidth, height: boardHeight, anchorX: 0.5, anchorY: 0.5, tint: 0xBBADA0 }); boardBackground.x = 2048 / 2; boardBackground.y = 2732 / 2; game.addChild(boardBackground); // Empty cells for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { var cellBg = LK.getAsset('tile', { width: CELL_SIZE, height: CELL_SIZE, anchorX: 0.5, anchorY: 0.5, tint: 0xCDC1B4 }); cellBg.x = getPositionX(j); cellBg.y = getPositionY(i); boardBackground.addChild(cellBg); } } } function initializeUI() { // Score scoreText = new Text2("Score: " + score, { size: 50, fill: 0x776E65 }); scoreText.anchor.set(0.5, 0); // Timer (deduction rate) timerText = new Text2("", { size: 40, fill: 0x776E65 }); timerText.anchor.set(0.5, 0); timerText.y = 70; // Instructions footer instructionsText = new Text2("Glaud warns: Hurry, time is running out.\nSwipe to move the tiles.\nCombine the same numbers to reach 2048!", { size: 40, fill: 0x776E65 }); instructionsText.anchor.set(0.5, 1); // Menu title var menuTitle = new Text2("2048", { size: 120, fill: 0x776E65 }); menuTitle.anchor.set(0.5, 0); menuTitle.x = 2048 / 2; menuTitle.y = 400; game.addChild(menuTitle); // Menu buttons var startButton = new MenuButton("Start Game", 900, 150); startButton.x = 2048 / 2; startButton.y = 2732 / 2 - 120; game.addChild(startButton); var leaderboardButton = new MenuButton("Leaderboard", 900, 150); leaderboardButton.x = 2048 / 2; leaderboardButton.y = 2732 / 2 + 60; game.addChild(leaderboardButton); var instructionsButton = new MenuButton("Instructions", 900, 150); instructionsButton.x = 2048 / 2; instructionsButton.y = 2732 / 2 + 240; game.addChild(instructionsButton); // ---------------- Restart button (hidden until game starts) ---------------- restartButton = new MenuButton("Restart", 400, 150); restartButton.visible = false; restartButton.down = function () { LK.getSound('click').play(); resetGame(); }; LK.gui.top.addChild(restartButton); // Leaderboard container (hidden initially) var leaderboardContainer = new Container(); leaderboardContainer.y = 220; leaderboardContainer.visible = false; LK.gui.top.addChild(leaderboardContainer); // Event handlers startButton.down = function () { LK.getSound('click').play(); // Hide menu elements menuTitle.visible = false; startButton.visible = false; leaderboardButton.visible = false; instructionsButton.visible = false; resetGame(); }; leaderboardButton.down = function () { LK.getSound('click').play(); showLeaderboard(); }; instructionsButton.down = function () { LK.getSound('click').play(); showInstructions(); }; // Add persistent UI to GUI layers LK.gui.top.addChild(scoreText); LK.gui.top.addChild(timerText); LK.gui.bottom.addChild(instructionsText); updateUIPositions(); } function updateUIPositions() { scoreText.y = 20; timerText.y = 80; instructionsText.y = -20; if (boardBackground && restartButton) { // Place restart button just above the board restartButton.x = 0; restartButton.y = 400; // restartButton.x = boardBackground.x; // restartButton.y = boardBackground.y - boardBackground.height / 2 - 60; } } // ------------------------------------------------------------ // Board effects // ------------------------------------------------------------ function shakeBoard(intensity) { if (!boardBackground) { return; } // Save original position var originalX = boardBackground.x; var originalY = boardBackground.y; // Cancel any ongoing shake animations tween.stop(boardBackground, { x: true, y: true }); // Shake in random direction tween(boardBackground, { x: originalX + (Math.random() - 0.5) * intensity, y: originalY + (Math.random() - 0.5) * intensity }, { duration: 50, onFinish: function onFinish() { // Shake again in different direction tween(boardBackground, { x: originalX + (Math.random() - 0.5) * intensity, y: originalY + (Math.random() - 0.5) * intensity }, { duration: 50, onFinish: function onFinish() { // Return to original position tween(boardBackground, { x: originalX, y: originalY }, { duration: 50 }); } }); } }); } // Create particles at tile position function createParticles(x, y, value) { // Get color based on tile value var colors = { 2: 0xEEE4DA, 4: 0xEDE0C8, 8: 0xF2B179, 16: 0xF59563, 32: 0xF67C5F, 64: 0xF65E3B, 128: 0xEDCF72, 256: 0xEDCC61, 512: 0xEDC850, 1024: 0xEDC53F, 2048: 0xEDC22E }; var color = colors[value] || 0xEDC22E; // Create multiple particles for (var i = 0; i < 12; i++) { var particle = new Particle(color); particle.x = x; particle.y = y; boardBackground.addChild(particle); } } // Helper positions (use CELL_SIZE) // ------------------------------------------------------------ function getPositionX(col) { return -((GRID_SIZE * CELL_SIZE + (GRID_SIZE + 1) * CELL_SPACING) / 2) + CELL_SPACING + col * (CELL_SIZE + CELL_SPACING) + CELL_SIZE / 2; } function getPositionY(row) { return -((GRID_SIZE * CELL_SIZE + (GRID_SIZE + 1) * CELL_SPACING) / 2) + CELL_SPACING + row * (CELL_SIZE + CELL_SPACING) + CELL_SIZE / 2; } // Initialize the game grid function initializeGrid() { grid = []; tiles = []; // Create empty grid for (var i = 0; i < GRID_SIZE; i++) { grid[i] = []; tiles[i] = []; for (var j = 0; j < GRID_SIZE; j++) { grid[i][j] = 0; tiles[i][j] = null; } } // Add initial tiles addRandomTile(); addRandomTile(); } // Add a random tile (2 or 4) to an empty cell function addRandomTile() { var emptyCells = []; // Find all empty cells for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (grid[i][j] === 0) { emptyCells.push({ row: i, col: j }); } } } // If there are no empty cells, return if (emptyCells.length === 0) { return; } // Choose a random empty cell var randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)]; // Create a tile with value 2 (90% chance) or 4 (10% chance) var value = Math.random() < 0.9 ? 2 : 4; grid[randomCell.row][randomCell.col] = value; // Create and add the tile object var tile = new Tile(value); tile.x = getPositionX(randomCell.col); tile.y = getPositionY(randomCell.row); tile.scale.x = 0; tile.scale.y = 0; boardBackground.addChild(tile); tiles[randomCell.row][randomCell.col] = tile; LK.getSound('spawn').play(); // Animate the tile appearing with a bounce effect tween(tile.scale, { x: 1.2, y: 1.2 }, { duration: 150, easing: tween.easeOutQuad, onFinish: function onFinish() { tween(tile.scale, { x: 1, y: 1 }, { duration: 100, easing: tween.easeInQuad }); } }); } // Move tiles in a specific direction function moveTiles(direction) { if (!gameActive || movesInProgress > 0) { return; } var hasMoved = false; var rowStart, rowEnd, rowStep; var colStart, colEnd, colStep; var tileAdded = false; // Track if a tile has been added in this move // Set up iteration direction based on swipe direction if (direction === 'up') { rowStart = 1; rowEnd = GRID_SIZE; rowStep = 1; colStart = 0; colEnd = GRID_SIZE; colStep = 1; } else if (direction === 'down') { rowStart = GRID_SIZE - 2; rowEnd = -1; rowStep = -1; colStart = 0; colEnd = GRID_SIZE; colStep = 1; } else if (direction === 'left') { rowStart = 0; rowEnd = GRID_SIZE; rowStep = 1; colStart = 1; colEnd = GRID_SIZE; colStep = 1; } else if (direction === 'right') { rowStart = 0; rowEnd = GRID_SIZE; rowStep = 1; colStart = GRID_SIZE - 2; colEnd = -1; colStep = -1; } // Create a temporary grid to track merged tiles var mergedGrid = []; for (var i = 0; i < GRID_SIZE; i++) { mergedGrid[i] = []; for (var j = 0; j < GRID_SIZE; j++) { mergedGrid[i][j] = false; } } // Perform the move for (var i = rowStart; i !== rowEnd; i += rowStep) { for (var j = colStart; j !== colEnd; j += colStep) { if (grid[i][j] !== 0) { var result = moveTile(i, j, direction, mergedGrid); if (result.moved) { hasMoved = true; } } } } // If no tiles moved, don't add a new random tile if (!hasMoved) { return; } // Play move sound LK.getSound('move').play(); // shakeBoard(30); // Add a new random tile after the animation completes LK.setTimeout(function () { if (gameActive) { // Only add one tile per move if (!tileAdded) { addRandomTile(); tileAdded = true; } // Check for game over if (!canMove()) { // Only trigger game over if we haven't won already if (!gameWon) { gameActive = false; LK.getSound('gameover').play(); // Update high score if needed if (score > storage.highScore) { storage.highScore = score; } // Add score to leaderboard if (score > 0) { // Ensure leaderboard exists if (!Array.isArray(storage.leaderboard)) { storage.leaderboard = []; } // Add score to leaderboard storage.leaderboard.push(score); // Sort leaderboard (highest scores first) storage.leaderboard.sort(function (a, b) { return b - a; }); // Keep only top 5 scores if (storage.leaderboard.length > 5) { storage.leaderboard = storage.leaderboard.slice(0, 5); } // Update the leaderboard display updateLeaderboard(); } LK.setTimeout(function () { LK.showGameOver(); }, 1000); } } } }, 250); } // Move a single tile in the specified direction function moveTile(row, col, direction, mergedGrid) { var targetRow = row; var targetCol = col; var moved = false; /* ------------------------------------------------------------------ 1. IDENTICAL directional search loops (no change) ------------------------------------------------------------------ */ if (direction === 'up') { while (targetRow > 0 && (grid[targetRow - 1][targetCol] === 0 || grid[targetRow - 1][targetCol] === grid[row][col] && !mergedGrid[targetRow - 1][targetCol])) { if (grid[targetRow - 1][targetCol] === 0) { targetRow--; } else { targetRow--; mergedGrid[targetRow][targetCol] = true; break; } } } else if (direction === 'down') { while (targetRow < GRID_SIZE - 1 && (grid[targetRow + 1][targetCol] === 0 || grid[targetRow + 1][targetCol] === grid[row][col] && !mergedGrid[targetRow + 1][targetCol])) { if (grid[targetRow + 1][targetCol] === 0) { targetRow++; } else { targetRow++; mergedGrid[targetRow][targetCol] = true; break; } } } else if (direction === 'left') { while (targetCol > 0 && (grid[targetRow][targetCol - 1] === 0 || grid[targetRow][targetCol - 1] === grid[row][col] && !mergedGrid[targetRow][targetCol - 1])) { if (grid[targetRow][targetCol - 1] === 0) { targetCol--; } else { targetCol--; mergedGrid[targetRow][targetCol] = true; break; } } } else if (direction === 'right') { while (targetCol < GRID_SIZE - 1 && (grid[targetRow][targetCol + 1] === 0 || grid[targetRow][targetCol + 1] === grid[row][col] && !mergedGrid[targetRow][targetCol + 1])) { if (grid[targetRow][targetCol + 1] === 0) { targetCol++; } else { targetCol++; mergedGrid[targetRow][targetCol] = true; break; } } } /* ------------------------------------------------------------------ 2. Same move bookkeeping, but save the sprite we may consume ------------------------------------------------------------------ */ if (targetRow !== row || targetCol !== col) { moved = true; movesInProgress++; var movingTile = tiles[row][col]; // sprite that moves var targetTile = tiles[targetRow][targetCol]; // <<< keep a reference var targetValue = grid[targetRow][targetCol]; var newValue = targetValue === 0 ? grid[row][col] : grid[row][col] * 2; // update model grid[targetRow][targetCol] = newValue; grid[row][col] = 0; // update sprite matrix tiles[targetRow][targetCol] = movingTile; tiles[row][col] = null; /* ------------------------------------------------------------------ 3. Animate – and if we merged, discard the absorbed sprite ------------------------------------------------------------------ */ tween(movingTile, { x: getPositionX(targetCol), y: getPositionY(targetRow) }, { duration: 150, easing: tween.easeOutQuad, onFinish: function onFinish() { if (targetValue !== 0) { // we DID merge if (targetTile && targetTile.parent) { // <<< remove duplicate targetTile.parent.removeChild(targetTile); } LK.getSound('merge').play(); movingTile.setValue(newValue); movingTile.updateAppearance(); // Create particles at the merge position createParticles(movingTile.x, movingTile.y, newValue); // Enhanced "pop" animation tween(movingTile.scale, { x: 1.3, y: 1.3 }, { duration: 120, easing: tween.elasticOut, onFinish: function onFinish() { return tween(movingTile.scale, { x: 1, y: 1 }, { duration: 150, easing: tween.easeOutQuad }); } }); // win check if (newValue === 2048 && !gameWon) { gameWon = true; LK.getSound('victory').play(); if (score > storage.highScore) { storage.highScore = score; } // Add score to leaderboard when winning if (score > 0) { // Ensure leaderboard exists if (!Array.isArray(storage.leaderboard)) { storage.leaderboard = []; } // Add score to leaderboard storage.leaderboard.push(score); // Sort leaderboard (highest scores first) storage.leaderboard.sort(function (a, b) { return b - a; }); // Keep only top 5 scores if (storage.leaderboard.length > 5) { storage.leaderboard = storage.leaderboard.slice(0, 5); } // Update the leaderboard display updateLeaderboard(); } LK.setTimeout(function () { return LK.showYouWin(); }, 1000); } } movesInProgress--; } }); } return { moved: moved }; } // Check if any moves are possible function canMove() { // Check for empty cells for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (grid[i][j] === 0) { return true; } } } // Check for possible merges for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { var currentValue = grid[i][j]; // Check adjacent cells for the same value if (i < GRID_SIZE - 1 && grid[i + 1][j] === currentValue) { return true; } if (j < GRID_SIZE - 1 && grid[i][j + 1] === currentValue) { return true; } } } // No moves possible return false; } // Reset game (for the reset button) function resetGame() { // Stop current game if active gameActive = false; // Clear the game board for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (tiles[i] && tiles[i][j] && tiles[i][j].parent) { tiles[i][j].parent.removeChild(tiles[i][j]); } } } // Start a new game startGame(); } // Show leaderboard popup function showLeaderboard() { // Ensure leaderboard is an array if (!Array.isArray(storage.leaderboard)) { storage.leaderboard = []; } // Prepare leaderboard content var content = ""; if (storage.leaderboard.length === 0) { content = "No scores yet. Play the game to set records!"; } else { // Sort leaderboard (highest scores first) storage.leaderboard.sort(function (a, b) { return b - a; }); // Keep only top 5 scores if (storage.leaderboard.length > 5) { storage.leaderboard = storage.leaderboard.slice(0, 5); } // Format leaderboard entries for (var i = 0; i < storage.leaderboard.length; i++) { content += i + 1 + ". " + Math.floor(storage.leaderboard[i]) + "\n\n"; } } // Create and show popup var leaderboardPopup = new Popup("Leaderboard", content); game.addChild(leaderboardPopup); } // Show instructions popup function showInstructions() { var instructions = "How to play 2048:\n\n" + "• Swipe to move all tiles\n\n" + "• When two tiles with the same number touch, they merge into one\n\n" + "• Create a tile with the number 2048 to win\n\n" + "• Score points decrease over time, so play quickly!\n\n" + "• Game ends when no more moves are possible\n\n\n\n Make queriell - Edit By Glaud"; var instructionsPopup = new Popup("Instructions", instructions); game.addChild(instructionsPopup); } // Add Glaud text in the upper right corner var glaudText = new Text2("Glaud", { size: 40, fill: 0xFFA500 // Orange color }); glaudText.anchor.set(1, 0); // Anchor to top right LK.gui.topRight.addChild(glaudText); // Update leaderboard display function updateLeaderboard() { // Get leaderboard container var leaderboardContainer = LK.gui.top.children.find(function (child) { return child instanceof Container && child.y === 220; }); if (!leaderboardContainer) { return; } // Clear existing entries while (leaderboardContainer.children.length > 0) { leaderboardContainer.removeChild(leaderboardContainer.children[0]); } // Ensure leaderboard is an array if (!Array.isArray(storage.leaderboard)) { storage.leaderboard = []; } // Add current high score if not in leaderboard var highScoreInLeaderboard = false; for (var i = 0; i < storage.leaderboard.length; i++) { if (storage.leaderboard[i] === storage.highScore) { highScoreInLeaderboard = true; break; } } if (!highScoreInLeaderboard && storage.highScore > 0) { storage.leaderboard.push(storage.highScore); } // Sort leaderboard (highest scores first) storage.leaderboard.sort(function (a, b) { return b - a; }); // Keep only top 5 scores if (storage.leaderboard.length > 5) { storage.leaderboard = storage.leaderboard.slice(0, 5); } // Add leaderboard entries for (var i = 0; i < storage.leaderboard.length; i++) { var entryText = new Text2(i + 1 + ". " + Math.floor(storage.leaderboard[i]), { size: 40, fill: 0x776E65 }); entryText.anchor.set(0.5, 0); entryText.y = i * 50; leaderboardContainer.addChild(entryText); } } // Start a new game function startGame() { // Clear any existing tiles // Make sure tiles array is properly initialized first if (!tiles || tiles.length === 0) { tiles = []; for (var i = 0; i < GRID_SIZE; i++) { tiles[i] = []; } } for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (tiles[i] && tiles[i][j] && tiles[i][j].parent) { tiles[i][j].parent.removeChild(tiles[i][j]); } } } // Reset game state score = START_SCORE; currentDeductRate = INITIAL_DEDUCT_RATE; gameActive = true; gameWon = false; lastUpdateTime = Date.now(); movesInProgress = 0; // Show game elements boardBackground.visible = true; scoreText.visible = true; timerText.visible = true; instructionsText.visible = true; restartButton.visible = true; // Initialize the grid and UI initializeGrid(); // Update score display updateScore(); // Update leaderboard display updateLeaderboard(); // Start playing background music LK.playMusic('bgMusic'); } // Update the score display function updateScore() { scoreText.setText("Score: " + Math.floor(score)); timerText.setText("Deduction: " + currentDeductRate + " points/sec"); } // Touch/swipe handling variables var touchStartX = 0; var touchStartY = 0; var touchEndX = 0; var touchEndY = 0; var minSwipeDistance = 50; // Minimum distance for a valid swipe // Game event handlers game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; }; game.up = function (x, y, obj) { touchEndX = x; touchEndY = y; // Calculate swipe distance and direction var dx = touchEndX - touchStartX; var dy = touchEndY - touchStartY; // Only process swipe if game is active if (gameActive && (Math.abs(dx) > minSwipeDistance || Math.abs(dy) > minSwipeDistance)) { // Determine swipe direction if (Math.abs(dx) > Math.abs(dy)) { // Horizontal swipe if (dx > 0) { moveTiles('right'); } else { moveTiles('left'); } } else { // Vertical swipe if (dy > 0) { moveTiles('down'); } else { moveTiles('up'); } } } }; // Game update loop game.update = function () { if (gameActive) { var currentTime = Date.now(); var deltaTime = (currentTime - lastUpdateTime) / 1000; // Convert to seconds lastUpdateTime = currentTime; // Set deduction rate based on current score if (score > 100000) { currentDeductRate = 2500; } else if (score > 10000) { currentDeductRate = 250; } else if (score > 1000) { currentDeductRate = 25; } else if (score > 0) { currentDeductRate = 1; } else { currentDeductRate = 0; } // Deduct points based on time passed and current rate score -= currentDeductRate * deltaTime; // Ensure score doesn't go below zero if (score < 0) { score = 0; gameActive = false; // Update high score if needed if (score > storage.highScore) { storage.highScore = score; } LK.getSound('gameover').play(); LK.setTimeout(function () { LK.showGameOver(); }, 1000); } // Update score display updateScore(); } }; // Initialize the game initializeBoard(); initializeUI(); // Start game timer var startTime = Date.now(); lastUpdateTime = startTime; // Initial leaderboard setup updateLeaderboard(); // Set game inactive until Start is pressed gameActive = false; // Hide board and game elements initially boardBackground.visible = false; scoreText.visible = false; timerText.visible = false; instructionsText.visible = false; // Play background music LK.playMusic('bgMusic', { fade: { start: 0, end: 0.4, duration: 1000 } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
leaderboard: []
});
/****
* Classes
****/
var MenuButton = Container.expand(function (text, width, height) {
var self = Container.call(this);
// Create background
var background = self.attachAsset('tile', {
width: width || 300,
height: height || 100,
tint: 0x8f7a66,
anchorX: 0.5,
anchorY: 0.5
});
// Add text
var buttonText = new Text2(text, {
size: 70,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
// Make interactive
self.interactive = true;
return self;
});
// -------------- Particle -----------------
var Particle = Container.expand(function (color) {
var self = Container.call(this);
var size = Math.random() * 35 + 20; // Make particles bigger
// Create particle
var particle = self.attachAsset('tile', {
width: size,
height: size,
tint: color || 0xEDC22E,
anchorX: 0.5,
anchorY: 0.5
});
// Random velocity
self.vx = (Math.random() - 0.5) * 10;
self.vy = (Math.random() - 0.5) * 10;
// Update particle movement
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.alpha -= 0.02;
// Remove when faded out
if (self.alpha <= 0 && self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
// -------------- Popup -----------------
var Popup = Container.expand(function (title, content) {
var self = Container.call(this);
// Ensure the whole popup container itself is centered
self.x = 2048 / 2;
self.y = 2732 / 2;
// Background overlay
var overlay = self.attachAsset('tile', {
width: 2048,
height: 2732,
tint: 0x000000,
anchorX: 0.5,
anchorY: 0.5
});
overlay.alpha = 0.7;
// Popup panel
var popup = self.attachAsset('tile', {
width: 1600,
height: 1600,
tint: 0xFAF8EF,
anchorX: 0.5,
anchorY: 0.5
});
// Title
var titleText = new Text2(title, {
size: 80,
fill: 0x776E65
});
titleText.anchor.set(0.5, 0);
titleText.y = -700;
popup.addChild(titleText);
// Content
var contentText = new Text2(content, {
size: 50,
fill: 0x776E65
});
contentText.anchor.set(0.5, 0.5);
popup.addChild(contentText);
// Close button
var closeButton = new MenuButton("Close", 200, 80);
closeButton.y = 700;
closeButton.down = function () {
LK.getSound('click').play();
if (self.parent) {
self.parent.removeChild(self);
}
};
popup.addChild(closeButton);
return self;
});
// -------------- Tile -----------------
var Tile = Container.expand(function (value) {
var self = Container.call(this);
self.value = value || 0;
// Background tile – uses global CELL_SIZE
var background = self.attachAsset('tile', {
anchorX: 0.5,
anchorY: 0.5,
width: CELL_SIZE,
height: CELL_SIZE
});
// Text
self.valueText = new Text2(self.value > 0 ? self.value.toString() : '', {
size: 60,
fill: 0x776E65
});
self.valueText.anchor.set(0.5, 0.5);
self.addChild(self.valueText);
// Update appearance helper
self.updateAppearance = function () {
var colors = {
0: 0xCDC1B4,
2: 0xEEE4DA,
4: 0xEDE0C8,
8: 0xF2B179,
16: 0xF59563,
32: 0xF67C5F,
64: 0xF65E3B,
128: 0xEDCF72,
256: 0xEDCC61,
512: 0xEDC850,
1024: 0xEDC53F,
2048: 0xEDC22E
};
background.tint = colors[self.value] || 0xCDC1B4;
self.valueText.setText(self.value > 0 ? self.value.toString() : '');
var fontSize = 60;
if (self.value >= 1000) {
fontSize = 40;
} else if (self.value >= 100) {
fontSize = 50;
}
if (self.valueText && self.valueText.style) {
self.valueText.style.size = fontSize;
self.valueText.style.fill = self.value <= 4 ? "#776E65" : "#FFFFFF";
}
};
self.setValue = function (newValue) {
self.value = newValue;
self.updateAppearance();
};
self.updateAppearance();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333
});
/****
* Game Code
****/
// Game constants
var GRID_SIZE = 4;
var CELL_SIZE = 250; // ← Bigger tiles
var CELL_SPACING = 15;
var GRID_PADDING = 20;
var START_SCORE = 1000000;
var INITIAL_DEDUCT_RATE = 100;
var MAX_DEDUCT_RATE = 5000;
var DEDUCT_INCREASE_TIME = 10000;
// Game state variables
var grid = [];
var tiles = [];
var score = START_SCORE;
var gameActive = false;
var lastUpdateTime = 0;
var currentDeductRate = INITIAL_DEDUCT_RATE;
var gameWon = false;
var movesInProgress = 0;
// UI elements
var boardBackground;
var scoreText;
var timerText;
var instructionsText;
var restartButton; // NEW
// ------------------------------------------------------------
// Board / UI setup
// ------------------------------------------------------------
function initializeBoard() {
var boardWidth = GRID_SIZE * CELL_SIZE + (GRID_SIZE + 1) * CELL_SPACING + 2 * GRID_PADDING;
var boardHeight = boardWidth;
boardBackground = LK.getAsset('tile', {
width: boardWidth,
height: boardHeight,
anchorX: 0.5,
anchorY: 0.5,
tint: 0xBBADA0
});
boardBackground.x = 2048 / 2;
boardBackground.y = 2732 / 2;
game.addChild(boardBackground);
// Empty cells
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
var cellBg = LK.getAsset('tile', {
width: CELL_SIZE,
height: CELL_SIZE,
anchorX: 0.5,
anchorY: 0.5,
tint: 0xCDC1B4
});
cellBg.x = getPositionX(j);
cellBg.y = getPositionY(i);
boardBackground.addChild(cellBg);
}
}
}
function initializeUI() {
// Score
scoreText = new Text2("Score: " + score, {
size: 50,
fill: 0x776E65
});
scoreText.anchor.set(0.5, 0);
// Timer (deduction rate)
timerText = new Text2("", {
size: 40,
fill: 0x776E65
});
timerText.anchor.set(0.5, 0);
timerText.y = 70;
// Instructions footer
instructionsText = new Text2("Glaud warns: Hurry, time is running out.\nSwipe to move the tiles.\nCombine the same numbers to reach 2048!", {
size: 40,
fill: 0x776E65
});
instructionsText.anchor.set(0.5, 1);
// Menu title
var menuTitle = new Text2("2048", {
size: 120,
fill: 0x776E65
});
menuTitle.anchor.set(0.5, 0);
menuTitle.x = 2048 / 2;
menuTitle.y = 400;
game.addChild(menuTitle);
// Menu buttons
var startButton = new MenuButton("Start Game", 900, 150);
startButton.x = 2048 / 2;
startButton.y = 2732 / 2 - 120;
game.addChild(startButton);
var leaderboardButton = new MenuButton("Leaderboard", 900, 150);
leaderboardButton.x = 2048 / 2;
leaderboardButton.y = 2732 / 2 + 60;
game.addChild(leaderboardButton);
var instructionsButton = new MenuButton("Instructions", 900, 150);
instructionsButton.x = 2048 / 2;
instructionsButton.y = 2732 / 2 + 240;
game.addChild(instructionsButton);
// ---------------- Restart button (hidden until game starts) ----------------
restartButton = new MenuButton("Restart", 400, 150);
restartButton.visible = false;
restartButton.down = function () {
LK.getSound('click').play();
resetGame();
};
LK.gui.top.addChild(restartButton);
// Leaderboard container (hidden initially)
var leaderboardContainer = new Container();
leaderboardContainer.y = 220;
leaderboardContainer.visible = false;
LK.gui.top.addChild(leaderboardContainer);
// Event handlers
startButton.down = function () {
LK.getSound('click').play();
// Hide menu elements
menuTitle.visible = false;
startButton.visible = false;
leaderboardButton.visible = false;
instructionsButton.visible = false;
resetGame();
};
leaderboardButton.down = function () {
LK.getSound('click').play();
showLeaderboard();
};
instructionsButton.down = function () {
LK.getSound('click').play();
showInstructions();
};
// Add persistent UI to GUI layers
LK.gui.top.addChild(scoreText);
LK.gui.top.addChild(timerText);
LK.gui.bottom.addChild(instructionsText);
updateUIPositions();
}
function updateUIPositions() {
scoreText.y = 20;
timerText.y = 80;
instructionsText.y = -20;
if (boardBackground && restartButton) {
// Place restart button just above the board
restartButton.x = 0;
restartButton.y = 400;
// restartButton.x = boardBackground.x;
// restartButton.y = boardBackground.y - boardBackground.height / 2 - 60;
}
}
// ------------------------------------------------------------
// Board effects
// ------------------------------------------------------------
function shakeBoard(intensity) {
if (!boardBackground) {
return;
}
// Save original position
var originalX = boardBackground.x;
var originalY = boardBackground.y;
// Cancel any ongoing shake animations
tween.stop(boardBackground, {
x: true,
y: true
});
// Shake in random direction
tween(boardBackground, {
x: originalX + (Math.random() - 0.5) * intensity,
y: originalY + (Math.random() - 0.5) * intensity
}, {
duration: 50,
onFinish: function onFinish() {
// Shake again in different direction
tween(boardBackground, {
x: originalX + (Math.random() - 0.5) * intensity,
y: originalY + (Math.random() - 0.5) * intensity
}, {
duration: 50,
onFinish: function onFinish() {
// Return to original position
tween(boardBackground, {
x: originalX,
y: originalY
}, {
duration: 50
});
}
});
}
});
}
// Create particles at tile position
function createParticles(x, y, value) {
// Get color based on tile value
var colors = {
2: 0xEEE4DA,
4: 0xEDE0C8,
8: 0xF2B179,
16: 0xF59563,
32: 0xF67C5F,
64: 0xF65E3B,
128: 0xEDCF72,
256: 0xEDCC61,
512: 0xEDC850,
1024: 0xEDC53F,
2048: 0xEDC22E
};
var color = colors[value] || 0xEDC22E;
// Create multiple particles
for (var i = 0; i < 12; i++) {
var particle = new Particle(color);
particle.x = x;
particle.y = y;
boardBackground.addChild(particle);
}
}
// Helper positions (use CELL_SIZE)
// ------------------------------------------------------------
function getPositionX(col) {
return -((GRID_SIZE * CELL_SIZE + (GRID_SIZE + 1) * CELL_SPACING) / 2) + CELL_SPACING + col * (CELL_SIZE + CELL_SPACING) + CELL_SIZE / 2;
}
function getPositionY(row) {
return -((GRID_SIZE * CELL_SIZE + (GRID_SIZE + 1) * CELL_SPACING) / 2) + CELL_SPACING + row * (CELL_SIZE + CELL_SPACING) + CELL_SIZE / 2;
}
// Initialize the game grid
function initializeGrid() {
grid = [];
tiles = [];
// Create empty grid
for (var i = 0; i < GRID_SIZE; i++) {
grid[i] = [];
tiles[i] = [];
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j] = 0;
tiles[i][j] = null;
}
}
// Add initial tiles
addRandomTile();
addRandomTile();
}
// Add a random tile (2 or 4) to an empty cell
function addRandomTile() {
var emptyCells = [];
// Find all empty cells
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (grid[i][j] === 0) {
emptyCells.push({
row: i,
col: j
});
}
}
}
// If there are no empty cells, return
if (emptyCells.length === 0) {
return;
}
// Choose a random empty cell
var randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
// Create a tile with value 2 (90% chance) or 4 (10% chance)
var value = Math.random() < 0.9 ? 2 : 4;
grid[randomCell.row][randomCell.col] = value;
// Create and add the tile object
var tile = new Tile(value);
tile.x = getPositionX(randomCell.col);
tile.y = getPositionY(randomCell.row);
tile.scale.x = 0;
tile.scale.y = 0;
boardBackground.addChild(tile);
tiles[randomCell.row][randomCell.col] = tile;
LK.getSound('spawn').play();
// Animate the tile appearing with a bounce effect
tween(tile.scale, {
x: 1.2,
y: 1.2
}, {
duration: 150,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
tween(tile.scale, {
x: 1,
y: 1
}, {
duration: 100,
easing: tween.easeInQuad
});
}
});
}
// Move tiles in a specific direction
function moveTiles(direction) {
if (!gameActive || movesInProgress > 0) {
return;
}
var hasMoved = false;
var rowStart, rowEnd, rowStep;
var colStart, colEnd, colStep;
var tileAdded = false; // Track if a tile has been added in this move
// Set up iteration direction based on swipe direction
if (direction === 'up') {
rowStart = 1;
rowEnd = GRID_SIZE;
rowStep = 1;
colStart = 0;
colEnd = GRID_SIZE;
colStep = 1;
} else if (direction === 'down') {
rowStart = GRID_SIZE - 2;
rowEnd = -1;
rowStep = -1;
colStart = 0;
colEnd = GRID_SIZE;
colStep = 1;
} else if (direction === 'left') {
rowStart = 0;
rowEnd = GRID_SIZE;
rowStep = 1;
colStart = 1;
colEnd = GRID_SIZE;
colStep = 1;
} else if (direction === 'right') {
rowStart = 0;
rowEnd = GRID_SIZE;
rowStep = 1;
colStart = GRID_SIZE - 2;
colEnd = -1;
colStep = -1;
}
// Create a temporary grid to track merged tiles
var mergedGrid = [];
for (var i = 0; i < GRID_SIZE; i++) {
mergedGrid[i] = [];
for (var j = 0; j < GRID_SIZE; j++) {
mergedGrid[i][j] = false;
}
}
// Perform the move
for (var i = rowStart; i !== rowEnd; i += rowStep) {
for (var j = colStart; j !== colEnd; j += colStep) {
if (grid[i][j] !== 0) {
var result = moveTile(i, j, direction, mergedGrid);
if (result.moved) {
hasMoved = true;
}
}
}
}
// If no tiles moved, don't add a new random tile
if (!hasMoved) {
return;
}
// Play move sound
LK.getSound('move').play();
// shakeBoard(30);
// Add a new random tile after the animation completes
LK.setTimeout(function () {
if (gameActive) {
// Only add one tile per move
if (!tileAdded) {
addRandomTile();
tileAdded = true;
}
// Check for game over
if (!canMove()) {
// Only trigger game over if we haven't won already
if (!gameWon) {
gameActive = false;
LK.getSound('gameover').play();
// Update high score if needed
if (score > storage.highScore) {
storage.highScore = score;
}
// Add score to leaderboard
if (score > 0) {
// Ensure leaderboard exists
if (!Array.isArray(storage.leaderboard)) {
storage.leaderboard = [];
}
// Add score to leaderboard
storage.leaderboard.push(score);
// Sort leaderboard (highest scores first)
storage.leaderboard.sort(function (a, b) {
return b - a;
});
// Keep only top 5 scores
if (storage.leaderboard.length > 5) {
storage.leaderboard = storage.leaderboard.slice(0, 5);
}
// Update the leaderboard display
updateLeaderboard();
}
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
}
}
}, 250);
}
// Move a single tile in the specified direction
function moveTile(row, col, direction, mergedGrid) {
var targetRow = row;
var targetCol = col;
var moved = false;
/* ------------------------------------------------------------------
1. IDENTICAL directional search loops (no change)
------------------------------------------------------------------ */
if (direction === 'up') {
while (targetRow > 0 && (grid[targetRow - 1][targetCol] === 0 || grid[targetRow - 1][targetCol] === grid[row][col] && !mergedGrid[targetRow - 1][targetCol])) {
if (grid[targetRow - 1][targetCol] === 0) {
targetRow--;
} else {
targetRow--;
mergedGrid[targetRow][targetCol] = true;
break;
}
}
} else if (direction === 'down') {
while (targetRow < GRID_SIZE - 1 && (grid[targetRow + 1][targetCol] === 0 || grid[targetRow + 1][targetCol] === grid[row][col] && !mergedGrid[targetRow + 1][targetCol])) {
if (grid[targetRow + 1][targetCol] === 0) {
targetRow++;
} else {
targetRow++;
mergedGrid[targetRow][targetCol] = true;
break;
}
}
} else if (direction === 'left') {
while (targetCol > 0 && (grid[targetRow][targetCol - 1] === 0 || grid[targetRow][targetCol - 1] === grid[row][col] && !mergedGrid[targetRow][targetCol - 1])) {
if (grid[targetRow][targetCol - 1] === 0) {
targetCol--;
} else {
targetCol--;
mergedGrid[targetRow][targetCol] = true;
break;
}
}
} else if (direction === 'right') {
while (targetCol < GRID_SIZE - 1 && (grid[targetRow][targetCol + 1] === 0 || grid[targetRow][targetCol + 1] === grid[row][col] && !mergedGrid[targetRow][targetCol + 1])) {
if (grid[targetRow][targetCol + 1] === 0) {
targetCol++;
} else {
targetCol++;
mergedGrid[targetRow][targetCol] = true;
break;
}
}
}
/* ------------------------------------------------------------------
2. Same move bookkeeping, but save the sprite we may consume
------------------------------------------------------------------ */
if (targetRow !== row || targetCol !== col) {
moved = true;
movesInProgress++;
var movingTile = tiles[row][col]; // sprite that moves
var targetTile = tiles[targetRow][targetCol]; // <<< keep a reference
var targetValue = grid[targetRow][targetCol];
var newValue = targetValue === 0 ? grid[row][col] : grid[row][col] * 2;
// update model
grid[targetRow][targetCol] = newValue;
grid[row][col] = 0;
// update sprite matrix
tiles[targetRow][targetCol] = movingTile;
tiles[row][col] = null;
/* ------------------------------------------------------------------
3. Animate – and if we merged, discard the absorbed sprite
------------------------------------------------------------------ */
tween(movingTile, {
x: getPositionX(targetCol),
y: getPositionY(targetRow)
}, {
duration: 150,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (targetValue !== 0) {
// we DID merge
if (targetTile && targetTile.parent) {
// <<< remove duplicate
targetTile.parent.removeChild(targetTile);
}
LK.getSound('merge').play();
movingTile.setValue(newValue);
movingTile.updateAppearance();
// Create particles at the merge position
createParticles(movingTile.x, movingTile.y, newValue);
// Enhanced "pop" animation
tween(movingTile.scale, {
x: 1.3,
y: 1.3
}, {
duration: 120,
easing: tween.elasticOut,
onFinish: function onFinish() {
return tween(movingTile.scale, {
x: 1,
y: 1
}, {
duration: 150,
easing: tween.easeOutQuad
});
}
});
// win check
if (newValue === 2048 && !gameWon) {
gameWon = true;
LK.getSound('victory').play();
if (score > storage.highScore) {
storage.highScore = score;
}
// Add score to leaderboard when winning
if (score > 0) {
// Ensure leaderboard exists
if (!Array.isArray(storage.leaderboard)) {
storage.leaderboard = [];
}
// Add score to leaderboard
storage.leaderboard.push(score);
// Sort leaderboard (highest scores first)
storage.leaderboard.sort(function (a, b) {
return b - a;
});
// Keep only top 5 scores
if (storage.leaderboard.length > 5) {
storage.leaderboard = storage.leaderboard.slice(0, 5);
}
// Update the leaderboard display
updateLeaderboard();
}
LK.setTimeout(function () {
return LK.showYouWin();
}, 1000);
}
}
movesInProgress--;
}
});
}
return {
moved: moved
};
}
// Check if any moves are possible
function canMove() {
// Check for empty cells
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (grid[i][j] === 0) {
return true;
}
}
}
// Check for possible merges
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
var currentValue = grid[i][j];
// Check adjacent cells for the same value
if (i < GRID_SIZE - 1 && grid[i + 1][j] === currentValue) {
return true;
}
if (j < GRID_SIZE - 1 && grid[i][j + 1] === currentValue) {
return true;
}
}
}
// No moves possible
return false;
}
// Reset game (for the reset button)
function resetGame() {
// Stop current game if active
gameActive = false;
// Clear the game board
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (tiles[i] && tiles[i][j] && tiles[i][j].parent) {
tiles[i][j].parent.removeChild(tiles[i][j]);
}
}
}
// Start a new game
startGame();
}
// Show leaderboard popup
function showLeaderboard() {
// Ensure leaderboard is an array
if (!Array.isArray(storage.leaderboard)) {
storage.leaderboard = [];
}
// Prepare leaderboard content
var content = "";
if (storage.leaderboard.length === 0) {
content = "No scores yet. Play the game to set records!";
} else {
// Sort leaderboard (highest scores first)
storage.leaderboard.sort(function (a, b) {
return b - a;
});
// Keep only top 5 scores
if (storage.leaderboard.length > 5) {
storage.leaderboard = storage.leaderboard.slice(0, 5);
}
// Format leaderboard entries
for (var i = 0; i < storage.leaderboard.length; i++) {
content += i + 1 + ". " + Math.floor(storage.leaderboard[i]) + "\n\n";
}
}
// Create and show popup
var leaderboardPopup = new Popup("Leaderboard", content);
game.addChild(leaderboardPopup);
}
// Show instructions popup
function showInstructions() {
var instructions = "How to play 2048:\n\n" + "• Swipe to move all tiles\n\n" + "• When two tiles with the same number touch, they merge into one\n\n" + "• Create a tile with the number 2048 to win\n\n" + "• Score points decrease over time, so play quickly!\n\n" + "• Game ends when no more moves are possible\n\n\n\n Make queriell - Edit By Glaud";
var instructionsPopup = new Popup("Instructions", instructions);
game.addChild(instructionsPopup);
}
// Add Glaud text in the upper right corner
var glaudText = new Text2("Glaud", {
size: 40,
fill: 0xFFA500 // Orange color
});
glaudText.anchor.set(1, 0); // Anchor to top right
LK.gui.topRight.addChild(glaudText);
// Update leaderboard display
function updateLeaderboard() {
// Get leaderboard container
var leaderboardContainer = LK.gui.top.children.find(function (child) {
return child instanceof Container && child.y === 220;
});
if (!leaderboardContainer) {
return;
}
// Clear existing entries
while (leaderboardContainer.children.length > 0) {
leaderboardContainer.removeChild(leaderboardContainer.children[0]);
}
// Ensure leaderboard is an array
if (!Array.isArray(storage.leaderboard)) {
storage.leaderboard = [];
}
// Add current high score if not in leaderboard
var highScoreInLeaderboard = false;
for (var i = 0; i < storage.leaderboard.length; i++) {
if (storage.leaderboard[i] === storage.highScore) {
highScoreInLeaderboard = true;
break;
}
}
if (!highScoreInLeaderboard && storage.highScore > 0) {
storage.leaderboard.push(storage.highScore);
}
// Sort leaderboard (highest scores first)
storage.leaderboard.sort(function (a, b) {
return b - a;
});
// Keep only top 5 scores
if (storage.leaderboard.length > 5) {
storage.leaderboard = storage.leaderboard.slice(0, 5);
}
// Add leaderboard entries
for (var i = 0; i < storage.leaderboard.length; i++) {
var entryText = new Text2(i + 1 + ". " + Math.floor(storage.leaderboard[i]), {
size: 40,
fill: 0x776E65
});
entryText.anchor.set(0.5, 0);
entryText.y = i * 50;
leaderboardContainer.addChild(entryText);
}
}
// Start a new game
function startGame() {
// Clear any existing tiles
// Make sure tiles array is properly initialized first
if (!tiles || tiles.length === 0) {
tiles = [];
for (var i = 0; i < GRID_SIZE; i++) {
tiles[i] = [];
}
}
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (tiles[i] && tiles[i][j] && tiles[i][j].parent) {
tiles[i][j].parent.removeChild(tiles[i][j]);
}
}
}
// Reset game state
score = START_SCORE;
currentDeductRate = INITIAL_DEDUCT_RATE;
gameActive = true;
gameWon = false;
lastUpdateTime = Date.now();
movesInProgress = 0;
// Show game elements
boardBackground.visible = true;
scoreText.visible = true;
timerText.visible = true;
instructionsText.visible = true;
restartButton.visible = true;
// Initialize the grid and UI
initializeGrid();
// Update score display
updateScore();
// Update leaderboard display
updateLeaderboard();
// Start playing background music
LK.playMusic('bgMusic');
}
// Update the score display
function updateScore() {
scoreText.setText("Score: " + Math.floor(score));
timerText.setText("Deduction: " + currentDeductRate + " points/sec");
}
// Touch/swipe handling variables
var touchStartX = 0;
var touchStartY = 0;
var touchEndX = 0;
var touchEndY = 0;
var minSwipeDistance = 50; // Minimum distance for a valid swipe
// Game event handlers
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
touchEndX = x;
touchEndY = y;
// Calculate swipe distance and direction
var dx = touchEndX - touchStartX;
var dy = touchEndY - touchStartY;
// Only process swipe if game is active
if (gameActive && (Math.abs(dx) > minSwipeDistance || Math.abs(dy) > minSwipeDistance)) {
// Determine swipe direction
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > 0) {
moveTiles('right');
} else {
moveTiles('left');
}
} else {
// Vertical swipe
if (dy > 0) {
moveTiles('down');
} else {
moveTiles('up');
}
}
}
};
// Game update loop
game.update = function () {
if (gameActive) {
var currentTime = Date.now();
var deltaTime = (currentTime - lastUpdateTime) / 1000; // Convert to seconds
lastUpdateTime = currentTime;
// Set deduction rate based on current score
if (score > 100000) {
currentDeductRate = 2500;
} else if (score > 10000) {
currentDeductRate = 250;
} else if (score > 1000) {
currentDeductRate = 25;
} else if (score > 0) {
currentDeductRate = 1;
} else {
currentDeductRate = 0;
}
// Deduct points based on time passed and current rate
score -= currentDeductRate * deltaTime;
// Ensure score doesn't go below zero
if (score < 0) {
score = 0;
gameActive = false;
// Update high score if needed
if (score > storage.highScore) {
storage.highScore = score;
}
LK.getSound('gameover').play();
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
// Update score display
updateScore();
}
};
// Initialize the game
initializeBoard();
initializeUI();
// Start game timer
var startTime = Date.now();
lastUpdateTime = startTime;
// Initial leaderboard setup
updateLeaderboard();
// Set game inactive until Start is pressed
gameActive = false;
// Hide board and game elements initially
boardBackground.visible = false;
scoreText.visible = false;
timerText.visible = false;
instructionsText.visible = false;
// Play background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});