/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { coins: 100, highestCar: 1, availableSlots: 4 }); /**** * Classes ****/ var BuyButton = Container.expand(function (startLevel) { var self = Container.call(this); self.carLevel = startLevel || 2; self.price = 10; var buttonGraphics = self.attachAsset('buy_button', { anchorX: 0.5, anchorY: 0.5 }); // Add coin icon var coinIcon = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: -70, y: 0, scaleX: 0.8, scaleY: 0.8 }); // Add price text var formattedPrice; // Will use formatCoin after game is initialized if (typeof game !== 'undefined' && game.formatCoin) { formattedPrice = game.formatCoin(self.price); } else { if (self.price >= 1000000000) { formattedPrice = Math.floor(self.price / 1000000000) + "B"; } else if (self.price >= 1000000) { formattedPrice = (self.price / 1000000).toFixed(1) + "m"; } else if (self.price >= 1000) { formattedPrice = (self.price / 1000).toFixed(1) + "k"; } else { formattedPrice = self.price.toString(); } } self.priceText = new Text2(formattedPrice, { size: 35, fill: 0xFFFFFF }); self.priceText.anchor.set(0, 0.5); self.priceText.x = -40; self.addChild(self.priceText); // Add "Buy Car" text var buyText = new Text2("Buy Car", { size: 25, fill: 0xFFFFFF }); buyText.anchor.set(0.5, 0.5); buyText.y = -40; self.addChild(buyText); // Add car level text self.levelText = new Text2("Level " + self.carLevel, { size: 25, fill: 0xFFFFFF }); self.levelText.anchor.set(0.5, 0.5); self.levelText.y = -10; self.addChild(self.levelText); self.down = function (x, y, obj) { tween(self, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; self.up = function (x, y, obj) { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); if (game.coins >= self.price) { game.coins -= self.price; storage.coins = game.coins; game.updateCoinsDisplay(); // Add car with level 1 instead of current car level game.addNewCar(1); LK.getSound('buy').play(); // Increase car level and increase price by 1.2 times for next car self.price = Math.round(self.price * 1.2); if (self.price >= 80) { self.carLevel *= 2; self.levelText.setText("Level " + self.carLevel); } var formattedPrice = game.formatCoin(self.price); self.priceText.setText(formattedPrice); } else { // Visual feedback for not enough coins tween(self, { rotation: 0.1 }, { duration: 100, onFinish: function onFinish() { tween(self, { rotation: -0.1 }, { duration: 100, onFinish: function onFinish() { tween(self, { rotation: 0 }, { duration: 100 }); } }); } }); } }; return self; }); var Car = Container.expand(function (carLevel) { var self = Container.call(this); self.carLevel = carLevel || 1; self.lastIncomeTime = Date.now(); var colorMap = { 1: 0x336699, // Blue 2: 0x66CC99, // Teal 3: 0xCC6633, // Orange 4: 0x9966CC, // Purple 5: 0xFFCC33, // Yellow 6: 0xFF3366, // Pink 7: 0x00AAFF, // Light Blue 8: 0xFF00AA, // Magenta 9: 0xAAFF00, // Lime Green 10: 0x55CCDD, // Aqua 11: 0xDD55CC, // Orchid 12: 0xCC8844, // Bronze 13: 0x44CC88, // Emerald 14: 0x8844CC, // Amethyst 15: 0xCC4488, // Ruby 16: 0x88CC44, // Peridot 17: 0x885522, // Brown 18: 0x228855, // Sea Green 19: 0x552288, // Indigo 20: 0xAA3344, // Crimson 21: 0x33AA44, // Forest Green 22: 0x4433AA, // Royal Blue 23: 0xDDAA33, // Gold 24: 0x33DDAA, // Turquoise 25: 0xAA33DD, // Violet 26: 0x77BBCC, // Steel Blue 27: 0xCC77BB, // Orchid Pink 28: 0xBBCC77, // Olive Green 29: 0x66AADD, // Sky Blue 30: 0xDD66AA, // Hot Pink 31: 0xAADD66, // Lime 32: 0x224466, // Navy Blue 33: 0x662244, // Burgundy 34: 0x442266, // Deep Purple 35: 0x226644, // Forest Green 36: 0xFF9900, // Orange 37: 0x99FF00, // Yellow-Green 38: 0x0099FF, // Sky Blue 39: 0xF0F0F0, // Silver 40: 0x0F0F0F, // Charcoal 41: 0x9900FF, // Violet 42: 0xFF0099, // Hot Pink 43: 0x00FF99, // Spring Green 44: 0xFFCC66, // Light Orange 45: 0x66FFCC, // Mint 46: 0xCC66FF, // Lavender 47: 0x775533, // Brown 48: 0x337755, // Teal 49: 0x553377, // Dark Violet 50: 0x990066, // Magenta 51: 0x009966, // Jade 52: 0x669900, // Olive 53: 0x660099, // Purple 54: 0x996600, // Amber 55: 0x006699, // Steel Blue 56: 0xAA5500, // Copper 57: 0x00AA55, // Emerald 58: 0x5500AA, // Royal Purple 59: 0xCC3300, // Brick Red 60: 0x00CC33, // Green 61: 0x3300CC, // Ultramarine 62: 0xFFAA77, // Peach 63: 0x77FFAA, // Light Green 64: 0xAA77FF // Periwinkle }; var assetId = 'car' + (self.carLevel <= 64 ? self.carLevel : 1 + self.carLevel % 64); var carGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9, scaleY: 0.9 }); // Text to display car level self.levelText = new Text2("Level " + self.carLevel.toString(), { size: 40, fill: 0xFFFFFF }); self.levelText.anchor.set(0.5, 0.5); self.addChild(self.levelText); self.isDragging = false; self.originalPosition = { x: 0, y: 0 }; self.originalSlot = null; // Make cars draggable self.down = function (x, y, obj) { self.isDragging = true; self.originalPosition.x = self.x; self.originalPosition.y = self.y; // Bring to front (since we can't use swapChildren, we'll handle in the game class) game.bringCarToFront(self); // Scale up to indicate being dragged tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); }; self.up = function (x, y, obj) { if (self.isDragging) { self.isDragging = false; // Scale back to normal tween(self, { scaleX: 1, scaleY: 1 }, { duration: 200 }); // Check for merge in game controller game.checkForMerge(self); } }; self.getValue = function () { // Base value is 5 for level 1, direct multiplication for other levels return 5 * self.carLevel; }; self.generateIncome = function () { // This function is no longer used for timed income generation // Income is now generated simultaneously for all cars in the game.update method // Keeping this function as a stub for compatibility return false; }; self.update = function () { // Income generation is now handled in the game.update method // This method is kept for compatibility }; return self; }); var GarageSlot = Container.expand(function (row, col) { var self = Container.call(this); self.row = row; self.col = col; self.isEmpty = true; self.currentCar = null; var slotGraphics = self.attachAsset('garage_slot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, scaleX: 0.9, scaleY: 0.9 }); self.highlight = function () { tween(slotGraphics, { alpha: 0.6 }, { duration: 200 }); }; self.unhighlight = function () { tween(slotGraphics, { alpha: 0.3 }, { duration: 200 }); }; self.setCar = function (car) { self.currentCar = car; self.isEmpty = false; }; self.removeCar = function () { self.currentCar = null; self.isEmpty = true; }; // Down handler for locked slots self.down = function (x, y, obj) { if (self.locked) { tween(self, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); } }; // Up handler for locked slots self.up = function (x, y, obj) { if (self.locked) { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); // Get the price for this slot var price = game.slotPrices[self.slotNumber] || 100000; // Check if player has enough coins if (game.coins >= price) { // Deduct coins game.coins -= price; storage.coins = game.coins; game.updateCoinsDisplay(); // Unlock the slot self.locked = false; game.availableSlots++; storage.availableSlots = game.availableSlots; // Remove all children (price text, coin icon, etc.) while (self.children.length > 1) { // Keep only the slot graphics self.removeChild(self.children[self.children.length - 1]); } // Change appearance tween(self.children[0], { alpha: 0.3 }, { duration: 200 }); // Play purchase sound LK.getSound('buy').play(); // Flash effect LK.effects.flashObject(self, 0xFFFFFF, 500); } else { // Not enough coins - shake effect tween(self, { rotation: 0.1 }, { duration: 100, onFinish: function onFinish() { tween(self, { rotation: -0.1 }, { duration: 100, onFinish: function onFinish() { tween(self, { rotation: 0 }, { duration: 100 }); } }); } }); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Game variables game.grid = { rows: 5, cols: 8 }; game.slotSize = 250; game.slots = []; game.cars = []; game.draggedCar = null; game.coins = storage.coins || 100; game.highestCar = storage.highestCar || 1; game.availableSlots = storage.availableSlots || 4; // Start background music LK.playMusic('bgmusic'); // Calculate garage position game.garageX = 2048 / 2 - game.grid.cols * game.slotSize / 2 + game.slotSize / 2; game.garageY = 300; // Adjust slot size to fit the wider grid game.slotSize = 220; // Initialize garage grid game.initializeGarage = function () { game.availableSlots = 4; // Start with only 4 slots game.slotPrices = { 5: Math.round(100 / 1.5), // 5th slot costs 67 coins (1.5x cheaper) 6: Math.round(200 / 1.5), // 6th slot costs 133 coins (1.5x cheaper) 7: Math.round(400 / 1.5), // 7th slot costs 267 coins (1.5x cheaper) 8: Math.round(800 / 1.5), // 8th slot costs 533 coins (1.5x cheaper) 9: Math.round(1600 / 1.5), // 9th slot costs 1067 coins (1.5x cheaper) 10: Math.round(3200 / 1.5), // 10th slot costs 2133 coins (1.5x cheaper) 11: Math.round(6400 / 1.5), // 11th slot costs 4267 coins (1.5x cheaper) 12: Math.round(12800 / 1.5), // 12th slot costs 8533 coins (1.5x cheaper) 13: Math.round(25600 / 1.5), // 13th slot costs 17067 coins (1.5x cheaper) 14: Math.round(51200 / 1.5), // 14th slot costs 34133 coins (1.5x cheaper) 15: Math.round(102400 / 1.5), // 15th slot costs 68267 coins (1.5x cheaper) 16: Math.round(204800 / 1.5), // 16th slot costs 136533 coins (1.5x cheaper) 17: Math.round(409600 / 1.5), // 17th slot costs 273067 coins (1.5x cheaper) 18: Math.round(819200 / 1.5), // 18th slot costs 546133 coins (1.5x cheaper) 19: Math.round(1638400 / 1.5), // 19th slot costs 1092267 coins (1.5x cheaper) 20: Math.round(3276800 / 1.5), // 20th slot costs 2184533 coins (1.5x cheaper) 21: Math.round(6600000 / 1.5), // 21st slot costs 4.4 million (1.5x cheaper) 22: Math.round(12200000 / 1.5), // 22nd slot costs 8.13 million (1.5x cheaper) 23: 24400000, // 23rd slot costs 24.4 million 24: 48800000, // 24th slot costs 48.8 million 25: 96600000, // 25th slot costs 96.6 million 26: 193200000, // 26th slot costs 193.2 million 27: 388800000, // 27th slot costs 388.8 million 28: 666600000, // 28th slot costs 666.6 million 29: 1220000000, // 29th slot costs 1.22 billion 30: 2220000000, // 30th slot costs 2.22 billion 31: 4440000000, // 31st slot costs 4.44 billion 32: 6660000000, // 32nd slot costs 6.66 billion 33: 12500000000, // 33rd slot costs 12.5 billion 34: 16600000000, // 34th slot costs 16.6 billion 35: 22200000000, // 35th slot costs 22.2 billion 36: 30000000000, // 36th slot costs 30 billion 37: 36600000000, // 37th slot costs 36.6 billion 38: 40000000000, // 38th slot costs 40 billion 39: 50000000000, // 39th slot costs 50 billion 40: 60000000000 // 40th slot costs 60 billion }; for (var row = 0; row < game.grid.rows; row++) { game.slots[row] = []; for (var col = 0; col < game.grid.cols; col++) { var slot = new GarageSlot(row, col); slot.x = game.garageX + col * game.slotSize; slot.y = game.garageY + row * game.slotSize; game.addChild(slot); game.slots[row][col] = slot; // Calculate the slot number (1-indexed) var slotNumber = row * game.grid.cols + col + 1; // If this slot is beyond the initially available slots if (slotNumber > game.availableSlots) { // Lock this slot slot.locked = true; slot.slotNumber = slotNumber; // Create price tag var price = game.slotPrices[slotNumber] || 100000; var formattedPrice; // Will use formatCoin if available during initialization if (typeof game.formatCoin === 'function') { formattedPrice = game.formatCoin(price); } else { if (price >= 1000000000) { formattedPrice = Math.floor(price / 1000000000) + "B"; } else if (price >= 1000000) { formattedPrice = (price / 1000000).toFixed(1) + "m"; } else if (price >= 1000) { formattedPrice = (price / 1000).toFixed(1) + "k"; } else { formattedPrice = price.toString(); } } var priceText = new Text2(formattedPrice, { size: 30, fill: 0xFFD700 }); priceText.anchor.set(0.5, 0.5); priceText.y = 30; slot.addChild(priceText); // Add coin icon var coinIcon = slot.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: -50, y: 30, scaleX: 0.6, scaleY: 0.6 }); // Add "tap to unlock" text var unlockText = new Text2("Tap to unlock", { size: 20, fill: 0xFFFFFF }); unlockText.anchor.set(0.5, 0.5); unlockText.y = -20; slot.addChild(unlockText); } } } }; // Create UI elements game.initializeUI = function () { // Coins display var coinIcon = LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, x: 50, y: 50 }); game.addChild(coinIcon); game.coinsText = new Text2(game.coins.toString(), { size: 45, fill: 0xFFD700 }); game.coinsText.anchor.set(0, 0.5); game.coinsText.x = 80; game.coinsText.y = 50; game.addChild(game.coinsText); // Game title var titleText = new Text2("Car Merge: Garage Tycoon", { size: 70, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0); titleText.x = 2048 / 2; titleText.y = 50; game.addChild(titleText); // Highest car level display game.highestCarText = new Text2("Highest Car: Level " + game.highestCar, { size: 35, fill: 0xFFFFFF }); game.highestCarText.anchor.set(1, 0.5); game.highestCarText.x = 2048 - 50; game.highestCarText.y = 50; game.addChild(game.highestCarText); // Buy button game.buyButton = new BuyButton(2); game.buyButton.x = 2048 / 2; game.buyButton.y = game.garageY + game.grid.rows * game.slotSize + 50; game.addChild(game.buyButton); // Instructions var instructionsText = new Text2("Drag matching cars together to merge them into higher level cars!", { size: 30, fill: 0xAAAAAA }); instructionsText.anchor.set(0.5, 0); instructionsText.x = 2048 / 2; instructionsText.y = game.garageY - 100; game.addChild(instructionsText); }; // Format coin amount game.formatCoin = function (amount) { var formatted; if (amount >= 1000000000) { formatted = Math.floor(amount / 1000000000) + "B"; } else if (amount >= 1000000) { formatted = (amount / 1000000).toFixed(1) + "m"; } else if (amount >= 1000) { formatted = (amount / 1000).toFixed(1) + "k"; } else { formatted = amount.toString(); } return formatted; }; // Update coins display game.updateCoinsDisplay = function () { var formattedCoins = game.formatCoin(game.coins); game.coinsText.setText(formattedCoins); }; // Update highest car display game.updateHighestCarDisplay = function () { game.highestCarText.setText("Highest Car: Level " + game.highestCar); }; // Find an empty slot game.findEmptySlot = function () { for (var row = 0; row < game.grid.rows; row++) { for (var col = 0; col < game.grid.cols; col++) { var slot = game.slots[row][col]; var slotNumber = row * game.grid.cols + col + 1; // Only consider unlocked slots if (!slot.locked && slot.isEmpty && slotNumber <= game.availableSlots) { return slot; } } } return null; }; // Add a new car of specified level game.addNewCar = function (level) { var emptySlot = game.findEmptySlot(); if (!emptySlot) { // No empty slots, can't add a car return false; } var car = new Car(level); car.x = emptySlot.x; car.y = emptySlot.y; car.originalSlot = emptySlot; emptySlot.setCar(car); game.addChild(car); game.cars.push(car); return true; }; // Move handler for drag operations game.move = function (x, y, obj) { game.cars.forEach(function (car) { if (car.isDragging) { car.x = x; car.y = y; } }); }; // Check if a car can be merged with another game.checkForMerge = function (draggedCar) { var foundMerge = false; // Find the closest slot to the car var closestSlot = null; var closestDistance = Number.MAX_VALUE; for (var row = 0; row < game.grid.rows; row++) { for (var col = 0; col < game.grid.cols; col++) { var slot = game.slots[row][col]; // Skip locked slots if (slot.locked) { continue; } var distance = Math.sqrt(Math.pow(draggedCar.x - slot.x, 2) + Math.pow(draggedCar.y - slot.y, 2)); if (distance < closestDistance) { closestDistance = distance; closestSlot = slot; } } } // If the closest slot has a car of the same level, merge them if (closestSlot && !closestSlot.isEmpty && closestSlot.currentCar !== draggedCar && closestSlot.currentCar.carLevel === draggedCar.carLevel) { // Perform the merge - level doubles when identical cars merge (1+1=2, 2+2=4, 4+4=8, etc.) var newLevel = draggedCar.carLevel + draggedCar.carLevel; var mergeTargetCar = closestSlot.currentCar; // Update the highest car level if needed if (newLevel > game.highestCar) { game.highestCar = newLevel; storage.highestCar = game.highestCar; game.updateHighestCarDisplay(); } // Remove both cars closestSlot.removeCar(); draggedCar.originalSlot.removeCar(); // Add coins based on the new car level var reward = newLevel; game.coins += reward; // Show coin reward visual game.showCoinReward(closestSlot.x, closestSlot.y, reward); storage.coins = game.coins; game.updateCoinsDisplay(); // Create the new car var newCar = new Car(newLevel); newCar.x = closestSlot.x; newCar.y = closestSlot.y; newCar.originalSlot = closestSlot; closestSlot.setCar(newCar); game.addChild(newCar); // Remove old cars from the array game.cars = game.cars.filter(function (car) { return car !== draggedCar && car !== mergeTargetCar; }); // Add new car to the array game.cars.push(newCar); // Visual and audio feedback LK.effects.flashObject(newCar, 0xFFFFFF, 500); LK.getSound('merge').play(); // Cleanup old car visuals draggedCar.destroy(); mergeTargetCar.destroy(); foundMerge = true; } else if (closestSlot && closestDistance < 100) { // If we're close to a slot but not merging, move to that slot if (closestSlot.isEmpty) { // Move to the empty slot if (draggedCar.originalSlot) { draggedCar.originalSlot.removeCar(); } draggedCar.originalSlot = closestSlot; closestSlot.setCar(draggedCar); tween(draggedCar, { x: closestSlot.x, y: closestSlot.y }, { duration: 200 }); } else { // Slot occupied, return to original position tween(draggedCar, { x: draggedCar.originalPosition.x, y: draggedCar.originalPosition.y }, { duration: 200 }); } } else { // If not close to any slot, return to original position tween(draggedCar, { x: draggedCar.originalPosition.x, y: draggedCar.originalPosition.y }, { duration: 200 }); } return foundMerge; }; // Bring a car to the front of the display list game.bringCarToFront = function (car) { // Since we can't use swapChildren, we'll remove and re-add game.removeChild(car); game.addChild(car); }; // Show floating coin reward game.showCoinReward = function (x, y, amount) { // Create coin display var rewardContainer = new Container(); rewardContainer.x = x; rewardContainer.y = y; // Add coin icon var coinIcon = rewardContainer.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Add reward text var formattedAmount = "+" + game.formatCoin(amount); var rewardText = new Text2(formattedAmount, { size: 40, fill: 0xFFD700 }); rewardText.anchor.set(0, 0.5); rewardText.x = 25; rewardContainer.addChild(rewardText); game.addChild(rewardContainer); // Animate the reward floating up and fading out tween(rewardContainer, { y: y - 100, alpha: 0 }, { duration: 1500, onFinish: function onFinish() { rewardContainer.destroy(); } }); }; // Initialize the game game.initializeGarage(); game.initializeUI(); // Add initial cars - only 4 at the beginning for (var i = 0; i < 4; i++) { game.addNewCar(1); } // No longer spawning cars automatically every 5 seconds // Instead, cars will generate income every 5 seconds // Initialize last coin time game.lastCoinTime = Date.now(); // Game update function game.update = function () { // Check if game is full var hasEmptySlot = game.findEmptySlot() !== null; game.buyButton.alpha = hasEmptySlot ? 1 : 0.5; // Check for automatic coin generation every 3 seconds var currentTime = Date.now(); if (currentTime - game.lastCoinTime >= 3000) { // Add coins game.coins++; // Generate income from all cars simultaneously for (var i = 0; i < game.cars.length; i++) { // Generate income equal to car level var income = game.cars[i].carLevel; game.coins += income; // Show coin reward for each car if (game.cars[i].originalSlot) { game.showCoinReward(game.cars[i].x, game.cars[i].y, income); } // Update the last income time for all cars game.cars[i].lastIncomeTime = currentTime; } storage.coins = game.coins; game.updateCoinsDisplay(); // Show coin reward animation in the middle of the screen for the base income game.showCoinReward(2048 / 2, 200, 1); // Update last coin time game.lastCoinTime = currentTime; } // Call other car update functions if needed, but don't generate income here for (var i = 0; i < game.cars.length; i++) { // Call update but skip income generation since we already did it // We're using a custom approach instead of calling the regular update if (game.cars[i].update && typeof game.cars[i].update === 'function') { // Still call update for any non-income related functionality game.cars[i].isDragging ? null : null; // Just a placeholder to call something safe } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 100,
highestCar: 1,
availableSlots: 4
});
/****
* Classes
****/
var BuyButton = Container.expand(function (startLevel) {
var self = Container.call(this);
self.carLevel = startLevel || 2;
self.price = 10;
var buttonGraphics = self.attachAsset('buy_button', {
anchorX: 0.5,
anchorY: 0.5
});
// Add coin icon
var coinIcon = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: -70,
y: 0,
scaleX: 0.8,
scaleY: 0.8
});
// Add price text
var formattedPrice;
// Will use formatCoin after game is initialized
if (typeof game !== 'undefined' && game.formatCoin) {
formattedPrice = game.formatCoin(self.price);
} else {
if (self.price >= 1000000000) {
formattedPrice = Math.floor(self.price / 1000000000) + "B";
} else if (self.price >= 1000000) {
formattedPrice = (self.price / 1000000).toFixed(1) + "m";
} else if (self.price >= 1000) {
formattedPrice = (self.price / 1000).toFixed(1) + "k";
} else {
formattedPrice = self.price.toString();
}
}
self.priceText = new Text2(formattedPrice, {
size: 35,
fill: 0xFFFFFF
});
self.priceText.anchor.set(0, 0.5);
self.priceText.x = -40;
self.addChild(self.priceText);
// Add "Buy Car" text
var buyText = new Text2("Buy Car", {
size: 25,
fill: 0xFFFFFF
});
buyText.anchor.set(0.5, 0.5);
buyText.y = -40;
self.addChild(buyText);
// Add car level text
self.levelText = new Text2("Level " + self.carLevel, {
size: 25,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0.5, 0.5);
self.levelText.y = -10;
self.addChild(self.levelText);
self.down = function (x, y, obj) {
tween(self, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
if (game.coins >= self.price) {
game.coins -= self.price;
storage.coins = game.coins;
game.updateCoinsDisplay();
// Add car with level 1 instead of current car level
game.addNewCar(1);
LK.getSound('buy').play();
// Increase car level and increase price by 1.2 times for next car
self.price = Math.round(self.price * 1.2);
if (self.price >= 80) {
self.carLevel *= 2;
self.levelText.setText("Level " + self.carLevel);
}
var formattedPrice = game.formatCoin(self.price);
self.priceText.setText(formattedPrice);
} else {
// Visual feedback for not enough coins
tween(self, {
rotation: 0.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
rotation: -0.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
rotation: 0
}, {
duration: 100
});
}
});
}
});
}
};
return self;
});
var Car = Container.expand(function (carLevel) {
var self = Container.call(this);
self.carLevel = carLevel || 1;
self.lastIncomeTime = Date.now();
var colorMap = {
1: 0x336699,
// Blue
2: 0x66CC99,
// Teal
3: 0xCC6633,
// Orange
4: 0x9966CC,
// Purple
5: 0xFFCC33,
// Yellow
6: 0xFF3366,
// Pink
7: 0x00AAFF,
// Light Blue
8: 0xFF00AA,
// Magenta
9: 0xAAFF00,
// Lime Green
10: 0x55CCDD,
// Aqua
11: 0xDD55CC,
// Orchid
12: 0xCC8844,
// Bronze
13: 0x44CC88,
// Emerald
14: 0x8844CC,
// Amethyst
15: 0xCC4488,
// Ruby
16: 0x88CC44,
// Peridot
17: 0x885522,
// Brown
18: 0x228855,
// Sea Green
19: 0x552288,
// Indigo
20: 0xAA3344,
// Crimson
21: 0x33AA44,
// Forest Green
22: 0x4433AA,
// Royal Blue
23: 0xDDAA33,
// Gold
24: 0x33DDAA,
// Turquoise
25: 0xAA33DD,
// Violet
26: 0x77BBCC,
// Steel Blue
27: 0xCC77BB,
// Orchid Pink
28: 0xBBCC77,
// Olive Green
29: 0x66AADD,
// Sky Blue
30: 0xDD66AA,
// Hot Pink
31: 0xAADD66,
// Lime
32: 0x224466,
// Navy Blue
33: 0x662244,
// Burgundy
34: 0x442266,
// Deep Purple
35: 0x226644,
// Forest Green
36: 0xFF9900,
// Orange
37: 0x99FF00,
// Yellow-Green
38: 0x0099FF,
// Sky Blue
39: 0xF0F0F0,
// Silver
40: 0x0F0F0F,
// Charcoal
41: 0x9900FF,
// Violet
42: 0xFF0099,
// Hot Pink
43: 0x00FF99,
// Spring Green
44: 0xFFCC66,
// Light Orange
45: 0x66FFCC,
// Mint
46: 0xCC66FF,
// Lavender
47: 0x775533,
// Brown
48: 0x337755,
// Teal
49: 0x553377,
// Dark Violet
50: 0x990066,
// Magenta
51: 0x009966,
// Jade
52: 0x669900,
// Olive
53: 0x660099,
// Purple
54: 0x996600,
// Amber
55: 0x006699,
// Steel Blue
56: 0xAA5500,
// Copper
57: 0x00AA55,
// Emerald
58: 0x5500AA,
// Royal Purple
59: 0xCC3300,
// Brick Red
60: 0x00CC33,
// Green
61: 0x3300CC,
// Ultramarine
62: 0xFFAA77,
// Peach
63: 0x77FFAA,
// Light Green
64: 0xAA77FF // Periwinkle
};
var assetId = 'car' + (self.carLevel <= 64 ? self.carLevel : 1 + self.carLevel % 64);
var carGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 0.9
});
// Text to display car level
self.levelText = new Text2("Level " + self.carLevel.toString(), {
size: 40,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0.5, 0.5);
self.addChild(self.levelText);
self.isDragging = false;
self.originalPosition = {
x: 0,
y: 0
};
self.originalSlot = null;
// Make cars draggable
self.down = function (x, y, obj) {
self.isDragging = true;
self.originalPosition.x = self.x;
self.originalPosition.y = self.y;
// Bring to front (since we can't use swapChildren, we'll handle in the game class)
game.bringCarToFront(self);
// Scale up to indicate being dragged
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200
});
};
self.up = function (x, y, obj) {
if (self.isDragging) {
self.isDragging = false;
// Scale back to normal
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
// Check for merge in game controller
game.checkForMerge(self);
}
};
self.getValue = function () {
// Base value is 5 for level 1, direct multiplication for other levels
return 5 * self.carLevel;
};
self.generateIncome = function () {
// This function is no longer used for timed income generation
// Income is now generated simultaneously for all cars in the game.update method
// Keeping this function as a stub for compatibility
return false;
};
self.update = function () {
// Income generation is now handled in the game.update method
// This method is kept for compatibility
};
return self;
});
var GarageSlot = Container.expand(function (row, col) {
var self = Container.call(this);
self.row = row;
self.col = col;
self.isEmpty = true;
self.currentCar = null;
var slotGraphics = self.attachAsset('garage_slot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: 0.9,
scaleY: 0.9
});
self.highlight = function () {
tween(slotGraphics, {
alpha: 0.6
}, {
duration: 200
});
};
self.unhighlight = function () {
tween(slotGraphics, {
alpha: 0.3
}, {
duration: 200
});
};
self.setCar = function (car) {
self.currentCar = car;
self.isEmpty = false;
};
self.removeCar = function () {
self.currentCar = null;
self.isEmpty = true;
};
// Down handler for locked slots
self.down = function (x, y, obj) {
if (self.locked) {
tween(self, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
}
};
// Up handler for locked slots
self.up = function (x, y, obj) {
if (self.locked) {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
// Get the price for this slot
var price = game.slotPrices[self.slotNumber] || 100000;
// Check if player has enough coins
if (game.coins >= price) {
// Deduct coins
game.coins -= price;
storage.coins = game.coins;
game.updateCoinsDisplay();
// Unlock the slot
self.locked = false;
game.availableSlots++;
storage.availableSlots = game.availableSlots;
// Remove all children (price text, coin icon, etc.)
while (self.children.length > 1) {
// Keep only the slot graphics
self.removeChild(self.children[self.children.length - 1]);
}
// Change appearance
tween(self.children[0], {
alpha: 0.3
}, {
duration: 200
});
// Play purchase sound
LK.getSound('buy').play();
// Flash effect
LK.effects.flashObject(self, 0xFFFFFF, 500);
} else {
// Not enough coins - shake effect
tween(self, {
rotation: 0.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
rotation: -0.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
rotation: 0
}, {
duration: 100
});
}
});
}
});
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game variables
game.grid = {
rows: 5,
cols: 8
};
game.slotSize = 250;
game.slots = [];
game.cars = [];
game.draggedCar = null;
game.coins = storage.coins || 100;
game.highestCar = storage.highestCar || 1;
game.availableSlots = storage.availableSlots || 4;
// Start background music
LK.playMusic('bgmusic');
// Calculate garage position
game.garageX = 2048 / 2 - game.grid.cols * game.slotSize / 2 + game.slotSize / 2;
game.garageY = 300;
// Adjust slot size to fit the wider grid
game.slotSize = 220;
// Initialize garage grid
game.initializeGarage = function () {
game.availableSlots = 4; // Start with only 4 slots
game.slotPrices = {
5: Math.round(100 / 1.5),
// 5th slot costs 67 coins (1.5x cheaper)
6: Math.round(200 / 1.5),
// 6th slot costs 133 coins (1.5x cheaper)
7: Math.round(400 / 1.5),
// 7th slot costs 267 coins (1.5x cheaper)
8: Math.round(800 / 1.5),
// 8th slot costs 533 coins (1.5x cheaper)
9: Math.round(1600 / 1.5),
// 9th slot costs 1067 coins (1.5x cheaper)
10: Math.round(3200 / 1.5),
// 10th slot costs 2133 coins (1.5x cheaper)
11: Math.round(6400 / 1.5),
// 11th slot costs 4267 coins (1.5x cheaper)
12: Math.round(12800 / 1.5),
// 12th slot costs 8533 coins (1.5x cheaper)
13: Math.round(25600 / 1.5),
// 13th slot costs 17067 coins (1.5x cheaper)
14: Math.round(51200 / 1.5),
// 14th slot costs 34133 coins (1.5x cheaper)
15: Math.round(102400 / 1.5),
// 15th slot costs 68267 coins (1.5x cheaper)
16: Math.round(204800 / 1.5),
// 16th slot costs 136533 coins (1.5x cheaper)
17: Math.round(409600 / 1.5),
// 17th slot costs 273067 coins (1.5x cheaper)
18: Math.round(819200 / 1.5),
// 18th slot costs 546133 coins (1.5x cheaper)
19: Math.round(1638400 / 1.5),
// 19th slot costs 1092267 coins (1.5x cheaper)
20: Math.round(3276800 / 1.5),
// 20th slot costs 2184533 coins (1.5x cheaper)
21: Math.round(6600000 / 1.5),
// 21st slot costs 4.4 million (1.5x cheaper)
22: Math.round(12200000 / 1.5),
// 22nd slot costs 8.13 million (1.5x cheaper)
23: 24400000,
// 23rd slot costs 24.4 million
24: 48800000,
// 24th slot costs 48.8 million
25: 96600000,
// 25th slot costs 96.6 million
26: 193200000,
// 26th slot costs 193.2 million
27: 388800000,
// 27th slot costs 388.8 million
28: 666600000,
// 28th slot costs 666.6 million
29: 1220000000,
// 29th slot costs 1.22 billion
30: 2220000000,
// 30th slot costs 2.22 billion
31: 4440000000,
// 31st slot costs 4.44 billion
32: 6660000000,
// 32nd slot costs 6.66 billion
33: 12500000000,
// 33rd slot costs 12.5 billion
34: 16600000000,
// 34th slot costs 16.6 billion
35: 22200000000,
// 35th slot costs 22.2 billion
36: 30000000000,
// 36th slot costs 30 billion
37: 36600000000,
// 37th slot costs 36.6 billion
38: 40000000000,
// 38th slot costs 40 billion
39: 50000000000,
// 39th slot costs 50 billion
40: 60000000000
// 40th slot costs 60 billion
};
for (var row = 0; row < game.grid.rows; row++) {
game.slots[row] = [];
for (var col = 0; col < game.grid.cols; col++) {
var slot = new GarageSlot(row, col);
slot.x = game.garageX + col * game.slotSize;
slot.y = game.garageY + row * game.slotSize;
game.addChild(slot);
game.slots[row][col] = slot;
// Calculate the slot number (1-indexed)
var slotNumber = row * game.grid.cols + col + 1;
// If this slot is beyond the initially available slots
if (slotNumber > game.availableSlots) {
// Lock this slot
slot.locked = true;
slot.slotNumber = slotNumber;
// Create price tag
var price = game.slotPrices[slotNumber] || 100000;
var formattedPrice;
// Will use formatCoin if available during initialization
if (typeof game.formatCoin === 'function') {
formattedPrice = game.formatCoin(price);
} else {
if (price >= 1000000000) {
formattedPrice = Math.floor(price / 1000000000) + "B";
} else if (price >= 1000000) {
formattedPrice = (price / 1000000).toFixed(1) + "m";
} else if (price >= 1000) {
formattedPrice = (price / 1000).toFixed(1) + "k";
} else {
formattedPrice = price.toString();
}
}
var priceText = new Text2(formattedPrice, {
size: 30,
fill: 0xFFD700
});
priceText.anchor.set(0.5, 0.5);
priceText.y = 30;
slot.addChild(priceText);
// Add coin icon
var coinIcon = slot.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: 30,
scaleX: 0.6,
scaleY: 0.6
});
// Add "tap to unlock" text
var unlockText = new Text2("Tap to unlock", {
size: 20,
fill: 0xFFFFFF
});
unlockText.anchor.set(0.5, 0.5);
unlockText.y = -20;
slot.addChild(unlockText);
}
}
}
};
// Create UI elements
game.initializeUI = function () {
// Coins display
var coinIcon = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
x: 50,
y: 50
});
game.addChild(coinIcon);
game.coinsText = new Text2(game.coins.toString(), {
size: 45,
fill: 0xFFD700
});
game.coinsText.anchor.set(0, 0.5);
game.coinsText.x = 80;
game.coinsText.y = 50;
game.addChild(game.coinsText);
// Game title
var titleText = new Text2("Car Merge: Garage Tycoon", {
size: 70,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 2048 / 2;
titleText.y = 50;
game.addChild(titleText);
// Highest car level display
game.highestCarText = new Text2("Highest Car: Level " + game.highestCar, {
size: 35,
fill: 0xFFFFFF
});
game.highestCarText.anchor.set(1, 0.5);
game.highestCarText.x = 2048 - 50;
game.highestCarText.y = 50;
game.addChild(game.highestCarText);
// Buy button
game.buyButton = new BuyButton(2);
game.buyButton.x = 2048 / 2;
game.buyButton.y = game.garageY + game.grid.rows * game.slotSize + 50;
game.addChild(game.buyButton);
// Instructions
var instructionsText = new Text2("Drag matching cars together to merge them into higher level cars!", {
size: 30,
fill: 0xAAAAAA
});
instructionsText.anchor.set(0.5, 0);
instructionsText.x = 2048 / 2;
instructionsText.y = game.garageY - 100;
game.addChild(instructionsText);
};
// Format coin amount
game.formatCoin = function (amount) {
var formatted;
if (amount >= 1000000000) {
formatted = Math.floor(amount / 1000000000) + "B";
} else if (amount >= 1000000) {
formatted = (amount / 1000000).toFixed(1) + "m";
} else if (amount >= 1000) {
formatted = (amount / 1000).toFixed(1) + "k";
} else {
formatted = amount.toString();
}
return formatted;
};
// Update coins display
game.updateCoinsDisplay = function () {
var formattedCoins = game.formatCoin(game.coins);
game.coinsText.setText(formattedCoins);
};
// Update highest car display
game.updateHighestCarDisplay = function () {
game.highestCarText.setText("Highest Car: Level " + game.highestCar);
};
// Find an empty slot
game.findEmptySlot = function () {
for (var row = 0; row < game.grid.rows; row++) {
for (var col = 0; col < game.grid.cols; col++) {
var slot = game.slots[row][col];
var slotNumber = row * game.grid.cols + col + 1;
// Only consider unlocked slots
if (!slot.locked && slot.isEmpty && slotNumber <= game.availableSlots) {
return slot;
}
}
}
return null;
};
// Add a new car of specified level
game.addNewCar = function (level) {
var emptySlot = game.findEmptySlot();
if (!emptySlot) {
// No empty slots, can't add a car
return false;
}
var car = new Car(level);
car.x = emptySlot.x;
car.y = emptySlot.y;
car.originalSlot = emptySlot;
emptySlot.setCar(car);
game.addChild(car);
game.cars.push(car);
return true;
};
// Move handler for drag operations
game.move = function (x, y, obj) {
game.cars.forEach(function (car) {
if (car.isDragging) {
car.x = x;
car.y = y;
}
});
};
// Check if a car can be merged with another
game.checkForMerge = function (draggedCar) {
var foundMerge = false;
// Find the closest slot to the car
var closestSlot = null;
var closestDistance = Number.MAX_VALUE;
for (var row = 0; row < game.grid.rows; row++) {
for (var col = 0; col < game.grid.cols; col++) {
var slot = game.slots[row][col];
// Skip locked slots
if (slot.locked) {
continue;
}
var distance = Math.sqrt(Math.pow(draggedCar.x - slot.x, 2) + Math.pow(draggedCar.y - slot.y, 2));
if (distance < closestDistance) {
closestDistance = distance;
closestSlot = slot;
}
}
}
// If the closest slot has a car of the same level, merge them
if (closestSlot && !closestSlot.isEmpty && closestSlot.currentCar !== draggedCar && closestSlot.currentCar.carLevel === draggedCar.carLevel) {
// Perform the merge - level doubles when identical cars merge (1+1=2, 2+2=4, 4+4=8, etc.)
var newLevel = draggedCar.carLevel + draggedCar.carLevel;
var mergeTargetCar = closestSlot.currentCar;
// Update the highest car level if needed
if (newLevel > game.highestCar) {
game.highestCar = newLevel;
storage.highestCar = game.highestCar;
game.updateHighestCarDisplay();
}
// Remove both cars
closestSlot.removeCar();
draggedCar.originalSlot.removeCar();
// Add coins based on the new car level
var reward = newLevel;
game.coins += reward;
// Show coin reward visual
game.showCoinReward(closestSlot.x, closestSlot.y, reward);
storage.coins = game.coins;
game.updateCoinsDisplay();
// Create the new car
var newCar = new Car(newLevel);
newCar.x = closestSlot.x;
newCar.y = closestSlot.y;
newCar.originalSlot = closestSlot;
closestSlot.setCar(newCar);
game.addChild(newCar);
// Remove old cars from the array
game.cars = game.cars.filter(function (car) {
return car !== draggedCar && car !== mergeTargetCar;
});
// Add new car to the array
game.cars.push(newCar);
// Visual and audio feedback
LK.effects.flashObject(newCar, 0xFFFFFF, 500);
LK.getSound('merge').play();
// Cleanup old car visuals
draggedCar.destroy();
mergeTargetCar.destroy();
foundMerge = true;
} else if (closestSlot && closestDistance < 100) {
// If we're close to a slot but not merging, move to that slot
if (closestSlot.isEmpty) {
// Move to the empty slot
if (draggedCar.originalSlot) {
draggedCar.originalSlot.removeCar();
}
draggedCar.originalSlot = closestSlot;
closestSlot.setCar(draggedCar);
tween(draggedCar, {
x: closestSlot.x,
y: closestSlot.y
}, {
duration: 200
});
} else {
// Slot occupied, return to original position
tween(draggedCar, {
x: draggedCar.originalPosition.x,
y: draggedCar.originalPosition.y
}, {
duration: 200
});
}
} else {
// If not close to any slot, return to original position
tween(draggedCar, {
x: draggedCar.originalPosition.x,
y: draggedCar.originalPosition.y
}, {
duration: 200
});
}
return foundMerge;
};
// Bring a car to the front of the display list
game.bringCarToFront = function (car) {
// Since we can't use swapChildren, we'll remove and re-add
game.removeChild(car);
game.addChild(car);
};
// Show floating coin reward
game.showCoinReward = function (x, y, amount) {
// Create coin display
var rewardContainer = new Container();
rewardContainer.x = x;
rewardContainer.y = y;
// Add coin icon
var coinIcon = rewardContainer.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Add reward text
var formattedAmount = "+" + game.formatCoin(amount);
var rewardText = new Text2(formattedAmount, {
size: 40,
fill: 0xFFD700
});
rewardText.anchor.set(0, 0.5);
rewardText.x = 25;
rewardContainer.addChild(rewardText);
game.addChild(rewardContainer);
// Animate the reward floating up and fading out
tween(rewardContainer, {
y: y - 100,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
rewardContainer.destroy();
}
});
};
// Initialize the game
game.initializeGarage();
game.initializeUI();
// Add initial cars - only 4 at the beginning
for (var i = 0; i < 4; i++) {
game.addNewCar(1);
}
// No longer spawning cars automatically every 5 seconds
// Instead, cars will generate income every 5 seconds
// Initialize last coin time
game.lastCoinTime = Date.now();
// Game update function
game.update = function () {
// Check if game is full
var hasEmptySlot = game.findEmptySlot() !== null;
game.buyButton.alpha = hasEmptySlot ? 1 : 0.5;
// Check for automatic coin generation every 3 seconds
var currentTime = Date.now();
if (currentTime - game.lastCoinTime >= 3000) {
// Add coins
game.coins++;
// Generate income from all cars simultaneously
for (var i = 0; i < game.cars.length; i++) {
// Generate income equal to car level
var income = game.cars[i].carLevel;
game.coins += income;
// Show coin reward for each car
if (game.cars[i].originalSlot) {
game.showCoinReward(game.cars[i].x, game.cars[i].y, income);
}
// Update the last income time for all cars
game.cars[i].lastIncomeTime = currentTime;
}
storage.coins = game.coins;
game.updateCoinsDisplay();
// Show coin reward animation in the middle of the screen for the base income
game.showCoinReward(2048 / 2, 200, 1);
// Update last coin time
game.lastCoinTime = currentTime;
}
// Call other car update functions if needed, but don't generate income here
for (var i = 0; i < game.cars.length; i++) {
// Call update but skip income generation since we already did it
// We're using a custom approach instead of calling the regular update
if (game.cars[i].update && typeof game.cars[i].update === 'function') {
// Still call update for any non-income related functionality
game.cars[i].isDragging ? null : null; // Just a placeholder to call something safe
}
}
};