X commited on
Commit
a3a494a
·
verified ·
1 Parent(s): b1257b3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +308 -105
app.py CHANGED
@@ -1,13 +1,21 @@
1
- # app.py
2
- import gradio as gr
3
- import numpy as np
4
  import random
 
5
  from PIL import Image, ImageDraw
 
 
 
 
 
6
 
7
  # ============== НАСТРОЙКИ ==============
8
- WORLD_SIZE = 30
9
- CELL_SIZE = 12
10
- MAX_CREATURES = 30
 
 
11
 
12
  # ============== ПРОСТАЯ НЕЙРОСЕТЬ ==============
13
  class SimpleBrain:
@@ -50,13 +58,11 @@ class Creature:
50
  self.brain = SimpleBrain()
51
 
52
  def think(self, world, creatures):
53
- # Входные данные
54
  hunger = 1.0 - self.food / 100.0
55
  thirst = 1.0 - self.water / 100.0
56
  health = self.health / 100.0
57
  energy = self.energy / 100.0
58
 
59
- # Обзор
60
  nearby = []
61
  for dx in [-1, 0, 1]:
62
  for dy in [-1, 0, 1]:
@@ -76,7 +82,6 @@ class Creature:
76
  else:
77
  nearby.append(0.0)
78
 
79
- # Друзья и враги
80
  partners = 0
81
  enemies = 0
82
  for other in creatures:
@@ -94,7 +99,6 @@ class Creature:
94
 
95
  output = self.brain.forward(inputs)
96
 
97
- # Действия
98
  dx = int(round(output[0] * 2))
99
  dy = int(round(output[1] * 2))
100
  build = output[2] > 0.3
@@ -233,29 +237,28 @@ def create_world():
233
  world = np.zeros((WORLD_SIZE, WORLD_SIZE, 3), dtype=np.uint8)
234
  world[:, :] = [100, 200, 100]
235
 
236
- # Ресурсы
237
- for _ in range(15):
238
  for _ in range(30):
239
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
240
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
241
  world[x, y] = [34, 139, 34]
242
  break
243
 
244
- for _ in range(12):
245
  for _ in range(30):
246
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
247
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
248
  world[x, y] = [0, 150, 0]
249
  break
250
 
251
- for _ in range(8):
252
  for _ in range(30):
253
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
254
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
255
  world[x, y] = [128, 128, 128]
256
  break
257
 
258
- for _ in range(4):
259
  for _ in range(30):
260
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
261
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
@@ -263,14 +266,26 @@ def create_world():
263
  break
264
 
265
  creatures = []
266
- creatures.append(Creature(5, 5, [255, 50, 50], "red"))
267
- creatures.append(Creature(25, 25, [50, 50, 255], "blue"))
268
- creatures.append(Creature(15, 15, [255, 215, 0], "gold"))
 
 
 
 
 
 
269
 
270
  return world, creatures
271
 
272
- # ============== СИМУЛЯЦИЯ ==============
273
- def simulate_step(world, creatures, step):
 
 
 
 
 
 
274
  new_creatures = []
275
 
276
  for creature in creatures[:]:
@@ -292,7 +307,7 @@ def simulate_step(world, creatures, step):
292
  creatures.extend(new_creatures)
293
  creatures = [c for c in creatures if c.alive]
294
 
295
- if step % 5 == 0:
296
  for _ in range(2):
297
  for _ in range(20):
298
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
@@ -306,11 +321,11 @@ def simulate_step(world, creatures, step):
306
  c.alive = False
307
  creatures = [c for c in creatures if c.alive]
308
 
309
- return world, creatures
310
 
311
- # ============== ОТРИСОВКА ==============
312
- def render_world(world, creatures, step):
313
- img = Image.new('RGB', (WORLD_SIZE * 12, WORLD_SIZE * 12 + 80), (30, 30, 30))
314
  draw = ImageDraw.Draw(img)
315
 
