X commited on
Commit
767a93b
·
verified ·
1 Parent(s): 4d94f7e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +265 -374
app.py CHANGED
@@ -6,104 +6,84 @@ import torch.nn as nn
6
  import random
7
  from collections import deque
8
  import math
9
- from datetime import datetime
10
- import os
11
  from PIL import Image, ImageDraw
12
 
13
  # ============== НАСТРОЙКИ ==============
14
- WORLD_SIZE = 50
15
- CELL_SIZE = 8
16
- MAX_CREATURES = 60
17
  MUTATION_RATE = 0.2
18
  MUTATION_SCALE = 0.25
19
  RESPAWN_RATE = 5
20
- SAVE_DIR = "saved_models"
21
-
22
- os.makedirs(SAVE_DIR, exist_ok=True)
23
 
24
  # ============== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==============
25
  def is_grass(color):
26
- """Проверка, является ли клетка травой"""
27
- return int(color[0]) == 100 and int(color[1]) == 200 and int(color[2]) == 100
28
 
29
  def is_tree(color):
30
- return int(color[0]) == 34 and int(color[1]) == 139 and int(color[2]) == 34
31
 
32
  def is_food(color):
33
- return int(color[0]) == 0 and int(color[1]) == 150 and int(color[2]) == 0
34
 
35
  def is_stone(color):
36
- return int(color[0]) == 128 and int(color[1]) == 128 and int(color[2]) == 128
37
 
38
  def is_metal(color):
39
- return int(color[0]) == 192 and int(color[1]) == 192 and int(color[2]) == 192
40
 
41
  def is_water(color):
42
- return int(color[0]) == 0 and int(color[1]) == 100 and int(color[2]) == 255
43
-
44
- def is_wall(color):
45
- return int(color[0]) == 139 and int(color[1]) == 69 and int(color[2]) == 19
46
 
47
  # ============== НЕЙРОСЕТЬ ==============
48
  class CreatureBrain(nn.Module):
49
- def __init__(self, input_size=14, hidden_size=20, output_size=6):
50
  super().__init__()
51
  self.fc1 = nn.Linear(input_size, hidden_size)
52
  self.fc2 = nn.Linear(hidden_size, hidden_size)
53
  self.fc3 = nn.Linear(hidden_size, output_size)
54
- self.dropout = nn.Dropout(0.1)
55
 
56
  def forward(self, x):
57
  x = torch.relu(self.fc1(x))
58
- x = self.dropout(x)
59
  x = torch.relu(self.fc2(x))
60
  x = torch.tanh(self.fc3(x))
61
  return x
62
 
63
  # ============== СУЩЕСТВО ==============
64
  class Creature:
65
- def __init__(self, x, y, color, brain=None, generation=0, team="neutral"):
66
  self.x = x
67
  self.y = y
68
  self.color = color
69
  self.team = team
70
  self.generation = generation
71
- self.genome_id = f"{datetime.now().timestamp()}{random.randint(1000,9999)}"
72
 
73
- # Ресурсы
74
  self.health = 100.0
75
  self.energy = 80.0
76
  self.food = 50.0
77
  self.water = 50.0
78
- self.resources = 0 # камни
79
  self.metal = 0
80
  self.age = 0
81
  self.alive = True
82
  self.fitness = 0
83
  self.children = 0
84
 
85
- # Память
86
  self.memory = deque(maxlen=5)
87
 
88
- # Нейросеть
89
  if brain is None:
90
  self.brain = CreatureBrain()
91
  else:
92
  self.brain = brain
93
-
94
- self.ancestors = []
95
-
96
  def get_inputs(self, world, creatures):
97
- """Сбор входных данных для нейросети"""
98
- # 1. Состояние существа
99
- hunger = 1.0 - (self.food / 100.0)
100
- thirst = 1.0 - (self.water / 100.0)
101
  health = self.health / 100.0
102
  energy = self.energy / 100.0
103
- resources = self.resources / 10.0
104
- metal = self.metal / 5.0
105
 
106
- # 2. Обзор вокруг (8 направлений)
107
  nearby = []
108
  for dx in [-1, 0, 1]:
109
  for dy in [-1, 0, 1]:
@@ -111,46 +91,38 @@ class Creature:
111
  continue
112
  nx, ny = self.x + dx, self.y + dy
113
  if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
114
- cell_color = world.grid[nx, ny]
115
- if is_tree(cell_color):
116
  nearby.append(1.0)
117
- elif is_food(cell_color):
118
  nearby.append(2.0)
119
- elif is_stone(cell_color):
120
  nearby.append(0.5)
121
- elif is_metal(cell_color):
122
  nearby.append(3.0)
123
- elif is_water(cell_color):
124
  nearby.append(4.0)
125
  else:
126
  nearby.append(0.0)
127
  else:
128
  nearby.append(0.0)
129
 
130
- # 3. Ближайшие существа
131
- partners = []
132
- enemies = []
133
  for other in creatures:
134
  if other is self or not other.alive:
135
  continue
136
  dist = math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
137
- if dist < 5:
138
  if other.team == self.team:
139
- partners.append(1.0 - dist/5.0)
140
  else:
141
- enemies.append(1.0 - dist/5.0)
142
 
