/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
money: 5000,
population: 0,
happiness: 50,
lastCityState: "undefined"
});
/****
* Classes
****/
var BuildButton = Container.expand(function (buildingType, cost) {
var self = Container.call(this);
self.buildingType = buildingType;
self.cost = cost;
self.isSelected = false;
self.background = self.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
var backgroundColor;
switch (buildingType) {
case 'residential':
backgroundColor = 0x3f51b5;
break;
case 'commercial':
backgroundColor = 0xff9800;
break;
case 'industrial':
backgroundColor = 0xffeb3b;
break;
case 'road':
backgroundColor = 0x424242;
break;
case 'park':
backgroundColor = 0x8bc34a;
break;
case 'hospital':
backgroundColor = 0xe91e63;
break;
case 'school':
backgroundColor = 0x00bcd4;
break;
case 'police':
backgroundColor = 0x3f51b5;
break;
case 'demolish':
backgroundColor = 0xf44336;
break;
default:
backgroundColor = 0x2196f3;
}
self.background.tint = backgroundColor;
var labelText = buildingType.charAt(0).toUpperCase() + buildingType.slice(1);
if (buildingType !== 'demolish') {
labelText += '\n($' + cost + ')';
}
self.label = new Text2(labelText, {
size: 24,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
self.select = function () {
if (!self.isSelected) {
self.isSelected = true;
tween(self.background, {
alpha: 1
}, {
duration: 200
});
}
};
self.deselect = function () {
if (self.isSelected) {
self.isSelected = false;
tween(self.background, {
alpha: 0.7
}, {
duration: 200
});
}
};
self.down = function (x, y, obj) {
buildMenu.selectButton(self);
LK.getSound('select').play();
};
return self;
});
var BuildMenu = Container.expand(function () {
var self = Container.call(this);
self.buttons = [];
self.selectedButton = null;
self.init = function () {
var buttonTypes = [{
type: 'residential',
cost: 200
}, {
type: 'commercial',
cost: 300
}, {
type: 'industrial',
cost: 400
}, {
type: 'road',
cost: 50
}, {
type: 'park',
cost: 100
}, {
type: 'hospital',
cost: 600
}, {
type: 'school',
cost: 500
}, {
type: 'police',
cost: 450
}, {
type: 'demolish',
cost: 0
}];
var buttonSpacing = 220;
var startX = 2048 / 2 - (buttonTypes.length - 1) * buttonSpacing / 2;
buttonTypes.forEach(function (buttonInfo, index) {
var button = new BuildButton(buttonInfo.type, buttonInfo.cost);
button.x = startX + index * buttonSpacing;
button.y = 2732 - 100;
self.buttons.push(button);
self.addChild(button);
});
};
self.selectButton = function (button) {
if (self.selectedButton) {
self.selectedButton.deselect();
}
if (self.selectedButton === button) {
// Deselect if clicking the same button
self.selectedButton = null;
} else {
self.selectedButton = button;
button.select();
}
};
return self;
});
var CityGrid = Container.expand(function () {
var self = Container.call(this);
self.tiles = [];
self.selectedTile = null;
self.gridSize = 10; // 10x10 grid
self.tileSize = 130;
self.init = function () {
// Calculate grid dimensions
var gridWidth = self.gridSize * self.tileSize;
var gridHeight = self.gridSize * self.tileSize;
// Center the grid on screen
var startX = (2048 - gridWidth) / 2 + self.tileSize / 2;
var startY = (2732 - gridHeight) / 2 + self.tileSize / 2;
// Create grid tiles
for (var y = 0; y < self.gridSize; y++) {
for (var x = 0; x < self.gridSize; x++) {
var tile = new GridTile();
tile.gridX = x;
tile.gridY = y;
tile.x = startX + x * self.tileSize;
tile.y = startY + y * self.tileSize;
self.tiles.push(tile);
self.addChild(tile);
}
}
};
self.selectTile = function (tile) {
if (self.selectedTile) {
self.selectedTile.deselect();
}
self.selectedTile = tile;
if (tile) {
tile.select();
if (buildMenu.selectedButton) {
var buildingType = buildMenu.selectedButton.buildingType;
var cost = buildMenu.selectedButton.cost;
if (buildingType === 'demolish') {
if (tile.type !== 'empty') {
// Calculate refund (half of original cost)
var refund;
switch (tile.type) {
case 'residential':
refund = 100;
break;
case 'commercial':
refund = 150;
break;
case 'industrial':
refund = 200;
break;
case 'hospital':
refund = 300;
break;
case 'school':
refund = 250;
break;
case 'police':
refund = 225;
break;
case 'road':
refund = 25;
break;
case 'park':
refund = 50;
break;
default:
refund = 0;
}
money += refund;
updateStats();
tile.changeType('empty');
LK.getSound('demolish').play();
recalculateCityMetrics();
}
} else if (money >= cost) {
// Place building
money -= cost;
updateStats();
tile.changeType(buildingType);
LK.getSound('build').play();
// Add score and update city metrics
LK.setScore(LK.getScore() + 10);
recalculateCityMetrics();
// Deselect the building button after placing to prevent multiple placements
buildMenu.selectedButton = null;
buildMenu.buttons.forEach(function (btn) {
btn.deselect();
});
}
}
}
};
self.getTileAt = function (gridX, gridY) {
for (var i = 0; i < self.tiles.length; i++) {
var tile = self.tiles[i];
if (tile.gridX === gridX && tile.gridY === gridY) {
return tile;
}
}
return null;
};
return self;
});
var GridTile = Container.expand(function () {
var self = Container.call(this);
self.type = 'empty';
self.gridX = 0;
self.gridY = 0;
self.building = null;
self.isSelected = false;
self.tileSprite = self.attachAsset('grassTile', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.select = function () {
if (!self.isSelected) {
self.isSelected = true;
tween(self.tileSprite, {
alpha: 1
}, {
duration: 200
});
}
};
self.deselect = function () {
if (self.isSelected) {
self.isSelected = false;
tween(self.tileSprite, {
alpha: 0.8
}, {
duration: 200
});
}
};
self.changeType = function (newType) {
if (self.type === newType) return;
if (self.building) {
self.removeChild(self.building);
self.building = null;
}
self.type = newType;
if (newType === 'empty') {
self.tileSprite.tint = 0x4caf50; // Grass color
} else if (newType === 'residential') {
self.building = LK.getAsset('residentialBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb; // Sky blue
} else if (newType === 'commercial') {
self.building = LK.getAsset('commercialBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'industrial') {
self.building = LK.getAsset('industrialBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'hospital') {
self.building = LK.getAsset('hospitalBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'school') {
self.building = LK.getAsset('schoolBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'police') {
self.building = LK.getAsset('policeBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'road') {
self.tileSprite.tint = 0x424242; // Road color
} else if (newType === 'park') {
self.tileSprite.tint = 0x8bc34a; // Park green
}
if (self.building) {
// Animate the building appearing
self.building.scale.set(0.01);
tween(self.building.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.elasticOut
});
}
};
self.down = function (x, y, obj) {
cityGrid.selectTile(self);
LK.getSound('select').play();
};
return self;
});
var Person = Container.expand(function () {
var self = Container.call(this);
// Create person sprite
self.sprite = self.attachAsset('commercialBuilding', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFFFFFF // White to represent a person
});
// Movement properties
self.targetX = 0;
self.targetY = 0;
self.speed = 1 + Math.random() * 2;
self.moveDelay = 0;
self.tile = null;
self.init = function (startTile) {
self.tile = startTile;
self.x = startTile.x;
self.y = startTile.y;
self.findNewTarget();
};
self.update = function () {
if (self.moveDelay > 0) {
self.moveDelay--;
return;
}
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
// Reached target, find a new one
self.findNewTarget();
} else {
// Move toward target
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.findNewTarget = function () {
// Find tiles with buildings (especially residential)
var potentialTargets = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
if (tile.type !== 'empty') {
// Prefer buildings over empty space
if (tile.type === 'residential') {
// Add residential tiles multiple times to increase probability
potentialTargets.push(tile);
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'commercial') {
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'hospital') {
// People visit hospitals when needed
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'school') {
// Children and parents visit schools
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'police') {
// Some people visit police stations
potentialTargets.push(tile);
} else {
potentialTargets.push(tile);
}
}
}
if (potentialTargets.length > 0) {
// Choose a random target
var newTarget = potentialTargets[Math.floor(Math.random() * potentialTargets.length)];
self.targetX = newTarget.x;
self.targetY = newTarget.y;
// Set a random delay before moving again
self.moveDelay = Math.floor(Math.random() * 120);
}
};
return self;
});
var StatsPanel = Container.expand(function () {
var self = Container.call(this);
self.background = self.attachAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.7,
width: 400,
height: 160
});
self.moneyText = new Text2("Money: $5000", {
size: 32,
fill: 0x000000
});
self.moneyText.anchor.set(0.5, 0);
self.moneyText.y = 20;
self.addChild(self.moneyText);
self.populationText = new Text2("Population: 0", {
size: 32,
fill: 0x000000
});
self.populationText.anchor.set(0.5, 0);
self.populationText.y = 60;
self.addChild(self.populationText);
self.happinessText = new Text2("Happiness: 50%", {
size: 32,
fill: 0x000000
});
self.happinessText.anchor.set(0.5, 0);
self.happinessText.y = 100;
self.addChild(self.happinessText);
self.updateStats = function (money, population, happiness) {
self.moneyText.setText("Money: $" + money);
self.populationText.setText("Population: " + population);
self.happinessText.setText("Happiness: " + happiness + "%");
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Sky blue background
});
/****
* Game Code
****/
// Game variables
var money = storage.money;
var population = storage.population;
var happiness = storage.happiness;
var gameTime = 0;
var revenueInterval = 10;
var cityGrid;
var buildMenu;
var statsPanel;
var scoreText;
var people = []; // Array to store people moving in the city
var lastPersonSpawn = 0;
var personSpawnRate = 300; // Ticks between spawning new people
// Initialize game elements
function initGame() {
// Set up city grid
cityGrid = new CityGrid();
cityGrid.init();
game.addChild(cityGrid);
// Set up build menu
buildMenu = new BuildMenu();
buildMenu.init();
game.addChild(buildMenu);
// Set up stats panel
statsPanel = new StatsPanel();
statsPanel.x = 2048 / 2;
statsPanel.y = 20;
game.addChild(statsPanel);
// Set up score text
scoreText = new Text2("City Score: " + LK.getScore(), {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = -20; // Add some padding from the right edge
LK.gui.topRight.addChild(scoreText);
// Load saved city if available
if (storage.lastCityState && storage.lastCityState !== "undefined" && storage.lastCityState !== "") {
try {
// Parse the city state string format (x,y,type|x,y,type)
var tileStrings = storage.lastCityState.split('|');
for (var i = 0; i < tileStrings.length; i++) {
if (tileStrings[i]) {
var parts = tileStrings[i].split(',');
if (parts.length >= 3) {
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
var type = parts[2];
var tile = cityGrid.getTileAt(x, y);
if (tile) {
tile.changeType(type);
}
}
}
}
recalculateCityMetrics();
} catch (e) {
console.log("Error loading saved city:", e);
}
}
// Start background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Update stats display
updateStats();
// Place a house and a road when the game starts
var centerX = Math.floor(cityGrid.gridSize / 2);
var centerY = Math.floor(cityGrid.gridSize / 2);
// Place a house at the center
var houseTile = cityGrid.getTileAt(centerX, centerY);
if (houseTile) {
houseTile.changeType('residential');
}
// Place a road below the house
var roadTile = cityGrid.getTileAt(centerX, centerY + 1);
if (roadTile) {
roadTile.changeType('road');
}
// Initialize people based on population if city has any residential buildings
if (population > 0) {
var residentialTiles = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
if (cityGrid.tiles[i].type === 'residential') {
residentialTiles.push(cityGrid.tiles[i]);
}
}
if (residentialTiles.length > 0) {
// Initialize some people to start with
var initialPeopleCount = Math.min(Math.floor(population / 100), 10);
for (var i = 0; i < initialPeopleCount; i++) {
var startTile = residentialTiles[Math.floor(Math.random() * residentialTiles.length)];
var person = new Person();
person.init(startTile);
people.push(person);
game.addChild(person);
}
}
}
}
// Update money, population, and happiness stats
function updateStats() {
statsPanel.updateStats(money, population, happiness);
scoreText.setText("City Score: " + LK.getScore());
// Save to storage
storage.money = money;
storage.population = population;
storage.happiness = happiness;
// Save city state
var cityState = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
if (tile.type !== 'empty') {
cityState.push({
x: tile.gridX,
y: tile.gridY,
type: tile.type
});
}
}
// Convert cityState to string for storage (without using JSON.stringify)
try {
// Directly create a string representation of the city state
var cityStateString = "";
for (var i = 0; i < cityState.length; i++) {
var tile = cityState[i];
cityStateString += tile.x + "," + tile.y + "," + tile.type;
if (i < cityState.length - 1) {
cityStateString += "|";
}
}
storage.lastCityState = cityStateString;
} catch (e) {
console.log("Error saving city state:", e);
storage.lastCityState = "";
}
// Check for win condition - 5000 population
if (population >= 5000 && !gameWon) {
gameWon = true;
LK.showYouWin();
}
}
// Recalculate city metrics based on buildings
function recalculateCityMetrics() {
// Count different building types
var residentialCount = 0;
var commercialCount = 0;
var industrialCount = 0;
var parkCount = 0;
var roadCount = 0;
var hospitalCount = 0;
var schoolCount = 0;
var policeCount = 0;
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
switch (tile.type) {
case 'residential':
residentialCount++;
break;
case 'commercial':
commercialCount++;
break;
case 'industrial':
industrialCount++;
break;
case 'park':
parkCount++;
break;
case 'road':
roadCount++;
break;
case 'hospital':
hospitalCount++;
break;
case 'school':
schoolCount++;
break;
case 'police':
policeCount++;
break;
}
}
// Calculate population (each residential building houses 50-100 people)
population = Math.floor(residentialCount * (50 + Math.random() * 50));
// Calculate happiness based on balance of buildings
// More parks and roads increase happiness, industry decreases it
var totalBuildings = residentialCount + commercialCount + industrialCount + hospitalCount + schoolCount + policeCount;
if (totalBuildings > 0) {
var parkRatio = parkCount / totalBuildings;
var roadRatio = roadCount / totalBuildings;
var industrialRatio = industrialCount / totalBuildings;
var hospitalRatio = hospitalCount / totalBuildings;
var schoolRatio = schoolCount / totalBuildings;
var policeRatio = policeCount / totalBuildings;
happiness = 50; // Base happiness
happiness += Math.floor(parkRatio * 100); // Parks add happiness
happiness += Math.floor(roadRatio * 50); // Roads add some happiness
happiness -= Math.floor(industrialRatio * 75); // Industry reduces happiness
happiness += Math.floor(hospitalRatio * 120); // Hospitals greatly increase happiness
happiness += Math.floor(schoolRatio * 100); // Schools increase happiness
happiness += Math.floor(policeRatio * 80); // Police increase happiness and safety
// Ensure happiness is in range 0-100
happiness = Math.max(0, Math.min(100, happiness));
} else {
happiness = 50; // Default happiness
}
// Update stats display
updateStats();
// Update person spawn rate based on population
if (population > 0) {
// Faster person spawning for larger populations
personSpawnRate = Math.max(60, 300 - population / 50);
}
}
// Calculate revenue from buildings
function collectRevenue() {
var revenue = 0;
// Each building type generates revenue
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
switch (tile.type) {
case 'residential':
// Revenue based on happiness
revenue += Math.floor(10 * (happiness / 100));
break;
case 'commercial':
// Commercial revenue based on population and happiness
revenue += Math.floor(20 * (population / 1000) * (happiness / 100));
break;
case 'industrial':
// Industrial revenue is steady but affects happiness
revenue += 25;
break;
case 'hospital':
// Hospitals cost money to maintain but increase population health
revenue -= 15;
break;
case 'school':
// Schools cost money to maintain but increase long-term property values
revenue -= 10;
break;
case 'police':
// Police stations cost money but increase safety and property values
revenue -= 5;
break;
}
}
// Add revenue to money
money += revenue;
updateStats();
}
// Main game update function
var lastCollectionTime = 0;
var gameWon = false;
game.update = function () {
gameTime++;
// Collect revenue every 10 seconds
if (gameTime - lastCollectionTime >= revenueInterval * 60) {
// 60 ticks = 1 second
collectRevenue();
lastCollectionTime = gameTime;
}
// Update difficulty based on city size
if (population > 2000 && revenueInterval > 5) {
revenueInterval = 5; // Faster revenue collection
}
// Handle people movement
for (var i = 0; i < people.length; i++) {
people[i].update();
}
// Spawn new people based on population
if (population > 0 && gameTime - lastPersonSpawn >= personSpawnRate) {
// Find residential buildings to spawn people at
var residentialTiles = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
if (cityGrid.tiles[i].type === 'residential') {
residentialTiles.push(cityGrid.tiles[i]);
}
}
if (residentialTiles.length > 0) {
// Calculate how many people to show (1 per 50 population, max 20)
var desiredPeopleCount = Math.min(Math.floor(population / 50), 20);
// If we have fewer people than desired, spawn more
if (people.length < desiredPeopleCount) {
var startTile = residentialTiles[Math.floor(Math.random() * residentialTiles.length)];
var person = new Person();
person.init(startTile);
people.push(person);
game.addChild(person);
}
lastPersonSpawn = gameTime;
}
}
};
// Handle game input
game.down = function (x, y, obj) {
// If we clicked on an empty area, deselect the current tile
if (!obj || obj === game) {
if (cityGrid.selectedTile) {
cityGrid.selectedTile.deselect();
cityGrid.selectedTile = null;
}
}
};
// Initialize the game
initGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
money: 5000,
population: 0,
happiness: 50,
lastCityState: "undefined"
});
/****
* Classes
****/
var BuildButton = Container.expand(function (buildingType, cost) {
var self = Container.call(this);
self.buildingType = buildingType;
self.cost = cost;
self.isSelected = false;
self.background = self.attachAsset('uiButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
var backgroundColor;
switch (buildingType) {
case 'residential':
backgroundColor = 0x3f51b5;
break;
case 'commercial':
backgroundColor = 0xff9800;
break;
case 'industrial':
backgroundColor = 0xffeb3b;
break;
case 'road':
backgroundColor = 0x424242;
break;
case 'park':
backgroundColor = 0x8bc34a;
break;
case 'hospital':
backgroundColor = 0xe91e63;
break;
case 'school':
backgroundColor = 0x00bcd4;
break;
case 'police':
backgroundColor = 0x3f51b5;
break;
case 'demolish':
backgroundColor = 0xf44336;
break;
default:
backgroundColor = 0x2196f3;
}
self.background.tint = backgroundColor;
var labelText = buildingType.charAt(0).toUpperCase() + buildingType.slice(1);
if (buildingType !== 'demolish') {
labelText += '\n($' + cost + ')';
}
self.label = new Text2(labelText, {
size: 24,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
self.select = function () {
if (!self.isSelected) {
self.isSelected = true;
tween(self.background, {
alpha: 1
}, {
duration: 200
});
}
};
self.deselect = function () {
if (self.isSelected) {
self.isSelected = false;
tween(self.background, {
alpha: 0.7
}, {
duration: 200
});
}
};
self.down = function (x, y, obj) {
buildMenu.selectButton(self);
LK.getSound('select').play();
};
return self;
});
var BuildMenu = Container.expand(function () {
var self = Container.call(this);
self.buttons = [];
self.selectedButton = null;
self.init = function () {
var buttonTypes = [{
type: 'residential',
cost: 200
}, {
type: 'commercial',
cost: 300
}, {
type: 'industrial',
cost: 400
}, {
type: 'road',
cost: 50
}, {
type: 'park',
cost: 100
}, {
type: 'hospital',
cost: 600
}, {
type: 'school',
cost: 500
}, {
type: 'police',
cost: 450
}, {
type: 'demolish',
cost: 0
}];
var buttonSpacing = 220;
var startX = 2048 / 2 - (buttonTypes.length - 1) * buttonSpacing / 2;
buttonTypes.forEach(function (buttonInfo, index) {
var button = new BuildButton(buttonInfo.type, buttonInfo.cost);
button.x = startX + index * buttonSpacing;
button.y = 2732 - 100;
self.buttons.push(button);
self.addChild(button);
});
};
self.selectButton = function (button) {
if (self.selectedButton) {
self.selectedButton.deselect();
}
if (self.selectedButton === button) {
// Deselect if clicking the same button
self.selectedButton = null;
} else {
self.selectedButton = button;
button.select();
}
};
return self;
});
var CityGrid = Container.expand(function () {
var self = Container.call(this);
self.tiles = [];
self.selectedTile = null;
self.gridSize = 10; // 10x10 grid
self.tileSize = 130;
self.init = function () {
// Calculate grid dimensions
var gridWidth = self.gridSize * self.tileSize;
var gridHeight = self.gridSize * self.tileSize;
// Center the grid on screen
var startX = (2048 - gridWidth) / 2 + self.tileSize / 2;
var startY = (2732 - gridHeight) / 2 + self.tileSize / 2;
// Create grid tiles
for (var y = 0; y < self.gridSize; y++) {
for (var x = 0; x < self.gridSize; x++) {
var tile = new GridTile();
tile.gridX = x;
tile.gridY = y;
tile.x = startX + x * self.tileSize;
tile.y = startY + y * self.tileSize;
self.tiles.push(tile);
self.addChild(tile);
}
}
};
self.selectTile = function (tile) {
if (self.selectedTile) {
self.selectedTile.deselect();
}
self.selectedTile = tile;
if (tile) {
tile.select();
if (buildMenu.selectedButton) {
var buildingType = buildMenu.selectedButton.buildingType;
var cost = buildMenu.selectedButton.cost;
if (buildingType === 'demolish') {
if (tile.type !== 'empty') {
// Calculate refund (half of original cost)
var refund;
switch (tile.type) {
case 'residential':
refund = 100;
break;
case 'commercial':
refund = 150;
break;
case 'industrial':
refund = 200;
break;
case 'hospital':
refund = 300;
break;
case 'school':
refund = 250;
break;
case 'police':
refund = 225;
break;
case 'road':
refund = 25;
break;
case 'park':
refund = 50;
break;
default:
refund = 0;
}
money += refund;
updateStats();
tile.changeType('empty');
LK.getSound('demolish').play();
recalculateCityMetrics();
}
} else if (money >= cost) {
// Place building
money -= cost;
updateStats();
tile.changeType(buildingType);
LK.getSound('build').play();
// Add score and update city metrics
LK.setScore(LK.getScore() + 10);
recalculateCityMetrics();
// Deselect the building button after placing to prevent multiple placements
buildMenu.selectedButton = null;
buildMenu.buttons.forEach(function (btn) {
btn.deselect();
});
}
}
}
};
self.getTileAt = function (gridX, gridY) {
for (var i = 0; i < self.tiles.length; i++) {
var tile = self.tiles[i];
if (tile.gridX === gridX && tile.gridY === gridY) {
return tile;
}
}
return null;
};
return self;
});
var GridTile = Container.expand(function () {
var self = Container.call(this);
self.type = 'empty';
self.gridX = 0;
self.gridY = 0;
self.building = null;
self.isSelected = false;
self.tileSprite = self.attachAsset('grassTile', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.select = function () {
if (!self.isSelected) {
self.isSelected = true;
tween(self.tileSprite, {
alpha: 1
}, {
duration: 200
});
}
};
self.deselect = function () {
if (self.isSelected) {
self.isSelected = false;
tween(self.tileSprite, {
alpha: 0.8
}, {
duration: 200
});
}
};
self.changeType = function (newType) {
if (self.type === newType) return;
if (self.building) {
self.removeChild(self.building);
self.building = null;
}
self.type = newType;
if (newType === 'empty') {
self.tileSprite.tint = 0x4caf50; // Grass color
} else if (newType === 'residential') {
self.building = LK.getAsset('residentialBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb; // Sky blue
} else if (newType === 'commercial') {
self.building = LK.getAsset('commercialBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'industrial') {
self.building = LK.getAsset('industrialBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'hospital') {
self.building = LK.getAsset('hospitalBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'school') {
self.building = LK.getAsset('schoolBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'police') {
self.building = LK.getAsset('policeBuilding', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.building);
self.tileSprite.tint = 0x87ceeb;
} else if (newType === 'road') {
self.tileSprite.tint = 0x424242; // Road color
} else if (newType === 'park') {
self.tileSprite.tint = 0x8bc34a; // Park green
}
if (self.building) {
// Animate the building appearing
self.building.scale.set(0.01);
tween(self.building.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.elasticOut
});
}
};
self.down = function (x, y, obj) {
cityGrid.selectTile(self);
LK.getSound('select').play();
};
return self;
});
var Person = Container.expand(function () {
var self = Container.call(this);
// Create person sprite
self.sprite = self.attachAsset('commercialBuilding', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFFFFFF // White to represent a person
});
// Movement properties
self.targetX = 0;
self.targetY = 0;
self.speed = 1 + Math.random() * 2;
self.moveDelay = 0;
self.tile = null;
self.init = function (startTile) {
self.tile = startTile;
self.x = startTile.x;
self.y = startTile.y;
self.findNewTarget();
};
self.update = function () {
if (self.moveDelay > 0) {
self.moveDelay--;
return;
}
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
// Reached target, find a new one
self.findNewTarget();
} else {
// Move toward target
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.findNewTarget = function () {
// Find tiles with buildings (especially residential)
var potentialTargets = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
if (tile.type !== 'empty') {
// Prefer buildings over empty space
if (tile.type === 'residential') {
// Add residential tiles multiple times to increase probability
potentialTargets.push(tile);
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'commercial') {
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'hospital') {
// People visit hospitals when needed
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'school') {
// Children and parents visit schools
potentialTargets.push(tile);
potentialTargets.push(tile);
} else if (tile.type === 'police') {
// Some people visit police stations
potentialTargets.push(tile);
} else {
potentialTargets.push(tile);
}
}
}
if (potentialTargets.length > 0) {
// Choose a random target
var newTarget = potentialTargets[Math.floor(Math.random() * potentialTargets.length)];
self.targetX = newTarget.x;
self.targetY = newTarget.y;
// Set a random delay before moving again
self.moveDelay = Math.floor(Math.random() * 120);
}
};
return self;
});
var StatsPanel = Container.expand(function () {
var self = Container.call(this);
self.background = self.attachAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.7,
width: 400,
height: 160
});
self.moneyText = new Text2("Money: $5000", {
size: 32,
fill: 0x000000
});
self.moneyText.anchor.set(0.5, 0);
self.moneyText.y = 20;
self.addChild(self.moneyText);
self.populationText = new Text2("Population: 0", {
size: 32,
fill: 0x000000
});
self.populationText.anchor.set(0.5, 0);
self.populationText.y = 60;
self.addChild(self.populationText);
self.happinessText = new Text2("Happiness: 50%", {
size: 32,
fill: 0x000000
});
self.happinessText.anchor.set(0.5, 0);
self.happinessText.y = 100;
self.addChild(self.happinessText);
self.updateStats = function (money, population, happiness) {
self.moneyText.setText("Money: $" + money);
self.populationText.setText("Population: " + population);
self.happinessText.setText("Happiness: " + happiness + "%");
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Sky blue background
});
/****
* Game Code
****/
// Game variables
var money = storage.money;
var population = storage.population;
var happiness = storage.happiness;
var gameTime = 0;
var revenueInterval = 10;
var cityGrid;
var buildMenu;
var statsPanel;
var scoreText;
var people = []; // Array to store people moving in the city
var lastPersonSpawn = 0;
var personSpawnRate = 300; // Ticks between spawning new people
// Initialize game elements
function initGame() {
// Set up city grid
cityGrid = new CityGrid();
cityGrid.init();
game.addChild(cityGrid);
// Set up build menu
buildMenu = new BuildMenu();
buildMenu.init();
game.addChild(buildMenu);
// Set up stats panel
statsPanel = new StatsPanel();
statsPanel.x = 2048 / 2;
statsPanel.y = 20;
game.addChild(statsPanel);
// Set up score text
scoreText = new Text2("City Score: " + LK.getScore(), {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = -20; // Add some padding from the right edge
LK.gui.topRight.addChild(scoreText);
// Load saved city if available
if (storage.lastCityState && storage.lastCityState !== "undefined" && storage.lastCityState !== "") {
try {
// Parse the city state string format (x,y,type|x,y,type)
var tileStrings = storage.lastCityState.split('|');
for (var i = 0; i < tileStrings.length; i++) {
if (tileStrings[i]) {
var parts = tileStrings[i].split(',');
if (parts.length >= 3) {
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
var type = parts[2];
var tile = cityGrid.getTileAt(x, y);
if (tile) {
tile.changeType(type);
}
}
}
}
recalculateCityMetrics();
} catch (e) {
console.log("Error loading saved city:", e);
}
}
// Start background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Update stats display
updateStats();
// Place a house and a road when the game starts
var centerX = Math.floor(cityGrid.gridSize / 2);
var centerY = Math.floor(cityGrid.gridSize / 2);
// Place a house at the center
var houseTile = cityGrid.getTileAt(centerX, centerY);
if (houseTile) {
houseTile.changeType('residential');
}
// Place a road below the house
var roadTile = cityGrid.getTileAt(centerX, centerY + 1);
if (roadTile) {
roadTile.changeType('road');
}
// Initialize people based on population if city has any residential buildings
if (population > 0) {
var residentialTiles = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
if (cityGrid.tiles[i].type === 'residential') {
residentialTiles.push(cityGrid.tiles[i]);
}
}
if (residentialTiles.length > 0) {
// Initialize some people to start with
var initialPeopleCount = Math.min(Math.floor(population / 100), 10);
for (var i = 0; i < initialPeopleCount; i++) {
var startTile = residentialTiles[Math.floor(Math.random() * residentialTiles.length)];
var person = new Person();
person.init(startTile);
people.push(person);
game.addChild(person);
}
}
}
}
// Update money, population, and happiness stats
function updateStats() {
statsPanel.updateStats(money, population, happiness);
scoreText.setText("City Score: " + LK.getScore());
// Save to storage
storage.money = money;
storage.population = population;
storage.happiness = happiness;
// Save city state
var cityState = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
if (tile.type !== 'empty') {
cityState.push({
x: tile.gridX,
y: tile.gridY,
type: tile.type
});
}
}
// Convert cityState to string for storage (without using JSON.stringify)
try {
// Directly create a string representation of the city state
var cityStateString = "";
for (var i = 0; i < cityState.length; i++) {
var tile = cityState[i];
cityStateString += tile.x + "," + tile.y + "," + tile.type;
if (i < cityState.length - 1) {
cityStateString += "|";
}
}
storage.lastCityState = cityStateString;
} catch (e) {
console.log("Error saving city state:", e);
storage.lastCityState = "";
}
// Check for win condition - 5000 population
if (population >= 5000 && !gameWon) {
gameWon = true;
LK.showYouWin();
}
}
// Recalculate city metrics based on buildings
function recalculateCityMetrics() {
// Count different building types
var residentialCount = 0;
var commercialCount = 0;
var industrialCount = 0;
var parkCount = 0;
var roadCount = 0;
var hospitalCount = 0;
var schoolCount = 0;
var policeCount = 0;
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
switch (tile.type) {
case 'residential':
residentialCount++;
break;
case 'commercial':
commercialCount++;
break;
case 'industrial':
industrialCount++;
break;
case 'park':
parkCount++;
break;
case 'road':
roadCount++;
break;
case 'hospital':
hospitalCount++;
break;
case 'school':
schoolCount++;
break;
case 'police':
policeCount++;
break;
}
}
// Calculate population (each residential building houses 50-100 people)
population = Math.floor(residentialCount * (50 + Math.random() * 50));
// Calculate happiness based on balance of buildings
// More parks and roads increase happiness, industry decreases it
var totalBuildings = residentialCount + commercialCount + industrialCount + hospitalCount + schoolCount + policeCount;
if (totalBuildings > 0) {
var parkRatio = parkCount / totalBuildings;
var roadRatio = roadCount / totalBuildings;
var industrialRatio = industrialCount / totalBuildings;
var hospitalRatio = hospitalCount / totalBuildings;
var schoolRatio = schoolCount / totalBuildings;
var policeRatio = policeCount / totalBuildings;
happiness = 50; // Base happiness
happiness += Math.floor(parkRatio * 100); // Parks add happiness
happiness += Math.floor(roadRatio * 50); // Roads add some happiness
happiness -= Math.floor(industrialRatio * 75); // Industry reduces happiness
happiness += Math.floor(hospitalRatio * 120); // Hospitals greatly increase happiness
happiness += Math.floor(schoolRatio * 100); // Schools increase happiness
happiness += Math.floor(policeRatio * 80); // Police increase happiness and safety
// Ensure happiness is in range 0-100
happiness = Math.max(0, Math.min(100, happiness));
} else {
happiness = 50; // Default happiness
}
// Update stats display
updateStats();
// Update person spawn rate based on population
if (population > 0) {
// Faster person spawning for larger populations
personSpawnRate = Math.max(60, 300 - population / 50);
}
}
// Calculate revenue from buildings
function collectRevenue() {
var revenue = 0;
// Each building type generates revenue
for (var i = 0; i < cityGrid.tiles.length; i++) {
var tile = cityGrid.tiles[i];
switch (tile.type) {
case 'residential':
// Revenue based on happiness
revenue += Math.floor(10 * (happiness / 100));
break;
case 'commercial':
// Commercial revenue based on population and happiness
revenue += Math.floor(20 * (population / 1000) * (happiness / 100));
break;
case 'industrial':
// Industrial revenue is steady but affects happiness
revenue += 25;
break;
case 'hospital':
// Hospitals cost money to maintain but increase population health
revenue -= 15;
break;
case 'school':
// Schools cost money to maintain but increase long-term property values
revenue -= 10;
break;
case 'police':
// Police stations cost money but increase safety and property values
revenue -= 5;
break;
}
}
// Add revenue to money
money += revenue;
updateStats();
}
// Main game update function
var lastCollectionTime = 0;
var gameWon = false;
game.update = function () {
gameTime++;
// Collect revenue every 10 seconds
if (gameTime - lastCollectionTime >= revenueInterval * 60) {
// 60 ticks = 1 second
collectRevenue();
lastCollectionTime = gameTime;
}
// Update difficulty based on city size
if (population > 2000 && revenueInterval > 5) {
revenueInterval = 5; // Faster revenue collection
}
// Handle people movement
for (var i = 0; i < people.length; i++) {
people[i].update();
}
// Spawn new people based on population
if (population > 0 && gameTime - lastPersonSpawn >= personSpawnRate) {
// Find residential buildings to spawn people at
var residentialTiles = [];
for (var i = 0; i < cityGrid.tiles.length; i++) {
if (cityGrid.tiles[i].type === 'residential') {
residentialTiles.push(cityGrid.tiles[i]);
}
}
if (residentialTiles.length > 0) {
// Calculate how many people to show (1 per 50 population, max 20)
var desiredPeopleCount = Math.min(Math.floor(population / 50), 20);
// If we have fewer people than desired, spawn more
if (people.length < desiredPeopleCount) {
var startTile = residentialTiles[Math.floor(Math.random() * residentialTiles.length)];
var person = new Person();
person.init(startTile);
people.push(person);
game.addChild(person);
}
lastPersonSpawn = gameTime;
}
}
};
// Handle game input
game.down = function (x, y, obj) {
// If we clicked on an empty area, deselect the current tile
if (!obj || obj === game) {
if (cityGrid.selectedTile) {
cityGrid.selectedTile.deselect();
cityGrid.selectedTile = null;
}
}
};
// Initialize the game
initGame();
Commercial building 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Grass tile 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Hospital building 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Road tile 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Industrial building 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Park tile 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
School tile 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Apartment tile 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Poilce Station tile 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Blank blue button 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows