X commited on
Commit
a39e92e
·
verified ·
1 Parent(s): 1a1fd0c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +17 -58
app.py CHANGED
@@ -23,7 +23,6 @@ import io
23
  # 1. ЗВУКИ (только эффекты, без фона)
24
  # =====================================================
25
 
26
- # Генерируем простые звуки в base64 (без внешних файлов)
27
  def generate_sound(freq=440, duration=0.1, waveform='sine'):
28
  """Генерирует простой звук в base64"""
29
  import struct
@@ -32,14 +31,12 @@ def generate_sound(freq=440, duration=0.1, waveform='sine'):
32
  sample_rate = 22050
33
  samples = int(sample_rate * duration)
34
 
35
- # Создаём WAV в памяти
36
  buffer = io.BytesIO()
37
  wav = wave.open(buffer, 'wb')
38
  wav.setnchannels(1)
39
  wav.setsampwidth(2)
40
  wav.setframerate(sample_rate)
41
 
42
- # Генерируем волну
43
  for i in range(samples):
44
  t = i / sample_rate
45
  if waveform == 'sine':
@@ -48,7 +45,7 @@ def generate_sound(freq=440, duration=0.1, waveform='sine'):
48
  val = int(32767 * 0.5 * (1 if math.sin(2 * math.pi * freq * t) > 0 else -1))
49
  elif waveform == 'noise':
50
  val = int(32767 * 0.3 * random.random())
51
- else: # sweep (для смерти)
52
  f = freq + i * 200 / samples
53
  val = int(32767 * 0.5 * math.sin(2 * math.pi * f * t))
54
  wav.writeframes(struct.pack('<h', val))
@@ -56,19 +53,16 @@ def generate_sound(freq=440, duration=0.1, waveform='sine'):
56
  wav.close()
57
  buffer.seek(0)
58
 
59
- # Конвертируем в base64
60
  audio_data = base64.b64encode(buffer.read()).decode('utf-8')
61
  return f'data:audio/wav;base64,{audio_data}'
62
 
63
- # Создаём звуки
64
  SOUNDS = {
65
- 'jump': generate_sound(600, 0.08, 'sine'), # Высокий писк
66
- 'coin': generate_sound(880, 0.05, 'sine'), # Очень высокий
67
- 'die': generate_sound(200, 0.3, 'sweep') # Падающий звук
68
  }
69
 
70
  def get_sound_html(sound_type):
71
- """Возвращает HTML для воспроизведения звука"""
72
  if sound_type in SOUNDS:
73
  return f'<audio autoplay><source src="{SOUNDS[sound_type]}" type="audio/wav"></audio>'
74
  return ''
@@ -129,7 +123,6 @@ class InfinitePlatformer:
129
  num_enemies = random.randint(1, 3) + int(self.difficulty * 0.5)
130
  num_coins = random.randint(5, 10) + int(self.difficulty)
131
 
132
- # Препятствия
133
  for _ in range(num_obstacles):
134
  x = base_x + random.randint(5, 25)
135
  height = random.randint(1, 3 + int(self.difficulty * 0.5))
@@ -139,7 +132,6 @@ class InfinitePlatformer:
139
  'width': width, 'height': height
140
  })
141
 
142
- # Ямы
143
  for _ in range(random.randint(1, 2)):
144
  x = base_x + random.randint(10, 20)
145
  width = random.randint(2, 4)
@@ -148,7 +140,6 @@ class InfinitePlatformer:
148
  'width': width, 'height': 1, 'is_pit': True
149
  })
150
 
151
- # Враги
152
  for _ in range(num_enemies):
153
  x = base_x + random.randint(5, 25)
154
  y = self.ground_level - 1
@@ -161,7 +152,6 @@ class InfinitePlatformer:
161
  'start_x': x
162
  })
163
 
164
- # Монетки
165
  for _ in range(num_coins):
166
  x = base_x + random.randint(2, 28)
167
  y = random.randint(5, self.ground_level - 2)
@@ -256,66 +246,54 @@ class InfinitePlatformer:
256
  return False
257
 
258
  def step(self, action, is_ai=False):
259
- """Выполняет действие. Возвращает (состояние, награда, конец, звук)"""
260
  px, py = self.player[0], self.player[1]
261
  sound = None
262
 
263
- # Горизонтальное движение
264
- if action == 1: # влево
265
  self.player[0] -= 0.5
266
- elif action == 2: # вправо
267
  self.player[0] += 0.5
268
 
