Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Autonomous Ultra City Builder</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: 'Arial', sans-serif;
- background: linear-gradient(135deg, #1e3c72, #2a5298);
- color: white;
- overflow: hidden;
- }
- .game-container {
- width: 100vw;
- height: 100vh;
- display: flex;
- flex-direction: column;
- }
- .header {
- height: 80px;
- background: rgba(0, 0, 0, 0.8);
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 20px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
- }
- .logo {
- font-size: 24px;
- font-weight: bold;
- background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- text-shadow: 0 0 20px rgba(255, 107, 107, 0.5);
- }
- .stats {
- display: flex;
- gap: 30px;
- font-size: 16px;
- }
- .stat {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .stat-icon {
- width: 20px;
- height: 20px;
- background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- }
- .controls {
- display: flex;
- gap: 15px;
- }
- .control-btn {
- padding: 8px 16px;
- background: linear-gradient(45deg, #667eea, #764ba2);
- border: none;
- border-radius: 20px;
- color: white;
- cursor: pointer;
- font-weight: bold;
- transition: all 0.3s ease;
- }
- .control-btn:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
- }
- .game-area {
- flex: 1;
- position: relative;
- overflow: hidden;
- }
- .city-grid {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- display: grid;
- grid-template-columns: repeat(20, 1fr);
- grid-template-rows: repeat(15, 1fr);
- gap: 1px;
- background: rgba(0, 0, 0, 0.2);
- padding: 10px;
- }
- .cell {
- background: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 4px;
- position: relative;
- transition: all 0.3s ease;
- cursor: pointer;
- }
- .cell:hover {
- background: rgba(255, 255, 255, 0.2);
- transform: scale(1.05);
- }
- .cell.residential {
- background: linear-gradient(45deg, #4ecdc4, #44a08d);
- box-shadow: 0 0 10px rgba(78, 205, 196, 0.3);
- }
- .cell.commercial {
- background: linear-gradient(45deg, #f093fb, #f5576c);
- box-shadow: 0 0 10px rgba(240, 147, 251, 0.3);
- }
- .cell.industrial {
- background: linear-gradient(45deg, #ffecd2, #fcb69f);
- box-shadow: 0 0 10px rgba(252, 182, 159, 0.3);
- }
- .cell.park {
- background: linear-gradient(45deg, #a8edea, #fed6e3);
- box-shadow: 0 0 10px rgba(168, 237, 234, 0.3);
- }
- .cell.road {
- background: linear-gradient(45deg, #606c88, #3f4c6b);
- box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
- }
- .cell.power {
- background: linear-gradient(45deg, #ff9a9e, #fecfef);
- box-shadow: 0 0 15px rgba(255, 154, 158, 0.5);
- animation: pulse 2s infinite;
- }
- .cell.water {
- background: linear-gradient(45deg, #00b4db, #0083b0);
- box-shadow: 0 0 15px rgba(0, 180, 219, 0.5);
- animation: pulse 2s infinite;
- }
- @keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.7; }
- }
- .building {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 20px;
- text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
- }
- .sim {
- position: absolute;
- width: 10px;
- height: 10px;
- background: #ffff00;
- border-radius: 50%;
- box-shadow: 0 0 8px rgba(255, 255, 0, 0.8);
- transition: all 0.5s ease;
- z-index: 10;
- }
- .sim.happy {
- background: #00ff00;
- box-shadow: 0 0 8px rgba(0, 255, 0, 0.8);
- }
- .sim.unhappy {
- background: #ff0000;
- box-shadow: 0 0 8px rgba(255, 0, 0, 0.8);
- }
- .sim.working {
- background: #4169e1;
- box-shadow: 0 0 8px rgba(65, 105, 225, 0.8);
- }
- .sim.shopping {
- background: #ff69b4;
- box-shadow: 0 0 8px rgba(255, 105, 180, 0.8);
- }
- .sim.relaxing {
- background: #9370db;
- box-shadow: 0 0 8px rgba(147, 112, 219, 0.8);
- }
- .notification {
- position: absolute;
- top: 100px;
- right: 20px;
- background: rgba(0, 0, 0, 0.8);
- padding: 15px;
- border-radius: 10px;
- border-left: 4px solid #4ecdc4;
- max-width: 300px;
- animation: slideIn 0.5s ease;
- z-index: 100;
- }
- @keyframes slideIn {
- from { transform: translateX(100%); }
- to { transform: translateX(0); }
- }
- .speed-control {
- position: absolute;
- bottom: 20px;
- right: 20px;
- background: rgba(0, 0, 0, 0.8);
- padding: 10px;
- border-radius: 10px;
- display: flex;
- gap: 10px;
- }
- .speed-btn {
- width: 40px;
- height: 40px;
- border: none;
- border-radius: 50%;
- background: linear-gradient(45deg, #667eea, #764ba2);
- color: white;
- cursor: pointer;
- font-weight: bold;
- transition: all 0.3s ease;
- }
- .speed-btn:hover {
- transform: scale(1.1);
- }
- .speed-btn.active {
- background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
- box-shadow: 0 0 20px rgba(255, 107, 107, 0.5);
- }
- .info-panel {
- position: absolute;
- bottom: 20px;
- left: 20px;
- background: rgba(0, 0, 0, 0.8);
- padding: 15px;
- border-radius: 10px;
- min-width: 200px;
- font-size: 14px;
- }
- .building-queue {
- position: absolute;
- top: 100px;
- left: 20px;
- background: rgba(0, 0, 0, 0.8);
- padding: 15px;
- border-radius: 10px;
- max-width: 250px;
- }
- .queue-item {
- padding: 8px;
- margin: 5px 0;
- background: rgba(255, 255, 255, 0.1);
- border-radius: 5px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .progress-bar {
- width: 100%;
- height: 4px;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 2px;
- margin-top: 5px;
- overflow: hidden;
- }
- .progress-fill {
- height: 100%;
- background: linear-gradient(90deg, #4ecdc4, #44a08d);
- transition: width 0.3s ease;
- }
- .weather {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- z-index: 5;
- }
- .rain-drop {
- position: absolute;
- width: 2px;
- height: 15px;
- background: linear-gradient(transparent, rgba(255, 255, 255, 0.6));
- animation: rain linear infinite;
- }
- @keyframes rain {
- from { transform: translateY(-100px); }
- to { transform: translateY(calc(100vh + 100px)); }
- }
- .snow-flake {
- position: absolute;
- width: 5px;
- height: 5px;
- background: white;
- border-radius: 50%;
- animation: snow linear infinite;
- }
- @keyframes snow {
- from { transform: translateY(-100px); }
- to { transform: translateY(calc(100vh + 100px)); }
- }
- .building-menu {
- position: absolute;
- background: rgba(0, 0, 0, 0.9);
- border-radius: 10px;
- padding: 10px;
- display: none;
- flex-wrap: wrap;
- width: 180px;
- z-index: 20;
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
- }
- .building-option {
- width: 50px;
- height: 50px;
- margin: 5px;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 20px;
- cursor: pointer;
- transition: all 0.2s ease;
- }
- .building-option:hover {
- transform: scale(1.1);
- }
- .building-option.disabled {
- opacity: 0.3;
- cursor: not-allowed;
- }
- .happiness-effect {
- position: absolute;
- width: 30px;
- height: 30px;
- background: rgba(0, 255, 0, 0.2);
- border-radius: 50%;
- border: 2px solid rgba(0, 255, 0, 0.5);
- z-index: 5;
- pointer-events: none;
- }
- .cash-flow {
- position: absolute;
- top: 100px;
- right: 20px;
- background: rgba(0, 0, 0, 0.8);
- padding: 15px;
- border-radius: 10px;
- max-width: 300px;
- z-index: 90;
- }
- .cash-positive {
- color: #4ecdc4;
- }
- .cash-negative {
- color: #ff6b6b;
- }
- .water-profit-indicator {
- position: absolute;
- font-size: 12px;
- color: #4ecdc4;
- font-weight: bold;
- text-shadow: 0 0 5px rgba(0, 0, 0, 0.8);
- animation: floatUp 2s ease-out;
- z-index: 15;
- pointer-events: none;
- }
- @keyframes floatUp {
- 0% { transform: translateY(0); opacity: 1; }
- 100% { transform: translateY(-30px); opacity: 0; }
- }
- </style>
- </head>
- <body>
- <div class="game-container">
- <div class="header">
- <div class="logo">🏙️ Ultra City Builder</div>
- <div class="stats">
- <div class="stat">
- <div class="stat-icon">👥</div>
- <span id="population">0</span>
- </div>
- <div class="stat">
- <div class="stat-icon">💰</div>
- <span id="money">1000000</span>
- </div>
- <div class="stat">
- <div class="stat-icon">😊</div>
- <span id="happiness">100</span>%
- </div>
- <div class="stat">
- <div class="stat-icon">⚡</div>
- <span id="power">50</span>
- </div>
- <div class="stat">
- <div class="stat-icon">💧</div>
- <span id="water">0</span>
- </div>
- </div>
- <div class="controls">
- <button class="control-btn" onclick="toggleAutoMode(this)">Auto: ON</button>
- <button class="control-btn" onclick="addRandomEvent()">Random Event</button>
- <button class="control-btn" onclick="resetCity()">Reset</button>
- </div>
- </div>
- <div class="game-area">
- <div class="city-grid" id="cityGrid"></div>
- <div class="weather" id="weatherContainer"></div>
- <div class="building-menu" id="buildingMenu">
- <div class="building-option" style="background: linear-gradient(45deg, #4ecdc4, #44a08d);" onclick="buildSelected('residential')">🏠</div>
- <div class="building-option" style="background: linear-gradient(45deg, #f093fb, #f5576c);" onclick="buildSelected('commercial')">🏢</div>
- <div class="building-option" style="background: linear-gradient(45deg, #ffecd2, #fcb69f);" onclick="buildSelected('industrial')">🏭</div>
- <div class="building-option disabled" style="background: linear-gradient(45deg, #a8edea, #fed6e3);" onclick="buildSelected('park')" id="parkOption">🌳</div>
- <div class="building-option" style="background: linear-gradient(45deg, #606c88, #3f4c6b);" onclick="buildSelected('road')">🛣️</div>
- <div class="building-option" style="background: linear-gradient(45deg, #ff9a9e, #fecfef);" onclick="buildSelected('power')">⚡</div>
- <div class="building-option" style="background: linear-gradient(45deg, #00b4db, #0083b0);" onclick="buildSelected('water')">💧</div>
- </div>
- <div class="speed-control">
- <button class="speed-btn active" onclick="setSpeed(1, this)">1x</button>
- <button class="speed-btn" onclick="setSpeed(2, this)">2x</button>
- <button class="speed-btn" onclick="setSpeed(4, this)">4x</button>
- <button class="speed-btn" onclick="setSpeed(0, this)">⏸</button>
- </div>
- <div class="info-panel" id="infoPanel">
- <h3>City Status</h3>
- <div>Residential: <span id="residentialCount">0</span></div>
- <div>Commercial: <span id="commercialCount">0</span></div>
- <div>Industrial: <span id="industrialCount">0</span></div>
- <div>Parks: <span id="parkCount">0</span></div>
- <div>Roads: <span id="roadCount">0</span></div>
- <div>Power Plants: <span id="powerCount">0</span></div>
- <div>Water Plants: <span id="waterCount">0</span></div>
- <div>Ultra Sims™: <span id="simCount">0</span>/<span id="simCapacity">0</span></div>
- <div>Income: <span id="income">0</span>/sec</div>
- <div>Expenses: <span id="expenses">0</span>/sec</div>
- <div>Net: <span id="netCashflow">0</span>/sec</div>
- </div>
- <div class="building-queue" id="buildingQueue">
- <h3>🏗️ Auto Construction</h3>
- <div id="queueList"></div>
- </div>
- <div class="cash-flow">
- <h3>💰 Cash Flow</h3>
- <div>Income: +<span id="cashIncome">0</span>/sec</div>
- <div>Expenses: -<span id="cashExpenses">0</span>/sec</div>
- <div>Net: <span id="cashNet" class="cash-positive">+0</span>/sec</div>
- </div>
- </div>
- </div>
- <script>
- class WeatherSystem {
- constructor(city) {
- this.city = city;
- this.currentWeather = 'clear';
- this.container = document.getElementById('weatherContainer');
- this.weatherEffects = {
- 'rain': this.createRain.bind(this),
- 'snow': this.createSnow.bind(this),
- 'clear': this.clearWeather.bind(this)
- };
- this.interval = null;
- this.startRandomWeather();
- }
- startRandomWeather() {
- // Change weather randomly every 30-60 seconds
- this.interval = setInterval(() => {
- const weatherTypes = ['clear', 'rain', 'snow'];
- const newWeather = weatherTypes[Math.floor(Math.random() * weatherTypes.length)];
- this.changeWeather(newWeather);
- }, 30000 + Math.random() * 30000);
- }
- changeWeather(type) {
- this.currentWeather = type;
- this.container.innerHTML = '';
- if (type !== 'clear') {
- this.weatherEffects[type]();
- // Weather effects on city
- if (type === 'rain') {
- this.city.happiness -= 5;
- this.city.showNotification('🌧️ Rainy weather - Sims are less happy');
- } else if (type === 'snow') {
- this.city.happiness += 5;
- this.city.showNotification('❄️ Snowy weather - Sims are happier');
- }
- }
- }
- createRain() {
- for (let i = 0; i < 100; i++) {
- const drop = document.createElement('div');
- drop.className = 'rain-drop';
- drop.style.left = Math.random() * 100 + '%';
- drop.style.animationDuration = (0.5 + Math.random() * 0.5) + 's';
- drop.style.animationDelay = Math.random() * 5 + 's';
- this.container.appendChild(drop);
- }
- }
- createSnow() {
- for (let i = 0; i < 50; i++) {
- const flake = document.createElement('div');
- flake.className = 'snow-flake';
- flake.style.left = Math.random() * 100 + '%';
- flake.style.animationDuration = (3 + Math.random() * 2) + 's';
- flake.style.animationDelay = Math.random() * 5 + 's';
- this.container.appendChild(flake);
- }
- }
- clearWeather() {
- // Already cleared the container
- }
- }
- class DisasterSystem {
- constructor(city) {
- this.city = city;
- this.disasters = [
- {
- name: 'Earthquake',
- chance: 0.001,
- effect: () => this.earthquake()
- },
- {
- name: 'Tornado',
- chance: 0.0005,
- effect: () => this.tornado()
- },
- {
- name: 'Fire',
- chance: 0.002,
- effect: () => this.fire()
- }
- ];
- }
- checkForDisasters() {
- this.disasters.forEach(disaster => {
- if (Math.random() < disaster.chance * this.city.gameSpeed) {
- disaster.effect();
- }
- });
- }
- earthquake() {
- this.city.showNotification('⚠️ Earthquake! Buildings damaged');
- // Damage random buildings
- for (let row = 0; row < 15; row++) {
- for (let col = 0; col < 20; col++) {
- if (this.city.grid[row][col] && Math.random() < 0.2) {
- // 20% chance to damage each building
- if (Math.random() < 0.3) { // 30% chance to destroy completely
- this.city.grid[row][col] = null;
- const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
- cell.className = 'cell';
- cell.innerHTML = '';
- }
- }
- }
- }
- this.city.happiness -= 10;
- }
- tornado() {
- this.city.showNotification('🌪️ Tornado! Path of destruction');
- // Create a path of destruction
- const startCol = Math.floor(Math.random() * 20);
- for (let row = 0; row < 15; row++) {
- const col = Math.max(0, Math.min(19, startCol + Math.floor(Math.random() * 5) - 2));
- if (this.city.grid[row][col]) {
- this.city.grid[row][col] = null;
- const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
- cell.className = 'cell';
- cell.innerHTML = '';
- }
- }
- this.city.happiness -= 15;
- }
- fire() {
- this.city.showNotification('🔥 Fire! Spreads to adjacent buildings');
- // Start a fire at a random building
- const startRow = Math.floor(Math.random() * 15);
- const startCol = Math.floor(Math.random() * 20);
- if (this.city.grid[startRow][startCol]) {
- this.spreadFire(startRow, startCol, 3);
- }
- }
- spreadFire(row, col, intensity) {
- if (intensity <= 0 || row < 0 || row >= 15 || col < 0 || col >= 20 || !this.city.grid[row][col]) {
- return;
- }
- // Destroy this building
- this.city.grid[row][col] = null;
- const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
- cell.className = 'cell';
- cell.innerHTML = '';
- // Spread to adjacent cells with decreasing intensity
- const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
- for (const [dRow, dCol] of directions) {
- if (Math.random() < 0.6) { // 60% chance to spread in each direction
- this.spreadFire(row + dRow, col + dCol, intensity - 1);
- }
- }
- }
- }
- class SimAI {
- static processSimBehaviors(city) {
- // Advanced AI behaviors for sims
- city.sims.forEach(sim => {
- // Sims with homes and jobs have higher happiness
- if (sim.home && sim.work) {
- sim.happiness += 0.1;
- }
- // Sims without homes or jobs get unhappy
- if (!sim.home || !sim.work) {
- sim.happiness -= 0.2;
- }
- // If sim is very unhappy, they might leave the city
- if (sim.happiness < 10 && Math.random() < 0.01) {
- const index = city.sims.indexOf(sim);
- if (index > -1) {
- city.sims.splice(index, 1);
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) simEl.remove();
- city.population--;
- city.showNotification('😢 A sim left the city due to unhappiness');
- }
- }
- // Sims interact with commercial/industrial buildings
- if (sim.state === 'wandering' && Math.random() < 0.05) {
- const buildingTypes = ['commercial', 'industrial'];
- const randomType = buildingTypes[Math.floor(Math.random() * buildingTypes.length)];
- const building = city.findNearestBuilding(sim, randomType);
- if (building && Math.random() < 0.3) {
- sim.targetX = (building.col * 5) + Math.random() * 5;
- sim.targetY = (building.row * 6.67) + Math.random() * 6.67;
- sim.state = randomType === 'commercial' ? 'shopping' : 'working';
- // Change sim appearance based on activity
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) {
- simEl.classList.remove('happy', 'unhappy');
- simEl.classList.add(randomType === 'commercial' ? 'shopping' : 'working');
- }
- // Set timeout to return to normal state
- setTimeout(() => {
- sim.state = 'wandering';
- if (simEl) {
- simEl.classList.remove('shopping', 'working');
- if (sim.happiness > 70) simEl.classList.add('happy');
- else if (sim.happiness < 30) simEl.classList.add('unhappy');
- }
- // Increase happiness from shopping or working
- sim.happiness += randomType === 'commercial' ? 5 : 3;
- sim.happiness = Math.min(100, sim.happiness);
- }, 5000 + Math.random() * 5000);
- }
- }
- // Sims visit parks if available
- if (sim.state === 'wandering' && Math.random() < 0.03 && city.getBuildingCounts().park > 0) {
- const park = city.findNearestBuilding(sim, 'park');
- if (park) {
- sim.targetX = (park.col * 5) + Math.random() * 5;
- sim.targetY = (park.row * 6.67) + Math.random() * 6.67;
- sim.state = 'relaxing';
- // Change sim appearance
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) {
- simEl.classList.remove('happy', 'unhappy', 'shopping', 'working');
- simEl.classList.add('relaxing');
- }
- // Set timeout to return to normal state
- setTimeout(() => {
- sim.state = 'wandering';
- if (simEl) {
- simEl.classList.remove('relaxing');
- if (sim.happiness > 70) simEl.classList.add('happy');
- else if (sim.happiness < 30) simEl.classList.add('unhappy');
- }
- // Big happiness boost from visiting parks
- sim.happiness += 15;
- sim.happiness = Math.min(100, sim.happiness);
- }, 8000 + Math.random() * 5000);
- }
- }
- // Clever sims with coder knowledge might build their own parks
- if (sim.coder && sim.state === 'wandering' && Math.random() < 0.005 * city.gameSpeed) {
- if (city.water > 0 && city.happiness < 80 && city.money >= city.buildingTypes.park.cost) {
- const simRow = Math.floor(sim.y * 15 / 100);
- const simCol = Math.floor(sim.x * 20 / 100);
- let found = false;
- for (let dr = -2; dr <= 2 && !found; dr++) {
- for (let dc = -2; dc <= 2 && !found; dc++) {
- const r = simRow + dr;
- const c = simCol + dc;
- if (r >= 0 && r < 15 && c >= 0 && c < 20 && !city.grid[r][c]) {
- city.placeBuilding(r, c, 'park');
- city.showNotification('🧑💻 A clever Ultra Sim™ coded a new park!');
- found = true;
- }
- }
- }
- }
- }
- });
- // Adjust city happiness based on average sim happiness
- if (city.sims.length > 0) {
- const totalHappiness = city.sims.reduce((sum, sim) => sum + sim.happiness, 0);
- city.happiness = totalHappiness / city.sims.length;
- }
- // Check if we need to spawn more sims based on residential capacity
- const residentialCount = city.getBuildingCounts().residential;
- const simCapacity = residentialCount * 5;
- const currentSims = city.sims.length;
- if (currentSims < simCapacity && city.happiness > 60 && Math.random() < 0.1) {
- const newSims = Math.min(3, simCapacity - currentSims);
- city.spawnSims(newSims);
- city.showNotification(`🏠 ${newSims} new Ultra Sims™ moved to the city!`);
- }
- }
- }
- class UltraCity {
- constructor() {
- this.grid = Array(15).fill().map(() => Array(20).fill(null));
- this.sims = [];
- this.money = 1000000;
- this.population = 0;
- this.happiness = 100;
- this.power = 50;
- this.water = 0;
- this.gameSpeed = 1;
- this.autoMode = true;
- this.buildQueue = [];
- this.notifications = [];
- this.lastUpdate = Date.now();
- this.selectedCell = null;
- this.parkEffects = [];
- this.income = 0;
- this.expenses = 0;
- this.netCashflow = 0;
- this.waterProfitEnabled = false;
- this.waterProfitThreshold = 100; // Population needed for water to generate profit
- this.buildingTypes = {
- residential: { cost: 2200, icon: '🏠', provides: 'population', requirement: 'power', income: 200, upkeep: 100 },
- commercial: { cost: 3300, icon: '🏢', provides: 'money', requirement: 'population', income: 52000, upkeep: 200 },
- industrial: { cost: 4400, icon: '🏭', provides: 'jobs', requirement: 'power', income: 31000, upkeep: 300 },
- park: { cost: 6500, icon: '🌳', provides: 'happiness', requirement: 'water', income: 0, upkeep: 200 },
- road: { cost: 500, icon: '🛣️', provides: 'connection', requirement: null, income: 0, upkeep: 0 },
- power: { cost: 11000, icon: '⚡', provides: 'power', requirement: null, income: 0, upkeep: 400 },
- water: { cost: 13000, icon: '💧', provides: 'water', requirement: 'power', income: 0, upkeep: 0 } // Changed upkeep to 0
- };
- this.init();
- }
- init() {
- this.createGrid();
- this.spawnInitialBuildings();
- this.updateBuildingOptions();
- this.startGameLoop();
- }
- createGrid() {
- const grid = document.getElementById('cityGrid');
- grid.innerHTML = '';
- for (let row = 0; row < 15; row++) {
- for (let col = 0; col < 20; col++) {
- const cell = document.createElement('div');
- cell.className = 'cell';
- cell.dataset.row = row;
- cell.dataset.col = col;
- cell.addEventListener('click', (e) => this.handleCellClick(e, row, col));
- grid.appendChild(cell);
- }
- }
- }
- spawnInitialBuildings() {
- // Start with a power plant
- this.placeBuilding(7, 10, 'power');
- // Add some initial roads
- for (let i = 5; i < 15; i++) {
- this.placeBuilding(7, i, 'road');
- }
- // Add initial residential
- this.placeBuilding(6, 8, 'residential');
- this.placeBuilding(8, 12, 'residential');
- this.spawnSims(5);
- this.updateDisplay();
- }
- handleCellClick(e, row, col) {
- if (this.autoMode) {
- // Show building menu in auto mode
- this.selectedCell = { row, col };
- const menu = document.getElementById('buildingMenu');
- menu.style.display = 'flex';
- menu.style.left = e.clientX + 'px';
- menu.style.top = e.clientY + 'px';
- } else {
- const availableTypes = Object.keys(this.buildingTypes);
- const randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
- this.placeBuilding(row, col, randomType);
- }
- }
- buildSelected(type) {
- if (this.selectedCell) {
- this.placeBuilding(this.selectedCell.row, this.selectedCell.col, type);
- this.selectedCell = null;
- document.getElementById('buildingMenu').style.display = 'none';
- }
- }
- updateBuildingOptions() {
- const parkOption = document.getElementById('parkOption');
- parkOption.classList.add('disabled');
- }
- placeBuilding(row, col, type) {
- if (this.grid[row][col] || this.money < this.buildingTypes[type].cost) {
- return false;
- }
- // Check if requirements are met
- if (this.buildingTypes[type].requirement) {
- if (this.buildingTypes[type].requirement === 'power' && this.power <= 0) {
- this.showNotification('Need more power to build this!');
- return false;
- }
- if (this.buildingTypes[type].requirement === 'water' && this.water <= 0) {
- this.showNotification('Need water supply to build parks!');
- return false;
- }
- if (this.buildingTypes[type].requirement === 'population' && this.population <= 10) {
- this.showNotification('Need more population to build this!');
- return false;
- }
- }
- this.grid[row][col] = { type, level: 1, age: 0 };
- this.money -= this.buildingTypes[type].cost;
- const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
- cell.className = `cell ${type}`;
- cell.innerHTML = `<div class="building">${this.buildingTypes[type].icon}</div>`;
- // If it's a park, create happiness effect
- if (type === 'park') {
- this.createParkEffect(row, col);
- }
- this.showNotification(`Built ${type} at (${row}, ${col})`);
- this.updateBuildingOptions();
- this.updateDisplay();
- return true;
- }
- createParkEffect(row, col) {
- // Create visual effect for park happiness area
- const grid = document.getElementById('cityGrid');
- for (let r = Math.max(0, row-2); r <= Math.min(14, row+2); r++) {
- for (let c = Math.max(0, col-2); c <= Math.min(19, col+2); c++) {
- const effect = document.createElement('div');
- effect.className = 'happiness-effect';
- effect.style.left = (c * 5) + '%';
- effect.style.top = (r * 6.67) + '%';
- effect.dataset.row = r;
- effect.dataset.col = c;
- grid.appendChild(effect);
- this.parkEffects.push(effect);
- }
- }
- }
- spawnSims(count) {
- for (let i = 0; i < count; i++) {
- const sim = {
- id: Date.now() + i,
- x: Math.random() * 100,
- y: Math.random() * 100,
- targetX: Math.random() * 100,
- targetY: Math.random() * 100,
- happiness: 50 + Math.random() * 50,
- home: null,
- work: null,
- state: 'wandering', // wandering, going_home, going_work, at_home, at_work, shopping, working, relaxing
- coder: Math.random() < 0.1 // 10% chance to have coder knowledge
- };
- this.sims.push(sim);
- this.createSimElement(sim);
- }
- this.population += count;
- }
- createSimElement(sim) {
- const simEl = document.createElement('div');
- simEl.className = 'sim';
- simEl.id = `sim-${sim.id}`;
- simEl.style.left = sim.x + '%';
- simEl.style.top = sim.y + '%';
- if (sim.happiness > 70) simEl.classList.add('happy');
- else if (sim.happiness < 30) simEl.classList.add('unhappy');
- document.querySelector('.city-grid').appendChild(simEl);
- }
- updateSims() {
- this.sims.forEach(sim => {
- // AI decision making for sims
- if (Math.random() < 0.1) { // 10% chance to make a decision
- this.makeSimDecision(sim);
- }
- // Move towards target
- const dx = sim.targetX - sim.x;
- const dy = sim.targetY - sim.y;
- const distance = Math.sqrt(dx * dx + dy * dy);
- if (distance > 2) {
- sim.x += (dx / distance) * 2;
- sim.y += (dy / distance) * 2;
- } else {
- // Reached target, pick new one
- sim.targetX = Math.random() * 100;
- sim.targetY = Math.random() * 100;
- }
- // Update happiness based on city conditions
- sim.happiness += (this.happiness - 50) * 0.01;
- sim.happiness = Math.max(0, Math.min(100, sim.happiness));
- // Update visual
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) {
- simEl.style.left = sim.x + '%';
- simEl.style.top = sim.y + '%';
- simEl.className = 'sim';
- if (sim.happiness > 70) simEl.classList.add('happy');
- else if (sim.happiness < 30) simEl.classList.add('unhappy');
- }
- });
- }
- makeSimDecision(sim) {
- const decisions = [
- 'find_home',
- 'find_work',
- 'visit_park',
- 'go_shopping',
- 'wander'
- ];
- const decision = decisions[Math.floor(Math.random() * decisions.length)];
- switch (decision) {
- case 'find_home':
- if (!sim.home) {
- const residential = this.findNearestBuilding(sim, 'residential');
- if (residential) {
- sim.home = residential;
- sim.targetX = (residential.col * 5) + Math.random() * 5;
- sim.targetY = (residential.row * 6.67) + Math.random() * 6.67;
- sim.state = 'going_home';
- }
- }
- break;
- case 'find_work':
- if (!sim.work) {
- const workplace = this.findNearestBuilding(sim, Math.random() > 0.5 ? 'commercial' : 'industrial');
- if (workplace) {
- sim.work = workplace;
- sim.targetX = (workplace.col * 5) + Math.random() * 5;
- sim.targetY = (workplace.row * 6.67) + Math.random() * 6.67;
- sim.state = 'going_work';
- }
- }
- break;
- case 'visit_park':
- if (this.water > 0) { // Only visit parks if water is available
- const park = this.findNearestBuilding(sim, 'park');
- if (park) {
- sim.targetX = (park.col * 5) + Math.random() * 5;
- sim.targetY = (park.row * 6.67) + Math.random() * 6.67;
- sim.state = 'relaxing';
- // Set timeout to return to normal state
- setTimeout(() => {
- if (sim.state === 'relaxing') {
- sim.state = 'wandering';
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) {
- simEl.classList.remove('relaxing');
- if (sim.happiness > 70) simEl.classList.add('happy');
- else if (sim.happiness < 30) simEl.classList.add('unhappy');
- }
- // Big happiness boost from parks
- sim.happiness += 15;
- sim.happiness = Math.min(100, sim.happiness);
- }
- }, 8000 + Math.random() * 5000);
- }
- }
- break;
- case 'go_shopping':
- const commercial = this.findNearestBuilding(sim, 'commercial');
- if (commercial) {
- sim.targetX = (commercial.col * 5) + Math.random() * 5;
- sim.targetY = (commercial.row * 6.67) + Math.random() * 6.67;
- sim.state = 'shopping';
- // Set timeout to return to normal state
- setTimeout(() => {
- if (sim.state === 'shopping') {
- sim.state = 'wandering';
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) {
- simEl.classList.remove('shopping');
- if (sim.happiness > 70) simEl.classList.add('happy');
- else if (sim.happiness < 30) simEl.classList.add('unhappy');
- }
- sim.happiness += 5;
- sim.happiness = Math.min(100, sim.happiness);
- }
- }, 3000 + Math.random() * 3000);
- }
- break;
- }
- }
- findNearestBuilding(sim, type) {
- let nearest = null;
- let nearestDistance = Infinity;
- for (let row = 0; row < 15; row++) {
- for (let col = 0; col < 20; col++) {
- if (this.grid[row][col] && this.grid[row][col].type === type) {
- const dx = (col * 5) - sim.x;
- const dy = (row * 6.67) - sim.y;
- const distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < nearestDistance) {
- nearestDistance = distance;
- nearest = { row, col, type };
- }
- }
- }
- }
- return nearest;
- }
- autoExpand() {
- if (!this.autoMode) return;
- const needs = this.analyzeNeeds();
- if (needs.length > 0 && this.money >= this.buildingTypes[needs[0]].cost) {
- const emptySpots = this.findEmptySpots();
- if (emptySpots.length > 0) {
- const spot = emptySpots[Math.floor(Math.random() * emptySpots.length)];
- // Add to queue first
- this.buildQueue.push({
- type: needs[0],
- row: spot.row,
- col: spot.col,
- progress: 0
- });
- this.updateBuildingQueue();
- }
- }
- // Process building queue
- this.processBuildingQueue();
- }
- analyzeNeeds() {
- const buildingCounts = this.getBuildingCounts();
- const needs = [];
- // Need more power?
- if (buildingCounts.power < Math.ceil(this.population / 50)) {
- needs.push('power');
- }
- // Need more water?
- if (buildingCounts.power > 0 && buildingCounts.water < Math.ceil(this.population / 60)) {
- needs.push('water');
- }
- // Need more residential?
- const residentialCapacity = buildingCounts.residential * 5;
- if (this.sims.length >= residentialCapacity * 0.8) {
- needs.push('residential');
- }
- // Need more commercial/industrial?
- if (buildingCounts.commercial + buildingCounts.industrial < Math.ceil(this.population / 15)) {
- needs.push(Math.random() > 0.5 ? 'commercial' : 'industrial');
- }
- // Need more happiness? (only if water is available)
- if (this.water > 0 && this.happiness < 70) {
- needs.push('park');
- }
- // Need more roads?
- if (buildingCounts.road < (buildingCounts.residential + buildingCounts.commercial + buildingCounts.industrial)) {
- needs.push('road');
- }
- return needs;
- }
- findEmptySpots() {
- const spots = [];
- for (let row = 0; row < 15; row++) {
- for (let col = 0; col < 20; col++) {
- if (!this.grid[row][col]) {
- spots.push({ row, col });
- }
- }
- }
- return spots;
- }
- processBuildingQueue() {
- this.buildQueue = this.buildQueue.filter(item => {
- item.progress += 2 + this.gameSpeed;
- if (item.progress >= 100) {
- this.placeBuilding(item.row, item.col, item.type);
- return false; // Remove from queue
- }
- return true; // Keep in queue
- });
- this.updateBuildingQueue();
- }
- updateBuildingQueue() {
- const queueList = document.getElementById('queueList');
- queueList.innerHTML = '';
- this.buildQueue.forEach(item => {
- const queueItem = document.createElement('div');
- queueItem.className = 'queue-item';
- queueItem.innerHTML = `
- <span>${this.buildingTypes[item.type].icon} ${item.type}</span>
- <div class="progress-bar">
- <div class="progress-fill" style="width: ${item.progress}%"></div>
- </div>
- `;
- queueList.appendChild(queueItem);
- });
- }
- getBuildingCounts() {
- const counts = {
- residential: 0, commercial: 0, industrial: 0,
- park: 0, road: 0, power: 0, water: 0
- };
- for (let row = 0; row < 15; row++) {
- for (let col = 0; col < 20; col++) {
- if (this.grid[row][col]) {
- counts[this.grid[row][col].type]++;
- }
- }
- }
- return counts;
- }
- updateEconomy() {
- const counts = this.getBuildingCounts();
- // Calculate income
- this.income = (counts.residential * this.buildingTypes.residential.income) +
- (counts.commercial * this.buildingTypes.commercial.income) +
- (counts.industrial * this.buildingTypes.industrial.income);
- // NEW: Water profit generation when enough Ultra Sims™ are present
- let waterProfit = 0;
- if (this.population >= this.waterProfitThreshold) {
- // Enable water profit if not already enabled
- if (!this.waterProfitEnabled) {
- this.waterProfitEnabled = true;
- this.showNotification("💧 Water facilities now generating income with enough Ultra Sims™!");
- }
- // Calculate water profit (more sims = more profit)
- waterProfit = counts.water * (this.population / 10);
- this.income += waterProfit;
- // Show visual indicators on water buildings
- if (waterProfit > 0 && Math.random() < 0.1) {
- this.showWaterProfitIndicators(counts.water);
- }
- } else {
- this.waterProfitEnabled = false;
- }
- // Calculate expenses (water upkeep is now 0)
- this.expenses = (counts.residential * this.buildingTypes.residential.upkeep) +
- (counts.commercial * this.buildingTypes.commercial.upkeep) +
- (counts.industrial * this.buildingTypes.industrial.upkeep) +
- (counts.park * this.buildingTypes.park.upkeep) +
- (counts.power * this.buildingTypes.power.upkeep);
- // Update money
- this.money += (this.income - this.expenses);
- // Update net cashflow
- this.netCashflow = this.income - this.expenses;
- // Update happiness
- this.happiness = 50 + (counts.park * 15) - (counts.industrial * 2); // Parks provide more happiness
- this.happiness = Math.max(0, Math.min(100, this.happiness));
- // Update power
- this.power = (counts.power * 100) - ((counts.residential + counts.commercial + counts.industrial) * 10);
- this.power = Math.max(0, this.power);
- // Update water
- this.water = (counts.water * 100) - ((counts.residential + counts.commercial + counts.industrial) * 8);
- this.water = Math.max(0, this.water);
- // Update cash flow display
- this.updateCashFlowDisplay();
- }
- showWaterProfitIndicators(waterCount) {
- // Show profit indicators on water buildings
- for (let row = 0; row < 15; row++) {
- for (let col = 0; col < 20; col++) {
- if (this.grid[row][col] && this.grid[row][col].type === 'water') {
- const cell = document.querySelector(`[data-row="${row}"][data-col="${col}"]`);
- if (cell) {
- const indicator = document.createElement('div');
- indicator.className = 'water-profit-indicator';
- indicator.textContent = '+$' + Math.round(this.population / 10);
- indicator.style.left = (col * 5 + 2.5) + '%';
- indicator.style.top = (row * 6.67 + 3.3) + '%';
- document.querySelector('.city-grid').appendChild(indicator);
- // Remove indicator after animation completes
- setTimeout(() => {
- indicator.remove();
- }, 2000);
- }
- }
- }
- }
- }
- updateCashFlowDisplay() {
- document.getElementById('cashIncome').textContent = this.income;
- document.getElementById('cashExpenses').textContent = this.expenses;
- const netElement = document.getElementById('cashNet');
- netElement.textContent = (this.netCashflow >= 0 ? '+' : '') + this.netCashflow;
- if (this.netCashflow >= 0) {
- netElement.className = 'cash-positive';
- } else {
- netElement.className = 'cash-negative';
- }
- }
- showNotification(message) {
- const notification = document.createElement('div');
- notification.className = 'notification';
- notification.textContent = message;
- document.body.appendChild(notification);
- setTimeout(() => {
- notification.remove();
- }, 3000);
- }
- updateDisplay() {
- document.getElementById('population').textContent = this.population;
- document.getElementById('money').textContent = Math.floor(this.money);
- document.getElementById('happiness').textContent = Math.round(this.happiness);
- document.getElementById('power').textContent = this.power;
- document.getElementById('water').textContent = this.water;
- const counts = this.getBuildingCounts();
- document.getElementById('residentialCount').textContent = counts.residential;
- document.getElementById('commercialCount').textContent = counts.commercial;
- document.getElementById('industrialCount').textContent = counts.industrial;
- document.getElementById('parkCount').textContent = counts.park;
- document.getElementById('roadCount').textContent = counts.road;
- document.getElementById('powerCount').textContent = counts.power;
- document.getElementById('waterCount').textContent = counts.water;
- document.getElementById('simCount').textContent = this.sims.length;
- document.getElementById('simCapacity').textContent = counts.residential * 5;
- document.getElementById('income').textContent = this.income;
- document.getElementById('expenses').textContent = this.expenses;
- document.getElementById('netCashflow').textContent = this.netCashflow;
- }
- startGameLoop() {
- // Initialize systems
- this.weatherSystem = new WeatherSystem(this);
- this.disasterSystem = new DisasterSystem(this);
- const gameLoop = () => {
- if (this.gameSpeed > 0) {
- const now = Date.now();
- const deltaTime = (now - this.lastUpdate) * this.gameSpeed;
- if (deltaTime >= 1000) { // Update every second (adjusted by speed)
- this.updateSims();
- this.autoExpand();
- this.updateEconomy();
- this.updateDisplay();
- this.disasterSystem.checkForDisasters();
- // Process advanced AI behaviors
- SimAI.processSimBehaviors(this);
- this.lastUpdate = now;
- }
- }
- requestAnimationFrame(gameLoop);
- };
- gameLoop();
- }
- setSpeed(speed, button) {
- this.gameSpeed = speed;
- document.querySelectorAll('.speed-btn').forEach(btn => btn.classList.remove('active'));
- button.classList.add('active');
- }
- toggleAutoMode(button) {
- this.autoMode = !this.autoMode;
- button.textContent = `Auto: ${this.autoMode ? 'ON' : 'OFF'}`;
- }
- addRandomEvent() {
- const events = [
- { type: 'boom', message: '💰 Economic boom! +2000 money', effect: () => this.money += 2000 },
- { type: 'festival', message: '🎉 City festival! +20 happiness', effect: () => this.happiness += 20 },
- { type: 'migration', message: '🏃♂️ New residents arrive!', effect: () => this.spawnSims(5) },
- { type: 'blackout', message: '⚡ Power outage! -50 power', effect: () => this.power = Math.max(0, this.power - 50) },
- { type: 'drought', message: '🏜️ Water shortage! -30 water', effect: () => this.water = Math.max(0, this.water - 30) },
- { type: 'discovery', message: '🔬 Technology breakthrough! Buildings cost 25% less for 30 seconds', effect: () => {
- Object.keys(this.buildingTypes).forEach(type => {
- this.buildingTypes[type].cost *= 0.75;
- });
- setTimeout(() => {
- Object.keys(this.buildingTypes).forEach(type => {
- this.buildingTypes[type].cost /= 0.75;
- });
- }, 30000);
- }}
- ];
- const event = events[Math.floor(Math.random() * events.length)];
- event.effect();
- this.showNotification(event.message);
- }
- resetCity() {
- this.grid = Array(15).fill().map(() => Array(20).fill(null));
- this.sims.forEach(sim => {
- const simEl = document.getElementById(`sim-${sim.id}`);
- if (simEl) simEl.remove();
- });
- this.sims = [];
- this.money = 1000000;
- this.population = 0;
- this.happiness = 100;
- this.power = 50;
- this.water = 0;
- this.buildQueue = [];
- this.income = 0;
- this.expenses = 0;
- this.netCashflow = 0;
- this.waterProfitEnabled = false;
- // Remove park effects
- this.parkEffects.forEach(effect => effect.remove());
- this.parkEffects = [];
- this.createGrid();
- this.spawnInitialBuildings();
- this.updateBuildingOptions();
- this.updateCashFlowDisplay();
- this.showNotification('🏗️ City reset! Starting fresh...');
- }
- }
- // Global functions for buttons
- let city;
- function setSpeed(speed, button) {
- if (city) city.setSpeed(speed, button);
- }
- function toggleAutoMode(button) {
- if (city) city.toggleAutoMode(button);
- }
- function addRandomEvent() {
- if (city) city.addRandomEvent();
- }
- function resetCity() {
- if (city) city.resetCity();
- }
- function buildSelected(type) {
- if (city) city.buildSelected(type);
- }
- // Close building menu when clicking elsewhere
- document.addEventListener('click', function(e) {
- const menu = document.getElementById('buildingMenu');
- if (menu.style.display === 'flex' && !menu.contains(e.target)) {
- menu.style.display = 'none';
- if (city) city.selectedCell = null;
- }
- });
- // Initialize the game when the page loads
- window.onload = function() {
- city = new UltraCity();
- };
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment