farm / index.html
Phoenixoni's picture
Add 2 files
d51d7c4 verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Формикарий - Муравьиная ферма</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@0.5.21/dist/vanta.net.min.js"></script>
<style>
#ant-farm {
width: 100%;
height: 70vh;
position: relative;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
}
.ant {
position: absolute;
width: 8px;
height: 4px;
background-color: #333;
border-radius: 4px;
transform-origin: center;
}
.food {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #f59e0b;
}
.tunnel {
position: absolute;
background-color: #d1d5db;
border-radius: 4px;
}
.queen {
position: absolute;
width: 12px;
height: 6px;
background-color: #8b5cf6;
border-radius: 6px;
}
.control-panel {
backdrop-filter: blur(10px);
background-color: rgba(255,255,255,0.8);
}
.ant-leg {
position: absolute;
width: 3px;
height: 1px;
background-color: #333;
}
@keyframes ant-walk {
0%, 100% { transform: rotate(0deg); }
50% { transform: rotate(10deg); }
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-2">3D Формикарий</h1>
<p class="text-lg text-gray-600">Интерактивный симулятор муравьиной колонии</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<div id="ant-farm" class="bg-amber-50 relative">
<!-- 3D сцена будет здесь -->
</div>
<div class="mt-4 bg-white rounded-lg shadow-md p-4">
<h2 class="text-xl font-semibold mb-3">Статистика колонии</h2>
<div class="grid grid-cols-3 gap-4 text-center">
<div class="bg-blue-50 p-3 rounded-lg">
<p class="text-sm text-blue-600">Муравьёв</p>
<p id="ant-count" class="text-2xl font-bold">1</p>
</div>
<div class="bg-purple-50 p-3 rounded-lg">
<p class="text-sm text-purple-600">Еды собрано</p>
<p id="food-count" class="text-2xl font-bold">0</p>
</div>
<div class="bg-green-50 p-3 rounded-lg">
<p class="text-sm text-green-600">Время</p>
<p id="time-count" class="text-2xl font-bold">0:00</p>
</div>
</div>
</div>
</div>
<div class="control-panel p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-4">Управление фермой</h2>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Скорость симуляции</label>
<input id="speed-slider" type="range" min="1" max="10" value="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>Медленно</span>
<span>Быстро</span>
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Количество еды</label>
<input id="food-slider" type="range" min="1" max="20" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Тип муравьёв</label>
<select id="ant-type" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
<option value="worker">Рабочие</option>
<option value="soldier">Солдаты</option>
<option value="harvester">Собиратели</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3 mb-6">
<button id="add-ant" class="bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition">+ Муравей</button>
<button id="add-queen" class="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 transition">+ Королева</button>
<button id="add-food" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 transition">+ Еда</button>
<button id="reset" class="bg-gray-600 text-white py-2 px-4 rounded-md hover:bg-gray-700 transition">Сброс</button>
</div>
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
<div class="flex">
<div class="ml-3">
<p class="text-sm text-yellow-700">
<strong>Совет:</strong> Кликните по ферме, чтобы добавить еду в указанное место.
</p>
</div>
</div>
</div>
<div class="border-t pt-4">
<h3 class="font-medium mb-2">Информация о колонии</h3>
<div id="colony-info" class="text-sm text-gray-600">
Колония только что основана. Добавьте больше муравьёв и еды для развития!
</div>
</div>
</div>
</div>
<div class="mt-8 bg-white rounded-lg shadow-md overflow-hidden">
<div class="px-6 py-4 border-b">
<h2 class="text-xl font-semibold">Журнал событий</h2>
</div>
<div id="event-log" class="px-6 py-4 h-40 overflow-y-auto text-sm space-y-2">
<p class="text-gray-500">[Система] Формикарий инициализирован. Добро пожаловать!</p>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Инициализация 3D сцены
const container = document.getElementById('ant-farm');
const width = container.clientWidth;
const height = container.clientHeight;
// Создаем 2D симуляцию вместо 3D для упрощения
const farmWidth = width;
const farmHeight = height;
// Состояние симуляции
let ants = [];
let foods = [];
let tunnels = [];
let queens = [];
let antCount = 1;
let foodCollected = 0;
let simulationTime = 0;
let simulationSpeed = 3;
let animationId = null;
// Создаем первого муравья (королеву)
addQueen(farmWidth / 2, farmHeight / 2);
// Добавляем начальные туннели
createInitialTunnels();
// Добавляем начальную еду
addInitialFood();
// Обработчики кнопок
document.getElementById('add-ant').addEventListener('click', function() {
const x = Math.random() * (farmWidth - 40) + 20;
const y = Math.random() * (farmHeight - 40) + 20;
addAnt(x, y);
logEvent(`Добавлен новый муравей в позиции (${Math.round(x)}, ${Math.round(y)})`);
});
document.getElementById('add-queen').addEventListener('click', function() {
const x = Math.random() * (farmWidth - 40) + 20;
const y = Math.random() * (farmHeight - 40) + 20;
addQueen(x, y);
logEvent(`Добавлена новая королева в позиции (${Math.round(x)}, ${Math.round(y)})`);
});
document.getElementById('add-food').addEventListener('click', function() {
const x = Math.random() * (farmWidth - 40) + 20;
const y = Math.random() * (farmHeight - 40) + 20;
addFood(x, y);
logEvent(`Добавлена новая еда в позиции (${Math.round(x)}, ${Math.round(y)})`);
});
document.getElementById('reset').addEventListener('click', function() {
resetSimulation();
logEvent(`Симуляция сброшена к начальному состоянию`);
});
document.getElementById('speed-slider').addEventListener('input', function(e) {
simulationSpeed = parseInt(e.target.value);
});
document.getElementById('food-slider').addEventListener('input', function(e) {
// Применяется при следующем сбросе
});
// Обработчик клика по ферме для добавления еды
container.addEventListener('click', function(e) {
const rect = container.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
addFood(x, y);
logEvent(`Игрок добавил еду в позиции (${Math.round(x)}, ${Math.round(y)})`);
});
// Функции симуляции
function addAnt(x, y) {
const ant = document.createElement('div');
ant.className = 'ant';
ant.style.left = `${x}px`;
ant.style.top = `${y}px`;
// Добавляем ножки для анимации
for (let i = 0; i < 6; i++) {
const leg = document.createElement('div');
leg.className = 'ant-leg';
leg.style.top = '2px';
leg.style.left = `${i % 2 === 0 ? -2 : 7}px`;
leg.style.transform = `rotate(${i % 2 === 0 ? -30 : 30}deg)`;
leg.style.animation = `ant-walk 0.3s infinite ${i * 0.1}s alternate`;
ant.appendChild(leg);
}
container.appendChild(ant);
ants.push({
element: ant,
x: x,
y: y,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
targetX: null,
targetY: null,
carryingFood: false,
type: document.getElementById('ant-type').value
});
antCount++;
updateStats();
}
function addQueen(x, y) {
const queen = document.createElement('div');
queen.className = 'queen';
queen.style.left = `${x}px`;
queen.style.top = `${y}px`;
container.appendChild(queen);
queens.push({
element: queen,
x: x,
y: y,
health: 100,
eggTimer: 0
});
updateColonyInfo();
}
function addFood(x, y) {
const food = document.createElement('div');
food.className = 'food';
food.style.left = `${x}px`;
food.style.top = `${y}px`;
container.appendChild(food);
foods.push({
element: food,
x: x,
y: y,
amount: 10
});
}
function addInitialFood() {
const foodCount = parseInt(document.getElementById('food-slider').value);
for (let i = 0; i < foodCount; i++) {
const x = Math.random() * (farmWidth - 40) + 20;
const y = Math.random() * (farmHeight - 40) + 20;
addFood(x, y);
}
}
function createInitialTunnels() {
// Создаем центральную камеру
const chamber = document.createElement('div');
chamber.className = 'tunnel';
chamber.style.width = '80px';
chamber.style.height = '60px';
chamber.style.left = `${farmWidth/2 - 40}px`;
chamber.style.top = `${farmHeight/2 - 30}px`;
container.appendChild(chamber);
tunnels.push({
element: chamber,
x: farmWidth/2,
y: farmHeight/2,
width: 80,
height: 60,
type: 'chamber'
});
// Создаем несколько туннелей
for (let i = 0; i < 4; i++) {
const angle = (i / 4) * Math.PI * 2;
const length = 50 + Math.random() * 50;
const tunnelWidth = 10;
const tunnel = document.createElement('div');
tunnel.className = 'tunnel';
tunnel.style.width = `${length}px`;
tunnel.style.height = `${tunnelWidth}px`;
tunnel.style.left = `${farmWidth/2}px`;
tunnel.style.top = `${farmHeight/2 - tunnelWidth/2}px`;
tunnel.style.transform = `rotate(${angle}rad)`;
tunnel.style.transformOrigin = 'left center';
container.appendChild(tunnel);
tunnels.push({
element: tunnel,
x: farmWidth/2,
y: farmHeight/2,
length: length,
width: tunnelWidth,
angle: angle,
type: 'tunnel'
});
}
}
function updateAnt(ant, deltaTime) {
// Простое ИИ муравья
if (!ant.carryingFood) {
// Поиск еды
if (!ant.targetX || !ant.targetY || Math.random() < 0.01) {
findFood(ant);
}
} else {
// Возвращение в колонию
ant.targetX = farmWidth/2;
ant.targetY = farmHeight/2;
}
// Движение к цели
if (ant.targetX && ant.targetY) {
const dx = ant.targetX - ant.x;
const dy = ant.targetY - ant.y;
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist < 5) {
// Достигли цели
if (ant.carryingFood) {
// Доставили еду в колонию
foodCollected++;
ant.carryingFood = false;
updateStats();
logEvent(`Муравей доставил еду в колонию! Всего собрано: ${foodCollected}`);
} else {
// Проверяем, есть ли здесь еда
for (let i = 0; i < foods.length; i++) {
const food = foods[i];
const fdx = food.x - ant.x;
const fdy = food.y - ant.y;
const fdist = Math.sqrt(fdx*fdx + fdy*fdy);
if (fdist < 15) {
// Собираем еду
food.amount--;
if (food.amount <= 0) {
container.removeChild(food.element);
foods.splice(i, 1);
}
ant.carryingFood = true;
logEvent(`Муравей нашёл еду и несёт её в колонию`);
break;
}
}
}
ant.targetX = null;
ant.targetY = null;
} else {
// Двигаемся к цели
const speed = 0.1 * simulationSpeed;
ant.vx = (dx / dist) * speed;
ant.vy = (dy / dist) * speed;
}
}
// Случайное блуждание, если нет цели
if (!ant.targetX || !ant.targetY) {
if (Math.random() < 0.05) {
ant.vx += (Math.random() - 0.5) * 0.2;
ant.vy += (Math.random() - 0.5) * 0.2;
}
}
// Ограничение скорости
const speed = Math.sqrt(ant.vx*ant.vx + ant.vy*ant.vy);
const maxSpeed = 0.15 * simulationSpeed;
if (speed > maxSpeed) {
ant.vx = (ant.vx / speed) * maxSpeed;
ant.vy = (ant.vy / speed) * maxSpeed;
}
// Обновление позиции
ant.x += ant.vx * deltaTime;
ant.y += ant.vy * deltaTime;
// Границы фермы
ant.x = Math.max(5, Math.min(farmWidth - 5, ant.x));
ant.y = Math.max(5, Math.min(farmHeight - 5, ant.y));
// Обновление DOM
ant.element.style.left = `${ant.x}px`;
ant.element.style.top = `${ant.y}px`;
// Направление движения
if (ant.vx !== 0 || ant.vy !== 0) {
const angle = Math.atan2(ant.vy, ant.vx);
ant.element.style.transform = `rotate(${angle}rad)`;
}
// Визуальное отличие для муравья с едой
if (ant.carryingFood) {
ant.element.style.backgroundColor = '#10b981';
} else {
ant.element.style.backgroundColor = '#333';
}
}
function findFood(ant) {
// Поиск ближайшей еды
let closestDist = Infinity;
let closestFood = null;
for (const food of foods) {
const dx = food.x - ant.x;
const dy = food.y - ant.y;
const dist = dx*dx + dy*dy;
if (dist < closestDist) {
closestDist = dist;
closestFood = food;
}
}
if (closestFood) {
ant.targetX = closestFood.x;
ant.targetY = closestFood.y;
} else {
// Случайное движение, если еды нет
ant.targetX = ant.x + (Math.random() - 0.5) * 100;
ant.targetY = ant.y + (Math.random() - 0.5) * 100;
}
}
function updateQueen(queen, deltaTime) {
// Королева откладывает яйца
queen.eggTimer += deltaTime * simulationSpeed / 1000;
if (queen.eggTimer > 10 && antCount < 100) {
queen.eggTimer = 0;
// Создаем нового муравья рядом с королевой
const angle = Math.random() * Math.PI * 2;
const dist = 20 + Math.random() * 10;
const x = queen.x + Math.cos(angle) * dist;
const y = queen.y + Math.sin(angle) * dist;
addAnt(x, y);
logEvent(`Королева произвела нового муравья! Всего муравьёв: ${antCount}`);
}
// Медленное движение королевы
if (Math.random() < 0.005) {
queen.x += (Math.random() - 0.5) * 5;
queen.y += (Math.random() - 0.5) * 5;
// Границы фермы
queen.x = Math.max(20, Math.min(farmWidth - 20, queen.x));
queen.y = Math.max(20, Math.min(farmHeight - 20, queen.y));
queen.element.style.left = `${queen.x}px`;
queen.element.style.top = `${queen.y}px`;
}
}
function updateStats() {
document.getElementById('ant-count').textContent = antCount;
document.getElementById('food-count').textContent = foodCollected;
// Обновляем информацию о колонии
updateColonyInfo();
}
function updateColonyInfo() {
let info = '';
if (queens.length === 0) {
info = 'Колония без королевы не может развиваться. Добавьте королеву!';
} else if (antCount < 5) {
info = 'Колония только что основана. Добавьте больше муравьёв и еды для развития!';
} else if (antCount < 20) {
info = 'Колония активно развивается. Муравьи собирают еду и расширяют туннели.';
} else {
info = 'Мощная колония! Муравьи эффективно работают и приносят много еды.';
}
if (foods.length < 3) {
info += ' Внимание: запасы еды заканчиваются!';
}
document.getElementById('colony-info').textContent = info;
}
function updateTime() {
simulationTime += 0.1 * simulationSpeed;
const minutes = Math.floor(simulationTime / 60);
const seconds = Math.floor(simulationTime % 60);
document.getElementById('time-count').textContent =
`${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
}
function logEvent(message) {
const now = new Date();
const timeString = `[${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}]`;
const eventElement = document.createElement('p');
eventElement.innerHTML = `<span class="text-gray-500">${timeString}</span> ${message}`;
const log = document.getElementById('event-log');
log.appendChild(eventElement);
log.scrollTop = log.scrollHeight;
// Ограничиваем количество сообщений
if (log.children.length > 50) {
log.removeChild(log.children[0]);
}
}
function resetSimulation() {
// Останавливаем анимацию
if (animationId) {
cancelAnimationFrame(animationId);
}
// Очищаем ферму
ants.forEach(ant => container.removeChild(ant.element));
foods.forEach(food => container.removeChild(food.element));
tunnels.forEach(tunnel => container.removeChild(tunnel.element));
queens.forEach(queen => container.removeChild(queen.element));
// Сбрасываем состояние
ants = [];
foods = [];
tunnels = [];
queens = [];
antCount = 0;
foodCollected = 0;
simulationTime = 0;
// Создаем новую колонию
addQueen(farmWidth / 2, farmHeight / 2);
createInitialTunnels();
addInitialFood();
// Обновляем статистику
updateStats();
// Запускаем симуляцию снова
startSimulation();
}
function startSimulation() {
let lastTime = performance.now();
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// Обновляем всех муравьев
ants.forEach(ant => updateAnt(ant, deltaTime));
// Обновляем королев
queens.forEach(queen => updateQueen(queen, deltaTime));
// Обновляем время
updateTime();
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
}
// Запускаем симуляцию
startSimulation();
// Инициализация Vanta.js для фона
VANTA.NET({
el: "#ant-farm",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x7c3aed,
backgroundColor: 0xfff7ed,
points: 10.00,
maxDistance: 20.00,
spacing: 15.00
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Phoenixoni/farm" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>