269
- # Прыжок
270
  if action == 3 and self.on_ground:
271
  self.velocity_y = self.jump_power
272
  self.on_ground = False
273
  sound = 'jump'
274
 
275
- # Гравитация
276
  self.velocity_y += self.gravity
277
  self.player[1] += self.velocity_y
278
 
279
- # Земля
280
  if self.player[1] >= self.ground_level:
281
  self.player[1] = self.ground_level
282
  self.velocity_y = 0
283
  self.on_ground = True
284
 
285
- # Падение в яму
286
  if self.player[1] > self.height:
287
  self.alive = False
288
  sound = 'die'
289
  return self.get_state(), -50, True, sound
290
 
291
- # Обновление врагов
292
  died = self.update_entities()
293
  if died:
294
  self.alive = False
295
  sound = 'die'
296
  return self.get_state(), -50, True, sound
297
 
298
- # Сбор моне��ок
299
  coins_collected = self.collect_coins()
300
  if coins_collected > 0:
301
  self.coins_collected += coins_collected
302
  sound = 'coin'
303
 
304
- # Проверка препятствий
305
  if self.check_obstacles():
306
  self.alive = False
307
  sound = 'die'
308
  return self.get_state(), -50, True, sound
309
 
310
- # Продвижение
311
  self.distance += 0.1 * self.speed
312
  self.score += coins_collected * 10 + 1
313
 
314
- # Сложность
315
  self.difficulty = 1 + self.distance / 1000
316
  self.speed = 1 + self.difficulty * 0.1
317
 
