/****
* 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
}
}
};