Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Harmonies Game 3D - Dynamic Simulation</title> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
} | |
canvas { | |
display: block; | |
} | |
.hud { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
background: rgba(0, 0, 0, 0.5); | |
color: white; | |
padding: 10px; | |
border-radius: 5px; | |
font-family: Arial, sans-serif; | |
} | |
.sidebar { | |
position: absolute; | |
top: 0; | |
right: 0; | |
width: 200px; | |
height: 100%; | |
background: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 10px; | |
overflow-y: auto; | |
font-family: Arial, sans-serif; | |
} | |
.character { | |
margin-bottom: 10px; | |
padding: 5px; | |
border: 1px solid white; | |
border-radius: 5px; | |
} | |
.timer { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background: rgba(0, 0, 0, 0.5); | |
color: white; | |
padding: 10px; | |
border-radius: 5px; | |
font-family: Arial, sans-serif; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="hud" id="hud"> | |
<p>Current Type: <span id="currentType">Swan 🦢</span></p> | |
</div> | |
<div class="sidebar" id="sidebar"> | |
<h3>Population Stats</h3> | |
</div> | |
<div class="timer" id="timer"> | |
<p>Simulation Time: <span id="simulationTime">0</span> seconds</p> | |
<p>Epoch: <span id="epoch">1</span></p> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script> | |
<script> | |
// Initialize scene, camera, and renderer | |
const scene = new THREE.Scene(); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
const renderer = new THREE.WebGLRenderer(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
// Timer variables | |
let simulationTime = 0; | |
let epoch = 1; | |
const timerElement = document.getElementById('simulationTime'); | |
const epochElement = document.getElementById('epoch'); | |
// Game board parameters | |
const boardSize = 50; // Gigantic board | |
const tileSize = 1; | |
const tiles = []; | |
const tileTypes = [ | |
{ type: 'water', color: 0x87ceeb }, | |
{ type: 'forest', color: 0x228b22 }, | |
{ type: 'grass', color: 0x98fb98 } | |
]; | |
// Sidebar for stats | |
const sidebar = document.getElementById('sidebar'); | |
// Particle system for motion simulation | |
const particleSystem = new THREE.Group(); | |
scene.add(particleSystem); | |
// Generate random character stats | |
function generateCharacter() { | |
const names = ["Swift Feather", "Bold Antler", "Mighty Claw", "Gentle Leaf", "Shadow Pelt"]; | |
return { | |
name: names[Math.floor(Math.random() * names.length)], | |
attack: Math.floor(Math.random() * 10) + 1, | |
hitPoints: Math.floor(Math.random() * 50) + 10, | |
strength: Math.floor(Math.random() * 10) + 1, | |
intelligence: Math.floor(Math.random() * 10) + 1, | |
wisdom: Math.floor(Math.random() * 10) + 1, | |
charisma: Math.floor(Math.random() * 10) + 1, | |
}; | |
} | |
// Create tiles and particles | |
for (let x = 0; x < boardSize; x++) { | |
for (let z = 0; z < boardSize; z++) { | |
const tileType = tileTypes[Math.floor(Math.random() * tileTypes.length)]; | |
const geometry = new THREE.BoxGeometry(tileSize, 0.1, tileSize); | |
const material = new THREE.MeshBasicMaterial({ color: tileType.color }); | |
const tile = new THREE.Mesh(geometry, material); | |
tile.position.set(x - boardSize / 2, 0, z - boardSize / 2); | |
tile.userData = { type: tileType.type, occupied: false }; | |
scene.add(tile); | |
tiles.push(tile); | |
// Add particles for motion simulation | |
const particleGeometry = new THREE.SphereGeometry(0.2, 16, 16); | |
const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); | |
const particle = new THREE.Mesh(particleGeometry, particleMaterial); | |
particle.position.set( | |
x - boardSize / 2 + Math.random(), | |
0.2, | |
z - boardSize / 2 + Math.random() | |
); | |
particle.userData = { | |
direction: new THREE.Vector3(Math.random(), 0, Math.random()).normalize(), | |
rotation: new THREE.Vector3(0, Math.random() * 2 * Math.PI, 0), | |
accelerate: Math.random() * 0.05 + 0.01, | |
brake: 0.02, | |
reverse: -0.02, | |
stats: generateCharacter(), | |
}; | |
// Add stats to the sidebar | |
const characterDiv = document.createElement('div'); | |
characterDiv.className = 'character'; | |
characterDiv.innerHTML = ` | |
<strong>${particle.userData.stats.name}</strong><br> | |
Attack: ${particle.userData.stats.attack}<br> | |
HP: ${particle.userData.stats.hitPoints}<br> | |
Strength: ${particle.userData.stats.strength}<br> | |
Intelligence: ${particle.userData.stats.intelligence}<br> | |
Wisdom: ${particle.userData.stats.wisdom}<br> | |
Charisma: ${particle.userData.stats.charisma}<br> | |
`; | |
sidebar.appendChild(characterDiv); | |
particleSystem.add(particle); | |
} | |
} | |
// Add light | |
const light = new THREE.AmbientLight(0xffffff, 0.8); | |
scene.add(light); | |
// HUD for displaying the current type | |
const hud = document.getElementById('hud'); | |
const currentTypeDisplay = document.getElementById('currentType'); | |
// Current type with emoji | |
const animalTypes = [ | |
{ name: 'Swan', emoji: '🦢' }, | |
{ name: 'Deer', emoji: '🦌' }, | |
{ name: 'Bear', emoji: '🐻' } | |
]; | |
let currentTypeIndex = 0; | |
function updateCurrentType() { | |
currentTypeDisplay.textContent = `${animalTypes[currentTypeIndex].name} ${animalTypes[currentTypeIndex].emoji}`; | |
} | |
updateCurrentType(); | |
// Handle key presses to switch types | |
window.addEventListener('keydown', (event) => { | |
if (event.key === 'ArrowRight') { | |
currentTypeIndex = (currentTypeIndex + 1) % animalTypes.length; | |
updateCurrentType(); | |
} else if (event.key === 'ArrowLeft') { | |
currentTypeIndex = (currentTypeIndex - 1 + animalTypes.length) % animalTypes.length; | |
updateCurrentType(); | |
} | |
}); | |
// Particle motion simulation | |
function updateParticles() { | |
particleSystem.children.forEach(particle => { | |
const direction = particle.userData.direction; | |
const position = particle.position; | |
// Move particle | |
position.add(direction.multiplyScalar(particle.userData.accelerate)); | |
// Detect collisions and adjust behavior | |
particleSystem.children.forEach(otherParticle => { | |
if (particle !== otherParticle) { | |
const distance = position.distanceTo(otherParticle.position); | |
if (distance < 0.5) { | |
// Reverse direction on collision | |
direction.negate(); | |
particle.userData.accelerate = particle.userData.reverse; | |
} | |
} | |
}); | |
// Gradually brake | |
if (particle.userData.accelerate > 0) { | |
particle.userData.accelerate -= particle.userData.brake; | |
if (particle.userData.accelerate < 0) { | |
particle.userData.accelerate = 0; | |
} | |
} | |
// Bounce off edges of the board | |
if (position.x < -boardSize / 2 || position.x > boardSize / 2) { | |
direction.x = -direction.x; | |
} | |
if (position.z < -boardSize / 2 || position.z > boardSize / 2) { | |
direction.z = -direction.z; | |
} | |
}); | |
} | |
// Timer update function | |
function updateTimer() { | |
simulationTime++; | |
timerElement.textContent = simulationTime; | |
if (simulationTime % 10 === 0) { | |
epoch++; | |
epochElement.textContent = epoch; | |
} | |
} | |
setInterval(updateTimer, 1000); | |
// Set camera position | |
camera.position.set(0, 50, 50); | |
camera.lookAt(0, 0, 0); | |
// Animation loop | |
function animate() { | |
requestAnimationFrame(animate); | |
updateParticles(); | |
renderer.render(scene, camera); | |
} | |
animate(); | |
</script> | |
</body> | |
</html> | |