316
  for i in range(WORLD_SIZE):
@@ -322,15 +337,15 @@ def render_world(world, creatures, step):
322
  if not creature.alive:
323
  continue
324
  x, y = creature.x * 12, creature.y * 12
325
- draw.ellipse([x+1, y+1, x+11, y+11], fill=tuple(creature.color), outline=(255,255,255))
326
 
327
- hw = int((creature.health / 100) * 12)
328
- fw = int((creature.food / 100) * 12)
329
- ww = int((creature.water / 100) * 12)
330
 
331
- draw.rectangle([x, y-6, x + hw, y-4], fill=(255, 0, 0))
332
- draw.rectangle([x, y-4, x + fw, y-2], fill=(255, 165, 0))
333
- draw.rectangle([x, y-2, x + ww, y], fill=(0, 200, 255))
334
 
335
  y_offset = WORLD_SIZE * 12 + 10
336
 
@@ -347,83 +362,271 @@ def render_world(world, creatures, step):
347
 
348
  best = max([c.fitness for c in creatures if c.alive] + [0])
349
 
350
- draw.text([10, y_offset], f"Шаг:{step} | Существ:{len(creatures)}", fill=(255,255,255))
351
- draw.text([10, y_offset + 20], f"🔴:{red} 🔵:{blue} 🟡:{gold}", fill=(255,255,255))
352
- draw.text([10, y_offset + 40], f"🌳:{trees} 🫐:{foods} 🪨:{stones}", fill=(255,255,255))
353
- draw.text([10, y_offset + 60], f"⭐ Лучший:{best}", fill=(255,255,255))
354
 
355
  return img
356
 