143
- # Формируем входной вектор
144
- inputs = [
145
- hunger, thirst, health, energy, resources, metal,
146
- max(partners) if partners else 0.0,
147
- max(enemies) if enemies else 0.0,
148
- self.children / 10.0,
149
- self.generation / 50.0
150
- ]
151
-
152
- # Добавляем обзор (берем первые 4 направления для экономии)
153
- inputs.extend(nearby[:4])
154
 
155
  # Добавляем память
156
  memory_flat = []
@@ -161,14 +133,13 @@ class Creature:
161
  inputs.extend(memory_flat[:4])
162
 
163
  # Обрезаем до нужного размера
164
- inputs = inputs[:14]
165
- while len(inputs) < 14:
166
  inputs.append(0.0)
167
 
168
  return torch.tensor(inputs, dtype=torch.float32)
169
 
170
  def think(self, world, creatures):
171
- """Принятие решений"""
172
  inputs = self.get_inputs(world, creatures)
173
 
174
  with torch.no_grad():
@@ -183,7 +154,7 @@ class Creature:
183
  self.move(dx, dy)
184
 
185
  # Строительство
186
- if actions[2] > 0.3:
187
  self.build(world)
188
 
189
  # Размножение
@@ -201,39 +172,34 @@ class Creature:
201
  return None
202
 
203
  def move(self, dx, dy):
204
- new_x = max(0, min(WORLD_SIZE-1, self.x + dx))
205
- new_y = max(0, min(WORLD_SIZE-1, self.y + dy))
206
- self.x, self.y = new_x, new_y
207
  self.energy -= 0.3
208
  self.food -= 0.2
209
  self.water -= 0.1
210
  self.age += 1
211
-
212
  def build(self, world):
213
- """Строительство стены"""
214
- if self.resources >= 2:
215
- self.resources -= 2
216
- for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
217
- nx, ny = self.x + dx, self.y + dy
218
- if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
219
- if is_grass(world.grid[nx, ny]):
220
- world.grid[nx, ny] = [139, 69, 19] # стена
221
- self.fitness += 15
222
- return True
223
  return False
224
 
225
  def mine_metal(self, world):
226
- """Добыча металла"""
227
- if is_metal(world.grid[self.x, self.y]):
228
  self.metal += 1
229
- world.grid[self.x, self.y] = [100, 200, 100]
230
  self.fitness += 15
231
  self.energy -= 3
232
  return True
233
  return False
234
 
235
  def reproduce(self, creatures):
236
- """Размножение"""
237
  if len(creatures) >= MAX_CREATURES:
238
  return None
239
 
@@ -247,21 +213,17 @@ class Creature:
247
  break
248
 
249
  if partner:
250
- # Половое размножение
251
  child_brain = CreatureBrain()
252
- # Смешиваем веса
253
- for child_param, parent1_param, parent2_param in zip(
254
  child_brain.parameters(),
255
  self.brain.parameters(),
256
  partner.brain.parameters()
257
  ):
258
- mask = torch.rand_like(parent1_param) > 0.5
259
- child_param.data = torch.where(mask, parent1_param.data, parent2_param.data)
260
- # Мутация
261
  if random.random() < MUTATION_RATE:
262
  child_param.data += torch.randn_like(child_param) * MUTATION_SCALE
263
  else:
264
- # Бесполое размножение (клон с мутацией)
265
  child_brain = CreatureBrain()
266
  child_brain.load_state_dict(self.brain.state_dict())
267
  for param in child_brain.parameters():
@@ -272,9 +234,9 @@ class Creature:
272
  self.x + random.randint(-3, 3),
273
  self.y + random.randint(-3, 3),
274
  self.color,
275
- brain=child_brain,
276
- generation=self.generation + 1,
277
- team=self.team
278
  )
279
  child.food = 30
280
  child.water = 30
@@ -289,13 +251,11 @@ class Creature:
289
  return child
290
 
291
  def attack(self, creatures):
292
- """Атака врага"""
293
  for other in creatures:
294
  if other is not self and other.alive and other.team != self.team:
295
  dist = abs(self.x - other.x) + abs(self.y - other.y)
296
  if dist <= 1:
297
- damage = 10 + (self.health / 10)
298
- other.health -= damage
299
  self.energy -= 5
300
  self.food -= 2
301
  self.fitness += 8
@@ -303,44 +263,40 @@ class Creature:
303
  return False
304
 
305
  def eat_food(self, world):
306
- """Поедание еды"""
307
- cell = world.grid[self.x, self.y]
308
  if is_food(cell):
309
  self.food = min(100, self.food + 30)
310
  self.health = min(100, self.health + 8)
311
- world.grid[self.x, self.y] = [100, 200, 100]
312
  self.fitness += 12
313
  return True
314
  elif is_tree(cell):
315
  self.food = min(100, self.food + 15)
316
  self.health = min(100, self.health + 5)
317
- world.grid[self.x, self.y] = [100, 200, 100]
318
  self.fitness += 8
319
  return True
320
  return False
321
 
322
  def drink_water(self, world):
323
- """Питье воды"""
324
- if is_water(world.grid[self.x, self.y]):
325
  self.water = min(100, self.water + 40)
326
  self.health = min(100, self.health + 5)
327
  if random.random() < 0.3:
328
- world.grid[self.x, self.y] = [100, 200, 100]
329
  self.fitness += 5
