User prompt
The button's text not come out of the button
User prompt
Move the city score to the top right and make one house and a road bellow it
User prompt
Make more types of buildings
User prompt
Make it the money decrease when bought a building and add people coming
User prompt
Make it so I can only place one building when purchased
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'stringify')' in or related to this line: 'storage.lastCityState = JSON.stringify(cityState);' Line Number: 456
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.lastCityState = cityState;' Line Number: 454 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
I can't see the buildings
Code edit (1 edits merged)
Please save this source code
User prompt
City Skylines: Metropolis Builder
Initial prompt
Make a city simulator game
/**** * 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();
===================================================================
--- original.js
+++ change.js
@@ -56,12 +56,12 @@
}
self.background.tint = backgroundColor;
var labelText = buildingType.charAt(0).toUpperCase() + buildingType.slice(1);
if (buildingType !== 'demolish') {
- labelText += ' ($' + cost + ')';
+ labelText += '\n($' + cost + ')';
}
self.label = new Text2(labelText, {
- size: 28,
+ size: 24,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
@@ -523,17 +523,24 @@
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") {
+ if (storage.lastCityState && storage.lastCityState !== "undefined" && storage.lastCityState !== "") {
try {
- // Parse the JSON string back to an array of objects
- var savedState = JSON.parse(storage.lastCityState);
- for (var i = 0; i < savedState.length; i++) {
- var tileData = savedState[i];
- var tile = cityGrid.getTileAt(tileData.x, tileData.y);
- if (tile) {
- tile.changeType(tileData.type);
+ // 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) {
@@ -603,22 +610,23 @@
type: tile.type
});
}
}
- // Convert cityState to JSON string before storing
+ // Convert cityState to string for storage (without using JSON.stringify)
try {
- // Check if JSON is available
- if (typeof JSON !== 'undefined' && JSON.stringify) {
- storage.lastCityState = JSON.stringify(cityState);
- } else {
- // Fallback: convert to simple string format
- storage.lastCityState = cityState.map(function (tile) {
- return tile.x + "," + tile.y + "," + tile.type;
- }).join("|");
+ // 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 = "[]";
+ storage.lastCityState = "";
}
// Check for win condition - 5000 population
if (population >= 5000 && !gameWon) {
gameWon = true;
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