Creature / index.html
Znfeoqm's picture
Update index.html
e839355 verified
Raw
History Blame Contribute Delete
20.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Creature Life Simulator</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #f0f4f8;
}
canvas {
border-radius: 1rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
background: linear-gradient(to bottom right, #e2e8f0, #cbd5e1);
}
</style>
</head>
<body class="p-4 flex flex-col items-center min-h-screen">
<div class="container mx-auto p-8 bg-white rounded-3xl shadow-xl flex flex-col lg:flex-row items-center lg:items-start space-y-8 lg:space-y-0 lg:space-x-8">
<div class="flex-1 w-full flex flex-col items-center space-y-6">
<h1 class="text-4xl font-bold text-gray-800 tracking-wide">Creature Simulation</h1>
<canvas id="creatureCanvas" class="w-full h-[500px] lg:h-[700px] border-4 border-gray-200"></canvas>
<div id="controls" class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
<button id="newLifeButton" class="px-6 py-3 bg-indigo-600 text-white font-bold rounded-full shadow-lg hover:bg-indigo-700 transition-all duration-300">
Create New Life
</button>
<button id="feedButton" class="px-6 py-3 bg-green-500 text-white font-bold rounded-full shadow-lg hover:bg-green-600 transition-all duration-300">
Provide Food
</button>
</div>
</div>
<div class="w-full lg:w-1/3 flex flex-col space-y-8">
<div class="bg-indigo-500 text-white p-6 rounded-2xl shadow-lg">
<h2 class="text-2xl font-bold mb-2">Life Clock</h2>
<div id="lifeClock" class="text-right">
<p class="text-xl font-medium">Time: <span id="time">--:--:--</span></p>
<p class="text-xl font-medium">Date: <span id="date">--/--/----</span></p>
<p class="text-5xl font-extrabold mt-4 animate-pulse">Age: <span id="ageDisplay">0</span></p>
</div>
</div>
<div class="bg-gray-100 p-6 rounded-2xl shadow-lg flex-1">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Creature's Mind & Body</h2>
<div id="thoughtPanel" class="text-gray-600 mb-6 min-h-[4rem] flex items-center justify-center text-center italic">
<p>A new life is waiting to be born...</p>
</div>
<div id="statusPanel" class="space-y-3 text-sm text-gray-700">
<p class="font-bold text-gray-800">Status:</p>
<div class="flex items-center space-x-2">
<span class="font-medium">State:</span>
<span id="stateDisplay" class="font-bold text-indigo-600">Dormant</span>
</div>
<div class="flex items-center space-x-2">
<span class="font-medium">Health:</span>
<div class="w-3/4 bg-gray-300 rounded-full h-2.5">
<div id="healthBar" class="bg-red-500 h-2.5 rounded-full transition-all duration-500" style="width: 100%;"></div>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="font-medium">Hunger:</span>
<div class="w-3/4 bg-gray-300 rounded-full h-2.5">
<div id="hungerBar" class="bg-yellow-500 h-2.5 rounded-full transition-all duration-500" style="width: 0%;"></div>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="font-medium">Energy:</span>
<div class="w-3/4 bg-gray-300 rounded-full h-2.5">
<div id="energyBar" class="bg-blue-500 h-2.5 rounded-full transition-all duration-500" style="width: 100%;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="messageModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden items-center justify-center p-4">
<div class="bg-white p-8 rounded-xl shadow-2xl text-center w-full max-w-md">
<p id="modalMessage" class="text-xl font-bold text-gray-800 mb-4"></p>
<button id="modalClose" class="px-6 py-2 bg-indigo-600 text-white rounded-full hover:bg-indigo-700 transition-all">OK</button>
</div>
</div>
<script>
// Use a self-contained script for the simulation logic
document.addEventListener('DOMContentLoaded', () => {
// --- Canvas and UI Elements Setup ---
const canvas = document.getElementById('creatureCanvas');
const ctx = canvas.getContext('2d');
const newLifeButton = document.getElementById('newLifeButton');
const feedButton = document.getElementById('feedButton');
const ageDisplay = document.getElementById('ageDisplay');
const timeDisplay = document.getElementById('time');
const dateDisplay = document.getElementById('date');
const thoughtPanel = document.getElementById('thoughtPanel');
const stateDisplay = document.getElementById('stateDisplay');
const healthBar = document.getElementById('healthBar');
const hungerBar = document.getElementById('hungerBar');
const energyBar = document.getElementById('energyBar');
const messageModal = document.getElementById('messageModal');
const modalMessage = document.getElementById('modalMessage');
const modalCloseButton = document.getElementById('modalClose');
let creature = null; // The creature object
let animationFrameId = null; // Store the animation frame ID
let gameTime = 0; // The total time elapsed in the simulation
// --- Constants and Configuration (Randomized for High Entropy) ---
const SIMULATION_SPEED = 100; // Milliseconds per game tick
const CELL_SIZE = 5;
const MAX_AGE_DAYS_MIN = 30; // Minimum days to live
const MAX_AGE_DAYS_MAX = 120; // Maximum days to live
const MAX_HEALTH = 100;
const MAX_HUNGER = 100;
const MAX_ENERGY = 100;
const HUNGER_GAIN_RATE = 0.05 + Math.random() * 0.1; // Hunger increases randomly
const ENERGY_DRAIN_RATE = 0.08 + Math.random() * 0.12; // Energy drains randomly
// Food particle properties
const FOOD_COUNT = 50;
let foodParticles = [];
// --- Creature Class: The core of the simulation ---
class Creature {
constructor() {
this.age = 0; // Age in game ticks
this.ageInDays = 0;
this.size = CELL_SIZE;
this.health = MAX_HEALTH;
this.hunger = 0;
this.energy = MAX_ENERGY;
this.state = 'cell'; // Initial state
this.color = '#3b82f6'; // Initial color (blue)
this.x = canvas.width / 2;
this.y = canvas.height / 2;
this.maxAge = (Math.floor(Math.random() * (MAX_AGE_DAYS_MAX - MAX_AGE_DAYS_MIN + 1)) + MAX_AGE_DAYS_MIN) * 100; // Random lifespan
this.thought = "I am a single cell, waiting to be born...";
}
update() {
// This is the core logic that runs every game tick
if (this.state === 'dead' || this.state === 'dying') {
return; // Stop updating if dead
}
this.age++;
this.ageInDays = Math.floor(this.age / 100); // 100 ticks = 1 day
// Random fluctuations for hunger and energy
this.hunger += HUNGER_GAIN_RATE * (1 + Math.random());
this.energy -= ENERGY_DRAIN_RATE * (1 + Math.random() * 0.5);
// Clamp values to stay within bounds
this.hunger = Math.min(this.hunger, MAX_HUNGER);
this.energy = Math.max(0, Math.min(this.energy, MAX_ENERGY));
// --- The "Thinking" and Decision-making Logic (High Entropy) ---
// The creature prioritizes needs but with random chance to deviate
let decisionMade = false;
// 1. High Priority: Hunger
if (this.hunger > 80 && Math.random() < 0.9) {
this.state = 'eating';
this.thought = 'I must find nourishment to survive.';
decisionMade = true;
}
// 2. High Priority: Energy
if (!decisionMade && this.energy < 20 && Math.random() < 0.8) {
this.state = 'sleeping';
this.thought = 'My body is tired. I need to rest.';
decisionMade = true;
}
// 3. Medium Priority: Growth and Learning
if (!decisionMade && this.ageInDays > 2 && Math.random() < 0.7) {
if (this.ageInDays < 50) {
this.state = 'growing';
this.size += 0.05; // Gradual growth
this.thought = 'I feel my body changing and growing.';
} else {
this.state = 'exploring';
this.thought = 'The world is so big. What is that over there?';
}
decisionMade = true;
}
// 4. Low Priority: Random movement
if (!decisionMade) {
this.state = 'wandering';
this.thought = 'Just drifting through the void...';
}
// --- Action effects based on state ---
if (this.state === 'eating') {
// Find food and move towards it
if (foodParticles.length > 0) {
const nearestFood = foodParticles.reduce((prev, curr) =>
Math.hypot(this.x - curr.x, this.y - curr.y) < Math.hypot(this.x - prev.x, this.y - prev.y) ? curr : prev
);
const dx = nearestFood.x - this.x;
const dy = nearestFood.y - this.y;
const dist = Math.hypot(dx, dy);
this.x += (dx / dist) * 2;
this.y += (dy / dist) * 2;
// "Eat" the food if close enough
if (dist < this.size) {
this.hunger = Math.max(0, this.hunger - 30);
foodParticles.splice(foodParticles.indexOf(nearestFood), 1);
this.thought = 'Mmm, delicious food!';
}
} else {
// No food available, so it gets hungry and wanders
this.hunger += 1;
this.state = 'wandering';
this.thought = 'I am hungry, but there is nothing to eat!';
}
} else if (this.state === 'sleeping') {
this.energy = Math.min(MAX_ENERGY, this.energy + 2); // Restore energy
this.hunger += 0.1; // Hunger still increases while sleeping
this.thought = 'Zzz... zzz...';
} else if (this.state === 'wandering' || this.state === 'exploring' || this.state === 'growing') {
// Random movement
this.x += (Math.random() - 0.5) * 4;
this.y += (Math.random() - 0.5) * 4;
}
// Bounce off walls
if (this.x < this.size || this.x > canvas.width - this.size) this.x = Math.min(Math.max(this.size, this.x), canvas.width - this.size);
if (this.y < this.size || this.y > canvas.height - this.size) this.y = Math.min(Math.max(this.size, this.y), canvas.height - this.size);
// --- Health and Death Conditions ---
if (this.age > this.maxAge) {
this.state = 'dying';
this.health -= 0.5;
this.thought = 'I feel weak. My time is coming to an end.';
} else if (this.hunger > 95) {
this.health -= 1.0; // Starvation damage
this.thought = 'My body is starving...';
} else if (this.energy <= 0) {
this.health -= 0.5; // Exhaustion damage
this.thought = 'I am completely exhausted...';
} else {
this.health = Math.min(MAX_HEALTH, this.health + 0.1); // Gradual health regeneration
}
if (this.health <= 0) {
this.state = 'dead';
this.thought = `A new life has ended after ${this.ageInDays} days.`;
cancelAnimationFrame(animationFrameId);
showMessage("The creature has died of natural causes.");
}
}
draw() {
// Draw the creature on the canvas
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowColor = 'rgba(0,0,0,0.2)';
ctx.shadowBlur = 10;
ctx.fill();
ctx.closePath();
// Add a pulse effect to the cell when it's a baby
if (this.ageInDays <= 1) {
const alpha = 0.5 + Math.sin(this.age * 0.1) * 0.5;
ctx.fillStyle = `rgba(59, 130, 246, ${alpha})`;
ctx.shadowColor = `rgba(59, 130, 246, ${alpha})`;
ctx.fill();
}
}
}
// --- Food Particle Class ---
class Food {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = 2 + Math.random() * 3;
this.color = '#10b981'; // Green color
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
// --- Core Game Loop and Functions ---
function generateFood() {
for (let i = 0; i < FOOD_COUNT; i++) {
foodParticles.push(new Food());
}
}
function showMessage(message) {
modalMessage.textContent = message;
messageModal.classList.remove('hidden');
messageModal.classList.add('flex');
}
function updateUI() {
if (creature) {
ageDisplay.textContent = creature.ageInDays;
stateDisplay.textContent = creature.state.charAt(0).toUpperCase() + creature.state.slice(1);
thoughtPanel.textContent = creature.thought;
healthBar.style.width = `${Math.max(0, creature.health)}%`;
hungerBar.style.width = `${creature.hunger}%`;
energyBar.style.width = `${creature.energy}%`;
// Update bar colors based on status
healthBar.classList.toggle('bg-red-500', creature.health > 50);
healthBar.classList.toggle('bg-red-700', creature.health <= 50);
healthBar.classList.toggle('bg-red-900', creature.health <= 20);
hungerBar.classList.toggle('bg-yellow-500', creature.hunger < 50);
hungerBar.classList.toggle('bg-yellow-700', creature.hunger >= 50);
energyBar.classList.toggle('bg-blue-500', creature.energy > 50);
energyBar.classList.toggle('bg-blue-700', creature.energy <= 50);
} else {
ageDisplay.textContent = '0';
stateDisplay.textContent = 'Dormant';
thoughtPanel.textContent = 'A new life is waiting to be born...';
healthBar.style.width = '100%';
hungerBar.style.width = '0%';
energyBar.style.width = '100%';
}
}
function updateTime() {
const now = new Date();
timeDisplay.textContent = now.toLocaleTimeString();
dateDisplay.textContent = now.toLocaleDateString();
}
function gameLoop() {
if (creature) {
// Update creature and redraw
creature.update();
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw food particles
foodParticles.forEach(food => food.draw());
// Draw the creature
creature.draw();
}
updateUI();
animationFrameId = requestAnimationFrame(gameLoop);
}
function startNewLife() {
if (creature && creature.state !== 'dead') {
// Optional: A message to the user that a creature is already alive
showMessage("A creature is already living. Let it live out its life or refresh the page.");
return;
}
// Reset everything for a new simulation
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
creature = new Creature();
foodParticles = [];
generateFood();
showMessage("A new life has been created! Watch it grow.");
gameLoop(); // Start the game loop after creating a new life
}
// --- Event Listeners ---
newLifeButton.addEventListener('click', startNewLife);
feedButton.addEventListener('click', () => {
if (creature && creature.state !== 'dead') {
for(let i=0; i<10; i++) foodParticles.push(new Food());
showMessage("More food has been added to the world!");
}
});
modalCloseButton.addEventListener('click', () => {
messageModal.classList.remove('flex');
messageModal.classList.add('hidden');
});
// Initial setup
updateTime();
setInterval(updateTime, 1000); // Update the clock every second
// Resize handler to make canvas responsive
window.addEventListener('resize', () => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
});
// Initial canvas size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
});
</script>
</body>
</html>