nicolaydef commited on
Commit
385f6c4
·
verified ·
1 Parent(s): dfc2d11

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +287 -0
app.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import random
4
+ import string
5
+ import time
6
+ import json
7
+ import os
8
+ from filelock import FileLock
9
+
10
+ # --- КОНФИГУРАЦИЯ СИСТЕМЫ ---
11
+ TOTAL_ROUNDS = 3
12
+ POLLINATIONS_URL = "https://text.pollinations.ai/"
13
+ DB_FILE = "game_db.json"
14
+ LOCK_FILE = f"{DB_FILE}.lock"
15
+ MAX_CHARS = 150 # Лимит символов
16
+
17
+ # --- СИСТЕМА ХРАНЕНИЯ ДАННЫХ ---
18
+
19
+ def load_db():
20
+ if not os.path.exists(DB_FILE): return {}
21
+ lock = FileLock(LOCK_FILE)
22
+ with lock:
23
+ try:
24
+ with open(DB_FILE, "r", encoding="utf-8") as f:
25
+ content = f.read()
26
+ return json.loads(content) if content else {}
27
+ except: return {}
28
+
29
+ def save_db(data):
30
+ lock = FileLock(LOCK_FILE)
31
+ with lock:
32
+ with open(DB_FILE, "w", encoding="utf-8") as f:
33
+ json.dump(data, f, ensure_ascii=False, indent=2)
34
+
35
+ # --- AI LOGIC (С ЖЕСТКИМ КОНТЕКСТОМ) ---
36
+
37
+ def ask_ai_judge(event, player_action):
38
+ system_prompt = (
39
+ "Roleplay: Ты безжалостный рассказчик в игре на выживание.\n"
40
+ f"ТЕКУЩАЯ КАТАСТРОФА: {event}\n"
41
+ f"ДЕЙСТВИЕ ИГРОКА: {player_action}\n\n"
42
+ "ЗАДАЧА: Напиши историю из 3 актов о том, как игрок пытается спастись ИМЕННО ОТ ЭТОЙ КАТАСТРОФЫ.\n"
43
+ "ПРАВИЛА:\n"
44
+ "1. В Акте 1 и 2 ОБЯЗАТЕЛЬНО описывай взаимодействие с {event}.\n"
45
+ "2. Используй разделитель '---' между актами.\n"
46
+ "3. В конце Акта 3 поставь вердикт: [Survived] или [Not_Survived].\n\n"
47
+ "СТРУКТУРА:\n"
48
+ "АКТ 1 (Начало): Игрок сталкивается с {event}...\n"
49
+ "---\n"
50
+ "АКТ 2 (Напряжение): Проблемы и хаос...\n"
51
+ "---\n"
52
+ "АКТ 3 (Финал): Итог. Тег в конце."
53
+ )
54
+
55
+ try:
56
+ response = requests.post(POLLINATIONS_URL, data=system_prompt.encode('utf-8'))
57
+ if response.status_code == 200:
58
+ return response.text
59
+ return f"Акт 1: {event} пришло.\n---\nАкт 2: Сбой ИИ...\n---\nАкт 3: Выжил. [Survived]"
60
+ except Exception as e:
61
+ print(f"AI Error: {e}")
62
+ return "Связь потеряна.\n---\nИИ молчит.\n---\nВыжил. [Survived]"
63
+
64
+ def ask_ai_congrats(winner_name, score):
65
+ prompt = f"Поздравь игрока {winner_name} (счет {score}) с победой в апокалипсисе. Эпично и коротко."
66
+ try:
67
+ return requests.get(f"{POLLINATIONS_URL}{prompt}").text
68
+ except:
69
+ return f"Поздравляем {winner_name}!"
70
+
71
+ # --- ИГРОВАЯ ЛОГИКА ---
72
+
73
+ def generate_code():
74
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))
75
+
76
+ def add_log(room_data, message):
77
+ timestamp = time.strftime("%H:%M:%S")
78
+ room_data["logs"].append(f"[{timestamp}] {message}")
79
+
80
+ def format_lore(raw_text):
81
+ if not raw_text: return ""
82
+ parts = raw_text.split("---")
83
+ if len(parts) < 3: return f"**История:**\n{raw_text}"
84
+ return (
85
+ f"### 🎬 АКТ 1\n{parts[0].strip()}\n\n"
86
+ f"### ⚡ АКТ 2\n{parts[1].strip()}\n\n"
87
+ f"### 🏁 ФИНАЛ\n{parts[2].strip()}"
88
+ )
89
+
90
+ def create_room_logic(player_name):
91
+ if not player_name: return None, "⚠️ Введи имя!", gr.update(), gr.update()
92
+ db, code = load_db(), generate_code()
93
+
94
+ db[code] = {
95
+ "host_name": player_name,
96
+ "phase": "LOBBY",
97
+ "round": 1,
98
+ "event": "",
99
+ "logs": [],
100
+ "winner_msg": "",
101
+ "players": {player_name: {"name": player_name, "score": 0, "action": None, "result": "", "is_host": True}}
102
+ }
103
+ add_log(db[code], f"Комната {code} создана.")
104
+ save_db(db)
105
+ return code, "", gr.update(visible=False), gr.update(visible=True)
106
+
107
+ def join_room_logic(player_name, code):
108
+ if not player_name or not code: return None, "⚠️ Данные?", gr.update(), gr.update()
109
+ code = code.upper().strip()
110
+ db = load_db()
111
+
112
+ if code not in db: return None, "⚠️ Нет комнаты!", gr.update(), gr.update()
113
+ room = db[code]
114
+
115
+ if player_name not in room["players"]:
116
+ if room["phase"] != "LOBBY": return None, "⚠️ Поздно!", gr.update(), gr.update()
117
+ room["players"][player_name] = {"name": player_name, "score": 0, "action": None, "result": "", "is_host": False}
118
+ add_log(room, f"{player_name} вошел.")
119
+ save_db(db)
120
+
121
+ return code, "", gr.update(visible=False), gr.update(visible=True)
122
+
123
+ def refresh_ui(room_code, player_name):
124
+ if not room_code: return (gr.update(visible=True), gr.update(visible=False), "", "", "", gr.update(), gr.update(), "")
125
+ db = load_db()
126
+ if room_code not in db: return (gr.update(visible=True), gr.update(visible=False), "Закрыто", "", "", gr.update(), gr.update(), "")
127
+
128
+ room = db[room_code]
129
+ player = room["players"].get(player_name)
130
+ if not player: return (gr.update(visible=True), gr.update(visible=False), "Ошибка", "", "", gr.update(), gr.update(), "")
131
+
132
+ # Список игроков
133
+ players_md = "| Статус | Игрок | Очки |\n| :--- | :--- | :--- |\n"
134
+ for p in room["players"].values():
135
+ icon = "👑" if p["is_host"] else "👤"
136
+ if "[Survived]" in p["result"]: s = "✅"
137
+ elif "[Not_Survived]" in p["result"]: s = "💀"
138
+ elif p["action"]: s = "📩"
139
+ else: s = "⏳"
140
+ if room["phase"] == "INPUT" and not room["event"]: s = "💤"
141
+ players_md += f"| {s} {icon} | **{p['name']}** | {p['score']} |\n"
142
+
143
+ # Логи и Лор
144
+ log_txt = "\n".join(room["logs"][-15:])
145
+ lore_display = format_lore(player["result"]) if player["result"] else "Ждем..."
146
+
147
+ # Управление
148
+ ph = room["phase"]
149
+ inp, btn, hdr = gr.update(visible=False), gr.update(visible=False), ""
150
+
151
+ if ph == "LOBBY":
152
+ hdr = "## ⏳ ОЖИДАНИЕ"
153
+ if player["is_host"]: btn = gr.update(visible=True, value="🚀 СТАРТ", variant="primary")
154
+ elif ph == "INPUT":
155
+ if not room["event"]:
156
+ hdr = f"## 🎬 РАУНД {room['round']}"
157
+ if player["is_host"]:
158
+ inp = gr.update(visible=True, label="Катастрофа", placeholder="Зомби-апокалипсис...")
159
+ btn = gr.update(visible=True, value="📢 ЗАДАТЬ", variant="stop")
160
+ else: hdr += "\nХост выбирает угрозу..."
161
+ else:
162
+ hdr = f"## ⚠️ УГРОЗА: {room['event']}"
163
+ if not player["action"]:
164
+ inp = gr.update(visible=True, label=f"Спасение (макс {MAX_CHARS})", placeholder="Я бегу...")
165
+ btn = gr.update(visible=True, value="✅ ОТПРАВИТЬ", variant="primary")
166
+ else: hdr += "\n\n**Принято.**"
167
+ elif ph == "PROCESSING": hdr = "## 🤖 ИИ ГЕНЕРИРУЕТ..."
168
+ elif ph == "RESULTS":
169
+ hdr = f"## 🏁 ИТОГИ {room['round']}"
170
+ if player["is_host"]:
171
+ txt = "➡️ СЛЕД. РАУНД" if room["round"] < TOTAL_ROUNDS else "🏆 ФИНАЛ"
172
+ btn = gr.update(visible=True, value=txt, variant="secondary")
173
+ elif ph == "GAMEOVER":
174
+ hdr = f"## 🏆 КОНЕЦ\n\n{room['winner_msg']}"
175
+ if player["is_host"]: btn = gr.update(visible=True, value="🔄 РЕСТАРТ", variant="primary")
176
+
177
+ return (gr.update(visible=False), gr.update(visible=True), log_txt, players_md, hdr, inp, btn, lore_display)
178
+
179
+ def handle_action(room_code, player_name, text_input):
180
+ if not room_code: return
181
+ db = load_db()
182
+ if room_code not in db: return
183
+ room = db[room_code]
184
+ player = room["players"][player_name]
185
+
186
+ if text_input and len(text_input) > MAX_CHARS: text_input = text_input[:MAX_CHARS]
187
+ save = False
188
+
189
+ if room["phase"] == "LOBBY" and player["is_host"]:
190
+ room["phase"] = "INPUT"
191
+ add_log(room, "СТАРТ ИГРЫ")
192
+ save = True
193
+
194
+ elif room["phase"] == "INPUT":
195
+ if not room["event"] and player["is_host"] and text_input:
196
+ room["event"] = text_input
197
+ add_log(room, f"⚠️ СОБЫТИЕ: {text_input}")
198
+ for p in room["players"].values(): p["action"], p["result"] = None, ""
199
+ save = True
200
+ elif room["event"] and text_input and not player["action"]:
201
+ player["action"] = text_input
202
+ save = True
203
+ if all(p["action"] for p in room["players"].values()):
204
+ room["phase"] = "PROCESSING"
205
+ save_db(db)
206
+ process_turn_sync(room_code)
207
+ return
208
+
209
+ elif room["phase"] == "RESULTS" and player["is_host"]:
210
+ if room["round"] < TOTAL_ROUNDS:
211
+ room["round"] += 1; room["event"] = ""; room["phase"] = "INPUT"
212
+ add_log(room, f"Раунд {room['round']}")
213
+ else:
214
+ room["phase"] = "GAMEOVER"
215
+ w = max(room["players"].values(), key=lambda x: x["score"])
216
+ room["winner_msg"] = ask_ai_congrats(w["name"], w["score"])
217
+ add_log(room, f"Победил {w['name']}")
218
+ save = True
219
+
220
+ elif room["phase"] == "GAMEOVER" and player["is_host"]:
221
+ room["phase"] = "LOBBY"; room["round"] = 1; room["event"] = ""
222
+ room["logs"] = ["Рестарт."]; room["winner_msg"] = ""
223
+ for p in room["players"].values(): p["score"], p["action"], p["result"] = 0, None, ""
224
+ save = True
225
+
226
+ if save: save_db(db)
227
+
228
+ def process_turn_sync(room_code):
229
+ db = load_db()
230
+ room = db[room_code]
231
+ add_log(room, "🤖 ИИ работает...")
232
+ save_db(db)
233
+
234
+ for p_name, p_data in room["players"].items():
235
+ lore = ask_ai_judge(room["event"], p_data["action"])
236
+ p_data["result"] = lore
237
+ if "[Survived]" in lore:
238
+ p_data["score"] += 2
239
+ add_log(room, f"✅ {p_name} жив")
240
+ else: add_log(room, f"💀 {p_name} мертв")
241
+
242
+ room["phase"] = "RESULTS"
243
+ save_db(db)
244
+
245
+ # --- ЗАПУСК ДЛЯ DOCKER ---
246
+ custom_css = """
247
+ body { background: #0b0f19; color: #fff; }
248
+ #lore_box { background: #1a202c; padding: 10px; border-left: 4px solid #805ad5; }
249
+ """
250
+
251
+ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css, title="AI Survival Docker") as demo:
252
+ s_code, s_name = gr.State(""), gr.State("")
253
+ gr.Markdown("# 💀 SURVIVAL AI: DOCKER EDITION")
254
+
255
+ with gr.Group() as g_login:
256
+ with gr.Row():
257
+ n_in = gr.Textbox(label="Ник")
258
+ b_crt = gr.Button("Создать", variant="primary")
259
+ c_in = gr.Textbox(label="Код")
260
+ b_join = gr.Button("Войти")
261
+ err = gr.Markdown("", color="red")
262
+
263
+ with gr.Group(visible=False) as g_game:
264
+ with gr.Row():
265
+ with gr.Column(scale=1):
266
+ md_rm = gr.Markdown("Комната: ---")
267
+ md_pl = gr.Markdown("Игроки...")
268
+ logs = gr.TextArea(label="Лог", interactive=False, lines=10)
269
+ with gr.Column(scale=2):
270
+ md_st = gr.Markdown("...")
271
+ md_lore = gr.Markdown("История...", elem_id="lore_box")
272
+ t_in = gr.Textbox(visible=False)
273
+ b_act = gr.Button(visible=False)
274
+
275
+ tmr = gr.Timer(1)
276
+ tmr.tick(refresh_ui, [s_code, s_name], [g_login, g_game, logs, md_pl, md_st, t_in, b_act, md_lore])
277
+
278
+ b_crt.click(create_room_logic, [n_in], [s_code, err, g_login, g_game]).then(lambda c: f"## 🏠 {c}", [s_code], [md_rm])
279
+ b_join.click(join_room_logic, [n_in, c_in], [s_code, err, g_login, g_game]).then(lambda c: f"## 🏠 {c}", [s_code], [md_rm])
280
+ b_act.click(handle_action, [s_code, s_name, t_in], None).then(lambda: "", None, [t_in])
281
+
282
+ if __name__ == "__main__":
283
+ if not os.path.exists(DB_FILE):
284
+ with open(DB_FILE, "w") as f: f.write("{}")
285
+
286
+ # ВАЖНО: Запуск на 0.0.0.0 и порт 7860 для Docker
287
+ demo.launch(server_name="0.0.0.0", server_port=7860)