/****
* 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
}
}); ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,985 @@
-/****
+/****
+* 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: 0x000000
+ 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
+ }
});
\ No newline at end of file