User prompt
Display the current score and the highest tile achieved after every move in a clear and readable format.
User prompt
After each valid move, spawn a new tile with a value of either 3 or 6 at a random empty position on the grid.
User prompt
Update the merge logic to ensure tiles merge only if their values are exactly the same. Tiles with different values should never merge.
User prompt
In addition to the existing game rules, ensure the merging logic correctly checks and performs addition only when two tiles have the exact same value. Specifically: Tiles should merge only if their values are equal. When merged, the resulting tile's value must be the sum of the two tiles. Prevent any merges of tiles with different values (e.g., 3 and 6 should NOT merge). Include clear validation in the code to avoid incorrect merges or unexpected results.
Code edit (1 edits merged)
Please save this source code
User prompt
Threes Merge: 3072 Puzzle
Initial prompt
Create a grid-based sliding puzzle game inspired by 2048, but instead of powers of 2, the tiles should be multiples of 3 (e.g., 3, 6, 12, 24, 48, etc.). The rules should be: Start with a 4x4 grid containing two tiles with values of 3 or 6. When two tiles with the same value are combined, they merge into one tile whose value is the sum (e.g., 6 + 6 = 12). After each move (up/down/left/right), a new tile (3 or 6) spawns in an empty cell. The game ends when there are no valid moves left. The goal is to reach a tile with the value 3072 (instead of 2048). Add the following features: Keyboard controls for movement (WASD or arrows) Display the grid and current score after every move Optional: track and show the highest tile reached Keep the code clean and easy to convert to GUI later (e.g., Unity, WinForms, or web)
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Tile class for each tile on the board var Tile = Container.expand(function () { var self = Container.call(this); self.value = 3; // default, will be set on creation self.row = 0; self.col = 0; // Create and attach a box asset for the tile self.tileBox = self.attachAsset('tileBox', { width: 400, height: 400, color: 0xeeeeee, anchorX: 0.5, anchorY: 0.5 }); // Text for the tile value self.valueText = new Text2('3', { size: 120, fill: 0x333333 }); self.valueText.anchor.set(0.5, 0.5); self.addChild(self.valueText); // Set tile value and update appearance self.setValue = function (val) { self.value = val; self.valueText.setText(val + ''); // Color by value var color = 0xeeeeee; if (val === 3) color = 0xF9F6F2;else if (val === 6) color = 0xEDE0C8;else if (val === 12) color = 0xF2B179;else if (val === 24) color = 0xF59563;else if (val === 48) color = 0xF67C5F;else if (val === 96) color = 0xF65E3B;else if (val === 192) color = 0xEDCF72;else if (val === 384) color = 0xEDCC61;else if (val === 768) color = 0xEDC850;else if (val === 1536) color = 0xEDC53F;else if (val === 3072) color = 0xEDC22E; self.tileBox.color = color; }; // Animate pop-in self.pop = function () { self.scaleX = self.scaleY = 0.2; tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf7f7f7 }); /**** * Game Code ****/ // --- Constants --- var GRID_SIZE = 4; var TILE_SIZE = 400; var TILE_MARGIN = 32; var BOARD_SIZE = GRID_SIZE * TILE_SIZE + (GRID_SIZE + 1) * TILE_MARGIN; var BOARD_X = (2048 - BOARD_SIZE) / 2; var BOARD_Y = (2732 - BOARD_SIZE) / 2 + 100; // --- State --- var board = []; // 2D array of tiles or null var tileNodes = []; // 2D array of Tile objects or null var score = 0; var bestTile = 3; var moving = false; // Prevent input during animation // --- GUI --- var scoreTxt = new Text2('Score: 0', { size: 90, fill: 0x333333 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var bestTxt = new Text2('Best: 3', { size: 60, fill: 0x888888 }); bestTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bestTxt); bestTxt.y = 110; // --- Board background --- var boardBg = LK.getAsset('boardBg', { width: BOARD_SIZE, height: BOARD_SIZE, color: 0xbbb9b6, anchorX: 0, anchorY: 0, x: BOARD_X, y: BOARD_Y }); game.addChild(boardBg); // --- Board grid background tiles --- for (var r = 0; r < GRID_SIZE; r++) { for (var c = 0; c < GRID_SIZE; c++) { var cellBg = LK.getAsset('cellBg', { width: TILE_SIZE, height: TILE_SIZE, color: 0xcdc1b4, anchorX: 0.5, anchorY: 0.5, x: BOARD_X + TILE_MARGIN + c * (TILE_SIZE + TILE_MARGIN) + TILE_SIZE / 2, y: BOARD_Y + TILE_MARGIN + r * (TILE_SIZE + TILE_MARGIN) + TILE_SIZE / 2 }); game.addChild(cellBg); } } // --- Helper: get position for a tile --- function getTilePos(row, col) { return { x: BOARD_X + TILE_MARGIN + col * (TILE_SIZE + TILE_MARGIN) + TILE_SIZE / 2, y: BOARD_Y + TILE_MARGIN + row * (TILE_SIZE + TILE_MARGIN) + TILE_SIZE / 2 }; } // --- Helper: spawn a new tile (3 or 6) in a random empty cell --- function spawnTile() { var empties = []; for (var r = 0; r < GRID_SIZE; r++) { for (var c = 0; c < GRID_SIZE; c++) { if (board[r][c] === null) { empties.push({ r: r, c: c }); } } } if (empties.length === 0) return false; var idx = Math.floor(Math.random() * empties.length); var pos = empties[idx]; var val = Math.random() < 0.8 ? 3 : 6; addTile(pos.r, pos.c, val, true); return true; } // --- Helper: add a tile to the board and scene --- function addTile(row, col, value, animate) { var tile = new Tile(); tile.setValue(value); tile.row = row; tile.col = col; var pos = getTilePos(row, col); tile.x = pos.x; tile.y = pos.y; board[row][col] = value; tileNodes[row][col] = tile; game.addChild(tile); if (animate) tile.pop(); } // --- Helper: remove a tile from the board and scene --- function removeTile(row, col) { if (tileNodes[row][col]) { tileNodes[row][col].destroy(); } board[row][col] = null; tileNodes[row][col] = null; } // --- Helper: update score and best tile display --- function updateScore(add) { score += add; scoreTxt.setText('Score: ' + score); } function updateBestTile(val) { if (val > bestTile) { bestTile = val; bestTxt.setText('Best: ' + bestTile); } } // --- Helper: check if any moves are possible --- function movesAvailable() { for (var r = 0; r < GRID_SIZE; r++) { for (var c = 0; c < GRID_SIZE; c++) { if (board[r][c] === null) return true; var v = board[r][c]; // Check right if (c < GRID_SIZE - 1 && board[r][c + 1] === v) return true; // Check down if (r < GRID_SIZE - 1 && board[r + 1][c] === v) return true; } } return false; } // --- Helper: check for win (tile 3072) --- function checkWin() { for (var r = 0; r < GRID_SIZE; r++) { for (var c = 0; c < GRID_SIZE; c++) { if (board[r][c] === 3072) { LK.showYouWin(); return true; } } } return false; } // --- Helper: move/merge tiles in a direction --- // dir: {x: -1, y:0} for left, {x:1,y:0} for right, etc. function moveTiles(dir) { if (moving) return; moving = true; var moved = false; var merged = []; for (var r = 0; r < GRID_SIZE; r++) { merged[r] = []; for (var c = 0; c < GRID_SIZE; c++) merged[r][c] = false; } // Order of traversal depends on direction var startR = dir.y > 0 ? GRID_SIZE - 1 : 0; var endR = dir.y > 0 ? -1 : GRID_SIZE; var stepR = dir.y > 0 ? -1 : 1; var startC = dir.x > 0 ? GRID_SIZE - 1 : 0; var endC = dir.x > 0 ? -1 : GRID_SIZE; var stepC = dir.x > 0 ? -1 : 1; var movedTiles = []; for (var i = 0; i < GRID_SIZE * GRID_SIZE; i++) { var r = dir.y !== 0 ? startR + stepR * (i % GRID_SIZE) : i / GRID_SIZE | 0; var c = dir.x !== 0 ? startC + stepC * (i % GRID_SIZE) : i % GRID_SIZE; r = Math.max(0, Math.min(GRID_SIZE - 1, r)); c = Math.max(0, Math.min(GRID_SIZE - 1, c)); if (board[r][c] === null) continue; var nr = r, nc = c; while (true) { var tr = nr + dir.y, tc = nc + dir.x; if (tr < 0 || tr >= GRID_SIZE || tc < 0 || tc >= GRID_SIZE) break; if (board[tr][tc] === null) { nr = tr; nc = tc; } else if (board[tr][tc] === board[r][c] && !merged[tr][tc]) { // Only merge if values are exactly equal nr = tr; nc = tc; break; } else { // Prevent merge if values are not exactly equal break; } } if (nr !== r || nc !== c) { moved = true; movedTiles.push({ fromR: r, fromC: c, toR: nr, toC: nc }); } } // Actually move and merge var toRemove = []; var toAdd = []; for (var m = 0; m < movedTiles.length; m++) { var move = movedTiles[m]; var val = board[move.fromR][move.fromC]; var destVal = board[move.toR][move.toC]; var mergedHere = false; if (board[move.toR][move.toC] === board[move.fromR][move.fromC] && !merged[move.toR][move.toC]) { // Only merge if values are exactly equal val = board[move.toR][move.toC] + board[move.fromR][move.fromC]; merged[move.toR][move.toC] = true; toRemove.push({ r: move.fromR, c: move.fromC }); toRemove.push({ r: move.toR, c: move.toC }); toAdd.push({ r: move.toR, c: move.toC, val: val, merged: true }); updateScore(val); updateBestTile(val); if (val === 3072) { LK.setTimeout(function () { LK.showYouWin(); }, 400); } } else { // Just move toRemove.push({ r: move.fromR, c: move.fromC }); toAdd.push({ r: move.toR, c: move.toC, val: board[move.fromR][move.fromC], merged: false }); } } // Animate all moves var animCount = 0; for (var m = 0; m < movedTiles.length; m++) { var move = movedTiles[m]; var tile = tileNodes[move.fromR][move.fromC]; var pos = getTilePos(move.toR, move.toC); animCount++; tween(tile, { x: pos.x, y: pos.y }, { duration: 120, easing: tween.easeInOut, onFinish: function onFinish() { animCount--; if (animCount === 0) finishMove(); } }); } if (movedTiles.length === 0) { moving = false; return; } function finishMove() { // Remove old tiles for (var i = 0; i < toRemove.length; i++) { removeTile(toRemove[i].r, toRemove[i].c); } // Add new tiles for (var i = 0; i < toAdd.length; i++) { addTile(toAdd[i].r, toAdd[i].c, toAdd[i].val, toAdd[i].merged); } // Spawn new tile spawnTile(); // Check for game over if (!movesAvailable()) { LK.setTimeout(function () { LK.showGameOver(); }, 400); } moving = false; } // If no animation (should not happen), call finishMove immediately if (animCount === 0) finishMove(); } // --- Input handling --- // Touch/drag swipe detection var touchStartX = null, touchStartY = null, touchStartTime = null; game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; touchStartTime = Date.now(); }; game.up = function (x, y, obj) { if (touchStartX === null || moving) return; var dx = x - touchStartX; var dy = y - touchStartY; var adx = Math.abs(dx), ady = Math.abs(dy); if (adx < 50 && ady < 50) { touchStartX = null; touchStartY = null; return; } if (adx > ady) { if (dx > 0) moveTiles({ x: 1, y: 0 }); // right else moveTiles({ x: -1, y: 0 }); // left } else { if (dy > 0) moveTiles({ x: 0, y: 1 }); // down else moveTiles({ x: 0, y: -1 }); // up } touchStartX = null; touchStartY = null; }; // Keyboard controls (for desktop) game.move = function (x, y, obj) { // No-op for drag, but we want to support keyboard if (obj && obj.event && obj.event.type === 'keydown' && !moving) { var key = obj.event.key; if (key === 'ArrowLeft') moveTiles({ x: -1, y: 0 });else if (key === 'ArrowRight') moveTiles({ x: 1, y: 0 });else if (key === 'ArrowUp') moveTiles({ x: 0, y: -1 });else if (key === 'ArrowDown') moveTiles({ x: 0, y: 1 }); } }; // --- Game initialization --- function resetGame() { // Clear board for (var r = 0; r < GRID_SIZE; r++) { for (var c = 0; c < GRID_SIZE; c++) { if (tileNodes[r] && tileNodes[r][c]) { tileNodes[r][c].destroy(); } } } board = []; tileNodes = []; for (var r = 0; r < GRID_SIZE; r++) { board[r] = []; tileNodes[r] = []; for (var c = 0; c < GRID_SIZE; c++) { board[r][c] = null; tileNodes[r][c] = null; } } score = 0; bestTile = 3; scoreTxt.setText('Score: 0'); bestTxt.setText('Best: 3'); moving = false; // Spawn two tiles spawnTile(); spawnTile(); } resetGame(); // --- Game tick (not used, but required for LK) --- game.update = function () { // No per-frame logic needed };
===================================================================
--- original.js
+++ change.js
Design a subtle, minimalistic background for the game. Use a soft pastel or muted gradient (e.g., light beige, soft gray, or pale blue). Avoid patterns or distractions — the focus should remain on the tiles. The background should give a calm, clean feeling and enhance the overall aesthetic without drawing attention.. In-Game asset. 2d. High contrast. No shadows
Prompt: Create a Dark Brown Hollow-Style Background Design a sleek and minimal background using a dark brown color (such as chocolate or espresso tones). Use a hollow or framed appearance — not a solid fill — to keep the center light and uncluttered. The central area should feel open, possibly with a slight transparency or soft inner shadow. The darker brown edges should add contrast and warmth without overpowering the game tiles.. In-Game asset. 2d. High contrast. No shadows
Design a background similar to the original 2048 game. Use a warm, soft beige or light brown tone as the main background. Include a grid layout with rounded square slots where tiles appear. Each slot should have a slightly darker shade than the background to show the empty grid clearly. Keep the overall design minimal, clean, and visually balanced.. In-Game asset. 2d. High contrast. No shadows