X commited on
Update app.py
Browse files
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 |
-
#
|
| 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 |
-
#
|
| 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 |
-
#
|
| 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__":
|