330
  return True
331
  return False
332
 
333
  def collect_stone(self, world):
334
- """Сбор камней"""
335
- if is_stone(world.grid[self.x, self.y]):
336
  self.resources += 1
337
- world.grid[self.x, self.y] = [100, 200, 100]
338
  self.fitness += 10
339
  return True
340
  return False
341
 
342
  def update(self):
343
- """Обновление состояния"""
344
  self.food -= 0.3
345
  self.water -= 0.2
346
  self.health -= 0.1
@@ -361,303 +317,238 @@ class Creature:
361
 
362
  return True
363
 
364
- # ============== МИР ==============
365
- class World:
366
- def __init__(self):
367
- self.grid = np.zeros((WORLD_SIZE, WORLD_SIZE, 3), dtype=np.uint8)
368
- self.grid[:, :] = [100, 200, 100]
369
- self.creatures = []
370
- self.resources = {'trees': [], 'food': [], 'stones': [], 'metal': [], 'water': []}
371
- self.step_count = 0
372
- self.scores = {'red': 0, 'blue': 0, 'gold': 0}
373
- self.genealogy = []
374
-
375
- self._generate_resources()
376
- self._spawn_creatures()
377
-
378
- def _generate_resources(self):
379
- """Генерация ресурсов"""
380
- for _ in range(25):
381
- x, y = self._random_free_cell()
382
- if x is not None:
383
- self.grid[x, y] = [34, 139, 34]
384
- self.resources['trees'].append((x, y))
385
-
386
- for _ in range(20):
387
- x, y = self._random_free_cell()
388
- if x is not None:
389
- self.grid[x, y] = [0, 150, 0]
390
- self.resources['food'].append((x, y))
391
-
392
- for _ in range(15):
393
- x, y = self._random_free_cell()
394
- if x is not None:
395
- self.grid[x, y] = [128, 128, 128]
396
- self.resources['stones'].append((x, y))
397
-
398
- for _ in range(8):
399
- x, y = self._random_free_cell()
400
- if x is not None:
401
- self.grid[x, y] = [192, 192, 192]
402
- self.resources['metal'].append((x, y))
403
-
404
- for _ in range(6):
405
- x, y = self._random_free_cell()
406
- if x is not None:
407
- self.grid[x, y] = [0, 100, 255]
408
- self.resources['water'].append((x, y))
409
-
410
- def _random_free_cell(self):
411
- """Поиск свободной клетки"""
412
  for _ in range(50):
413
- x = random.randint(0, WORLD_SIZE-1)
414
- y = random.randint(0, WORLD_SIZE-1)
415
- if is_grass(self.grid[x, y]):
416
- return x, y
417
- return None, None
418
-
419
- def _spawn_creatures(self):
420
- """Создание существ"""
421
- # Красный вид
422
- brain1 = CreatureBrain()
423
- c1 = Creature(10, 10, [255, 50, 50], brain1, team="red")
424
- self.creatures.append(c1)
425
-
426
- # Синий вид
427
- brain2 = CreatureBrain()
428
- c2 = Creature(40, 40, [50, 50, 255], brain2, team="blue")
429
- self.creatures.append(c2)
430
-
431
- # Золотой вид (нейтральный)
432
- brain3 = CreatureBrain()
433
- c3 = Creature(25, 25, [255, 215, 0], brain3, team="gold")
434
- self.creatures.append(c3)
435
-
436
- def step(self):
437
- """Один шаг симуляции"""
438
- self.step_count += 1
439
-
440
- # Действия существ
441
- new_creatures = []
442
- for creature in self.creatures[:]:
443
- if not creature.alive:
444
- continue
445
-
446
- creature.eat_food(self.grid)
447
- creature.drink_water(self.grid)
448
- creature.collect_stone(self.grid)
449
-
450
- child = creature.think(self.grid, self.creatures)
451
- if child:
452
- new_creatures.append(child)
453
-
454
- if not creature.update():
455
- creature.alive = False
456
- self.grid[creature.x, creature.y] = [200, 100, 100]
457
- self.genealogy.append({
458
- 'team': creature.team,
459
- 'generation': creature.generation,
460
- 'fitness': creature.fitness,
461
- 'children': creature.children
462
- })
463
-
464
- self.creatures.extend(new_creatures)
465
- self.creatures = [c for c in self.creatures if c.alive]
466
-
467
- # Восстановление ресурсов
468
- if self.step_count % RESPAWN_RATE == 0:
469
- if len(self.resources['food']) < 15:
470
- x, y = self._random_free_cell()
471
- if x is not None:
472
- self.grid[x, y] = [0, 150, 0]
473
- self.resources['food'].append((x, y))
474
-
475
- if len(self.resources['trees']) < 20:
476
- x, y = self._random_free_cell()
477
- if x is not None:
478
- self.grid[x, y] = [34, 139, 34]
479
- self.resources['trees'].append((x, y))
480
-
481
- # Естественный отбор
482
- if len(self.creatures) > MAX_CREATURES:
483
- self.creatures.sort(key=lambda c: c.fitness, reverse=True)
484
- self.creatures = self.creatures[:int(MAX_CREATURES * 0.7)]
485
-
486
- # Обновление очков
487
- if self.step_count % 20 == 0:
488
- for creature in self.creatures:
489
- if creature.alive:
490
- self.scores[creature.team] += creature.fitness / 100
491
-
492
- def render(self):
493
- """Отрисовка мира"""
494
- img = Image.new('RGB', (WORLD_SIZE * CELL_SIZE, WORLD_SIZE * CELL_SIZE + 120), (30, 30, 30))
495
- draw = ImageDraw.Draw(img)
496
-
497
- # Рисуем мир
498
- for i in range(WORLD_SIZE):
499
- for j in range(WORLD_SIZE):
500
- x, y = i * CELL_SIZE, j * CELL_SIZE
501
- color = tuple(self.grid[i, j].tolist())
502
- draw.rectangle([x, y, x + CELL_SIZE, y + CELL_SIZE], fill=color)
503
-
504
- # Рисуем существ
505
- for creature in self.creatures:
506
- if not creature.alive:
507
- continue
508
- x, y = creature.x * CELL_SIZE, creature.y * CELL_SIZE
509
- color = tuple(creature.color)
510
-
511
- draw.ellipse([x+1, y+1, x+CELL_SIZE-1, y+CELL_SIZE-1],
512
- fill=color, outline=(255,255,255))
513
-
514
- # Индикаторы
515
- health_w = (creature.health / 100) * CELL_SIZE
516
- food_w = (creature.food / 100) * CELL_SIZE
517
- water_w = (creature.water / 100) * CELL_SIZE
518
-
519
- draw.rectangle([x, y-6, x + health_w, y-4], fill=(255, 0, 0))
520
- draw.rectangle([x, y-4, x + food_w, y-2], fill=(255, 165, 0))
521
- draw.rectangle([x, y-2, x + water_w, y], fill=(0, 200, 255))
522
-
523
- # Статистика
524
- y_offset = WORLD_SIZE * CELL_SIZE + 10
525
-
526
- red = sum(1 for c in self.creatures if c.alive and c.team == 'red')
527
- blue = sum(1 for c in self.creatures if c.alive and c.team == 'blue')
528
- gold = sum(1 for c in self.creatures if c.alive and c.team == 'gold')
529
 
