User prompt
I don't want to drag and drop tiles. I just want to click it to add the bar
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading '0')' in or related to this line: 'if (prevGrid[row][col]) {' Line Number: 326
User prompt
Make it harder every level
User prompt
I want to see my level and my point
User prompt
Make the layer system
User prompt
No every color tile need to 3 or it's multiples
User prompt
I can't clear the layer because tiles not matching at the end. Make tile count 3 or it's multiplication
User prompt
I want a multiple layer design. Every level layers gonna increase
User prompt
I want to select tiles and grab to bottom bar to match same shaped tiles
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'row')' in or related to this line: 'var temp = board[tileA.row][tileA.col];' Line Number: 132
Code edit (1 edits merged)
Please save this source code
User prompt
Tile Match Mania
Initial prompt
Make me a basic tile matching game for mobile devices
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Tile class
var Tile = Container.expand(function () {
var self = Container.call(this);
// Properties
self.row = 0;
self.col = 0;
self.colorId = 0; // 0-5
self.isMoving = false;
// Attach asset
self.setColor = function (colorId) {
self.colorId = colorId;
if (self.tileAsset) {
self.removeChild(self.tileAsset);
}
var colorNames = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var assetId = 'tile_' + colorNames[colorId];
self.tileAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
// Animate to a new position (row, col)
self.moveTo = function (row, col, duration, _onFinish) {
self.row = row;
self.col = col;
self.isMoving = true;
var targetX = boardOffsetX + col * tileSize + tileSize / 2;
var targetY = boardOffsetY + row * tileSize + tileSize / 2;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.cubicOut,
onFinish: function onFinish() {
self.isMoving = false;
if (_onFinish) _onFinish();
}
});
};
// Instantly set position
self.setPosition = function (row, col) {
self.row = row;
self.col = col;
self.x = boardOffsetX + col * tileSize + tileSize / 2;
self.y = boardOffsetY + row * tileSize + tileSize / 2;
};
// Flash when matched
self.flash = function (_onFinish2) {
tween(self.tileAsset, {
alpha: 0
}, {
duration: 150,
easing: tween.linear,
onFinish: function onFinish() {
self.tileAsset.alpha = 1;
if (_onFinish2) _onFinish2();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Simple pop sound for matches
// 6 tile colors for variety
// --- Board settings ---
var boardRows = 8;
var boardCols = 8;
var tileSize = 180;
var boardWidth = boardCols * tileSize;
var boardHeight = boardRows * tileSize;
var boardOffsetX = Math.floor((2048 - boardWidth) / 2);
var boardOffsetY = Math.floor((2732 - boardHeight) / 2);
// --- Game state ---
var board = []; // 2D array [row][col] of Tile
var tiles = []; // Flat array of all Tile objects
var selectedTile = null;
var swappingTile = null;
var isSwapping = false;
var isProcessing = false;
var score = 0;
var timeLeft = 60; // seconds
var timerInterval = null;
var scoreTxt = null;
var timerTxt = null;
// --- Utility functions ---
function randomColorId() {
return Math.floor(Math.random() * 6);
}
// Check if two tiles are adjacent
function areAdjacent(tileA, tileB) {
var dr = Math.abs(tileA.row - tileB.row);
var dc = Math.abs(tileA.col - tileB.col);
return dr + dc === 1;
}
// Swap two tiles in board and animate
function swapTiles(tileA, tileB, animate, onFinish) {
// Defensive: Only swap if both tiles are valid
if (!tileA || !tileB) {
if (onFinish) onFinish();
return;
}
// Swap in board array
var temp = board[tileA.row][tileA.col];
board[tileA.row][tileA.col] = board[tileB.row][tileB.col];
board[tileB.row][tileB.col] = temp;
// Swap row/col
var tempRow = tileA.row,
tempCol = tileA.col;
if (animate) {
tileA.moveTo(tileB.row, tileB.col, 120);
tileB.moveTo(tempRow, tempCol, 120, onFinish);
} else {
tileA.setPosition(tileB.row, tileB.col);
tileB.setPosition(tempRow, tempCol);
if (onFinish) onFinish();
}
var trow = tileA.row,
tcol = tileA.col;
tileA.row = tileB.row;
tileA.col = tileB.col;
tileB.row = tempRow;
tileB.col = tempCol;
}
// Find all matches on the board. Returns array of arrays of matched tiles.
function findAllMatches() {
var matches = [];
// Horizontal
for (var r = 0; r < boardRows; r++) {
var run = [board[r][0]];
for (var c = 1; c < boardCols; c++) {
if (board[r][c].colorId === board[r][c - 1].colorId) {
run.push(board[r][c]);
} else {
if (run.length >= 3) matches.push(run.slice());
run = [board[r][c]];
}
}
if (run.length >= 3) matches.push(run.slice());
}
// Vertical
for (var c = 0; c < boardCols; c++) {
var run = [board[0][c]];
for (var r = 1; r < boardRows; r++) {
if (board[r][c].colorId === board[r - 1][c].colorId) {
run.push(board[r][c]);
} else {
if (run.length >= 3) matches.push(run.slice());
run = [board[r][c]];
}
}
if (run.length >= 3) matches.push(run.slice());
}
return matches;
}
// Remove matched tiles, return array of removed tiles
function removeMatches(matches, onFinish) {
var removed = [];
var toRemove = {};
for (var i = 0; i < matches.length; i++) {
for (var j = 0; j < matches[i].length; j++) {
var t = matches[i][j];
var key = t.row + ',' + t.col;
if (!toRemove[key]) {
toRemove[key] = t;
removed.push(t);
}
}
}
var count = removed.length;
if (count === 0) {
if (onFinish) onFinish();
return;
}
var finished = 0;
for (var i = 0; i < removed.length; i++) {
(function (tile) {
tile.flash(function () {
tile.visible = false;
finished++;
if (finished === count && onFinish) onFinish();
});
})(removed[i]);
}
LK.getSound('pop').play();
score += removed.length * 10;
scoreTxt.setText(score);
}
// Drop tiles to fill empty spaces, return true if any tile moved
function dropTiles(onFinish) {
var moved = false;
var dropCount = 0;
for (var c = 0; c < boardCols; c++) {
for (var r = boardRows - 1; r >= 0; r--) {
if (!board[r][c].visible) {
// Find nearest above visible tile
for (var rr = r - 1; rr >= 0; rr--) {
if (board[rr][c].visible) {
// Move tile down
var tile = board[rr][c];
board[r][c] = tile;
tile.moveTo(r, c, 120, function () {});
board[rr][c] = new Tile();
board[rr][c].setColor(randomColorId());
board[rr][c].setPosition(rr, c);
board[rr][c].visible = false;
game.addChild(board[rr][c]);
moved = true;
dropCount++;
break;
}
}
}
}
}
// Animate new tiles for empty spaces at top
for (var c = 0; c < boardCols; c++) {
for (var r = 0; r < boardRows; r++) {
if (!board[r][c].visible) {
var tile = board[r][c];
tile.setColor(randomColorId());
tile.visible = true;
tile.y = boardOffsetY - tileSize / 2;
tile.moveTo(r, c, 180, function () {});
moved = true;
dropCount++;
}
}
}
// Wait for all drops to finish
if (dropCount === 0) {
if (onFinish) onFinish();
} else {
LK.setTimeout(function () {
if (onFinish) onFinish();
}, 200);
}
}
// Check if any tile is moving
function anyTileMoving() {
for (var i = 0; i < tiles.length; i++) {
if (tiles[i].isMoving) return true;
}
return false;
}
// Deselect all tiles
function deselectTiles() {
if (selectedTile) {
tween.stop(selectedTile.tileAsset, {
scaleX: true,
scaleY: true
});
selectedTile.tileAsset.scaleX = 1;
selectedTile.tileAsset.scaleY = 1;
selectedTile = null;
}
}
// --- Board initialization ---
function fillBoardNoMatches() {
// Fill board with random tiles, but avoid initial matches
for (var r = 0; r < boardRows; r++) {
board[r] = [];
for (var c = 0; c < boardCols; c++) {
var colorId;
do {
colorId = randomColorId();
// Avoid horizontal match
if (c >= 2 && board[r][c - 1].colorId === colorId && board[r][c - 2].colorId === colorId) continue;
// Avoid vertical match
if (r >= 2 && board[r - 1][c].colorId === colorId && board[r - 2][c].colorId === colorId) continue;
break;
} while (true);
var tile = new Tile();
tile.setColor(colorId);
tile.setPosition(r, c);
board[r][c] = tile;
tiles.push(tile);
game.addChild(tile);
}
}
}
// --- GUI ---
scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
timerTxt = new Text2('60', {
size: 100,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(timerTxt);
// --- Timer ---
function startTimer() {
timeLeft = 60;
timerTxt.setText(timeLeft);
if (timerInterval) LK.clearInterval(timerInterval);
timerInterval = LK.setInterval(function () {
timeLeft--;
timerTxt.setText(timeLeft);
if (timeLeft <= 0) {
LK.clearInterval(timerInterval);
LK.showGameOver();
}
}, 1000);
}
// --- Input handling ---
var dragStartTile = null;
var dragStartX = 0,
dragStartY = 0;
var dragMoved = false;
function getTileAtPos(x, y) {
var col = Math.floor((x - boardOffsetX) / tileSize);
var row = Math.floor((y - boardOffsetY) / tileSize);
if (row >= 0 && row < boardRows && col >= 0 && col < boardCols) {
return board[row][col];
}
return null;
}
game.down = function (x, y, obj) {
if (isProcessing || anyTileMoving()) return;
if (x < boardOffsetX || x > boardOffsetX + boardWidth || y < boardOffsetY || y > boardOffsetY + boardHeight) return;
var tile = getTileAtPos(x, y);
if (!tile) return;
dragStartTile = tile;
dragStartX = x;
dragStartY = y;
dragMoved = false;
deselectTiles();
selectedTile = tile;
tween(selectedTile.tileAsset, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 80,
easing: tween.linear
});
};
game.move = function (x, y, obj) {
if (!dragStartTile || isProcessing || anyTileMoving()) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (!dragMoved && (Math.abs(dx) > tileSize / 3 || Math.abs(dy) > tileSize / 3)) {
// Determine direction
var dir = null;
if (Math.abs(dx) > Math.abs(dy)) {
dir = dx > 0 ? 'right' : 'left';
} else {
dir = dy > 0 ? 'down' : 'up';
}
var row = dragStartTile.row,
col = dragStartTile.col;
var targetRow = row,
targetCol = col;
if (dir === 'right') targetCol++;
if (dir === 'left') targetCol--;
if (dir === 'down') targetRow++;
if (dir === 'up') targetRow--;
if (targetRow >= 0 && targetRow < boardRows && targetCol >= 0 && targetCol < boardCols) {
var targetTile = board[targetRow][targetCol];
dragMoved = true;
isProcessing = true;
deselectTiles();
swapTiles(dragStartTile, targetTile, true, function () {
// After swap, check for matches
var matches = findAllMatches();
if (matches.length > 0) {
processMatches(function () {
isProcessing = false;
});
} else {
// No match, swap back
swapTiles(dragStartTile, targetTile, true, function () {
isProcessing = false;
});
}
});
}
}
};
game.up = function (x, y, obj) {
dragStartTile = null;
dragMoved = false;
deselectTiles();
};
// --- Match processing loop ---
function processMatches(onFinish) {
var matches = findAllMatches();
if (matches.length === 0) {
if (onFinish) onFinish();
return;
}
removeMatches(matches, function () {
dropTiles(function () {
LK.setTimeout(function () {
processMatches(onFinish);
}, 80);
});
});
}
// --- Game update ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Start game ---
function startGame() {
// Reset state
for (var i = 0; i < tiles.length; i++) {
tiles[i].destroy();
}
tiles = [];
board = [];
score = 0;
scoreTxt.setText(score);
deselectTiles();
fillBoardNoMatches();
startTimer();
}
startGame(); ===================================================================
--- original.js
+++ change.js
@@ -112,8 +112,13 @@
return dr + dc === 1;
}
// Swap two tiles in board and animate
function swapTiles(tileA, tileB, animate, onFinish) {
+ // Defensive: Only swap if both tiles are valid
+ if (!tileA || !tileB) {
+ if (onFinish) onFinish();
+ return;
+ }
// Swap in board array
var temp = board[tileA.row][tileA.col];
board[tileA.row][tileA.col] = board[tileB.row][tileB.col];
board[tileB.row][tileB.col] = temp;