357
- # ============== GRADIO ==============
358
- world, creatures = create_world()
359
- step_counter = 0
360
-
361
- def run_simulation(steps):
362
- global world, creatures, step_counter
363
-
364
- images = []
365
-
366
- for i in range(steps):
367
- step_counter += 1
368
- world, creatures = simulate_step(world, creatures, step_counter)
369
-
370
- if i % 2 == 0:
371
- img = render_world(world, creatures, step_counter)
372
- images.append(img)
373
-
374
- # Статистика
375
- red = sum(1 for c in creatures if c.alive and c.team == "red")
376
- blue = sum(1 for c in creatures if c.alive and c.team == "blue")
377
- gold = sum(1 for c in creatures if c.alive and c.team == "gold")
378
-
379
- trees = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
380
- if world[i, j][0] == 34 and world[i, j][1] == 139 and world[i, j][2] == 34)
381
- foods = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
382
- if world[i, j][0] == 0 and world[i, j][1] == 150 and world[i, j][2] == 0)
383
- stones = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
384
- if world[i, j][0] == 128 and world[i, j][1] == 128 and world[i, j][2] == 128)
385
- best = max([c.fitness for c in creatures if c.alive] + [0])
386
-
387
- stats = f"""СТАТИСТИКА
388
-
389
- Шаг: {step_counter}
390
- Существ: {len(creatures)}
391
-
392
- 🔴 Красных: {red}
393
- 🔵 Синих: {blue}
394
- 🟡 Золотых: {gold}
395
 
396
- 🌳 Деревьев: {trees}
397
- 🫐 Ягод: {foods}
398
- 🪨 Камней: {stones}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
- ⭐ Лучший фитнес: {best}"""
401
-
402
- return images, stats
403
-
404
- def reset_simulation():
405
- global world, creatures, step_counter
406
- world, creatures = create_world()
407
- step_counter = 0
408
- img = render_world(world, creatures, 0)
409
- return [img], "Мир сброшен"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
- # ============== ИНТЕРФЕЙС ==============
412
- with gr.Blocks() as demo:
413
- gr.Markdown("# 🧬 Эволюционная симуляция")
414
- gr.Markdown("🔴 Красные | 🔵 Синие | 🟡 Золотые")
415
-
416
- with gr.Row():
417
- gallery = gr.Gallery(label="Мир", columns=1, rows=1, height=500)
418
- stats = gr.Textbox(label="Статистика", lines=15)
419
-
420
- with gr.Row():
421
- steps = gr.Slider(minimum=5, maximum=50, value=20, step=5, label="Шагов")
422
- run_btn = gr.Button("▶️ Запустить")
423
- reset_btn = gr.Button("🔄 Сбросить")
424
-
425
- run_btn.click(run_simulation, inputs=[steps], outputs=[gallery, stats])
426
- reset_btn.click(reset_simulation, outputs=[gallery, stats])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
- if __name__ == "__main__":
429
- demo.launch(share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # server.py
2
+ import http.server
3
+ import socketserver
4
  import random
5
+ import numpy as np
6
  from PIL import Image, ImageDraw
7
+ import io
8
+ import base64
9
+ from urllib.parse import parse_qs, urlparse
10
+ import threading
11
+ import time
12
 
13
  # ============== НАСТРОЙКИ ==============
14
+ WORLD_SIZE = 40
15
+ CELL_SIZE = 10
16
+ MAX_CREATURES = 50
17
+ PORT = 7890
18
+ UPDATE_INTERVAL = 0.3 # секунд между обновлениями
19
 
20
  # ============== ПРОСТАЯ НЕЙРОСЕТЬ ==============
21
  class SimpleBrain:
 
58
  self.brain = SimpleBrain()
59
 
60
  def think(self, world, creatures):
 
61
  hunger = 1.0 - self.food / 100.0
62
  thirst = 1.0 - self.water / 100.0
63
  health = self.health / 100.0
64
  energy = self.energy / 100.0
65
 
 
66
  nearby = []
67
  for dx in [-1, 0, 1]:
68
  for dy in [-1, 0, 1]:
 
82
  else:
83
  nearby.append(0.0)
84
 
 
85
  partners = 0
86
  enemies = 0
87
  for other in creatures:
 
99
 
100
  output = self.brain.forward(inputs)
101
 
 
102
  dx = int(round(output[0] * 2))
103
  dy = int(round(output[1] * 2))
104
  build = output[2] > 0.3
 
237
  world = np.zeros((WORLD_SIZE, WORLD_SIZE, 3), dtype=np.uint8)
238
  world[:, :] = [100, 200, 100]
239
 
240
+ for _ in range(20):
 
241
  for _ in range(30):
242
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
243
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
244
  world[x, y] = [34, 139, 34]
245
  break
246
 
247
+ for _ in range(15):
248
  for _ in range(30):
249
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
250
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
251
  world[x, y] = [0, 150, 0]
252
  break
253
 
254
+ for _ in range(10):
255
  for _ in range(30):
256
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
257
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
258
  world[x, y] = [128, 128, 128]
259
  break
260
 
261
+ for _ in range(5):
262
  for _ in range(30):
263
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
264
  if world[x, y][0] == 100 and world[x, y][1] == 200 and world[x, y][2] == 100:
 
266
  break
267
 
268
  creatures = []
269
+ # Красные (верхний левый угол)
270
+ for i in range(3):
271
+ creatures.append(Creature(5 + i*2, 5 + i*2, [255, 50, 50], "red"))
272
+ # Синие (нижний правый угол)
273
+ for i in range(3):
274
+ creatures.append(Creature(WORLD_SIZE-5 - i*2, WORLD_SIZE-5 - i*2, [50, 50, 255], "blue"))
275
+ # Золотые (центр)
276
+ for i in range(2):
277
+ creatures.append(Creature(WORLD_SIZE//2 + i*2, WORLD_SIZE//2 + i*2, [255, 215, 0], "gold"))
278
 
279
  return world, creatures
280
 
281
+ # ============== ГЛОБАЛЬНОЕ СОСТОЯНИЕ ==============
282
+ world, creatures = create_world()
283
+ step_counter = 0
284
+
285
+ # ============== ФУНКЦИИ СИМУЛЯЦИИ ==============
286
+ def simulate_step():
287
+ global world, creatures, step_counter
288
+
289
  new_creatures = []
290
 
291
  for creature in creatures[:]:
 
307
  creatures.extend(new_creatures)
308
  creatures = [c for c in creatures if c.alive]
309
 
310
+ if step_counter % 5 == 0:
311
  for _ in range(2):
312
  for _ in range(20):
313
  x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
 
321
  c.alive = False
322
  creatures = [c for c in creatures if c.alive]
323
 
324
+ step_counter += 1
325
 
326
+ def render_world():
327
+ # Размер изображения подстраивается под окно
328
+ img = Image.new('RGB', (WORLD_SIZE * 12, WORLD_SIZE * 12 + 60), (30, 30, 30))
329
  draw = ImageDraw.Draw(img)
330
 
331
  for i in range(WORLD_SIZE):
 
337
  if not creature.alive:
338
  continue
339
  x, y = creature.x * 12, creature.y * 12
340
+ draw.ellipse([x+2, y+2, x+10, y+10], fill=tuple(creature.color), outline=(255,255,255))
341
 
342
+ hw = int((creature.health / 100) * 10)
343
+ fw = int((creature.food / 100) * 10)
344
+ ww = int((creature.water / 100) * 10)
345
 
346
+ draw.rectangle([x+1, y-4, x + hw, y-2], fill=(255, 0, 0))
347
+ draw.rectangle([x+1, y-2, x + fw, y], fill=(255, 165, 0))
348
+ draw.rectangle([x+1, y, x + ww, y+2], fill=(0, 200, 255))
349
 
350
  y_offset = WORLD_SIZE * 12 + 10
351
 
 
362
 
363
  best = max([c.fitness for c in creatures if c.alive] + [0])
364
 
365
+ draw.text([10, y_offset], f"Шаг: {step_counter} | Существ: {len(creatures)}", fill=(255,255,255))
366
+ draw.text([10, y_offset + 20], f"🔴: {red} | 🔵: {blue} | 🟡: {gold}", fill=(255,255,255))
367
+ draw.text([10, y_offset + 40], f"🌳: {trees} | 🫐: {foods} | 🪨: {stones} | ⭐: {best}", fill=(255,255,255))
 
368
 
369
  return img
370
 
371
+ # ============== ПОТОК СИМУЛЯЦИИ ==============
372
+ def simulation_loop():
373
+ while True:
374
+ simulate_step()
375
+ time.sleep(UPDATE_INTERVAL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ # ============== HTTP СЕРВЕР ==============
378
+ HTML_TEMPLATE = """
379
+ <!DOCTYPE html>
380
+ <html>
381
+ <head>
382
+ <meta charset="UTF-8">
383
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
384
+ <title>🧬 Эволюция</title>
385
+ <style>
386
+ * { margin: 0; padding: 0; }
387
+ html, body {
388
+ width: 100%;
389
+ height: 100%;
390
+ overflow: hidden;
391
+ background: #0a0a1a;
392
+ display: flex;
393
+ justify-content: center;
394
+ align-items: center;
395
+ }
396
+ body {
397
+ font-family: 'Segoe UI', Arial, sans-serif;
398
+ }
399
+ .container {
400
+ width: 100vw;
401
+ height: 100vh;
402
+ display: flex;
403
+ justify-content: center;
404
+ align-items: center;
405
+ background: #0a0a1a;
406
+ }
407
+ .world-wrapper {
408
+ width: 100%;
409
+ height: 100%;
410
+ display: flex;
411
+ justify-content: center;
412
+ align-items: center;
413
+ padding: 10px;
414
+ }
415
+ .world-wrapper img {
416
+ max-width: 100%;
417
+ max-height: 100%;
418
+ width: auto;
419
+ height: auto;
420
+ image-rendering: pixelated;
421
+ border-radius: 5px;
422
+ box-shadow: 0 0 60px rgba(78, 205, 196, 0.1);
423
+ }
424
+ .stats-overlay {
425
+ position: fixed;
426
+ bottom: 20px;
427
+ left: 50%;
428
+ transform: translateX(-50%);
429
+ background: rgba(10, 10, 30, 0.85);
430
+ padding: 12px 25px;
431
+ border-radius: 12px;
432
+ border: 1px solid rgba(78, 205, 196, 0.3);
433
+ color: #aaa;
434
+ font-size: 14px;
435
+ text-align: center;
436
+ backdrop-filter: blur(10px);
437
+ pointer-events: none;
438
+ user-select: none;
439
+ display: flex;
440
+ gap: 20px;
441
+ flex-wrap: wrap;
442
+ justify-content: center;
443
+ }
444
+ .stats-overlay span {
445
+ color: #eee;
446
+ }
447
+ .stat-label {
448
+ color: #666;
449
+ font-size: 12px;
450
+ text-transform: uppercase;
451
+ letter-spacing: 1px;
452
+ }
453
+ .stat-value {
454
+ color: #4ecdc4;
455
+ font-weight: bold;
456
+ font-size: 16px;
457
+ }
458
+ .stat-red .stat-value { color: #ff6b6b; }
459
+ .stat-blue .stat-value { color: #4dabf7; }
460
+ .stat-gold .stat-value { color: #ffd93d; }
461
+ .stat-item {
462
+ display: flex;
463
+ align-items: baseline;
464
+ gap: 5px;
465
+ }
466
+ @media (max-width: 700px) {
467
+ .stats-overlay {
468
+ font-size: 11px;
469
+ padding: 8px 15px;
470
+ gap: 10px;
471
+ bottom: 10px;
472
+ flex-wrap: wrap;
473
+ }
474
+ .stat-value { font-size: 13px; }
475
+ }
476
+ </style>
477
+ </head>
478
+ <body>
479
+ <div class="container">
480
+ <div class="world-wrapper">
481
+ <img id="worldImage" src="/image" alt="Эволюция">
482
+ </div>
483
+ </div>
484
+
485
+ <div class="stats-overlay" id="stats">
486
+ <div class="stat-item">
487
+ <span class="stat-label">Шаг</span>
488
+ <span class="stat-value" id="step">0</span>
489
+ </div>
490
+ <div class="stat-item">
491
+ <span class="stat-label">Существ</span>
492
+ <span class="stat-value" id="count">0</span>
493
+ </div>
494
+ <div class="stat-item stat-red">
495
+ <span class="stat-label">🔴</span>
496
+ <span class="stat-value" id="red">0</span>
497
+ </div>
498
+ <div class="stat-item stat-blue">
499
+ <span class="stat-label">🔵</span>
500
+ <span class="stat-value" id="blue">0</span>
501
+ </div>
502
+ <div class="stat-item stat-gold">
503
+ <span class="stat-label">🟡</span>
504
+ <span class="stat-value" id="gold">0</span>
505
+ </div>
506
+ <div class="stat-item">
507
+ <span class="stat-label">🌳</span>
508
+ <span class="stat-value" id="trees">0</span>
509
+ </div>
510
+ <div class="stat-item">
511
+ <span class="stat-label">🫐</span>
512
+ <span class="stat-value" id="food">0</span>
513
+ </div>
514
+ <div class="stat-item">
515
+ <span class="stat-label">⭐</span>
516
+ <span class="stat-value" id="fitness">0</span>
517
+ </div>
518
+ </div>
519
 
520
+ <script>
521
+ function updateImage() {
522
+ document.getElementById('worldImage').src = '/image?t=' + Date.now();
523
+ }
524
+
525
+ function updateStats() {
526
+ fetch('/stats')
527
+ .then(r => r.json())
528
+ .then(data => {
529
+ document.getElementById('step').textContent = data.step;
530
+ document.getElementById('count').textContent = data.total;
531
+ document.getElementById('red').textContent = data.red;
532
+ document.getElementById('blue').textContent = data.blue;
533
+ document.getElementById('gold').textContent = data.gold;
534
+ document.getElementById('trees').textContent = data.trees;
535
+ document.getElementById('food').textContent = data.food;
536
+ document.getElementById('fitness').textContent = data.best_fitness;
537
+ })
538
+ .catch(() => {});
539
+ }
540
+
541
+ function update() {
542
+ updateImage();
543
+ updateStats();
544
+ }
545
+
546
+ // Обновляем каждые 300ms
547
+ setInterval(update, 300);
548
+
549
+ // Первое обновление
550
+ update();
551
+ </script>
552
+ </body>
553
+ </html>
554
+ """
555
 
556
+ class SimulationHandler(http.server.BaseHTTPRequestHandler):
557
+ def do_GET(self):
558
+ parsed = urlparse(self.path)
559
+ path = parsed.path
560
+
561
+ if path == '/':
562
+ self.send_response(200)
563
+ self.send_header('Content-type', 'text/html; charset=utf-8')
564
+ self.end_headers()
565
+ self.wfile.write(HTML_TEMPLATE.encode('utf-8'))
566
+
567
+ elif path == '/image':
568
+ img = render_world()
569
+ buffer = io.BytesIO()
570
+ img.save(buffer, format='PNG')
571
+ buffer.seek(0)
572
+
573
+ self.send_response(200)
574
+ self.send_header('Content-type', 'image/png')
575
+ self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
576
+ self.end_headers()
577
+ self.wfile.write(buffer.getvalue())
578
+
579
+ elif path == '/stats':
580
+ red = sum(1 for c in creatures if c.alive and c.team == "red")
581
+ blue = sum(1 for c in creatures if c.alive and c.team == "blue")
582
+ gold = sum(1 for c in creatures if c.alive and c.team == "gold")
583
+
584
+ trees = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
585
+ if world[i, j][0] == 34 and world[i, j][1] == 139 and world[i, j][2] == 34)
586
+ foods = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
587
+ if world[i, j][0] == 0 and world[i, j][1] == 150 and world[i, j][2] == 0)
588
+ stones = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE)
589
+ if world[i, j][0] == 128 and world[i, j][1] == 128 and world[i, j][2] == 128)
590
+
591
+ best = max([c.fitness for c in creatures if c.alive] + [0])
592
+
593
+ stats = {
594
+ 'total': len([c for c in creatures if c.alive]),
595
+ 'red': red,
596
+ 'blue': blue,
597
+ 'gold': gold,
598
+ 'trees': trees,
599
+ 'food': foods,
600
+ 'stones': stones,
601
+ 'best_fitness': best,
602
+ 'step': step_counter
603
+ }
604
+
605
+ self.send_response(200)
606
+ self.send_header('Content-type', 'application/json')
607
+ self.end_headers()
608
+ self.wfile.write(json.dumps(stats).encode())
609
+
610
+ else:
611
+ self.send_response(404)
612
+ self.end_headers()
613
+
614
+ def log_message(self, format, *args):
615
+ pass
616
 
617
+ # ============== ЗАПУСК ==============
618
+ if __name__ == '__main__':
619
+ # Запускаем симуляцию в отдельном потоке
620
+ sim_thread = threading.Thread(target=simulation_loop, daemon=True)
621
+ sim_thread.start()
622
+
623
+ print(f"🧬 Эволюционная симуляция запущена!")
624
+ print(f"🌍 Открой в браузере: http://localhost:{PORT}")
625
+ print("=" * 50)
626
+ print("⏹️ Для остановки нажми Ctrl+C")
627
+
628
+ with socketserver.TCPServer(("", PORT), SimulationHandler) as httpd:
629
+ try:
630
+ httpd.serve_forever()
631
+ except KeyboardInterrupt:
632
+ print("\n⏹️ Остановка сервера...")