530
- draw.text([10, y_offset], f"Шаг: {self.step_count} | 👥: {len(self.creatures)}", fill=(255,255,255))
531
- draw.text([10, y_offset + 20], f"🔴 Красные: {red} | 🔵 Синие: {blue} | 🟡 Золотые: {gold}", fill=(255,255,255))
532
- draw.text([10, y_offset + 40], f"🌳 Деревья: {len(self.resources['trees'])} | 🫐 Еда: {len(self.resources['food'])}", fill=(255,255,255))
533
- draw.text([10, y_offset + 60], f"🪨 Камни: {len(self.resources['stones'])} | ⚙️ Металл: {len(self.resources['metal'])} | 💧 Вода: {len(self.resources['water'])}", fill=(255,255,255))
534
 
535
- best_fitness = max([c.fitness for c in self.creatures if c.alive] + [0])
536
- draw.text([10, y_offset + 80], f"⭐ Лучший фитнес: {best_fitness:.1f}", fill=(255,255,255))
 
537
 
538
- return img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
 
540
- # ============== GRADIO ИНТЕРФЕЙС ==============
541
- world = World()
542
- simulation_running = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
  def run_simulation(steps):
545
- global world, simulation_running
546
-
547
- if not simulation_running:
548
- world = World()
549
- simulation_running = True
550
 
551
  images = []
552
- stats = []
553
 
554
  for i in range(steps):
555
- world.step()
556
-
557
- red = sum(1 for c in world.creatures if c.alive and c.team == 'red')
558
- blue = sum(1 for c in world.creatures if c.alive and c.team == 'blue')
559
- gold = sum(1 for c in world.creatures if c.alive and c.team == 'gold')
560
-
561
- stats.append({
562
- 'step': world.step_count,
563
- 'red': red,
564
- 'blue': blue,
565
- 'gold': gold,
566
- 'total': len(world.creatures),
567
- 'trees': len(world.resources['trees']),
568
- 'food': len(world.resources['food']),
569
- 'stones': len(world.resources['stones']),
570
- 'metal': len(world.resources['metal']),
571
- 'water': len(world.resources['water']),
572
- 'best_fitness': max([c.fitness for c in world.creatures if c.alive] + [0])
573
- })
574
 
575
  if i % 2 == 0:
576
- images.append(world.render())
 
577
 
578
- return images, stats
579
-
580
- def reset_world():
581
- global world, simulation_running
582
- world = World()
583
- simulation_running = False
584
- return [world.render()], []
585
-
586
- def update_stats(stats):
587
- if not stats:
588
- return "Нет данных"
589
 
590
- last = stats[-1]
591
- text = f"""
 
 
 
 
 
 
592
  📊 СТАТИСТИКА:
593
 
594
- Шаг: {last['step']}
595
- Всего: {last['total']}
596
- 🔴 Красных: {last['red']}
597
- 🔵 Синих: {last['blue']}
598
- 🟡 Золотых: {last['gold']}
599
 
600
  🌳 Ресурсы:
601
- Деревья: {last['trees']}
602
- 🫐 Еда: {last['food']}
603
- 🪨 Камни: {last['stones']}
604
- ⚙️ Металл: {last['metal']}
605
- 💧 Вода: {last['water']}
606
 
607
- ⭐ Лучший фитнес: {last['best_fitness']:.1f}
608
  """
