neuron / app.py
X
Update app.py
3902bd5 verified
Raw
History Blame Contribute Delete
22.5 kB
# server.py
import http.server
import socketserver
import random
import numpy as np
from PIL import Image, ImageDraw
import io
import json
from urllib.parse import parse_qs, urlparse
import threading
import time
# ============== НАСТРОЙКИ ==============
WORLD_SIZE = 40
CELL_SIZE = 10
MAX_CREATURES = 50
PORT = 7890
UPDATE_INTERVAL = 0.3
# ============== НЕЙРОСЕТЬ (10 входов -> 5 выходов) ==============
class SimpleBrain:
def __init__(self):
# 10 входов (исправлено!), 5 выходов
self.weights = np.random.randn(10, 5) * 0.5
self.bias = np.random.randn(5) * 0.5
def forward(self, inputs):
x = np.array(inputs)
output = np.tanh(np.dot(x, self.weights) + self.bias)
return output
def copy(self):
new_brain = SimpleBrain()
new_brain.weights = self.weights.copy()
new_brain.bias = self.bias.copy()
return new_brain
def mutate(self):
if random.random() < 0.2:
self.weights += np.random.randn(10, 5) * 0.3
if random.random() < 0.2:
self.bias += np.random.randn(5) * 0.3
# ============== СУЩЕСТВО ==============
class Creature:
def __init__(self, x, y, color, team):
self.x = x
self.y = y
self.color = color
self.team = team
self.health = 100
self.energy = 80
self.food = 50
self.water = 50
self.resources = 0
self.age = 0
self.alive = True
self.fitness = 0
self.brain = SimpleBrain()
def think(self, world, creatures):
# ---- ВХОДНЫЕ ДАННЫЕ (10 штук) ----
hunger = 1.0 - self.food / 100.0
thirst = 1.0 - self.water / 100.0
health = self.health / 100.0
energy = self.energy / 100.0
# Смотрим вокруг (4 направления: вверх, вниз, влево, вправо)
nearby = []
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
cell = world[nx, ny]
if cell[0] == 34 and cell[1] == 139 and cell[2] == 34: # дерево
nearby.append(1.0)
elif cell[0] == 0 and cell[1] == 150 and cell[2] == 0: # ягоды
nearby.append(2.0)
elif cell[0] == 128 and cell[1] == 128 and cell[2] == 128: # камень
nearby.append(0.5)
else:
nearby.append(0.0)
else:
nearby.append(0.0)
# Свои и враги
partners = 0
enemies = 0
for other in creatures:
if other is self or not other.alive:
continue
dist = abs(self.x - other.x) + abs(self.y - other.y)
if dist < 3:
if other.team == self.team:
partners = max(partners, 1.0 - dist/3.0)
else:
enemies = max(enemies, 1.0 - dist/3.0)
# Собираем 10 входов
inputs = [
hunger, # 1
thirst, # 2
health, # 3
energy, # 4
partners, # 5
enemies, # 6
nearby[0] if len(nearby) > 0 else 0.0, # 7 - вверх
nearby[1] if len(nearby) > 1 else 0.0, # 8 - вниз
nearby[2] if len(nearby) > 2 else 0.0, # 9 - влево
nearby[3] if len(nearby) > 3 else 0.0 # 10 - вправо
]
# ---- ПРЯМОЙ ПРОХОД ----
output = self.brain.forward(inputs)
# ---- ДЕЙСТВИЯ ----
dx = int(round(output[0] * 2))
dy = int(round(output[1] * 2))
build = output[2] > 0.3
reproduce = output[3] > 0.5
attack = output[4] > 0.4
self.move(dx, dy)
if build and self.resources >= 2:
self.build(world)
if reproduce and self.food > 50 and self.energy > 60:
return self.reproduce(creatures)
if attack:
self.attack(creatures)
return None
def move(self, dx, dy):
self.x = max(0, min(WORLD_SIZE-1, self.x + dx))
self.y = max(0, min(WORLD_SIZE-1, self.y + dy))
self.energy -= 0.3
self.food -= 0.2
self.water -= 0.1
self.age += 1
def build(self, world):
self.resources -= 2
for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
nx, ny = self.x + dx, self.y + dy
if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
cell = world[nx, ny]
if cell[0] == 100 and cell[1] == 200 and cell[2] == 100:
world[nx, ny] = [139, 69, 19]
self.fitness += 15
return True
return False
def reproduce(self, creatures):
if len(creatures) >= MAX_CREATURES:
return None
child_brain = self.brain.copy()
child_brain.mutate()
child = Creature(
self.x + random.randint(-2, 2),
self.y + random.randint(-2, 2),
self.color,
self.team
)
child.brain = child_brain
child.food = 30
child.water = 30
child.energy = 40
self.food -= 30
self.energy -= 20
self.fitness += 25
creatures.append(child)
return child
def attack(self, creatures):
for other in creatures:
if other is not self and other.alive and other.team != self.team:
dist = abs(self.x - other.x) + abs(self.y - other.y)
if dist <= 1:
other.health -= 15
self.energy -= 5
self.food -= 2
self.fitness += 8
return True
return False
def eat(self, world):
cell = world[self.x, self.y]
if cell[0] == 0 and cell[1] == 150 and cell[2] == 0:
self.food = min(100, self.food + 30)
self.health = min(100, self.health + 8)
world[self.x, self.y] = [100, 200, 100]
self.fitness += 12
return True
elif cell[0] == 34 and cell[1] == 139 and cell[2] == 34:
self.food = min(100, self.food + 15)
self.health = min(100, self.health + 5)
world[self.x, self.y] = [100, 200, 100]
self.fitness += 8
return True
return False
def drink(self, world):
cell = world[self.x, self.y]
if cell[0] == 0 and cell[1] == 100 and cell[2] == 255:
self.water = min(100, self.water + 40)
self.health = min(100, self.health + 5)
if random.random() < 0.3:
world[self.x, self.y] = [100, 200, 100]
self.fitness += 5
return True
return False
def collect_stone(self, world):
cell = world[self.x, self.y]
if cell[0] == 128 and cell[1] == 128 and cell[2] == 128:
self.resources += 1
world[self.x, self.y] = [100, 200, 100]
self.fitness += 10
return True
return False
def update(self):
self.food -= 0.3
self.water -= 0.2
self.health -= 0.1
self.energy -= 0.2
if self.food <= 0:
self.health -= 2
if self.water <= 0:
self.health -= 1.5
if self.health <= 0 or self.energy <= 0:
self.alive = False
return False
if self.age > 300:
self.alive = False
return False
return True
# ============== МИР ==============
def create_world():
world = np.zeros((WORLD_SIZE, WORLD_SIZE, 3), dtype=np.uint8)
world[:, :] = [100, 200, 100]
# Деревья
for _ in range(20):
for _ in range(30):
x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
world[x, y] = [34, 139, 34]
break
# Ягоды (еда)
for _ in range(15):
for _ in range(30):
x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
world[x, y] = [0, 150, 0]
break
# Камни
for _ in range(10):
for _ in range(30):
x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
world[x, y] = [128, 128, 128]
break
# Вода
for _ in range(5):
for _ in range(30):
x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
world[x, y] = [0, 100, 255]
break
creatures = []
# Красные
for i in range(3):
creatures.append(Creature(5 + i*2, 5 + i*2, [255, 50, 50], "red"))
# Синие
for i in range(3):
creatures.append(Creature(WORLD_SIZE-5 - i*2, WORLD_SIZE-5 - i*2, [50, 50, 255], "blue"))
# Золотые
for i in range(2):
creatures.append(Creature(WORLD_SIZE//2 + i*2, WORLD_SIZE//2 + i*2, [255, 215, 0], "gold"))
return world, creatures
# ============== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ==============
world, creatures = create_world()
step_counter = 0
# ============== СИМУЛЯЦИЯ ==============
def simulate_step():
global world, creatures, step_counter
new_creatures = []
for creature in creatures[:]:
if not creature.alive:
continue
creature.eat(world)
creature.drink(world)
creature.collect_stone(world)
child = creature.think(world, creatures)
if child:
new_creatures.append(child)
if not creature.update():
creature.alive = False
world[creature.x, creature.y] = [200, 100, 100]
creatures.extend(new_creatures)
creatures = [c for c in creatures if c.alive]
# Восстановление ягод
if step_counter % 5 == 0:
for _ in range(2):
for _ in range(20):
x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
world[x, y] = [0, 150, 0]
break
# Отбор
if len(creatures) > MAX_CREATURES:
creatures.sort(key=lambda c: c.fitness, reverse=True)
for c in creatures[MAX_CREATURES:]:
c.alive = False
creatures = [c for c in creatures if c.alive]
step_counter += 1
def render_world():
img = Image.new('RGB', (WORLD_SIZE * 12, WORLD_SIZE * 12 + 60), (30, 30, 30))
draw = ImageDraw.Draw(img)
for i in range(WORLD_SIZE):
for j in range(WORLD_SIZE):
x, y = i * 12, j * 12
draw.rectangle([x, y, x + 12, y + 12], fill=tuple(world[i, j].tolist()))
for creature in creatures:
if not creature.alive:
continue
x, y = creature.x * 12, creature.y * 12
draw.ellipse([x+2, y+2, x+10, y+10], fill=tuple(creature.color), outline=(255,255,255))
hw = int((creature.health / 100) * 10)
fw = int((creature.food / 100) * 10)
ww = int((creature.water / 100) * 10)
draw.rectangle([x+1, y-4, x + hw, y-2], fill=(255, 0, 0))
draw.rectangle([x+1, y-2, x + fw, y], fill=(255, 165, 0))
draw.rectangle([x+1, y, x + ww, y+2], fill=(0, 200, 255))
y_offset = WORLD_SIZE * 12 + 10
red = sum(1 for c in creatures if c.alive and c.team == "red")
blue = sum(1 for c in creatures if c.alive and c.team == "blue")
gold = sum(1 for c in creatures if c.alive and c.team == "gold")
trees = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
if world[i, j][0] == 34 and world[i, j][1] == 139 and world[i, j][2] == 34)
foods = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
if world[i, j][0] == 0 and world[i, j][1] == 150 and world[i, j][2] == 0)
stones = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
if world[i, j][0] == 128 and world[i, j][1] == 128 and world[i, j][2] == 128)
best = max([c.fitness for c in creatures if c.alive] + [0])
draw.text([10, y_offset], f"Шаг: {step_counter} | Существ: {len(creatures)}", fill=(255,255,255))
draw.text([10, y_offset + 20], f"🔴: {red} | 🔵: {blue} | 🟡: {gold}", fill=(255,255,255))
draw.text([10, y_offset + 40], f"🌳: {trees} | 🫐: {foods} | 🪨: {stones} | ⭐: {best}", fill=(255,255,255))
return img
# ============== ПОТОК СИМУЛЯЦИИ ==============
def simulation_loop():
while True:
try:
simulate_step()
time.sleep(UPDATE_INTERVAL)
except Exception as e:
print(f"Ошибка в симуляции: {e}")
time.sleep(1)
# ============== HTTP СЕРВЕР ==============
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🧬 Эволюция</title>
<style>
* { margin: 0; padding: 0; }
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #0a0a1a;
display: flex;
justify-content: center;
align-items: center;
}
body {
font-family: 'Segoe UI', Arial, sans-serif;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: #0a0a1a;
}
.world-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
}
.world-wrapper img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
image-rendering: pixelated;
border-radius: 5px;
box-shadow: 0 0 60px rgba(78, 205, 196, 0.1);
}
.stats-overlay {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(10, 10, 30, 0.85);
padding: 12px 25px;
border-radius: 12px;
border: 1px solid rgba(78, 205, 196, 0.3);
color: #aaa;
font-size: 14px;
text-align: center;
backdrop-filter: blur(10px);
pointer-events: none;
user-select: none;
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
}
.stats-overlay span {
color: #eee;
}
.stat-label {
color: #666;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1px;
}
.stat-value {
color: #4ecdc4;
font-weight: bold;
font-size: 16px;
}
.stat-red .stat-value { color: #ff6b6b; }
.stat-blue .stat-value { color: #4dabf7; }
.stat-gold .stat-value { color: #ffd93d; }
.stat-item {
display: flex;
align-items: baseline;
gap: 5px;
}
@media (max-width: 700px) {
.stats-overlay {
font-size: 11px;
padding: 8px 15px;
gap: 10px;
bottom: 10px;
flex-wrap: wrap;
}
.stat-value { font-size: 13px; }
}
</style>
</head>
<body>
<div class="container">
<div class="world-wrapper">
<img id="worldImage" src="/image" alt="Эволюция">
</div>
</div>
<div class="stats-overlay" id="stats">
<div class="stat-item">
<span class="stat-label">Шаг</span>
<span class="stat-value" id="step">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Существ</span>
<span class="stat-value" id="count">0</span>
</div>
<div class="stat-item stat-red">
<span class="stat-label">🔴</span>
<span class="stat-value" id="red">0</span>
</div>
<div class="stat-item stat-blue">
<span class="stat-label">🔵</span>
<span class="stat-value" id="blue">0</span>
</div>
<div class="stat-item stat-gold">
<span class="stat-label">🟡</span>
<span class="stat-value" id="gold">0</span>
</div>
<div class="stat-item">
<span class="stat-label">🌳</span>
<span class="stat-value" id="trees">0</span>
</div>
<div class="stat-item">
<span class="stat-label">🫐</span>
<span class="stat-value" id="food">0</span>
</div>
<div class="stat-item">
<span class="stat-label">⭐</span>
<span class="stat-value" id="fitness">0</span>
</div>
</div>
<script>
function updateImage() {
document.getElementById('worldImage').src = '/image?t=' + Date.now();
}
function updateStats() {
fetch('/stats')
.then(r => r.json())
.then(data => {
document.getElementById('step').textContent = data.step;
document.getElementById('count').textContent = data.total;
document.getElementById('red').textContent = data.red;
document.getElementById('blue').textContent = data.blue;
document.getElementById('gold').textContent = data.gold;
document.getElementById('trees').textContent = data.trees;
document.getElementById('food').textContent = data.food;
document.getElementById('fitness').textContent = data.best_fitness;
})
.catch(() => {});
}
function update() {
updateImage();
updateStats();
}
setInterval(update, 300);
update();
</script>
</body>
</html>
"""
# ============== HTTP ОБРАБОТЧИК ==============
class SimulationHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
path = parsed.path
if path == '/':
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(HTML_TEMPLATE.encode('utf-8'))
elif path == '/image':
img = render_world()
buffer = io.BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
self.send_response(200)
self.send_header('Content-type', 'image/png')
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.end_headers()
self.wfile.write(buffer.getvalue())
elif path == '/stats':
red = sum(1 for c in creatures if c.alive and c.team == "red")
blue = sum(1 for c in creatures if c.alive and c.team == "blue")
gold = sum(1 for c in creatures if c.alive and c.team == "gold")
trees = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
if world[i, j][0] == 34 and world[i, j][1] == 139 and world[i, j][2] == 34)
foods = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
if world[i, j][0] == 0 and world[i, j][1] == 150 and world[i, j][2] == 0)
stones = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
if world[i, j][0] == 128 and world[i, j][1] == 128 and world[i, j][2] == 128)
best = max([c.fitness for c in creatures if c.alive] + [0])
stats = {
'total': len([c for c in creatures if c.alive]),
'red': red,
'blue': blue,
'gold': gold,
'trees': trees,
'food': foods,
'stones': stones,
'best_fitness': best,
'step': step_counter
}
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(stats).encode())
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass
# ============== ЗАПУСК ==============
if __name__ == '__main__':
# Запускаем симуляцию в потоке
sim_thread = threading.Thread(target=simulation_loop, daemon=True)
sim_thread.start()
print(f"🧬 Эволюционная симуляция запущена!")
print(f"🌍 Открой в браузере: http://localhost:{PORT}")
print("=" * 50)
print("⏹️ Для остановки нажми Ctrl+C")
with socketserver.TCPServer(("", PORT), SimulationHandler) as httpd:
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n⏹️ Остановка сервера...")