my1 / index.html
aicoding101's picture
Add 3 files
4216bef verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple SimCity</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<style>
#game-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
#ui-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
pointer-events: none;
z-index: 10;
}
.building-option {
pointer-events: all;
transition: all 0.2s;
}
.building-option:hover {
transform: scale(1.05);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
}
.selected {
border: 2px solid white;
box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
}
#grid-highlight {
position: absolute;
background-color: rgba(255, 255, 255, 0.3);
border: 1px dashed white;
pointer-events: none;
z-index: 5;
}
</style>
</head>
<body class="bg-gray-900 text-white">
<div id="game-container">
<div id="ui-overlay" class="p-4">
<div class="flex justify-between items-start">
<!-- Building selection panel -->
<div class="bg-gray-800 bg-opacity-80 rounded-lg p-4 shadow-lg">
<h2 class="text-xl font-bold mb-3">Buildings</h2>
<div class="grid grid-cols-3 gap-3">
<div class="building-option bg-blue-600 rounded p-2 cursor-pointer text-center" data-type="house">
<div class="h-12 w-12 bg-blue-400 mx-auto mb-1"></div>
<span>House</span>
</div>
<div class="building-option bg-green-600 rounded p-2 cursor-pointer text-center" data-type="office">
<div class="h-12 w-12 bg-green-400 mx-auto mb-1"></div>
<span>Office</span>
</div>
<div class="building-option bg-yellow-600 rounded p-2 cursor-pointer text-center" data-type="park">
<div class="h-12 w-12 bg-yellow-400 mx-auto mb-1"></div>
<span>Park</span>
</div>
<div class="building-option bg-red-600 rounded p-2 cursor-pointer text-center" data-type="hospital">
<div class="h-12 w-12 bg-red-400 mx-auto mb-1"></div>
<span>Hospital</span>
</div>
<div class="building-option bg-purple-600 rounded p-2 cursor-pointer text-center" data-type="school">
<div class="h-12 w-12 bg-purple-400 mx-auto mb-1"></div>
<span>School</span>
</div>
<div class="building-option bg-gray-600 rounded p-2 cursor-pointer text-center" data-type="road">
<div class="h-12 w-12 bg-gray-400 mx-auto mb-1"></div>
<span>Road</span>
</div>
</div>
</div>
<!-- Stats panel -->
<div class="bg-gray-800 bg-opacity-80 rounded-lg p-4 shadow-lg">
<h2 class="text-xl font-bold mb-3">City Stats</h2>
<div class="space-y-2">
<div>Population: <span id="population">0</span></div>
<div>Buildings: <span id="building-count">0</span></div>
<div>Money: $<span id="money">10000</span></div>
</div>
<button id="demolish-btn" class="mt-4 bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded pointer-events-all">
Demolish Mode
</button>
</div>
</div>
<!-- Bottom panel -->
<div class="absolute bottom-4 left-0 w-full flex justify-center">
<div class="bg-gray-800 bg-opacity-80 rounded-lg p-3 shadow-lg">
<div id="message" class="text-center">Select a building to place</div>
</div>
</div>
</div>
<div id="grid-highlight"></div>
</div>
<script>
// Game variables
let selectedBuildingType = null;
let demolishMode = false;
let money = 10000;
let population = 0;
let buildingCount = 0;
// Building costs
const buildingCosts = {
house: 500,
office: 1000,
park: 300,
hospital: 1500,
school: 1200,
road: 100
};
// Building population values
const buildingPopulation = {
house: 10,
office: -5, // Offices reduce population (workers come from houses)
park: 0,
hospital: 0,
school: 0,
road: 0
};
// Three.js variables
let scene, camera, renderer, controls;
let grid = [];
const gridSize = 20;
const cellSize = 2;
const buildings = [];
// Initialize the game
init();
function init() {
// Set up Three.js scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Set up camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(20, 30, 20);
camera.lookAt(0, 0, 0);
// Set up renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('game-container').appendChild(renderer.domElement);
// Add orbit controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Add lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Create ground grid
createGround();
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Set up UI event listeners
setupUI();
// Start animation loop
animate();
}
function createGround() {
// Create ground plane
const groundGeometry = new THREE.PlaneGeometry(gridSize * cellSize, gridSize * cellSize);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3a5f0b,
side: THREE.DoubleSide
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
// Create grid lines
const gridHelper = new THREE.GridHelper(gridSize * cellSize, gridSize);
scene.add(gridHelper);
// Initialize grid array
for (let i = 0; i < gridSize; i++) {
grid[i] = [];
for (let j = 0; j < gridSize; j++) {
grid[i][j] = null; // null means empty cell
}
}
}
function setupUI() {
// Building selection
document.querySelectorAll('.building-option').forEach(option => {
option.addEventListener('click', function() {
selectedBuildingType = this.getAttribute('data-type');
demolishMode = false;
// Update UI
document.querySelectorAll('.building-option').forEach(el => el.classList.remove('selected'));
this.classList.add('selected');
document.getElementById('demolish-btn').classList.remove('bg-red-700');
document.getElementById('demolish-btn').classList.add('bg-red-600');
document.getElementById('message').textContent = `Selected: ${selectedBuildingType.charAt(0).toUpperCase() + selectedBuildingType.slice(1)} (Cost: $${buildingCosts[selectedBuildingType]})`;
});
});
// Demolish mode button
document.getElementById('demolish-btn').addEventListener('click', function() {
demolishMode = !demolishMode;
selectedBuildingType = null;
// Update UI
document.querySelectorAll('.building-option').forEach(el => el.classList.remove('selected'));
if (demolishMode) {
this.classList.remove('bg-red-600');
this.classList.add('bg-red-700');
document.getElementById('message').textContent = 'Demolish mode active - Click buildings to remove them';
} else {
this.classList.remove('bg-red-700');
this.classList.add('bg-red-600');
document.getElementById('message').textContent = 'Select a building to place';
}
});
// Mouse move for grid highlight
document.addEventListener('mousemove', onMouseMove);
// Click to place building
document.addEventListener('click', onClick);
}
function onMouseMove(event) {
if (!selectedBuildingType && !demolishMode) {
document.getElementById('grid-highlight').style.display = 'none';
return;
}
// Get mouse position in normalized device coordinates (-1 to +1)
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// Raycast to find intersection with ground
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
const point = intersects[0].point;
// Snap to grid
const gridX = Math.round(point.x / cellSize) * cellSize;
const gridZ = Math.round(point.z / cellSize) * cellSize;
// Convert to grid coordinates
const gridI = Math.round(point.x / cellSize) + gridSize / 2;
const gridJ = Math.round(point.z / cellSize) + gridSize / 2;
// Check if within grid bounds
if (gridI >= 0 && gridI < gridSize && gridJ >= 0 && gridJ < gridSize) {
// Show highlight
const highlight = document.getElementById('grid-highlight');
highlight.style.width = `${cellSize * 50}px`;
highlight.style.height = `${cellSize * 50}px`;
highlight.style.left = `${event.clientX - cellSize * 25}px`;
highlight.style.top = `${event.clientY - cellSize * 25}px`;
highlight.style.display = 'block';
// Change color based on availability
if (grid[gridI][gridJ] === null || demolishMode) {
highlight.style.backgroundColor = 'rgba(255, 255, 255, 0.3)';
} else {
highlight.style.backgroundColor = 'rgba(255, 0, 0, 0.3)';
}
} else {
document.getElementById('grid-highlight').style.display = 'none';
}
} else {
document.getElementById('grid-highlight').style.display = 'none';
}
}
function onClick(event) {
if (!selectedBuildingType && !demolishMode) return;
// Get mouse position in normalized device coordinates (-1 to +1)
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// Raycast to find intersection with ground
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
const point = intersects[0].point;
// Convert to grid coordinates
const gridI = Math.round(point.x / cellSize) + gridSize / 2;
const gridJ = Math.round(point.z / cellSize) + gridSize / 2;
// Check if within grid bounds
if (gridI >= 0 && gridI < gridSize && gridJ >= 0 && gridJ < gridSize) {
if (demolishMode) {
// Demolish building
if (grid[gridI][gridJ] !== null) {
removeBuilding(gridI, gridJ);
}
} else {
// Place building
if (grid[gridI][gridJ] === null) {
if (money >= buildingCosts[selectedBuildingType]) {
placeBuilding(selectedBuildingType, gridI, gridJ);
} else {
document.getElementById('message').textContent = `Not enough money! Need $${buildingCosts[selectedBuildingType]}`;
}
}
}
}
}
}
function placeBuilding(type, gridI, gridJ) {
// Calculate world position
const x = (gridI - gridSize / 2) * cellSize;
const z = (gridJ - gridSize / 2) * cellSize;
let buildingMesh;
let height = 1;
// Create different building types
switch (type) {
case 'house':
height = 1.5;
const houseGeometry = new THREE.BoxGeometry(1.8, height, 1.8);
const houseMaterial = new THREE.MeshStandardMaterial({ color: 0x4682B4 });
buildingMesh = new THREE.Mesh(houseGeometry, houseMaterial);
break;
case 'office':
height = 3;
const officeGeometry = new THREE.BoxGeometry(1.8, height, 1.8);
const officeMaterial = new THREE.MeshStandardMaterial({ color: 0x708090 });
buildingMesh = new THREE.Mesh(officeGeometry, officeMaterial);
break;
case 'park':
height = 0.2;
const parkGeometry = new THREE.BoxGeometry(1.8, height, 1.8);
const parkMaterial = new THREE.MeshStandardMaterial({ color: 0x32CD32 });
buildingMesh = new THREE.Mesh(parkGeometry, parkMaterial);
break;
case 'hospital':
height = 2.5;
const hospitalGeometry = new THREE.BoxGeometry(1.8, height, 1.8);
const hospitalMaterial = new THREE.MeshStandardMaterial({ color: 0xFFFAFA });
buildingMesh = new THREE.Mesh(hospitalGeometry, hospitalMaterial);
// Add red cross
const crossGeometry = new THREE.BoxGeometry(1.8, 0.2, 0.2);
const crossMaterial = new THREE.MeshStandardMaterial({ color: 0xFF0000 });
const cross1 = new THREE.Mesh(crossGeometry, crossMaterial);
cross1.position.y = height + 0.1;
const cross2 = new THREE.Mesh(crossGeometry, crossMaterial);
cross2.position.y = height + 0.1;
cross2.rotation.y = Math.PI / 2;
scene.add(cross1);
scene.add(cross2);
break;
case 'school':
height = 2;
const schoolGeometry = new THREE.BoxGeometry(1.8, height, 1.8);
const schoolMaterial = new THREE.MeshStandardMaterial({ color: 0xF5DEB3 });
buildingMesh = new THREE.Mesh(schoolGeometry, schoolMaterial);
break;
case 'road':
height = 0.1;
const roadGeometry = new THREE.BoxGeometry(1.8, height, 1.8);
const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x696969 });
buildingMesh = new THREE.Mesh(roadGeometry, roadMaterial);
break;
}
// Position the building
buildingMesh.position.set(x, height / 2, z);
scene.add(buildingMesh);
// Add to grid
grid[gridI][gridJ] = { type, mesh: buildingMesh };
</html>