609
- return text
 
 
 
 
 
 
 
 
610
 
611
  # ============== ИНТЕРФЕЙС ==============
612
- with gr.Blocks(title="🧬 Эволюционная симуляция", theme=gr.themes.Soft()) as demo:
613
  gr.Markdown("""
614
  # 🧬 Эволюционная симуляция с нейросетями
615
 
616
- **3 вида:** 🔴 Красные | 🔵 Синие | 🟡 Золотые
617
 
618
- **Ресурсы:** 🌳 Деревья | 🫐 Ягоды | 🪨 Камни | ⚙Металл | 💧 Вода
619
  """)
620
 
621
  with gr.Row():
622
- with gr.Column(scale=2):
623
- gallery = gr.Gallery(label="Мир", columns=1, rows=1, height=700)
624
-
625
- with gr.Column(scale=1):
626
- stats_output = gr.Textbox(label="Статистика", lines=15, interactive=False)
627
-
628
- with gr.Row():
629
- steps_input = gr.Slider(label="Шагов", minimum=10, maximum=200, value=50, step=10)
630
-
631
- with gr.Row():
632
- run_btn = gr.Button("▶️ Запустить", variant="primary")
633
- reset_btn = gr.Button("🔄 Сбросить", variant="secondary")
634
-
635
- state = gr.State({"images": [], "stats": []})
636
-
637
- def run_action(steps, state_data):
638
- images, stats = run_simulation(steps)
639
- state_data["images"] = images
640
- state_data["stats"] = stats
641
- return images, update_stats(stats), state_data
642
-
643
- def reset_action(state_data):
644
- images, stats = reset_world()
645
- state_data["images"] = images
646
- state_data["stats"] = stats
647
- return images, update_stats(stats), state_data
648
-
649
- run_btn.click(
650
- run_action,
651
- inputs=[steps_input, state],
652
- outputs=[gallery, stats_output, state]
653
- )
654
-
655
- reset_btn.click(
656
- reset_action,
657
- inputs=[state],
658
- outputs=[gallery, stats_output, state]
659
- )
660
 
661
- # ============== ЗАПУСК С share=False ==============
662
  if __name__ == "__main__":
663
- demo.launch(share=False)
 
6
  import random
7
  from collections import deque
8
  import math
 
 
9
  from PIL import Image, ImageDraw
10
 
11
  # ============== НАСТРОЙКИ ==============
12
+ WORLD_SIZE = 40
13
+ CELL_SIZE = 10
14
+ MAX_CREATURES = 40
15
  MUTATION_RATE = 0.2
16
  MUTATION_SCALE = 0.25
17
  RESPAWN_RATE = 5
 
 
 
18
 
19
  # ============== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==============
20
  def is_grass(color):
21
+ return color[0] == 100 and color[1] == 200 and color[2] == 100
 
22
 
23
  def is_tree(color):
24
+ return color[0] == 34 and color[1] == 139 and color[2] == 34
25
 
26
  def is_food(color):
27
+ return color[0] == 0 and color[1] == 150 and color[2] == 0
28
 
29
  def is_stone(color):
30
+ return color[0] == 128 and color[1] == 128 and color[2] == 128
31
 
32
  def is_metal(color):
33
+ return color[0] == 192 and color[1] == 192 and color[2] == 192
34
 
35
  def is_water(color):
36
+ return color[0] == 0 and color[1] == 100 and color[2] == 255
 
 
 
37
 
38
  # ============== НЕЙРОСЕТЬ ==============
39
  class CreatureBrain(nn.Module):
40
+ def __init__(self, input_size=12, hidden_size=16, output_size=6):
41
  super().__init__()
42
  self.fc1 = nn.Linear(input_size, hidden_size)
43
  self.fc2 = nn.Linear(hidden_size, hidden_size)
44
  self.fc3 = nn.Linear(hidden_size, output_size)
 
45
 
46
  def forward(self, x):
47
  x = torch.relu(self.fc1(x))
 
48
  x = torch.relu(self.fc2(x))
49
  x = torch.tanh(self.fc3(x))
50
  return x
51
 
52
  # ============== СУЩЕСТВО ==============
53
  class Creature:
54
+ def __init__(self, x, y, color, team, brain=None, generation=0):
55
  self.x = x
56
  self.y = y
57
  self.color = color
58
  self.team = team
59
  self.generation = generation
 
60
 
 
61
  self.health = 100.0
62
  self.energy = 80.0
63
  self.food = 50.0
64
  self.water = 50.0
65
+ self.resources = 0
66
  self.metal = 0
67
  self.age = 0
68
  self.alive = True
69
  self.fitness = 0
70
  self.children = 0
71
 
 
72
  self.memory = deque(maxlen=5)
73
 
 
74
  if brain is None:
75
  self.brain = CreatureBrain()
76
  else:
77
  self.brain = brain
78
+
 
 
79
  def get_inputs(self, world, creatures):
80
+ # Состояние
81
+ hunger = 1.0 - self.food / 100.0
82
+ thirst = 1.0 - self.water / 100.0
 
83
  health = self.health / 100.0
84
  energy = self.energy / 100.0
 
 
85
 
86
+ # Обзор вокруг
87
  nearby = []
