/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Car = Container.expand(function (carType) {
var self = Container.call(this);
self.carType = carType || 'redCar';
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isMatched = false;
var carGraphics = self.attachAsset(self.carType, {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function (x, y, obj) {
if (self.isAnimating || gameState !== 'playing' || gameCompleted) return;
selectedCar = self;
startDragX = x;
startDragY = y;
clearSuggestion();
startSuggestionTimer();
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridOffsetX + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = gridOffsetY + gridY * CELL_SIZE + CELL_SIZE / 2;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
self.destroy = function () {
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game constants
var GRID_SIZE = 8;
var CELL_SIZE = 200;
var gridOffsetX = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var gridOffsetY = (2732 - GRID_SIZE * CELL_SIZE) / 2;
// Car types
var carTypes = ['redCar', 'blueCar', 'greenCar', 'yellowCar', 'purpleCar', 'orangeCar'];
// Game state
var grid = [];
var cars = [];
var selectedCar = null;
var startDragX = 0;
var startDragY = 0;
var gameState = 'playing'; // 'playing', 'animating', 'checking'
var score = 0;
var level = 1;
var targetScore = 1000;
var maxLevel = 10;
var gameCompleted = false;
var suggestedCars = [];
var suggestionTimer = null;
var musicPlayed = false;
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 100;
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelText);
levelText.x = 150;
levelText.y = 50;
var targetText = new Text2('Target: 1000', {
size: 60,
fill: 0xFFFFFF
});
targetText.anchor.set(1, 0);
LK.gui.topRight.addChild(targetText);
targetText.y = 50;
// Create grid background
var gridBg = game.addChild(LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
gridBg.x = 2048 / 2;
gridBg.y = 2732 / 2;
// Create cell backgrounds
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
var cellBg = game.addChild(LK.getAsset('cellBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
cellBg.x = gridOffsetX + i * CELL_SIZE + CELL_SIZE / 2;
cellBg.y = gridOffsetY + j * CELL_SIZE + CELL_SIZE / 2;
}
}
// Initialize grid
function initializeGrid() {
grid = [];
cars = [];
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var carType = getRandomCarType();
var car = new Car(carType);
car.setGridPosition(x, y);
grid[y][x] = car;
cars.push(car);
game.addChild(car);
}
}
// Remove initial matches
while (findMatches().length > 0) {
regenerateGrid();
}
}
function getRandomCarType() {
return carTypes[Math.floor(Math.random() * carTypes.length)];
}
function regenerateGrid() {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[y][x]) {
grid[y][x].carType = getRandomCarType();
grid[y][x].removeChild(grid[y][x].children[0]);
var newGraphics = grid[y][x].attachAsset(grid[y][x].carType, {
anchorX: 0.5,
anchorY: 0.5
});
}
}
}
}
function isValidPosition(x, y) {
return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE;
}
function swapCars(car1, car2) {
if (!car1 || !car2) return false;
var tempX = car1.gridX;
var tempY = car1.gridY;
var originalCar1X = car1.gridX;
var originalCar1Y = car1.gridY;
var originalCar2X = car2.gridX;
var originalCar2Y = car2.gridY;
// Temporarily update grid to test for matches
grid[car1.gridY][car1.gridX] = car2;
grid[car2.gridY][car2.gridX] = car1;
// Temporarily update car positions for match checking
car2.gridX = tempX;
car2.gridY = tempY;
car1.gridX = originalCar2X;
car1.gridY = originalCar2Y;
// Check if this swap creates any matches
var matches = findMatches();
var hasMatches = matches.length > 0;
if (!hasMatches) {
// Revert the temporary changes - no matches found, invalid move
grid[originalCar1Y][originalCar1X] = car1;
grid[originalCar2Y][originalCar2X] = car2;
car1.gridX = originalCar1X;
car1.gridY = originalCar1Y;
car2.gridX = originalCar2X;
car2.gridY = originalCar2Y;
return false;
}
// Animate to new positions
gameState = 'animating';
var animationsComplete = 0;
car1.animateToPosition(gridOffsetX + car1.gridX * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + car1.gridY * CELL_SIZE + CELL_SIZE / 2, 300, function () {
animationsComplete++;
if (animationsComplete === 2) {
checkForMatches();
}
});
car2.animateToPosition(gridOffsetX + car2.gridX * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + car2.gridY * CELL_SIZE + CELL_SIZE / 2, 300, function () {
animationsComplete++;
if (animationsComplete === 2) {
checkForMatches();
}
});
LK.playMusic('song', {
loop: false
});
return true;
}
function findMatches() {
var matches = [];
var matchGroups = []; // Track individual match groups for bonus scoring
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
var count = 1;
var currentType = grid[y][0] ? grid[y][0].carType : null;
for (var x = 1; x < GRID_SIZE; x++) {
if (grid[y][x] && currentType && grid[y][x].carType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = x - count; i < x; i++) {
var matchPos = {
x: i,
y: y
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
currentType = grid[y][x] ? grid[y][x].carType : null;
count = 1;
}
}
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
var matchPos = {
x: i,
y: y
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
var count = 1;
var currentType = grid[0][x] ? grid[0][x].carType : null;
for (var y = 1; y < GRID_SIZE; y++) {
if (grid[y][x] && currentType && grid[y][x].carType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = y - count; i < y; i++) {
var matchPos = {
x: x,
y: i
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
currentType = grid[y][x] ? grid[y][x].carType : null;
count = 1;
}
}
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
var matchPos = {
x: x,
y: i
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
}
// Store match groups for bonus scoring
matches.matchGroups = matchGroups;
return matches;
}
function removeMatches(matches) {
var uniqueMatches = [];
// Remove duplicates
for (var i = 0; i < matches.length; i++) {
var isUnique = true;
for (var j = 0; j < uniqueMatches.length; j++) {
if (matches[i].x === uniqueMatches[j].x && matches[i].y === uniqueMatches[j].y) {
isUnique = false;
break;
}
}
if (isUnique) {
uniqueMatches.push(matches[i]);
}
}
// Animate matched cars before removing them
var animationsCompleted = 0;
var totalAnimations = uniqueMatches.length;
for (var i = 0; i < uniqueMatches.length; i++) {
var match = uniqueMatches[i];
if (grid[match.y][match.x]) {
var car = grid[match.y][match.x];
car.isMatched = true;
// Scale up and fade out animation
tween(car, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
animationsCompleted++;
if (animationsCompleted === totalAnimations) {
// All animations complete, now actually remove the cars
for (var k = 0; k < uniqueMatches.length; k++) {
var removeMatch = uniqueMatches[k];
if (grid[removeMatch.y][removeMatch.x]) {
var carToRemove = grid[removeMatch.y][removeMatch.x];
carToRemove.destroy();
grid[removeMatch.y][removeMatch.x] = null;
// Remove from cars array
for (var j = cars.length - 1; j >= 0; j--) {
if (cars[j] === carToRemove) {
cars.splice(j, 1);
break;
}
}
}
}
// Continue with dropping and filling after all cars are removed
LK.setTimeout(function () {
if (dropCars()) {
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
checkForMatches();
}, 400);
}, 300);
} else {
fillEmptySpaces();
LK.setTimeout(function () {
checkForMatches();
}, 400);
}
}, 100);
}
}
});
}
}
// Calculate score with bonus points for 4+ and 5+ combinations
var totalScore = 0;
var basePoints = 10;
if (matches.matchGroups) {
for (var i = 0; i < matches.matchGroups.length; i++) {
var matchGroup = matches.matchGroups[i];
var groupScore = matchGroup.count * basePoints;
// Apply bonus multipliers
if (matchGroup.count >= 5) {
groupScore *= 3; // Triple points for 5+ matches
} else if (matchGroup.count >= 4) {
groupScore *= 2; // Double points for 4+ matches
}
totalScore += groupScore;
}
} else {
// Fallback to old scoring if no match groups
totalScore = uniqueMatches.length * basePoints;
}
score += totalScore;
scoreText.setText('Score: ' + score);
if (uniqueMatches.length > 0) {
LK.getSound('match').play();
}
return uniqueMatches.length;
}
function dropCars() {
var moved = false;
for (var x = 0; x < GRID_SIZE; x++) {
// Find empty spaces from bottom up
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (!grid[y][x]) {
// Find the next car above this empty space
for (var aboveY = y - 1; aboveY >= 0; aboveY--) {
if (grid[aboveY][x]) {
var car = grid[aboveY][x];
grid[y][x] = car;
grid[aboveY][x] = null;
car.gridY = y;
car.animateToPosition(gridOffsetX + x * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + y * CELL_SIZE + CELL_SIZE / 2, 200);
moved = true;
break;
}
}
}
}
}
return moved;
}
function fillEmptySpaces() {
var added = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (!grid[y][x]) {
var carType = getRandomCarType();
var car = new Car(carType);
car.setGridPosition(x, y);
car.y = gridOffsetY - CELL_SIZE;
grid[y][x] = car;
cars.push(car);
game.addChild(car);
car.animateToPosition(gridOffsetX + x * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + y * CELL_SIZE + CELL_SIZE / 2, 300);
added = true;
}
}
}
return added;
}
function checkForMatches() {
var matches = findMatches();
if (matches.length > 0) {
clearSuggestion();
removeMatches(matches);
} else {
gameState = 'playing';
checkLevelComplete();
startSuggestionTimer();
}
}
function checkLevelComplete() {
if (score >= targetScore) {
if (level >= maxLevel || score >= 10000) {
// Game completed - show finish screen
gameCompleted = true;
gameState = 'completed';
LK.effects.flashScreen(0x00ff00, 2000);
LK.setTimeout(function () {
LK.showYouWin();
}, 2000);
return;
}
level++;
targetScore = level * 1000;
levelText.setText('Level: ' + level);
targetText.setText('Target: ' + targetScore);
// Flash screen green for level complete
LK.effects.flashScreen(0x00ff00, 1000);
}
}
function findPossibleMoves() {
var possibleMoves = [];
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (!grid[y][x]) continue;
// Check right neighbor
if (x < GRID_SIZE - 1 && grid[y][x + 1]) {
if (wouldCreateMatch(x, y, x + 1, y)) {
possibleMoves.push({
car1: {
x: x,
y: y
},
car2: {
x: x + 1,
y: y
}
});
}
}
// Check down neighbor
if (y < GRID_SIZE - 1 && grid[y + 1][x]) {
if (wouldCreateMatch(x, y, x, y + 1)) {
possibleMoves.push({
car1: {
x: x,
y: y
},
car2: {
x: x,
y: y + 1
}
});
}
}
}
}
return possibleMoves;
}
function wouldCreateMatch(x1, y1, x2, y2) {
if (!grid[y1][x1] || !grid[y2][x2]) return false;
// Temporarily swap the cars
var car1 = grid[y1][x1];
var car2 = grid[y2][x2];
grid[y1][x1] = car2;
grid[y2][x2] = car1;
car2.gridX = x1;
car2.gridY = y1;
car1.gridX = x2;
car1.gridY = y2;
// Check for matches
var matches = findMatches();
var hasMatches = matches.length > 0;
// Restore original positions
grid[y1][x1] = car1;
grid[y2][x2] = car2;
car1.gridX = x1;
car1.gridY = y1;
car2.gridX = x2;
car2.gridY = y2;
return hasMatches;
}
function showSuggestion() {
clearSuggestion();
if (gameState !== 'playing') return;
var possibleMoves = findPossibleMoves();
if (possibleMoves.length > 0) {
// Show a random possible move
var randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
var car1 = grid[randomMove.car1.y][randomMove.car1.x];
var car2 = grid[randomMove.car2.y][randomMove.car2.x];
if (car1 && car2) {
suggestedCars = [car1, car2];
// Add pulsing glow effect to suggested cars
tween(car1, {
alpha: 0.6
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (suggestedCars.indexOf(car1) !== -1) {
tween(car1, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (suggestedCars.indexOf(car1) !== -1) {
showSuggestion(); // Continue pulsing
}
}
});
}
}
});
tween(car2, {
alpha: 0.6
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (suggestedCars.indexOf(car2) !== -1) {
tween(car2, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}
}
});
}
}
}
function clearSuggestion() {
for (var i = 0; i < suggestedCars.length; i++) {
var car = suggestedCars[i];
if (car && !car.isMatched) {
tween.stop(car, {
alpha: true
});
car.alpha = 1;
}
}
suggestedCars = [];
}
function startSuggestionTimer() {
if (suggestionTimer) {
LK.clearTimeout(suggestionTimer);
}
if (!gameCompleted && gameState === 'playing') {
suggestionTimer = LK.setTimeout(function () {
showSuggestion();
}, 5000); // Show suggestion after 5 seconds of inactivity
}
}
// Game input handling
game.move = function (x, y, obj) {
if (!selectedCar || gameState !== 'playing' || gameCompleted) return;
var deltaX = x - startDragX;
var deltaY = y - startDragY;
var threshold = 30;
if (Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold) {
var targetX = selectedCar.gridX;
var targetY = selectedCar.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
targetX += deltaX > 0 ? 1 : -1;
} else {
targetY += deltaY > 0 ? 1 : -1;
}
if (isValidPosition(targetX, targetY)) {
var targetCar = grid[targetY][targetX];
if (targetCar) {
clearSuggestion();
if (swapCars(selectedCar, targetCar)) {
startSuggestionTimer();
} else {
startSuggestionTimer();
}
}
}
selectedCar = null;
}
};
game.up = function (x, y, obj) {
selectedCar = null;
if (gameState === 'playing') {
startSuggestionTimer();
}
};
// Initialize the game
initializeGrid();
startSuggestionTimer();
// Music plays on each successful swap - no initialization needed here /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Car = Container.expand(function (carType) {
var self = Container.call(this);
self.carType = carType || 'redCar';
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isMatched = false;
var carGraphics = self.attachAsset(self.carType, {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function (x, y, obj) {
if (self.isAnimating || gameState !== 'playing' || gameCompleted) return;
selectedCar = self;
startDragX = x;
startDragY = y;
clearSuggestion();
startSuggestionTimer();
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridOffsetX + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = gridOffsetY + gridY * CELL_SIZE + CELL_SIZE / 2;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
self.destroy = function () {
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game constants
var GRID_SIZE = 8;
var CELL_SIZE = 200;
var gridOffsetX = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var gridOffsetY = (2732 - GRID_SIZE * CELL_SIZE) / 2;
// Car types
var carTypes = ['redCar', 'blueCar', 'greenCar', 'yellowCar', 'purpleCar', 'orangeCar'];
// Game state
var grid = [];
var cars = [];
var selectedCar = null;
var startDragX = 0;
var startDragY = 0;
var gameState = 'playing'; // 'playing', 'animating', 'checking'
var score = 0;
var level = 1;
var targetScore = 1000;
var maxLevel = 10;
var gameCompleted = false;
var suggestedCars = [];
var suggestionTimer = null;
var musicPlayed = false;
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 100;
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelText);
levelText.x = 150;
levelText.y = 50;
var targetText = new Text2('Target: 1000', {
size: 60,
fill: 0xFFFFFF
});
targetText.anchor.set(1, 0);
LK.gui.topRight.addChild(targetText);
targetText.y = 50;
// Create grid background
var gridBg = game.addChild(LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
gridBg.x = 2048 / 2;
gridBg.y = 2732 / 2;
// Create cell backgrounds
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
var cellBg = game.addChild(LK.getAsset('cellBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
cellBg.x = gridOffsetX + i * CELL_SIZE + CELL_SIZE / 2;
cellBg.y = gridOffsetY + j * CELL_SIZE + CELL_SIZE / 2;
}
}
// Initialize grid
function initializeGrid() {
grid = [];
cars = [];
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var carType = getRandomCarType();
var car = new Car(carType);
car.setGridPosition(x, y);
grid[y][x] = car;
cars.push(car);
game.addChild(car);
}
}
// Remove initial matches
while (findMatches().length > 0) {
regenerateGrid();
}
}
function getRandomCarType() {
return carTypes[Math.floor(Math.random() * carTypes.length)];
}
function regenerateGrid() {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[y][x]) {
grid[y][x].carType = getRandomCarType();
grid[y][x].removeChild(grid[y][x].children[0]);
var newGraphics = grid[y][x].attachAsset(grid[y][x].carType, {
anchorX: 0.5,
anchorY: 0.5
});
}
}
}
}
function isValidPosition(x, y) {
return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE;
}
function swapCars(car1, car2) {
if (!car1 || !car2) return false;
var tempX = car1.gridX;
var tempY = car1.gridY;
var originalCar1X = car1.gridX;
var originalCar1Y = car1.gridY;
var originalCar2X = car2.gridX;
var originalCar2Y = car2.gridY;
// Temporarily update grid to test for matches
grid[car1.gridY][car1.gridX] = car2;
grid[car2.gridY][car2.gridX] = car1;
// Temporarily update car positions for match checking
car2.gridX = tempX;
car2.gridY = tempY;
car1.gridX = originalCar2X;
car1.gridY = originalCar2Y;
// Check if this swap creates any matches
var matches = findMatches();
var hasMatches = matches.length > 0;
if (!hasMatches) {
// Revert the temporary changes - no matches found, invalid move
grid[originalCar1Y][originalCar1X] = car1;
grid[originalCar2Y][originalCar2X] = car2;
car1.gridX = originalCar1X;
car1.gridY = originalCar1Y;
car2.gridX = originalCar2X;
car2.gridY = originalCar2Y;
return false;
}
// Animate to new positions
gameState = 'animating';
var animationsComplete = 0;
car1.animateToPosition(gridOffsetX + car1.gridX * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + car1.gridY * CELL_SIZE + CELL_SIZE / 2, 300, function () {
animationsComplete++;
if (animationsComplete === 2) {
checkForMatches();
}
});
car2.animateToPosition(gridOffsetX + car2.gridX * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + car2.gridY * CELL_SIZE + CELL_SIZE / 2, 300, function () {
animationsComplete++;
if (animationsComplete === 2) {
checkForMatches();
}
});
LK.playMusic('song', {
loop: false
});
return true;
}
function findMatches() {
var matches = [];
var matchGroups = []; // Track individual match groups for bonus scoring
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
var count = 1;
var currentType = grid[y][0] ? grid[y][0].carType : null;
for (var x = 1; x < GRID_SIZE; x++) {
if (grid[y][x] && currentType && grid[y][x].carType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = x - count; i < x; i++) {
var matchPos = {
x: i,
y: y
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
currentType = grid[y][x] ? grid[y][x].carType : null;
count = 1;
}
}
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
var matchPos = {
x: i,
y: y
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
var count = 1;
var currentType = grid[0][x] ? grid[0][x].carType : null;
for (var y = 1; y < GRID_SIZE; y++) {
if (grid[y][x] && currentType && grid[y][x].carType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = y - count; i < y; i++) {
var matchPos = {
x: x,
y: i
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
currentType = grid[y][x] ? grid[y][x].carType : null;
count = 1;
}
}
if (count >= 3 && currentType) {
var matchGroup = {
count: count,
positions: []
};
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
var matchPos = {
x: x,
y: i
};
matches.push(matchPos);
matchGroup.positions.push(matchPos);
}
matchGroups.push(matchGroup);
}
}
// Store match groups for bonus scoring
matches.matchGroups = matchGroups;
return matches;
}
function removeMatches(matches) {
var uniqueMatches = [];
// Remove duplicates
for (var i = 0; i < matches.length; i++) {
var isUnique = true;
for (var j = 0; j < uniqueMatches.length; j++) {
if (matches[i].x === uniqueMatches[j].x && matches[i].y === uniqueMatches[j].y) {
isUnique = false;
break;
}
}
if (isUnique) {
uniqueMatches.push(matches[i]);
}
}
// Animate matched cars before removing them
var animationsCompleted = 0;
var totalAnimations = uniqueMatches.length;
for (var i = 0; i < uniqueMatches.length; i++) {
var match = uniqueMatches[i];
if (grid[match.y][match.x]) {
var car = grid[match.y][match.x];
car.isMatched = true;
// Scale up and fade out animation
tween(car, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
animationsCompleted++;
if (animationsCompleted === totalAnimations) {
// All animations complete, now actually remove the cars
for (var k = 0; k < uniqueMatches.length; k++) {
var removeMatch = uniqueMatches[k];
if (grid[removeMatch.y][removeMatch.x]) {
var carToRemove = grid[removeMatch.y][removeMatch.x];
carToRemove.destroy();
grid[removeMatch.y][removeMatch.x] = null;
// Remove from cars array
for (var j = cars.length - 1; j >= 0; j--) {
if (cars[j] === carToRemove) {
cars.splice(j, 1);
break;
}
}
}
}
// Continue with dropping and filling after all cars are removed
LK.setTimeout(function () {
if (dropCars()) {
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
checkForMatches();
}, 400);
}, 300);
} else {
fillEmptySpaces();
LK.setTimeout(function () {
checkForMatches();
}, 400);
}
}, 100);
}
}
});
}
}
// Calculate score with bonus points for 4+ and 5+ combinations
var totalScore = 0;
var basePoints = 10;
if (matches.matchGroups) {
for (var i = 0; i < matches.matchGroups.length; i++) {
var matchGroup = matches.matchGroups[i];
var groupScore = matchGroup.count * basePoints;
// Apply bonus multipliers
if (matchGroup.count >= 5) {
groupScore *= 3; // Triple points for 5+ matches
} else if (matchGroup.count >= 4) {
groupScore *= 2; // Double points for 4+ matches
}
totalScore += groupScore;
}
} else {
// Fallback to old scoring if no match groups
totalScore = uniqueMatches.length * basePoints;
}
score += totalScore;
scoreText.setText('Score: ' + score);
if (uniqueMatches.length > 0) {
LK.getSound('match').play();
}
return uniqueMatches.length;
}
function dropCars() {
var moved = false;
for (var x = 0; x < GRID_SIZE; x++) {
// Find empty spaces from bottom up
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (!grid[y][x]) {
// Find the next car above this empty space
for (var aboveY = y - 1; aboveY >= 0; aboveY--) {
if (grid[aboveY][x]) {
var car = grid[aboveY][x];
grid[y][x] = car;
grid[aboveY][x] = null;
car.gridY = y;
car.animateToPosition(gridOffsetX + x * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + y * CELL_SIZE + CELL_SIZE / 2, 200);
moved = true;
break;
}
}
}
}
}
return moved;
}
function fillEmptySpaces() {
var added = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (!grid[y][x]) {
var carType = getRandomCarType();
var car = new Car(carType);
car.setGridPosition(x, y);
car.y = gridOffsetY - CELL_SIZE;
grid[y][x] = car;
cars.push(car);
game.addChild(car);
car.animateToPosition(gridOffsetX + x * CELL_SIZE + CELL_SIZE / 2, gridOffsetY + y * CELL_SIZE + CELL_SIZE / 2, 300);
added = true;
}
}
}
return added;
}
function checkForMatches() {
var matches = findMatches();
if (matches.length > 0) {
clearSuggestion();
removeMatches(matches);
} else {
gameState = 'playing';
checkLevelComplete();
startSuggestionTimer();
}
}
function checkLevelComplete() {
if (score >= targetScore) {
if (level >= maxLevel || score >= 10000) {
// Game completed - show finish screen
gameCompleted = true;
gameState = 'completed';
LK.effects.flashScreen(0x00ff00, 2000);
LK.setTimeout(function () {
LK.showYouWin();
}, 2000);
return;
}
level++;
targetScore = level * 1000;
levelText.setText('Level: ' + level);
targetText.setText('Target: ' + targetScore);
// Flash screen green for level complete
LK.effects.flashScreen(0x00ff00, 1000);
}
}
function findPossibleMoves() {
var possibleMoves = [];
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (!grid[y][x]) continue;
// Check right neighbor
if (x < GRID_SIZE - 1 && grid[y][x + 1]) {
if (wouldCreateMatch(x, y, x + 1, y)) {
possibleMoves.push({
car1: {
x: x,
y: y
},
car2: {
x: x + 1,
y: y
}
});
}
}
// Check down neighbor
if (y < GRID_SIZE - 1 && grid[y + 1][x]) {
if (wouldCreateMatch(x, y, x, y + 1)) {
possibleMoves.push({
car1: {
x: x,
y: y
},
car2: {
x: x,
y: y + 1
}
});
}
}
}
}
return possibleMoves;
}
function wouldCreateMatch(x1, y1, x2, y2) {
if (!grid[y1][x1] || !grid[y2][x2]) return false;
// Temporarily swap the cars
var car1 = grid[y1][x1];
var car2 = grid[y2][x2];
grid[y1][x1] = car2;
grid[y2][x2] = car1;
car2.gridX = x1;
car2.gridY = y1;
car1.gridX = x2;
car1.gridY = y2;
// Check for matches
var matches = findMatches();
var hasMatches = matches.length > 0;
// Restore original positions
grid[y1][x1] = car1;
grid[y2][x2] = car2;
car1.gridX = x1;
car1.gridY = y1;
car2.gridX = x2;
car2.gridY = y2;
return hasMatches;
}
function showSuggestion() {
clearSuggestion();
if (gameState !== 'playing') return;
var possibleMoves = findPossibleMoves();
if (possibleMoves.length > 0) {
// Show a random possible move
var randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
var car1 = grid[randomMove.car1.y][randomMove.car1.x];
var car2 = grid[randomMove.car2.y][randomMove.car2.x];
if (car1 && car2) {
suggestedCars = [car1, car2];
// Add pulsing glow effect to suggested cars
tween(car1, {
alpha: 0.6
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (suggestedCars.indexOf(car1) !== -1) {
tween(car1, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (suggestedCars.indexOf(car1) !== -1) {
showSuggestion(); // Continue pulsing
}
}
});
}
}
});
tween(car2, {
alpha: 0.6
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (suggestedCars.indexOf(car2) !== -1) {
tween(car2, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}
}
});
}
}
}
function clearSuggestion() {
for (var i = 0; i < suggestedCars.length; i++) {
var car = suggestedCars[i];
if (car && !car.isMatched) {
tween.stop(car, {
alpha: true
});
car.alpha = 1;
}
}
suggestedCars = [];
}
function startSuggestionTimer() {
if (suggestionTimer) {
LK.clearTimeout(suggestionTimer);
}
if (!gameCompleted && gameState === 'playing') {
suggestionTimer = LK.setTimeout(function () {
showSuggestion();
}, 5000); // Show suggestion after 5 seconds of inactivity
}
}
// Game input handling
game.move = function (x, y, obj) {
if (!selectedCar || gameState !== 'playing' || gameCompleted) return;
var deltaX = x - startDragX;
var deltaY = y - startDragY;
var threshold = 30;
if (Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold) {
var targetX = selectedCar.gridX;
var targetY = selectedCar.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
targetX += deltaX > 0 ? 1 : -1;
} else {
targetY += deltaY > 0 ? 1 : -1;
}
if (isValidPosition(targetX, targetY)) {
var targetCar = grid[targetY][targetX];
if (targetCar) {
clearSuggestion();
if (swapCars(selectedCar, targetCar)) {
startSuggestionTimer();
} else {
startSuggestionTimer();
}
}
}
selectedCar = null;
}
};
game.up = function (x, y, obj) {
selectedCar = null;
if (gameState === 'playing') {
startSuggestionTimer();
}
};
// Initialize the game
initializeGrid();
startSuggestionTimer();
// Music plays on each successful swap - no initialization needed here