Spaces:
Running
Running
| <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> |