88
  for dx in [-1, 0, 1]:
89
  for dy in [-1, 0, 1]:
 
91
  continue
92
  nx, ny = self.x + dx, self.y + dy
93
  if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
94
+ cell = world[nx, ny]
95
+ if is_tree(cell):
96
  nearby.append(1.0)
97
+ elif is_food(cell):
98
  nearby.append(2.0)
99
+ elif is_stone(cell):
100
  nearby.append(0.5)
101
+ elif is_metal(cell):
102
  nearby.append(3.0)
103
+ elif is_water(cell):
104
  nearby.append(4.0)
105
  else:
106
  nearby.append(0.0)
107
  else:
108
  nearby.append(0.0)
109
 
110
+ # Ближайшие существа
111
+ partners = 0
112
+ enemies = 0
113
  for other in creatures:
114
  if other is self or not other.alive:
115
  continue
116
  dist = math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
117
+ if dist < 4:
118
  if other.team == self.team:
119
+ partners = max(partners, 1.0 - dist/4.0)
120
  else:
121
+ enemies = max(enemies, 1.0 - dist/4.0)
122
 
123
+ # Формируем вход
124
+ inputs = [hunger, thirst, health, energy, partners, enemies]
125
+ inputs.extend(nearby[:6])
 
 
 
 
 
 
 
 
126
 
127
  # Добавляем память
128
  memory_flat = []
 
133
  inputs.extend(memory_flat[:4])
134
 
135
  # Обрезаем до нужного размера
136
+ inputs = inputs[:12]
137
+ while len(inputs) < 12:
138
  inputs.append(0.0)
139
 
140
  return torch.tensor(inputs, dtype=torch.float32)
141
 
142
  def think(self, world, creatures):
 
143
  inputs = self.get_inputs(world, creatures)
144
 
145
  with torch.no_grad():
 
154
  self.move(dx, dy)
155
 
156
  # Строительство
157
+ if actions[2] > 0.3 and self.resources >= 2:
158
  self.build(world)
159
 
160
  # Размножение
 
172
  return None
173
 
174
  def move(self, dx, dy):
175
+ self.x = max(0, min(WORLD_SIZE-1, self.x + dx))
176
+ self.y = max(0, min(WORLD_SIZE-1, self.y + dy))
 
177
  self.energy -= 0.3
178
  self.food -= 0.2
179
  self.water -= 0.1
180
  self.age += 1
181
+
182
  def build(self, world):
183
+ self.resources -= 2
184
+ for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
185
+ nx, ny = self.x + dx, self.y + dy
186
+ if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
187
+ if is_grass(world[nx, ny]):
188
+ world[nx, ny] = [139, 69, 19]
189
+ self.fitness += 15
190
+ return True
 
 
191
  return False
192
 
193
  def mine_metal(self, world):
194
+ if is_metal(world[self.x, self.y]):
 
195
  self.metal += 1
196
+ world[self.x, self.y] = [100, 200, 100]
197
  self.fitness += 15
198
  self.energy -= 3
199
  return True
200
  return False
201
 
202
  def reproduce(self, creatures):
 
203
  if len(creatures) >= MAX_CREATURES:
204
  return None
205
 
 
213
  break
214
 
215
  if partner:
 
216
  child_brain = CreatureBrain()
217
+ for child_param, p1_param, p2_param in zip(
 
218
  child_brain.parameters(),
219
  self.brain.parameters(),
220
  partner.brain.parameters()
221
  ):
222
+ mask = torch.rand_like(p1_param) > 0.5
223
+ child_param.data = torch.where(mask, p1_param.data, p2_param.data)
 
224
  if random.random() < MUTATION_RATE:
225
  child_param.data += torch.randn_like(child_param) * MUTATION_SCALE
226
  else:
 
227
  child_brain = CreatureBrain()
228
  child_brain.load_state_dict(self.brain.state_dict())
229
  for param in child_brain.parameters():
 
234
  self.x + random.randint(-3, 3),
235
  self.y + random.randint(-3, 3),
236
  self.color,
237
+ self.team,
238
+ child_brain,
239
+ self.generation + 1
240
  )
241
  child.food = 30
242
  child.water = 30
 
251
  return child
252
 
253
  def attack(self, creatures):
 
254
  for other in creatures:
255
  if other is not self and other.alive and other.team != self.team:
256
  dist = abs(self.x - other.x) + abs(self.y - other.y)
257
  if dist <= 1:
258
+ other.health -= 10 + self.health / 10
 
259
  self.energy -= 5
260
  self.food -= 2
261
  self.fitness += 8
 
263
  return False
264
 
265
  def eat_food(self, world):
266
+ cell = world[self.x, self.y]
 
267
  if is_food(cell):
268
  self.food = min(100, self.food + 30)
269
  self.health = min(100, self.health + 8)
270
+ world[self.x, self.y] = [100, 200, 100]
271
  self.fitness += 12
272
  return True
273
  elif is_tree(cell):
274
  self.food = min(100, self.food + 15)
275
  self.health = min(100, self.health + 5)
276
+ world[self.x, self.y] = [100, 200, 100]
277
  self.fitness += 8
278
  return True
279
  return False
280
 
281
  def drink_water(self, world):
282
+ if is_water(world[self.x, self.y]):
 
