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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -530
app.py CHANGED
@@ -3,113 +3,98 @@ import gradio as gr
3
  import numpy as np
4
  import torch
5
  import torch.nn as nn
6
- import json
7
- import os
8
- from PIL import Image, ImageDraw
9
  import random
10
- from collections import deque, defaultdict
11
  import math
12
  from datetime import datetime
13
- import base64
14
- import io
15
 
16
  # ============== НАСТРОЙКИ ==============
17
- WORLD_SIZE = 80
18
- CELL_SIZE = 6
19
- MAX_CREATURES = 80
20
  MUTATION_RATE = 0.2
21
  MUTATION_SCALE = 0.25
22
- RESPAWN_RATE = 3
23
  SAVE_DIR = "saved_models"
24
 
25
  os.makedirs(SAVE_DIR, exist_ok=True)
26
 
27
- # ============== РАСШИРЕННАЯ НЕЙРОСЕТЬ ==============
28
- class AdvancedBrain(nn.Module):
29
- def __init__(self, input_size=16, hidden_size=32, output_size=6):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  super().__init__()
31
  self.fc1 = nn.Linear(input_size, hidden_size)
32
- self.fc2 = nn.Linear(hidden_size, hidden_size * 2)
33
- self.fc3 = nn.Linear(hidden_size * 2, hidden_size)
34
- self.fc4 = nn.Linear(hidden_size, output_size)
35
- self.dropout = nn.Dropout(0.15)
36
- self.batch_norm = nn.BatchNorm1d(hidden_size)
37
 
38
  def forward(self, x):
39
  x = torch.relu(self.fc1(x))
40
  x = self.dropout(x)
41
  x = torch.relu(self.fc2(x))
42
- x = self.batch_norm(x.unsqueeze(0)).squeeze(0) if len(x.shape) == 1 else x
43
- x = torch.relu(self.fc3(x))
44
- x = torch.tanh(self.fc4(x)) # -1..1
45
  return x
46
 
47
- # ============== ГЕНЕТИЧЕСКИЙ АЛГОРИТМ ==============
48
- class GeneticAlgorithm:
49
- @staticmethod
50
- def crossover(parent1, parent2):
51
- """Скрещивание двух нейросетей"""
52
- child = AdvancedBrain()
53
- state_dict1 = parent1.state_dict()
54
- state_dict2 = parent2.state_dict()
55
- child_dict = {}
56
-
57
- for key in state_dict1.keys():
58
- mask = torch.rand_like(state_dict1[key]) > 0.5
59
- child_dict[key] = torch.where(mask, state_dict1[key], state_dict2[key])
60
-
61
- child.load_state_dict(child_dict)
62
- return child
63
-
64
- @staticmethod
65
- def mutate(brain, rate=MUTATION_RATE, scale=MUTATION_SCALE):
66
- """Мутация весов"""
67
- for param in brain.parameters():
68
- if random.random() < rate:
69
- param.data += torch.randn_like(param) * scale
70
- # Ограничиваем значения
71
- param.data = torch.clamp(param.data, -3, 3)
72
- return brain
73
-
74
- # ============== СУЩЕСТВО С ГЕНАМИ ==============
75
  class Creature:
76
- def __init__(self, x, y, color, brain=None, generation=0, genome_id=None):
77
  self.x = x
78
  self.y = y
79
  self.color = color
 
80
  self.generation = generation
81
- self.genome_id = genome_id or f"{datetime.now().timestamp()}{random.randint(1000,9999)}"
82
- self.parent_id = None
83
 
84
- # Основные ресурсы
85
  self.health = 100.0
86
  self.energy = 80.0
87
- self.food = 50.0 # НОВЫЙ ресурс - ЕДА в желудке
 
88
  self.resources = 0 # камни
89
- self.metal = 0 # НОВЫЙ ресурс - металл
90
- self.water = 50.0 # НОВЫЙ ресурс - вода
91
-
92
  self.age = 0
93
  self.alive = True
94
  self.fitness = 0
95
  self.children = 0
96
 
97
  # Память
98
- self.memory = deque(maxlen=10)
99
- self.last_action = np.zeros(6)
100
 
101
  # Нейросеть
102
  if brain is None:
103
- self.brain = AdvancedBrain()
104
  else:
105
  self.brain = brain
106
 
107
- # Генеалогическое древо
108
  self.ancestors = []
109
- self.achievements = []
110
 
111
  def get_inputs(self, world, creatures):
112
- """Расширенный вход для нейросети"""
113
  # 1. Состояние существа
114
  hunger = 1.0 - (self.food / 100.0)
115
  thirst = 1.0 - (self.water / 100.0)
@@ -118,101 +103,98 @@ class Creature:
118
  resources = self.resources / 10.0
119
  metal = self.metal / 5.0
120
 
121
- # 2. Обзор (12 направлений с разными дистанциями)
122
- view_radius = 3
123
  nearby = []
