X commited on
Commit
c16052b
·
verified ·
1 Parent(s): 160df23

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +910 -85
app.py CHANGED
@@ -1,106 +1,931 @@
1
-
2
-
3
- # Шаг 2. Импорт
4
- import torch
5
- from diffusers import StableDiffusionPipeline
6
- from PIL import Image, ImageFilter, ImageEnhance
7
- import numpy as np
8
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- # Шаг 3. Проверка GPU
11
- device = "cuda" if torch.cuda.is_available() else "cpu"
12
- print(f"Используется: {device}")
 
 
 
 
 
13
 
14
- # Шаг 4. Загрузка модели (без float16 для CPU)
15
- print("Загрузка модели...")
16
- model_id = "runwayml/stable-diffusion-v1-5"
17
 
18
- if device == "cuda":
19
- pipe = StableDiffusionPipeline.from_pretrained(
20
- model_id,
21
- torch_dtype=torch.float16,
22
- safety_checker=None,
23
- requires_safety_checker=False
24
- )
25
- else:
26
- pipe = StableDiffusionPipeline.from_pretrained(
27
- model_id,
28
- safety_checker=None,
29
- requires_safety_checker=False
30
- )
 
 
 
 
 
 
31
 
32
- pipe = pipe.to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- # Экономия памяти
35
- try:
36
- pipe.enable_attention_slicing()
37
- except:
38
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- try:
41
- pipe.vae.enable_slicing()
42
- except:
43
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- print("Модель загружена!")
 
 
46
 
47
- # Шаг 5. Функция генерации
48
- def generate_image(prompt, negative_prompt):
49
- generator = torch.Generator(device=device).manual_seed(2021)
50
-
51
- with torch.no_grad():
52
- image = pipe(
53
- prompt=prompt,
54
- negative_prompt=negative_prompt if negative_prompt else "",
55
- width=512,
56
- height=768,
57
- num_inference_steps=10,
58
- guidance_scale=11.0,
59
- generator=generator,
60
- eta=0.8
61
- ).images[0]
62
 
63
- # Постобработка
64
- img_array = np.array(image)
65
- noise = np.random.randint(-25, 25, img_array.shape, dtype=np.int16)
66
- img_noisy = np.clip(img_array.astype(np.int16) + noise, 0, 255).astype(np.uint8)
67
 
68
- img_final = Image.fromarray(img_noisy).filter(ImageFilter.GaussianBlur(radius=0.6))
 
69
 
70
- enhancer = ImageEnhance.Contrast(img_final)
71
- img_final = enhancer.enhance(1.3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- enhancer = ImageEnhance.Color(img_final)
74
- img_final = enhancer.enhance(1.3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- return img_final
 
 
77
 
78
- # Шаг 6. Интерфейс
79
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
80
- gr.Markdown("#")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  with gr.Row():
 
 
 
83
  with gr.Column(scale=1):
84
- prompt_input = gr.Textbox(
85
- label="",
86
- placeholder="Ваш промпт...",
87
- lines=10
88
- )
89
- negative_input = gr.Textbox(
90
- label="",
91
- placeholder="Ваш негативный промпт...",
92
- lines=5
93
- )
94
- generate_btn = gr.Button("Создать", variant="primary")
95
-
96
- with gr.Column(scale=2):
97
- output = gr.Image(label="", type="pil", height=600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- generate_btn.click(
100
- fn=generate_image,
101
- inputs=[prompt_input, negative_input],
102
- outputs=output
103
  )
104
 
105
- # Шаг 7. Запуск
106
- demo.launch(share=True, debug=False)
 
 
1
+ # app.py
 
 
 
 
 
 
2
  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)
116
+ health = self.health / 100.0
117
+ energy = self.energy / 100.0
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
+
219
+ return None
220
+
221
+ def move(self, dx, dy):
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
316
+
317
+ self.food -= 30
318
+ self.energy -= 20
319
+ self.children += 1
320
+ self.fitness += 25
321
+
322
+ creatures.append(child)
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]
355
+ self.fitness += 8
356
+ return True
357
+ return False
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
368
+ return True
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
376
+ return True
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
612
+ color = tuple(self.grid[i, j].tolist())
613
+ draw.rectangle([x, y, x + CELL_SIZE, y + CELL_SIZE], fill=color)
614
+
615
+ # Рисуем существ
616
+ for creature in self.creatures:
617
+ if not creature.alive:
618
+ continue
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:
755
+ world = World()
756
+ simulation_running = True
 
757
 
758
+ images = []
759
+ stats = []
760
 
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,
771
+ 'red': red,
772
+ 'blue': blue,
773
+ 'gold': gold,
774
+ 'total': len(world.creatures),
775
+ 'trees': len(world.resources['trees']),
776
+ 'food': len(world.resources['food']),
777
+ 'stones': len(world.resources['stones']),
778
+ 'metal': len(world.resources['metal']),
779
+ 'water': len(world.resources['water']),
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:
811
+ return "Нет данных"
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']}
822
+
823
+ 🌳 Ресурсы:
824
+ Деревья: {last['trees']}
825
+ 🫐 Еда: {last['food']}
826
+ 🪨 Камни: {last['stones']}
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
+
907
+ reset_btn.click(
908
+ reset_action,
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()