283
  self.water = min(100, self.water + 40)
284
  self.health = min(100, self.health + 5)
285
  if random.random() < 0.3:
286
+ world[self.x, self.y] = [100, 200, 100]
287
  self.fitness += 5
288
  return True
289
  return False
290
 
291
  def collect_stone(self, world):
292
+ if is_stone(world[self.x, self.y]):
 
293
  self.resources += 1
294
+ world[self.x, self.y] = [100, 200, 100]
295
  self.fitness += 10
296
  return True
297
  return False
298
 
299
  def update(self):
 
300
  self.food -= 0.3
301
  self.water -= 0.2
302
  self.health -= 0.1
 
317
 
318
  return True
319
 
320
+ # ============== СИМУЛЯЦИЯ ==============
321
+ def create_world():
322
+ world = np.zeros((WORLD_SIZE, WORLD_SIZE, 3), dtype=np.uint8)
323
+ world[:, :] = [100, 200, 100]
324
+
325
+ # Ресурсы
326
+ for _ in range(20):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  for _ in range(50):
328
+ x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
329
+ if is_grass(world[x, y]):
330
+ world[x, y] = [34, 139, 34]
331
+ break
332
+
333
+ for _ in range(15):
334
+ for _ in range(50):
335
+ x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
336
+ if is_grass(world[x, y]):
337
+ world[x, y] = [0, 150, 0]
338
+ break
339
+
340
+ for _ in range(10):
341
+ for _ in range(50):
342
+ x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
343
+ if is_grass(world[x, y]):
344
+ world[x, y] = [128, 128, 128]
345
+ break
346
+
347
+ for _ in range(5):
348
+ for _ in range(50):
349
+ x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
350
+ if is_grass(world[x, y]):
351
+ world[x, y] = [192, 192, 192]
352
+ break
353
+
354
+ for _ in range(4):
355
+ for _ in range(50):
356
+ x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
357
+ if is_grass(world[x, y]):
358
+ world[x, y] = [0, 100, 255]
359
+ break
360
+
361
+ # Существа
362
+ creatures = []
363
+
364
+ c1 = Creature(10, 10, [255, 50, 50], "red")
365
+ creatures.append(c1)
366
+
367
+ c2 = Creature(30, 30, [50, 50, 255], "blue")
368
+ creatures.append(c2)
369
+
370
+ c3 = Creature(20, 20, [255, 215, 0], "gold")
371
+ creatures.append(c3)
372
+
373
+ return world, creatures
374
+
375
+ # ============== ОСНОВНАЯ ФУНКЦИЯ ==============
376
+ def simulate_step(world, creatures, step_count):
377
+ new_creatures = []
378
+
379
+ for creature in creatures[:]:
380
+ if not creature.alive:
381
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ creature.eat_food(world)
384
+ creature.drink_water(world)
385
+ creature.collect_stone(world)
 
386
 
387
+ child = creature.think(world, creatures)
388
+ if child:
389
+ new_creatures.append(child)
390
 
391
+ if not creature.update():
392
+ creature.alive = False
393
+ world[creature.x, creature.y] = [200, 100, 100]
394
+
395
+ creatures.extend(new_creatures)
396
+ creatures = [c for c in creatures if c.alive]
397
+
398
+ # Восстановление ресурсов
399
+ if step_count % RESPAWN_RATE == 0:
400
+ for _ in range(2):
401
+ for _ in range(30):
402
+ x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
403
+ if is_grass(world[x, y]):
404
+ world[x, y] = [0, 150, 0]
405
+ break
406
+
407
+ # Естественный отбор
408
+ if len(creatures) > MAX_CREATURES:
409
+ creatures.sort(key=lambda c: c.fitness, reverse=True)
410
+ for c in creatures[MAX_CREATURES:]:
411
+ c.alive = False
412
+ creatures = [c for c in creatures if c.alive]
413
+
414
+ return world, creatures
415
 