124
- for dx in range(-view_radius, view_radius + 1):
125
- for dy in range(-view_radius, view_radius + 1):
126
  if dx == 0 and dy == 0:
127
  continue
128
  nx, ny = self.x + dx, self.y + dy
129
  if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
130
- cell = world.grid[nx, ny]
131
- # Кодируем тип ресурса
132
- if np.array_equal(cell, [34, 139, 34]): # дерево
133
  nearby.append(1.0)
134
- elif np.array_equal(cell, [0, 150, 0]): # еда (ягоды)
135
  nearby.append(2.0)
136
- elif np.array_equal(cell, [128, 128, 128]): # камень
137
  nearby.append(0.5)
138
- elif np.array_equal(cell, [192, 192, 192]): # металл
139
  nearby.append(3.0)
140
- elif np.array_equal(cell, [0, 100, 255]): # вода
141
  nearby.append(4.0)
142
  else:
143
  nearby.append(0.0)
144
  else:
145
  nearby.append(0.0)
146
 
147
- # 3. Ближайшие существа (сородичи и враги)
148
  partners = []
149
  enemies = []
150
  for other in creatures:
151
  if other is self or not other.alive:
152
  continue
153
  dist = math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
154
- if dist < 8:
155
- if np.array_equal(other.color, self.color):
156
- partners.append(1.0 - dist/8.0)
157
  else:
158
- enemies.append(1.0 - dist/8.0)
159
 
160
- # 4. Память
161
- memory_flat = []
162
- for mem in self.memory:
163
- memory_flat.extend(mem[:3]) # берем только важные действия
164
- while len(memory_flat) < 15:
165
- memory_flat.append(0.0)
166
-
167
- # Собираем вход
168
- inputs = np.array([
169
  hunger, thirst, health, energy, resources, metal,
170
- *nearby[:8], # 8 ближайших направлений
171
  max(partners) if partners else 0.0,
172
  max(enemies) if enemies else 0.0,
173
  self.children / 10.0,
174
- self.generation / 100.0,
175
- *memory_flat[:10]
176
- ], dtype=np.float32)
 
 
 
 
 
 
 
 
 
 
177
 
178
  # Обрезаем до нужного размера
179
- if len(inputs) > 16:
180
- inputs = inputs[:16]
181
- elif len(inputs) < 16:
182
- inputs = np.pad(inputs, (0, 16 - len(inputs)))
183
 
184
  return torch.tensor(inputs, dtype=torch.float32)
185
 
186
  def think(self, world, creatures):
187
- """Принимает решения с расширенными действиями"""
188
  inputs = self.get_inputs(world, creatures)
189
 
190
  with torch.no_grad():
191
  output = self.brain(inputs.unsqueeze(0)).squeeze().numpy()
192
 
193
- # [движение_x, движение_y, строительство, размножение, атака, добыча_металла]
194
  actions = np.clip(output, -1, 1)
195
-
196
  self.memory.append(actions.copy())
197
 
198
- # 1. Движение
199
- dx = int(np.round(actions[0] * 2.5))
200
- dy = int(np.round(actions[1] * 2.5))
201
  self.move(dx, dy)
202
 
203
- # 2. Строительство (камни + металл для улучшенных стен)
204
  if actions[2] > 0.3:
205
  self.build(world)
206
 
207
- # 3. Размножение
208
  if actions[3] > 0.5 and self.food > 50 and self.energy > 60:
209
  return self.reproduce(creatures)
210
 
211
- # 4. Атака
212
  if actions[4] > 0.4:
213
  self.attack(creatures)
214
 
215
- # 5. Добыча металла
216
  if actions[5] > 0.5:
217
  self.mine_metal(world)
218
 
@@ -222,94 +204,78 @@ class Creature:
222
  new_x = max(0, min(WORLD_SIZE-1, self.x + dx))
223
  new_y = max(0, min(WORLD_SIZE-1, self.y + dy))
224
  self.x, self.y = new_x, new_y
225
-
226
- # Тратим ресурсы на движение
227
  self.energy -= 0.3
228
  self.food -= 0.2
229
  self.water -= 0.1
230
  self.age += 1
231
 
232
  def build(self, world):
233
- """Строительство с улучшенными материалами"""
234
- cost = 2 if self.metal < 2 else 3
235
- required_resources = self.resources >= 2 or self.metal >= 1
236
-
237
- if required_resources:
238
- # Выбираем материал
239
- if self.metal >= 1 and random.random() > 0.5:
240
- # Металлическая стена (крепче)
241
- wall_color = [160, 160, 180]
242
- self.metal -= 1
243
- else:
244
- # Каменная стена
245
- wall_color = [139, 69, 19]
246
- self.resources -= 2
247
-
248
- # Строим вокруг себя
249
  for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
250
  nx, ny = self.x + dx, self.y + dy
251
  if 0 <= nx < WORLD_SIZE and 0 <= ny < WORLD_SIZE:
252
- if np.array_equal(world.grid[nx, ny], [100, 200, 100]): # трава
253
- world.grid[nx, ny] = wall_color
254
  self.fitness += 15