318
- # Генерация чанков
319
  current_chunk = int(self.player[0] // 30)
320
  for i in range(current_chunk, current_chunk + 3):
321
  self.generate_chunk(i)
@@ -334,7 +312,6 @@ class InfinitePlatformer:
334
  if coin not in self.coins:
335
  self.coins.append(coin)
336
 
337
- # Удаление далёких объектов
338
  self.obstacles = [o for o in self.obstacles if abs(o['x'] - self.player[0]) < self.width]
339
  self.entities = [e for e in self.entities if abs(e['x'] - self.player[0]) < self.width]
340
  self.coins = [c for c in self.coins if abs(c['x'] - self.player[0]) < self.width]
@@ -347,14 +324,11 @@ class InfinitePlatformer:
347
  return self.get_state(), reward, False, sound
348
 
349
  def render(self, show_player=True):
350
- """Визуализация"""
351
  grid = np.zeros((self.height, 80, 3), dtype=np.uint8)
352
  grid.fill(200)
353
 
354
- # Земля
355
  grid[self.ground_level:self.ground_level+2, :] = [100, 80, 40]
356
 
357
- # Препятствия
358
  for obs in self.obstacles:
359
  if obs.get('is_pit', False):
360
  x = int(obs['x'] - self.player[0] + 40)
@@ -370,7 +344,6 @@ class InfinitePlatformer:
370
  if 0 <= sx < 80 and 0 <= sy < self.height:
371
  grid[sy, sx] = [150, 120, 80]
372
 
373
- # Враги
374
  for enemy in self.entities:
375
  x = int(enemy['x'] - self.player[0] + 40)
376
  y = int(enemy['y'])
@@ -379,7 +352,6 @@ class InfinitePlatformer:
379
  if enemy['type'] == 'jumper':
380
  grid[y-1, x] = [200, 0, 0]
381
 
382
- # Монетки
383
  for coin in self.coins:
384
  if not coin['collected']:
385
  x = int(coin['x'] - self.player[0] + 40)
@@ -387,7 +359,6 @@ class InfinitePlatformer:
387
  if 0 <= x < 80 and 0 <= y < self.height:
388
  grid[y, x] = [255, 215, 0]
389
 
390
- # Игрок
391
  if show_player:
392
  px = 40
393
  py = int(self.player[1])
@@ -396,7 +367,6 @@ class InfinitePlatformer:
396
  grid[py-1, px] = [0, 200, 0]
397
  grid[py-2, px] = [0, 150, 0]
398
 
399
- # Информация
400
  img = Image.fromarray(grid, 'RGB')
401
  return img
402
 
@@ -546,22 +516,22 @@ class ChatMemory:
546
  return None
547
 
548
  # =====================================================
549
- # 5. ИГРОВОЙ ЦИКЛ
550
  # =====================================================
551
 
552
  agent = DQNAgent()
553
  chat_memory = ChatMemory()
554
  training_thread = None
555
  is_training = False
556
-
557
- # Синхронизированный seed для обоих игроков
558
  current_seed = random.randint(0, 999999)
559
 
 
 
 
 
560
  def game_loop(player_action=None):
561
- """Основной игровой цикл для двух игроков"""
562
  global current_seed, agent
563
 
564
- # Создаём два экземпляра игры с одинаковым seed
565
  ai_env = InfinitePlatformer(seed=current_seed)
566
  player_env = InfinitePlatformer(seed=current_seed)
567
 
@@ -577,7 +547,6 @@ def game_loop(player_action=None):
577
  sound_html = ''
578
 
579
  for step in range(200):
580
- # Ход ИИ
581
  if ai_alive:
582
  ai_action = agent.act(ai_state)
583
  ai_next_state, ai_reward, ai_done, ai_sound = ai_env.step(ai_action, is_ai=True)
@@ -587,9 +556,7 @@ def game_loop(player_action=None):
587
  ai_score = ai_env.score
588
  ai_alive = not ai_done
589
 
590
- # Ход игрока
591
  if player_alive and player_action is not None:
592
- # player_action: 0-ничего, 1-влево, 2-вправо, 3-прыжок
593
  player_next_state, player_reward, player_done, player_sound = player_env.step(player_action, is_ai=False)
594
  if player_sound and player_alive:
595
  sound_html += get_sound_html(player_sound)
@@ -597,29 +564,24 @@ def game_loop(player_action=None):
597
  player_score = player_env.score
598
  player_alive = not player_done
599
 
600
- # Рендерим оба окна
601
  frames_ai.append(ai_env.render(show_player=ai_alive))
602
  frames_player.append(player_env.render(show_player=player_alive))
603
 
604
  if not ai_alive and not player_alive:
605
  break
606
 
607
- # Создаём анимацию через HTML
608
- from PIL import Image as PILImage
609
- import io
610
-
611
- # Конвертируем кадры в GIF (для демонстрации используем последний кадр)
612
  final_img_ai = frames_ai[-1] if frames_ai else ai_env.render()
613
  final_img_player = frames_player[-1] if frames_player else player_env.render()
614
 
615
- return final_img_ai, final_img_player, f"🤖 ИИ: {ai_score} | 🎮 Ты: {player_score}", sound_html
616
 
617
  # =====================================================
618
- # 6. CHAT FUNCTIONS
619
  # =====================================================
620
 
621
  def chat_ai(message):
622
- """Обработчик чата"""
 
623
  if message.startswith('/ai '):
624
  question = message[4:].strip()
625
  if not question:
@@ -660,7 +622,6 @@ def chat_ai(message):
660
  """
661
 
662
  elif message == '/train':
663
- global training_thread, is_training, current_seed
664
  if not is_training:
665
  is_training = True
666
  def train():
@@ -681,12 +642,10 @@ def chat_ai(message):
681
  return "🤖 Команды:\n/ai <вопрос>\n/data вопрос|ответ\n/stats\n/train"
682
 
683
  def player_move(action):
684
- """Обработка действий игрока"""
685
- # action: 0-ничего, 1-влево, 2-вправо, 3-прыжок
686
  return game_loop(action)
687
 
688
  # =====================================================
689
- # 7. GRADIO INTERFACE
690
  # =====================================================
691
 
692
  with gr.Blocks(title="🎮 AI vs Player Platformer", theme=gr.themes.Soft()) as demo:
@@ -757,7 +716,7 @@ with gr.Blocks(title="🎮 AI vs Player Platformer", theme=gr.themes.Soft()) as
757
  demo.load(reset_game, outputs=[ai_output, player_output, stats_output, sound_output])
758
 
759
  # =====================================================
760
- # 8. ЗАПУСК
761
  # =====================================================
762
 
763
  if __name__ == "__main__":
 
23
  # 1. ЗВУКИ (только эффекты, без фона)
24
  # =====================================================
25
 
 
26
  def generate_sound(freq=440, duration=0.1, waveform='sine'):
27
  """Генерирует простой звук в base64"""
28
  import struct
 
31
  sample_rate = 22050
32
  samples = int(sample_rate * duration)
33
 
 
34
  buffer = io.BytesIO()
35
  wav = wave.open(buffer, 'wb')
36
  wav.setnchannels(1)
37
  wav.setsampwidth(2)
38
  wav.setframerate(sample_rate)
39
 
 
40
  for i in range(samples):
41
  t = i / sample_rate
42
  if waveform == 'sine':
 
45
  val = int(32767 * 0.5 * (1 if math.sin(2 * math.pi * freq * t) > 0 else -1))
46
  elif waveform == 'noise':
47
  val = int(32767 * 0.3 * random.random())
48
+ else: # sweep
49
  f = freq + i * 200 / samples
50
  val = int(32767 * 0.5 * math.sin(2 * math.pi * f * t))
51
  wav.writeframes(struct.pack('<h', val))
 
53
  wav.close()
54
  buffer.seek(0)
55
 
 
56
  audio_data = base64.b64encode(buffer.read()).decode('utf-8')
57
  return f'data:audio/wav;base64,{audio_data}'
58
 
 
59
  SOUNDS = {
60
+ 'jump': generate_sound(600, 0.08, 'sine'),
61
+ 'coin': generate_sound(880, 0.05, 'sine'),
62
+ 'die': generate_sound(200, 0.3, 'sweep')
63
  }
64
 
65
  def get_sound_html(sound_type):
 
66
  if sound_type in SOUNDS:
67
  return f'<audio autoplay><source src="{SOUNDS[sound_type]}" type="audio/wav"></audio>'
68
  return ''
 
123
  num_enemies = random.randint(1, 3) + int(self.difficulty * 0.5)
124
  num_coins = random.randint(5, 10) + int(self.difficulty)
125
 
 
126
  for _ in range(num_obstacles):
127
  x = base_x + random.randint(5, 25)
128
  height = random.randint(1, 3 + int(self.difficulty * 0.5))
 
132
  'width': width, 'height': height
133
  })
134
 
 
135
  for _ in range(random.randint(1, 2)):
136
  x = base_x + random.randint(10, 20)
137
  width = random.randint(2, 4)
 
140
  'width': width, 'height': 1, 'is_pit': True
141
  })
142
 
 
143
  for _ in range(num_enemies):
144
  x = base_x + random.randint(5, 25)
145
  y = self.ground_level - 1
 
152
  'start_x': x
153
  })
154
 
 
155
  for _ in range(num_coins):
156
  x = base_x + random.randint(2, 28)
157
  y = random.randint(5, self.ground_level - 2)
 
246
  return False
247
 
248
  def step(self, action, is_ai=False):
 
249
  px, py = self.player[0], self.player[1]
250
  sound = None
251
 
252
+ if action == 1:
 
253
  self.player[0] -= 0.5
254
+ elif action == 2:
255
  self.player[0] += 0.5
256
 
 
257
  if action == 3 and self.on_ground:
258
  self.velocity_y = self.jump_power
259
  self.on_ground = False
260
  sound = 'jump'
261
 
 
262
  self.velocity_y += self.gravity
263
  self.player[1] += self.velocity_y
264
 
 
265
  if self.player[1] >= self.ground_level:
266
  self.player[1] = self.ground_level
267
  self.velocity_y = 0
268
  self.on_ground = True
269
 
 
270
  if self.player[1] > self.height:
271
  self.alive = False
272
  sound = 'die'
273
  return self.get_state(), -50, True, sound
274
 
 
275
  died = self.update_entities()
276
  if died:
277
  self.alive = False
278
  sound = 'die'
279
  return self.get_state(), -50, True, sound
280
 
 
281
  coins_collected = self.collect_coins()
282
  if coins_collected > 0:
283
  self.coins_collected += coins_collected
284
  sound = 'coin'
285
 
 
286
  if self.check_obstacles():
287
  self.alive = False
288
  sound = 'die'
289
  return self.get_state(), -50, True, sound
290
 
 
291
  self.distance += 0.1 * self.speed
292
  self.score += coins_collected * 10 + 1
293
 
 
294
  self.difficulty = 1 + self.distance / 1000
295
  self.speed = 1 + self.difficulty * 0.1
296
 
 
297
  current_chunk = int(self.player[0] // 30)
298
  for i in range(current_chunk, current_chunk + 3):
299
  self.generate_chunk(i)
 
312
  if coin not in self.coins:
313
  self.coins.append(coin)
314
 
 
315
  self.obstacles = [o for o in self.obstacles if abs(o['x'] - self.player[0]) < self.width]
316
  self.entities = [e for e in self.entities if abs(e['x'] - self.player[0]) < self.width]
317
  self.coins = [c for c in self.coins if abs(c['x'] - self.player[0]) < self.width]
 
324
  return self.get_state(), reward, False, sound
325
 
326
  def render(self, show_player=True):
 
327
  grid = np.zeros((self.height, 80, 3), dtype=np.uint8)
328
  grid.fill(200)
329
 
 
330
  grid[self.ground_level:self.ground_level+2, :] = [100, 80, 40]
331
 
 
332
  for obs in self.obstacles:
333
  if obs.get('is_pit', False):
334
  x = int(obs['x'] - self.player[0] + 40)
 
344
  if 0 <= sx < 80 and 0 <= sy < self.height:
345
  grid[sy, sx] = [150, 120, 80]
346
 
 
347
  for enemy in self.entities:
348
  x = int(enemy['x'] - self.player[0] + 40)
349
  y = int(enemy['y'])
 
352
  if enemy['type'] == 'jumper':
353
  grid[y-1, x] = [200, 0, 0]
354
 
 
355
  for coin in self.coins:
356
  if not coin['collected']:
357
  x = int(coin['x'] - self.player[0] + 40)
 
359
  if 0 <= x < 80 and 0 <= y < self.height:
360
  grid[y, x] = [255, 215, 0]
361
 
 
362
  if show_player:
363
  px = 40
364
  py = int(self.player[1])
 
367
  grid[py-1, px] = [0, 200, 0]
368
  grid[py-2, px] = [0, 150, 0]
369
 
 
370
  img = Image.fromarray(grid, 'RGB')
371
  return img
372
 
 
516
  return None
517
 
518
  # =====================================================
519
+ # 5. ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ (объявлены ДО использования)
520
  # =====================================================
521
 
522
  agent = DQNAgent()
523
  chat_memory = ChatMemory()
524
  training_thread = None
525
  is_training = False
 
 
526
  current_seed = random.randint(0, 999999)
527
 
528
+ # =====================================================
529
+ # 6. ИГРОВОЙ ЦИКЛ
530
+ # =====================================================
531
+
532
  def game_loop(player_action=None):
 
533
  global current_seed, agent
534
 
 
535
  ai_env = InfinitePlatformer(seed=current_seed)
536
  player_env = InfinitePlatformer(seed=current_seed)
537
 
 
547
  sound_html = ''
548
 
549
  for step in range(200):
 
550
  if ai_alive:
551
  ai_action = agent.act(ai_state)
552
  ai_next_state, ai_reward, ai_done, ai_sound = ai_env.step(ai_action, is_ai=True)
 
556
  ai_score = ai_env.score
557
  ai_alive = not ai_done
558
 
 
559
  if player_alive and player_action is not None:
 
560
  player_next_state, player_reward, player_done, player_sound = player_env.step(player_action, is_ai=False)
561
  if player_sound and player_alive:
562
  sound_html += get_sound_html(player_sound)
 
564
  player_score = player_env.score
565
  player_alive = not player_done
566
 
 
567
  frames_ai.append(ai_env.render(show_player=ai_alive))
568
  frames_player.append(player_env.render(show_player=player_alive))
569
 
570
  if not ai_alive and not player_alive:
571
  break
572
 
 
 
 
 
 
573
  final_img_ai = frames_ai[-1] if frames_ai else ai_env.render()
574
  final_img_player = frames_player[-1] if frames_player else player_env.render()
575
 
576
+ return final_img_ai, final_img_player, f"🤖 ИИ: {ai_score} | 🎮 Ты: {player_score} | 🪙 Монет: {player_env.coins_collected}", sound_html
577
 
578
  # =====================================================
579
+ # 7. CHAT FUNCTIONS
580
  # =====================================================
581
 
582
  def chat_ai(message):
583
+ global training_thread, is_training, current_seed
584
+
585
  if message.startswith('/ai '):
586
  question = message[4:].strip()
587
  if not question:
 
622
  """
623
 
624
  elif message == '/train':
 
625
  if not is_training:
626
  is_training = True
627
  def train():
 
642
  return "🤖 Команды:\n/ai <вопрос>\n/data вопрос|ответ\n/stats\n/train"
643
 
644
  def player_move(action):
 
 
645
  return game_loop(action)
646
 
647
  # =====================================================
648
+ # 8. GRADIO INTERFACE
649
  # =====================================================
650
 
651
  with gr.Blocks(title="🎮 AI vs Player Platformer", theme=gr.themes.Soft()) as demo:
 
716
  demo.load(reset_game, outputs=[ai_output, player_output, stats_output, sound_output])
717
 
718
  # =====================================================
719
+ # 9. ЗАПУСК
720
  # =====================================================
721
 
722
  if __name__ == "__main__":