416
+ # ============== РЕНДЕРИНГ ==============
417
+ def render_world(world, creatures, step_count):
418
+ img = Image.new('RGB', (WORLD_SIZE * CELL_SIZE, WORLD_SIZE * CELL_SIZE + 100), (30, 30, 30))
419
+ draw = ImageDraw.Draw(img)
420
+
421
+ # Мир
422
+ for i in range(WORLD_SIZE):
423
+ for j in range(WORLD_SIZE):
424
+ x, y = i * CELL_SIZE, j * CELL_SIZE
425
+ draw.rectangle([x, y, x + CELL_SIZE, y + CELL_SIZE], fill=tuple(world[i, j]))
426
+
427
+ # Существа
428
+ for creature in creatures:
429
+ if not creature.alive:
430
+ continue
431
+ x, y = creature.x * CELL_SIZE, creature.y * CELL_SIZE
432
+ draw.ellipse([x+1, y+1, x+CELL_SIZE-1, y+CELL_SIZE-1],
433
+ fill=tuple(creature.color), outline=(255,255,255))
434
+
435
+ # Индикаторы
436
+ hw = (creature.health / 100) * CELL_SIZE
437
+ fw = (creature.food / 100) * CELL_SIZE
438
+ ww = (creature.water / 100) * CELL_SIZE
439
+
440
+ draw.rectangle([x, y-6, x + hw, y-4], fill=(255, 0, 0))
441
+ draw.rectangle([x, y-4, x + fw, y-2], fill=(255, 165, 0))
442
+ draw.rectangle([x, y-2, x + ww, y], fill=(0, 200, 255))
443
+
444
+ # Статистика
445
+ y_offset = WORLD_SIZE * CELL_SIZE + 10
446
+
447
+ red = sum(1 for c in creatures if c.alive and c.team == "red")
448
+ blue = sum(1 for c in creatures if c.alive and c.team == "blue")
449
+ gold = sum(1 for c in creatures if c.alive and c.team == "gold")
450
+
451
+ trees = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_tree(world[i, j]))
452
+ foods = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_food(world[i, j]))
453
+ stones = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_stone(world[i, j]))
454
+ metals = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_metal(world[i, j]))
455
+ waters = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_water(world[i, j]))
456
+
457
+ best_fitness = max([c.fitness for c in creatures if c.alive] + [0])
458
+
459
+ draw.text([10, y_offset], f"Шаг: {step_count} | Существ: {len(creatures)}", fill=(255,255,255))
460
+ draw.text([10, y_offset + 20], f"🔴: {red} | 🔵: {blue} | 🟡: {gold}", fill=(255,255,255))
461
+ draw.text([10, y_offset + 40], f"🌳: {trees} | 🫐: {foods} | 🪨: {stones} | ⚙️: {metals} | 💧: {waters}", fill=(255,255,255))
462
+ draw.text([10, y_offset + 60], f"⭐ Лучший фитнес: {best_fitness:.1f}", fill=(255,255,255))
463
+
464
+ return img
465
+
466
+ # ============== GRADIO ==============
467
+ world, creatures = create_world()
468
+ step_count = 0
469
 
470
  def run_simulation(steps):
471
+ global world, creatures, step_count
 
 
 
 
472
 
473
  images = []
474
+ stats_text = ""
475
 
476
  for i in range(steps):
477
+ step_count += 1
478
+ world, creatures = simulate_step(world, creatures, step_count)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
 
480
  if i % 2 == 0:
481
+ img = render_world(world, creatures, step_count)
482
+ images.append(img)
483
 
484
+ # Статистика
485
+ red = sum(1 for c in creatures if c.alive and c.team == "red")
486
+ blue = sum(1 for c in creatures if c.alive and c.team == "blue")
487
+ gold = sum(1 for c in creatures if c.alive and c.team == "gold")
 
 
 
 
 
 
 
488
 
489
+ trees = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_tree(world[i, j]))
490
+ foods = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_food(world[i, j]))
491
+ stones = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_stone(world[i, j]))
492
+ metals = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_metal(world[i, j]))
493
+ waters = sum(1 for i in range(WORLD_SIZE) for j in range(WORLD_SIZE) if is_water(world[i, j]))
494
+ best_fitness = max([c.fitness for c in creatures if c.alive] + [0])
495
+
496
+ stats_text = f"""
497
  📊 СТАТИСТИКА:
498
 
499
+ Шаг: {step_count}
500
+ Существ: {len(creatures)}
501
+ 🔴 Красных: {red}
502
+ 🔵 Синих: {blue}
503
+ 🟡 Золотых: {gold}
504
 
505
  🌳 Ресурсы:
506
+ Деревья: {trees}
507
+ 🫐 Еда: {foods}
508
+ 🪨 Камни: {stones}
509
+ ⚙️ Металл: {metals}
510
+ 💧 Вода: {waters}
511
 
512
+ ⭐ Лучший фитнес: {best_fitness:.1f}
513
  """
514
+
515
+ return images, stats_text
516
+
517
+ def reset_simulation():
518
+ global world, creatures, step_count
519
+ world, creatures = create_world()
520
+ step_count = 0
521
+ img = render_world(world, creatures, step_count)
522
+ return [img], "Мир сброшен!"
523
 
524
  # ============== ИНТЕРФЕЙС ==============
525
+ with gr.Blocks(title="🧬 Эволюционная симуляция") as demo:
526
  gr.Markdown("""
527
  # 🧬 Эволюционная симуляция с нейросетями
528
 
529
+ 🔴 Красные | 🔵 Синие | 🟡 Золотые
530
 
531
+ 🍽️ Едят, 💧 пьют, 🏗️ строят, воюют, 👶 размножаются!
532
  """)
533
 
534
  with gr.Row():
535
+ gallery = gr.Gallery(label="Мир", columns=1, rows=1, height=600)
536
+ stats = gr.Textbox(label="Статистика", lines=18, interactive=False)
537
+
538
+ with gr.Row():
539
+ steps = gr.Slider(label="Шагов", minimum=10, maximum=100, value=30, step=5)
540
+ run_btn = gr.Button("▶️ Запустить", variant="primary")
541
+ reset_btn = gr.Button("🔄 Сбросить", variant="secondary")
542
+
543
+ def run_action(steps_val):
544
+ return run_simulation(steps_val)
545
+
546
+ def reset_action():
547
+ return reset_simulation()
548
+
549
+ run_btn.click(run_action, inputs=[steps], outputs=[gallery, stats])
550
+ reset_btn.click(reset_action, outputs=[gallery, stats])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
+ # ============== ЗАПУСК ==============
553
  if __name__ == "__main__":
554
+ demo.launch(share=True)