255
- self.achievements.append(f"built_wall_{datetime.now()}")
256
  return True
257
  return False
258
 
259
  def mine_metal(self, world):
260
  """Добыча металла"""
261
- if np.array_equal(world.grid[self.x, self.y], [192, 192, 192]):
262
  self.metal += 1
263
- world.grid[self.x, self.y] = [100, 200, 100] # трава
264
  self.fitness += 15
265
  self.energy -= 3
266
  return True
267
  return False
268
 
269
  def reproduce(self, creatures):
270
- """Размножение с генетическим алгоритмом"""
271
  if len(creatures) >= MAX_CREATURES:
272
  return None
273
 
274
  # Ищем партнера
275
  partner = None
276
  for other in creatures:
277
- if other is not self and other.alive and np.array_equal(other.color, self.color):
278
  dist = math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
279
- if dist < 5 and other.food > 40:
280
  partner = other
281
  break
282
 
283
- if not partner:
284
- # Может размножаться бесполым путем (с большей мутацией)
285
- child_brain = GeneticAlgorithm.mutate(
286
- self.brain,
287
- rate=MUTATION_RATE * 1.5,
288
- scale=MUTATION_SCALE * 1.2
289
- )
290
- child = Creature(
291
- self.x + random.randint(-4, 4),
292
- self.y + random.randint(-4, 4),
293
- self.color,
294
- brain=child_brain,
295
- generation=self.generation + 1
296
- )
297
- else:
298
  # Половое размножение
