/****
* 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
}
});