299
- child_brain = GeneticAlgorithm.crossover(self.brain, partner.brain)
300
- child_brain = GeneticAlgorithm.mutate(child_brain)
301
- child = Creature(
302
- self.x + random.randint(-4, 4),
303
- self.y + random.randint(-4, 4),
304
- [min(255, (self.color[0] + partner.color[0]) // 2),
305
- min(255, (self.color[1] + partner.color[1]) // 2),
306
- min(255, (self.color[2] + partner.color[2]) // 2)],
307
- brain=child_brain,
308
- generation=max(self.generation, partner.generation) + 1
309
- )
310
- child.parent_id = f"{self.genome_id}_{partner.genome_id}"
311
- child.ancestors = self.ancestors + [self.genome_id, partner.genome_id]
312
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  child.food = 30
314
  child.water = 30
315
  child.energy = 40
@@ -323,32 +289,29 @@ class Creature:
323
  return child
324
 
325
  def attack(self, creatures):
326
- """Атака с учетом силы"""
327
  for other in creatures:
328
- if other is not self and other.alive:
329
  dist = abs(self.x - other.x) + abs(self.y - other.y)
330
- if dist <= 1 and not np.array_equal(other.color, self.color):
331
- # Урон зависит от здоровья, еды и металла
332
- damage = 10 + (self.health / 20) + (self.metal * 5)
333
  other.health -= damage
334
  self.energy -= 5
335
- self.food -= 3
336
  self.fitness += 8
337
- self.achievements.append(f"attacked_{datetime.now()}")
338
  return True
339
  return False
340
 
341
  def eat_food(self, world):
342
- """Потребление еды"""
343
- if np.array_equal(world.grid[self.x, self.y], [0, 150, 0]): # ягоды
 
344
  self.food = min(100, self.food + 30)
345
  self.health = min(100, self.health + 8)
346
- world.grid[self.x, self.y] = [100, 200, 100] # трава
347
  self.fitness += 12
348
  return True
349
-
350
- # Деревья дают меньше еды, но больше ресурсов
351
- if np.array_equal(world.grid[self.x, self.y], [34, 139, 34]): # дерево
352
  self.food = min(100, self.food + 15)
353
  self.health = min(100, self.health + 5)
354
  world.grid[self.x, self.y] = [100, 200, 100]
@@ -358,10 +321,9 @@ class Creature:
358
 
359
  def drink_water(self, world):
360
  """Питье воды"""
361
- if np.array_equal(world.grid[self.x, self.y], [0, 100, 255]): # вода
362
  self.water = min(100, self.water + 40)
363
  self.health = min(100, self.health + 5)
364
- # Вода не исчезает, но может "загрязниться"
365
  if random.random() < 0.3:
366
  world.grid[self.x, self.y] = [100, 200, 100]
367
  self.fitness += 5
@@ -369,7 +331,8 @@ class Creature:
369
  return False
370
 
371
  def collect_stone(self, world):
372
- if np.array_equal(world.grid[self.x, self.y], [128, 128, 128]):
 
373
  self.resources += 1
374
  world.grid[self.x, self.y] = [100, 200, 100]
375
  self.fitness += 10
@@ -377,235 +340,161 @@ class Creature:
377
  return False
378
 
379
  def update(self):
380
- """Обновление состояния с новыми ресурсами"""
381
- # Естественные потери
382
- self.food -= 0.5
383
- self.water -= 0.3
384
  self.health -= 0.1
385
  self.energy -= 0.2
386
 
387
- # Если нет еды или воды - быстро умираем
388
  if self.food <= 0:
389
  self.health -= 2
390
  if self.water <= 0:
391
  self.health -= 1.5
392
 
393
- # Смерть
394
  if self.health <= 0 or self.energy <= 0 or self.food < -10 or self.water < -10:
395
  self.alive = False
396
  return False
397
 
398
- # Старение
399
- if self.age > 800:
400
  self.alive = False
401
  return False
402
 
403
  return True
404
-
405
- def save(self, filename):
406
- """Сохранение генома"""
407
- data = {
408
- 'genome_id': self.genome_id,
409
- 'generation': self.generation,
410
- 'fitness': self.fitness,
411
- 'children': self.children,
412
- 'ancestors': self.ancestors[-10:],
413
- 'brain_state': self.brain.state_dict(),
414
- 'achievements': self.achievements[-20:]
415
- }
416
- torch.save(data, filename)
417
-
418
- @staticmethod
419
- def load(filename, x=None, y=None, color=None):
420
- """Загрузка генома"""
421
- data = torch.load(filename, map_location='cpu')
422
- brain = AdvancedBrain()
423
- brain.load_state_dict(data['brain_state'])
424
-
425
- creature = Creature(
426
- x or random.randint(0, WORLD_SIZE-1),
427
- y or random.randint(0, WORLD_SIZE-1),
428
- color or [255, 255, 255],
429
- brain=brain,
430
- generation=data['generation'],
431
- genome_id=data['genome_id']
432
- )
433
- creature.fitness = data['fitness']
434
- creature.children = data['children']
435
- return creature
436
 
437
- # ============== МИР С НОВЫМИ РЕСУРСАМИ ==============
438
  class World:
439
  def __init__(self):
440
- self.grid = None
 
441
  self.creatures = []
442
  self.resources = {'trees': [], 'food': [], 'stones': [], 'metal': [], 'water': []}
443
- self.genealogy = []
444
- self.scores = {'red': 0, 'blue': 0}
445
  self.step_count = 0
446
- self.best_fitness_history = []
447
- self.population_history = []
448
-
449
- # 3D данные (изометрия)
450
- self.iso_height = np.zeros((WORLD_SIZE, WORLD_SIZE))
451
-
452
- self.reset()
453
-
454
- def reset(self):
455
- self.grid = np.zeros((WORLD_SIZE, WORLD_SIZE, 3), dtype=np.uint8)
456
- self.grid[:, :] = [100, 200, 100] # трава
457
- self.iso_height = np.random.rand(WORLD_SIZE, WORLD_SIZE) * 0.3
458
 
459
- # Генерация ресурсов
460
  self._generate_resources()
461
-
462
- # Создание существ
463
- self.creatures = []
464
- self._spawn_creature(20, 20, [255, 50, 50], "red") # Красный
465
- self._spawn_creature(60, 60, [50, 50, 255], "blue") # Синий
466
- self._spawn_creature(40, 40, [255, 215, 0], "gold") # Золотой (нейтральный)
467
-
468
- self.step_count = 0
469
- self.genealogy = []
470
- self.scores = {'red': 0, 'blue': 0, 'gold': 0}
471
 
472
  def _generate_resources(self):
473
- """Генерация всех типов ресурсов"""
474
- # Деревья (дают древесину и немного еды)
475
- for _ in range(35):
476
- x, y = self._random_free_cell()
477
- self.grid[x, y] = [34, 139, 34]
478
- self.resources['trees'].append((x, y))
479
-
480
- # Ягоды/еда (дают много еды)
481
  for _ in range(25):
482
  x, y = self._random_free_cell()
483
- self.grid[x, y] = [0, 150, 0]
484
- self.resources['food'].append((x, y))
485
-
486
- # Камни (стройматериалы)
487
  for _ in range(20):
488
  x, y = self._random_free_cell()
489
- self.grid[x, y] = [128, 128, 128]
490
- self.resources['stones'].append((x, y))
491
-
492
- # Металл (ул��чшенные материалы)
493
- for _ in range(10):
494
  x, y = self._random_free_cell()
495
- self.grid[x, y] = [192, 192, 192]
496
- self.resources['metal'].append((x, y))
497
-
498
- # Вода (источник жизни)
499
  for _ in range(8):
500
  x, y = self._random_free_cell()
501
- self.grid[x, y] = [0, 100, 255]
502
- self.resources['water'].append((x, y))
 
 
 
 
 
 
 
503
 
504
  def _random_free_cell(self):
505
- attempts = 100
506
- for _ in range(attempts):
507
- x, y = random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
508
- if np.array_equal(self.grid[x, y], [100, 200, 100]):
 
509
  return x, y
510
- return random.randint(0, WORLD_SIZE-1), random.randint(0, WORLD_SIZE-1)
511
 
512
- def _spawn_creature(self, x, y, color, team):
513
- brain = AdvancedBrain()
514
- creature = Creature(x, y, color, brain)
515
- creature.team = team
516
- self.creatures.append(creature)
517
- return creature
 
 
 
 
 
 
 
 
 
 
518
 
519
  def step(self):
 
520
  self.step_count += 1
521
 
522
- # 1. Существа действуют
523
  new_creatures = []
524
  for creature in self.creatures[:]:
525
  if not creature.alive:
526
  continue
527
 
528
- # Взаимодействия с миром
529
  creature.eat_food(self.grid)
530
  creature.drink_water(self.grid)
531
  creature.collect_stone(self.grid)
532
- creature.mine_metal(self.grid)
533
 
534
- # Мышление
535
  child = creature.think(self.grid, self.creatures)
536
  if child:
537
  new_creatures.append(child)
538
 
539
- # Обновление
540
  if not creature.update():
541
  creature.alive = False
542
  self.grid[creature.x, creature.y] = [200, 100, 100]
543
- # Добавляем в генеалогию
544
  self.genealogy.append({
545
- 'id': creature.genome_id,
546
  'generation': creature.generation,
547
  'fitness': creature.fitness,
548
- 'children': creature.children,
549
- 'team': getattr(creature, 'team', 'unknown'),
550
- 'ancestors': creature.ancestors[-5:]
551
  })
552
 
553
  self.creatures.extend(new_creatures)
554
  self.creatures = [c for c in self.creatures if c.alive]
555
 
556
- # 2. Обновление ресурсов
557
  if self.step_count % RESPAWN_RATE == 0:
558
- self._respawn_resources()
559
-
560
- # 3. Естественный отбор
561
- if len(self.creatures) > MAX_CREATURES * 0.8:
 
 
 
 
 
 
 
 
 
 
562
  self.creatures.sort(key=lambda c: c.fitness, reverse=True)
563
- survivors = int(len(self.creatures) * 0.6)
564
- for creature in self.creatures[survivors:]:
565
- creature.alive = False
566
- self.creatures = [c for c in self.creatures if c.alive]
567
-
568
- # 4. Соревновательный режим
569
- if self.step_count % 50 == 0:
570
- self._update_scores()
571
-
572
- # 5. Сохраняем статистику
573
- self.best_fitness_history.append(
574
- max([c.fitness for c in self.creatures if c.alive] + [0])
575
- )
576
- self.population_history.append(len(self.creatures))
577
-
578
- def _respawn_resources(self):
579
- """Восстановление ресурсов"""
580
- # Восстанавливаем еду
581
- if len(self.resources['food']) < 20:
582
- x, y = self._random_free_cell()
583
- if x is not None:
584
- self.grid[x, y] = [0, 150, 0]
585
- self.resources['food'].append((x, y))
586
 
587
- # Восстанавливаем деревья
588
- if len(self.resources['trees']) < 30:
589
- x, y = self._random_free_cell()
590
- if x is not None:
591
- self.grid[x, y] = [34, 139, 34]
592
- self.resources['trees'].append((x, y))
593
-
594
- def _update_scores(self):
595
- """Обновление соревновательных очков"""
596
- for creature in self.creatures:
597
- if not creature.alive:
598
- continue
599
- team = getattr(creature, 'team', 'unknown')
600
- if team in self.scores:
601
- self.scores[team] += creature.fitness / 100
602
 
603
  def render(self):
604
- """2D рендеринг с расширенной информацией"""
605
- img = Image.new('RGB', (WORLD_SIZE * CELL_SIZE, WORLD_SIZE * CELL_SIZE + 200), (50, 50, 50))
606
  draw = ImageDraw.Draw(img)
607
 
608
- # Рендерим мир
609
  for i in range(WORLD_SIZE):
610
  for j in range(WORLD_SIZE):
611
  x, y = i * CELL_SIZE, j * CELL_SIZE
@@ -619,136 +508,40 @@ class World:
619
  x, y = creature.x * CELL_SIZE, creature.y * CELL_SIZE
620
  color = tuple(creature.color)
621
 
622
- # Тело с градиентом
623
  draw.ellipse([x+1, y+1, x+CELL_SIZE-1, y+CELL_SIZE-1],
624
  fill=color, outline=(255,255,255))
625
 
626
- # Показатели здоровья, еды, воды
627
  health_w = (creature.health / 100) * CELL_SIZE
628
  food_w = (creature.food / 100) * CELL_SIZE
629
  water_w = (creature.water / 100) * CELL_SIZE
630
 
631
- # HP (красный)
632
  draw.rectangle([x, y-6, x + health_w, y-4], fill=(255, 0, 0))
633
- # Food (оранжевый)
634
  draw.rectangle([x, y-4, x + food_w, y-2], fill=(255, 165, 0))
635
- # Water (голубой)
636
  draw.rectangle([x, y-2, x + water_w, y], fill=(0, 200, 255))
637
-
638
- # Ресурсы
639
- info = ""
640
- if creature.resources > 0:
641
- info += f"🪨{creature.resources}"
642
- if creature.metal > 0:
643
- info += f"⚙️{creature.metal}"
644
- if creature.children > 0:
645
- info += f"👶{creature.children}"
646
-
647
- if info:
648
- draw.text([x, y-CELL_SIZE-5], info, fill=(255,255,255), font_size=5)
649
 
650
- # Статистика внизу
651
  y_offset = WORLD_SIZE * CELL_SIZE + 10
652
 
653
- # Общая статистика
654
- red = sum(1 for c in self.creatures if c.alive and getattr(c, 'team', '') == 'red')
655
- blue = sum(1 for c in self.creatures if c.alive and getattr(c, 'team', '') == 'blue')
656
- gold = sum(1 for c in self.creatures if c.alive and getattr(c, 'team', '') == 'gold')
657
 
658
  draw.text([10, y_offset], f"Шаг: {self.step_count} | 👥: {len(self.creatures)}", fill=(255,255,255))
659
  draw.text([10, y_offset + 20], f"🔴 Красные: {red} | 🔵 Синие: {blue} | 🟡 Золотые: {gold}", fill=(255,255,255))
660
  draw.text([10, y_offset + 40], f"🌳 Деревья: {len(self.resources['trees'])} | 🫐 Еда: {len(self.resources['food'])}", fill=(255,255,255))
661
  draw.text([10, y_offset + 60], f"🪨 Камни: {len(self.resources['stones'])} | ⚙️ Металл: {len(self.resources['metal'])} | 💧 Вода: {len(self.resources['water'])}", fill=(255,255,255))
662
 
663
- # Соревновательные очки
664
- draw.text([10, y_offset + 80], f"🏆 Очки: Красные: {int(self.scores['red'])} | Синие: {int(self.scores['blue'])} | Золотые: {int(self.scores['gold'])}", fill=(255,255,255))
665
-
666
- # Лучший фитнес
667
  best_fitness = max([c.fitness for c in self.creatures if c.alive] + [0])
668
- draw.text([10, y_offset + 100], f"⭐ Лучший фитнес: {best_fitness:.1f}", fill=(255,255,255))
669
 
670
  return img
671
-
672
- def render_3d(self):
673
- """Изометрический 3D вид (псевдо-3D)"""
674
- # Создаем более крупное изображение для 3D
675
- img = Image.new('RGB', (800, 600), (50, 50, 80))
676
- draw = ImageDraw.Draw(img)
677
-
678
- # Изометрическая проекция
679
- scale = 4
680
- offset_x, offset_y = 400, 100
681
-
682
- for i in range(WORLD_SIZE):
683
- for j in range(WORLD_SIZE):
684
- # Изометрические координаты
685
- x_iso = (i - j) * scale + offset_x
686
- y_iso = (i + j) * scale / 2 + offset_y - self.iso_height[i, j] * 20
687
-
688
- color = tuple(self.grid[i, j].tolist())
689
- # Тень в зависимости от высоты
690
- brightness = 1.0 - self.iso_height[i, j] * 0.3
691
- color = tuple(int(c * brightness) for c in color)
692
-
693
- # Рисуем ромб
694
- points = [
695
- (x_iso, y_iso - 10),
696
- (x_iso + 10, y_iso + 5),
697
- (x_iso, y_iso + 20),
698
- (x_iso - 10, y_iso + 5)
699
- ]
700
- draw.polygon(points, fill=color, outline=(100,100,100))
701
-
702
- # Рисуем существ в 3D
703
- for creature in self.creatures:
704
- if not creature.alive:
705
- continue
706
- x_iso = (creature.x - creature.y) * scale + offset_x
707
- y_iso = (creature.x + creature.y) * scale / 2 + offset_y - self.iso_height[creature.x, creature.y] * 20 - 15
708
-
709
- color = tuple(creature.color)
710
- draw.ellipse([x_iso-8, y_iso-8, x_iso+8, y_iso+8], fill=color, outline=(255,255,255))
711
-
712
- # Показатели
713
- draw.text([x_iso-10, y_iso-25], f"❤️{int(creature.health)}", fill=(255,255,255), font_size=8)
714
-
715
- # Легенда
716
- draw.text([10, 10], "3D ВИД (изометрический)", fill=(255,255,255))
717
- draw.text([10, 30], f"Существ: {len(self.creatures)}", fill=(255,255,255))
718
-
719
- return img
720
-
721
- def get_genealogy(self, limit=20):
722
- """Возвращает древо эволюции"""
723
- if not self.genealogy:
724
- return "Нет данных о предках"
725
-
726
- recent = self.genealogy[-limit:]
727
- text = "🧬 ДРЕВО ЭВОЛЮЦИИ (последние):\n\n"
728
-
729
- for i, genome in enumerate(recent):
730
- text += f"#{i+1} Поколение {genome['generation']} | Команда: {genome['team']}\n"
731
- text += f" Фитнес: {genome['fitness']:.1f} | Детей: {genome['children']}\n"
732
- if genome['ancestors']:
733
- text += f" Предки: {', '.join(genome['ancestors'][:3])}\n"
734
- text += "\n"
735
-
736
- return text
737
-
738
- def get_best_model(self):
739
- """Возвращает лучшую модель для сохранения"""
740
- best = max([c for c in self.creatures if c.alive], key=lambda c: c.fitness, default=None)
741
- if best:
742
- filename = f"{SAVE_DIR}/best_model_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pth"
743
- best.save(filename)
744
- return filename
745
- return None
746
 
747
  # ============== GRADIO ИНТЕРФЕЙС ==============
748
  world = World()
749
  simulation_running = False
750
 
751
- def run_simulation(steps, view_mode):
752
  global world, simulation_running
753
 
754
  if not simulation_running:
@@ -761,10 +554,9 @@ def run_simulation(steps, view_mode):
761
  for i in range(steps):
762
  world.step()
763
 
764
- # Сбор статистики
765
- red = sum(1 for c in world.creatures if c.alive and getattr(c, 'team', '') == 'red')
766
- blue = sum(1 for c in world.creatures if c.alive and getattr(c, 'team', '') == 'blue')
767
- gold = sum(1 for c in world.creatures if c.alive and getattr(c, 'team', '') == 'gold')
768
 
769
  stats.append({
770
  'step': world.step_count,
@@ -780,31 +572,16 @@ def run_simulation(steps, view_mode):
780
  'best_fitness': max([c.fitness for c in world.creatures if c.alive] + [0])
781
  })
782
 
783
- # Рендеринг
784
- if view_mode == "2D":
785
  images.append(world.render())
786
- else:
787
- images.append(world.render_3d())
788
 
789
  return images, stats
790
 
791
- def save_best_model():
792
- """Сохранить лучшую модель"""
793
- filename = world.get_best_model()
794
- if filename:
795
- return f"✅ Модель сохранена: {filename}"
796
- return "❌ Нет живых существ для сохранения"
797
-
798
- def load_model(model_file):
799
- """Загрузить модель"""
800
- try:
801
- creature = Creature.load(model_file.name,
802
- random.randint(0, WORLD_SIZE-1),
803
- random.randint(0, WORLD_SIZE-1))
804
- world.creatures.append(creature)
805
- return f"✅ Модель загружена: {creature.genome_id}"
806
- except Exception as e:
807
- return f"❌ Ошибка загрузки: {str(e)}"
808
 
809
  def update_stats(stats):
810
  if not stats:
@@ -812,10 +589,10 @@ def update_stats(stats):
812
 
813
  last = stats[-1]
814
  text = f"""
815
- 📊 СТАТИСТИКА СИМУЛЯЦИИ:
816
 
817
  Шаг: {last['step']}
818
- Всего существ: {last['total']}
819
  🔴 Красных: {last['red']}
820
  🔵 Синих: {last['blue']}
821
  🟡 Золотых: {last['gold']}
@@ -827,80 +604,51 @@ def update_stats(stats):
827
  ⚙️ Металл: {last['metal']}
828
  💧 Вода: {last['water']}
829
 
830
- 🏆 Лучший фитнес: {last['best_fitness']:.1f}
831
-
832
- 📈 Популяция: {last['total']}
833
  """
834
  return text
835
 
836
- # Создаем интерфейс
837
- with gr.Blocks(title="🌍 Эволюционная симуляция 2.0", theme=gr.themes.Soft()) as demo:
838
  gr.Markdown("""
839
- # 🧬 ЭВОЛЮЦИОННАЯ СИМУЛЯЦИЯ 2.0
840
 
841
- ### С нейросетями, генетическим алгоритмом, 3D видом и расширенными ресурсами!
842
 
843
- **3 вида существ:** 🔴 Красные | 🔵 Синие | 🟡 Золотые (нейтральные)
844
-
845
- **Ресурсы:** 🌳 Деревья (еда+древесина) | 🫐 Ягоды (много еды) | 🪨 Камни (стройматериалы) | ⚙️ Металл (улучшения) | 💧 Вода (жизненно важна)
846
-
847
- **Что умеют существа:**
848
- - 🍽️ Есть и пить
849
- - 🏗️ Строить стены (каменные и металлические)
850
- - 👶 Размножаться (половое и бесполое)
851
- - ⚔️ Атаковать врагов
852
- - ⛏️ Добывать ресурсы
853
- - 🧬 Эволюционировать через генетический алгоритм
854
  """)
855
 
856
  with gr.Row():
857
- with gr.Column(scale=3):
858
- gallery = gr.Gallery(label="Симуляция", columns=1, rows=1, height=800)
859
-
860
  with gr.Column(scale=1):
861
- stats_output = gr.Textbox(label="📊 Статистика", lines=20, interactive=False)
862
- genealogy_output = gr.Textbox(label="🧬 Древо эволюции", lines=10, interactive=False)
863
 
864
  with gr.Row():
865
- view_mode = gr.Radio(choices=["2D", "3D"], value="2D", label="Вид")
866
-
867
- with gr.Row():
868
- steps_input = gr.Slider(label="Шагов", minimum=10, maximum=300, value=50, step=10)
869
 
870
  with gr.Row():
871
  run_btn = gr.Button("▶️ Запустить", variant="primary")
872
  reset_btn = gr.Button("🔄 Сбросить", variant="secondary")
873
-
874
- with gr.Row():
875
- save_btn = gr.Button("💾 Сохранить лучшую")
876
- load_btn = gr.UploadButton("📂 Загрузить модель", file_types=[".pth"])
877
-
878
- with gr.Row():
879
- genealogy_btn = gr.Button("🧬 Показать древо")
880
 
881
- # Состояние
882
- state = gr.State({"images": [], "stats": [], "genealogy": []})
883
 
884
- def run_action(steps, view_mode, state_data):
885
- images, stats = run_simulation(steps, view_mode)
886
  state_data["images"] = images
887
  state_data["stats"] = stats
888
  return images, update_stats(stats), state_data
889
 
890
  def reset_action(state_data):
891
- global world
892
- world = World()
893
- state_data["images"] = []
894
- state_data["stats"] = []
895
- img = world.render()
896
- return [img], update_stats([]), state_data
897
-
898
- def show_genealogy():
899
- return world.get_genealogy()
900
 
901
  run_btn.click(
902
  run_action,
903
- inputs=[steps_input, view_mode, state],
904
  outputs=[gallery, stats_output, state]
905
  )
906
 
@@ -909,23 +657,7 @@ with gr.Blocks(title="🌍 Эволюционная симуляция 2.0", the
909
  inputs=[state],
910
  outputs=[gallery, stats_output, state]
911
  )
912
-
913
- genealogy_btn.click(
914
- show_genealogy,
915
- outputs=[genealogy_output]
916
- )
917
-
918
- save_btn.click(
919
- save_best_model,
920
- outputs=[gr.Textbox(label="Статус сохранения")]
921
- )
922
-
923
- load_btn.upload(
924
- load_model,
925
- inputs=[load_btn],
926
- outputs=[gr.Textbox(label="Статус загрузки")]
927
- )
928
 
929
- # ============== ЗАПУСК ==============
930
  if __name__ == "__main__":
931
- demo.launch()
 
3
  import numpy as np
4
  import torch
5
  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)
 
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]:
110
  if dx == 0 and dy == 0:
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 = []
157
+ for mem in self.memory:
158
+ memory_flat.extend(mem[:2])
159
+ while len(memory_flat) < 4:
160
+ memory_flat.append(0.0)
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():
175
  output = self.brain(inputs.unsqueeze(0)).squeeze().numpy()
176
 
 
177
  actions = np.clip(output, -1, 1)
 
178
  self.memory.append(actions.copy())
179
 
180
+ # Движение
181
+ dx = int(np.round(actions[0] * 2))
182
+ dy = int(np.round(actions[1] * 2))
183
  self.move(dx, dy)
184
 
185
+ # Строительство
186
  if actions[2] > 0.3:
187
  self.build(world)
188
 
189
+ # Размножение
190
  if actions[3] > 0.5 and self.food > 50 and self.energy > 60:
191
  return self.reproduce(creatures)
192
 
193
+ # Атака
194
  if actions[4] > 0.4:
195
  self.attack(creatures)
196
 
197
+ # Добыча металла
198
  if actions[5] > 0.5:
199
  self.mine_metal(world)
200
 
 
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
 
240
  # Ищем партнера
241
  partner = None
242
  for other in creatures:
243
+ if other is not self and other.alive and other.team == self.team:
244
  dist = math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
245
+ if dist < 4 and other.food > 40:
246
  partner = other
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():
268
+ if random.random() < MUTATION_RATE * 1.5:
269
+ param.data += torch.randn_like(param) * MUTATION_SCALE * 1.2
270
+
271
+ child = 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
281
  child.energy = 40
 
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
 
302
  return True
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]
 
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
 
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
 
340
  return False
341
 
342
  def update(self):
343
+ """Обновление состояния"""
344
+ self.food -= 0.3
345
+ self.water -= 0.2
 
346
  self.health -= 0.1
347
  self.energy -= 0.2
348
 
 
349
  if self.food <= 0:
350
  self.health -= 2
351
  if self.water <= 0:
352
  self.health -= 1.5
353
 
 
354
  if self.health <= 0 or self.energy <= 0 or self.food < -10 or self.water < -10:
355
  self.alive = False
356
  return False
357
 
358
+ if self.age > 500:
 
359
  self.alive = False
360
  return False
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
 
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:
 
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,
 
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:
 
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']}
 
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
 
 
657
  inputs=[state],
658
  outputs=[gallery, stats_output, state]
659
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
 
661
+ # ============== ЗАПУСК С share=False ==============
662
  if __name__ == "__main__":
663
+ demo.launch(share=False)