Lukeetah commited on
Commit
ce8e825
·
verified ·
1 Parent(s): e927a21

Upload 19 files

Browse files
.gitattributes CHANGED
@@ -6,4 +6,10 @@ icon_wine_1764982832885.png filter=lfs diff=lfs merge=lfs -text
6
  item_dolar_1764982497080.png filter=lfs diff=lfs merge=lfs -text
7
  item_mate_1764982483347.png filter=lfs diff=lfs merge=lfs -text
8
  map_argentina_cyber_1764982818748.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
9
  vida_logo_1764982455490.png filter=lfs diff=lfs merge=lfs -text
 
6
  item_dolar_1764982497080.png filter=lfs diff=lfs merge=lfs -text
7
  item_mate_1764982483347.png filter=lfs diff=lfs merge=lfs -text
8
  map_argentina_cyber_1764982818748.png filter=lfs diff=lfs merge=lfs -text
9
+ static/assets/bg_obelisco_1764982469642.png filter=lfs diff=lfs merge=lfs -text
10
+ static/assets/icon_wine_1764982832885.png filter=lfs diff=lfs merge=lfs -text
11
+ static/assets/item_dolar_1764982497080.png filter=lfs diff=lfs merge=lfs -text
12
+ static/assets/item_mate_1764982483347.png filter=lfs diff=lfs merge=lfs -text
13
+ static/assets/map_argentina_cyber_1764982818748.png filter=lfs diff=lfs merge=lfs -text
14
+ static/assets/vida_logo_1764982455490.png filter=lfs diff=lfs merge=lfs -text
15
  vida_logo_1764982455490.png filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -1,366 +1,117 @@
1
  import gradio as gr
2
- import pandas as pd
3
- import plotly.express as px
4
- import plotly.graph_objects as go
5
- from datetime import datetime, timedelta
6
- import json
7
  import os
8
- import requests
9
- from groq import Groq
10
- import threading
11
- import time
12
  import random
13
- import hashlib
14
- from typing import Dict, List, Optional
15
- import math
16
 
17
- # --- CONFIGURACIÓN ---
18
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
19
- NEWS_API_KEY = os.getenv("NEWS_API_KEY", "")
20
- DOLAR_API_URL = os.getenv("DOLAR_API_URL", "https://api.bluelytics.com.ar/v2/latest")
21
- IMAGE_PATH = os.path.dirname(os.path.abspath(__file__))
22
-
23
- # --- PERSISTENCIA ---
24
- GAME_DATA_FILE = "vida_game_data.json"
25
- USER_SAVES_FILE = "vida_user_saves.json"
26
-
27
- class Province:
28
- def __init__(self, name, description, risk, trade_good, buy_price, sell_price, traits):
29
- self.name = name
30
- self.description = description
31
- self.risk = risk # 0-100
32
- self.trade_good = trade_good
33
- self.buy_price = buy_price
34
- self.sell_price = sell_price
35
- self.traits = traits
36
-
37
- PROVINCES = {
38
- "CABA": Province("CABA", "La Ciudad de la Furia. Oportunidades y caos.",
39
- risk=80, trade_good="Dolar", buy_price=1200, sell_price=1100, traits=["Political Hub", "Stress"]),
40
- "Mendoza": Province("Mendoza", "Tierra del Sol y del Buen Vino.",
41
- risk=30, trade_good="Vino", buy_price=2000, sell_price=5000, traits=["Relax", "Tourism"]),
42
- "Cordoba": Province("Córdoba", "La República del Fernet. Humor y sierras.",
43
- risk=40, trade_good="Fernet", buy_price=3000, sell_price=6000, traits=["Humor", "Mountains"]),
44
- "Jujuy": Province("Jujuy", "El Norte Profundo. Litio y Pachamama.",
45
- risk=50, trade_good="Litio", buy_price=100, sell_price=10000, traits=["Mining", "Culture"])
46
- }
47
-
48
- class VidaGameEngine:
49
- def __init__(self):
50
- self.groq_client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
51
- self.users = self.load_users()
52
- self.news_cache = []
53
- self.update_news()
54
-
55
- def load_users(self):
56
- if os.path.exists(USER_SAVES_FILE):
57
- try:
58
- with open(USER_SAVES_FILE, 'r', encoding='utf-8') as f:
59
- return json.load(f)
60
- except: return {}
61
- return {}
62
-
63
- def save_users(self):
64
- with open(USER_SAVES_FILE, 'w', encoding='utf-8') as f:
65
- json.dump(self.users, f, ensure_ascii=False, indent=2)
66
-
67
- def update_news(self):
68
- self.news_cache = [
69
- "El Presidente declara feriado nacional porque ganó Boca",
70
- "La inflación bajó 0.1% y el gobierno celebra con asado de polenta",
71
- "Un carpincho es elegido intendente en Nordelta",
72
- "Prohiben la yerba mate por considerarla sustancia psicoactiva",
73
- "El dólar blue cotiza en cripto-patacones"
74
- ]
75
- if NEWS_API_KEY:
76
- try:
77
- url = f"https://newsapi.org/v2/top-headlines?country=ar&apiKey={NEWS_API_KEY}&pageSize=5"
78
- res = requests.get(url, timeout=5)
79
- if res.status_code == 200:
80
- articles = res.json().get('articles', [])
81
- if articles:
82
- self.news_cache = [a['title'] for a in articles]
83
- except: pass
84
-
85
- def create_character(self, name, profession):
86
- user_id = hashlib.md5(name.encode()).hexdigest()[:8]
87
-
88
- base_stats = {
89
- "Estudiante": {"salary": 150000, "sanity": 100, "savings": 20000},
90
- "Docente": {"salary": 450000, "sanity": 80, "savings": 40000},
91
- "Freelancer IT": {"salary": 1500000, "sanity": 60, "savings": 5000},
92
- "Jubilado": {"salary": 200000, "sanity": 50, "savings": 100000}
93
- }
94
-
95
- stats = base_stats.get(profession, base_stats["Estudiante"])
96
-
97
- self.users[user_id] = {
98
- "name": name,
99
- "profession": profession,
100
- "days_survived": 0,
101
- "money": stats["savings"],
102
- "salary": stats["salary"],
103
- "sanity": stats["sanity"],
104
- "inventory": {"Mate": 0, "Dolar": 0, "Vino": 0, "Fernet": 0, "Litio": 0},
105
- "current_province": "CABA",
106
- "status": "ALIVE"
107
  }
108
- self.save_users()
109
- return user_id, self.users[user_id]
110
-
111
- def get_user(self, user_id):
112
- return self.users.get(user_id)
113
-
114
- def travel(self, user_id, destination):
115
- user = self.get_user(user_id)
116
- if user["money"] < 20000:
117
- return "No tenés plata para el pasaje ($20.000).", False
118
-
119
- user["money"] -= 20000
120
- user["current_province"] = destination
121
- user["days_survived"] += 1 # Viajar toma 1 día
122
- self.save_users()
123
- return f"Viajaste a {destination}.", True
124
-
125
- def generate_daily_event(self, user_id):
126
- user = self.get_user(user_id)
127
- if not user or user["status"] != "ALIVE":
128
- return None
129
-
130
- if not self.news_cache: self.update_news()
131
- news_item = random.choice(self.news_cache)
132
- province = PROVINCES.get(user["current_province"], PROVINCES["CABA"])
133
-
134
- # Prompt Regional
135
- system_prompt = f"""
136
- Eres el narrador de un juego de rol profundo en Argentina.
137
- Provincia actual: {province.name} ({province.description}).
138
- Genera un evento narrativo complejo basado en la noticia y la región.
139
- Usa modismos locales (ej: si es CABA porteño, si es Cordoba cordobés).
140
- Dame 3 opciones con consecuencias morales y económicas.
141
- JSON output: {{text: str, options: [{{text: str, cost_money: int, cost_sanity: int}}]}}
142
- """
143
- user_context = f"Rol: {user['profession']}, Plata: ${user['money']}, Cordura: {user['sanity']}%. Noticia: {news_item}"
144
-
145
- try:
146
- if self.groq_client:
147
- completion = self.groq_client.chat.completions.create(
148
- messages=[
149
- {"role": "system", "content": system_prompt},
150
- {"role": "user", "content": user_context}
151
- ],
152
- model="llama-3.1-70b-versatile",
153
- response_format={"type": "json_object"}
154
- )
155
- event_data = json.loads(completion.choices[0].message.content)
156
- else:
157
- raise Exception("No AI")
158
- except:
159
- event_data = {
160
- "text": f"Estás en {province.name}. {news_item}. ¿Qué hacés?",
161
- "options": [
162
- {"text": "Seguir sobreviviendo", "cost_money": -1000, "cost_sanity": -5},
163
- {"text": "Buscar oportunidades", "cost_money": -5000, "cost_sanity": 5},
164
- {"text": "Dormir", "cost_money": 0, "cost_sanity": 10}
165
- ]
166
- }
167
-
168
- return event_data
169
-
170
- def process_turn(self, user_id, option_idx, event_data):
171
- user = self.get_user(user_id)
172
- selected_option = event_data["options"][option_idx]
173
-
174
- user["money"] += selected_option.get("cost_money", 0)
175
- user["sanity"] += selected_option.get("cost_sanity", 0)
176
- user["days_survived"] += 1
177
-
178
- # Game Over Checks
179
- game_over = False
180
- death_reason = ""
181
-
182
- if user["money"] < -50000:
183
- user["status"] = "BANKRUPT"
184
- death_reason = "Quiebra total. Terminaste vendiendo pañuelitos en el subte."
185
- game_over = True
186
- elif user["sanity"] <= 0:
187
- user["status"] = "BURNED_OUT"
188
- death_reason = "La locura te consumió."
189
- game_over = True
190
-
191
- self.save_users()
192
- return user, f"{selected_option['text']}", game_over, death_reason
193
-
194
- def trade_item(self, user_id, action, item):
195
- user = self.get_user(user_id)
196
- province = PROVINCES.get(user["current_province"], PROVINCES["CABA"])
197
-
198
- # Precios dinámicos según provincia
199
- buy_price = 5000 # Base
200
- sell_price = 1000 # Base
201
-
202
- # Regional goods
203
- if item == province.trade_good:
204
- buy_price = province.buy_price # Barato aquí
205
- else:
206
- buy_price = province.buy_price * 2 # Caro aquí (importado)
207
-
208
- # Venta
209
- if item == province.trade_good:
210
- sell_price = province.sell_price / 2 # Vender lo local paga poco
211
- else:
212
- sell_price = province.sell_price # Vender lo de afuera paga mucho
213
-
214
- if action == "buy":
215
- if user["money"] >= buy_price:
216
- user["money"] -= buy_price
217
- user["inventory"][item] = user["inventory"].get(item, 0) + 1
218
- self.save_users()
219
- return f"Compraste {item} por ${buy_price}"
220
- return "No tenés plata."
221
- elif action == "sell":
222
- if user["inventory"].get(item, 0) > 0:
223
- user["inventory"][item] -= 1
224
- user["money"] += sell_price
225
- self.save_users()
226
- return f"Vendiste {item} por ${sell_price}"
227
- return "No tenés ese item."
228
-
229
- # --- ASSETS ---
230
- def get_img(name):
231
- path = os.path.join(IMAGE_PATH, name)
232
- return path if os.path.exists(path) else None
233
-
234
- # --- UI LOGIC ---
235
- game = VidaGameEngine()
236
-
237
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="cyan")) as demo:
238
- state_uid = gr.State()
239
- state_event = gr.State()
240
 
241
- # HEADER
242
- with gr.Row():
243
- logo_img = get_img("vida_logo.png")
244
- if logo_img: gr.Image(logo_img, show_label=False, show_download_button=False, container=False, height=120)
245
- gr.Markdown("# 🇦🇷 VIDA 3.0: Argentina Federal")
246
-
247
- # CREATION
248
- with gr.Group(visible=True) as screen_creation:
249
- with gr.Row():
250
- name_input = gr.Textbox(label="Nombre")
251
- prof_input = gr.Radio(["Estudiante", "Docente", "Freelancer IT", "Jubilado"], label="Rol", value="Estudiante")
252
- start_btn = gr.Button("Iniciar Travesía", variant="primary")
253
-
254
- # MAIN GAME
255
- with gr.Group(visible=False) as screen_game:
256
- # HUD
257
- with gr.Row(variant="panel"):
258
- with gr.Column(scale=1):
259
- cur_prov_disp = gr.Markdown("### 📍 Provincia: ...")
260
- with gr.Column(scale=1):
261
- inv_money = gr.Number(label="Efectivo", value=0)
262
- with gr.Column(scale=1):
263
- inv_sanity = gr.Number(label="Cordura", value=100)
264
- with gr.Column(scale=1):
265
- inv_days = gr.Number(label="Días", value=0)
266
-
267
- # CENTRAL AREA
268
- with gr.Row():
269
- # MAP / TRAVEL
270
- with gr.Column(scale=1):
271
- map_img = get_img("map_argentina_cyber.png")
272
- if map_img: gr.Image(map_img, show_label=False, container=False)
273
- gr.Markdown("### ✈️ Viajar ($20.000)")
274
- dest_dropdown = gr.Dropdown(list(PROVINCES.keys()), label="Destino")
275
- btn_travel = gr.Button("Viajar")
276
- travel_log = gr.Markdown("")
277
-
278
- # EVENTS
279
- with gr.Column(scale=2):
280
- event_display = gr.Markdown("...")
281
- btn_opt1 = gr.Button("...")
282
- btn_opt2 = gr.Button("...")
283
- btn_opt3 = gr.Button("...")
284
-
285
- # MARKET
286
- with gr.Column(scale=1):
287
- gr.Markdown("### 📦 Mercado Regional")
288
- gr.Image(get_img("icon_wine.png"), height=50, show_label=False, container=False)
289
- with gr.Row():
290
- btn_buy_wine = gr.Button("Comprar Vino")
291
- btn_sell_wine = gr.Button("Vender Vino")
292
- gr.Markdown("---")
293
- inv_wine_disp = gr.Number(label="Vinos", value=0)
294
- inv_lith_disp = gr.Number(label="Litio", value=0)
295
- market_log = gr.Markdown("")
296
-
297
- # HANDLERS
298
- def refresh_ui(uid):
299
- u = game.get_user(uid)
300
- return (
301
- f"### 📍 Provincia: {u['current_province']}",
302
- u['money'], u['sanity'], u['days_survived'],
303
- u['inventory'].get('Vino', 0), u['inventory'].get('Litio', 0)
304
- )
305
-
306
- def on_start(n, p):
307
- if not n: return (None,)*15
308
- uid, u = game.create_character(n, p)
309
- evt = game.generate_daily_event(uid)
310
- opts = evt['options']
311
- prov, m, s, d, i_w, i_l = refresh_ui(uid)
312
- return (
313
- uid, evt,
314
- gr.update(visible=False), gr.update(visible=True),
315
- prov, m, s, d, i_w, i_l,
316
- f"### 📰 Noticias: {evt['text']}",
317
- gr.update(value=opts[0]['text']),
318
- gr.update(value=opts[1]['text']),
319
- gr.update(value=opts[2]['text'])
320
- )
321
-
322
- start_btn.click(on_start, [name_input, prof_input],
323
- [state_uid, state_event, screen_creation, screen_game,
324
- cur_prov_disp, inv_money, inv_sanity, inv_days, inv_wine_disp, inv_lith_disp,
325
- event_display, btn_opt1, btn_opt2, btn_opt3])
326
-
327
- def on_option(uid, evt, idx):
328
- u, log, over, reason = game.process_turn(uid, idx, evt)
329
- if over: return (evt, f"# 💀 {reason}", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)) # Lazy game over handling
330
-
331
- new_evt = game.generate_daily_event(uid)
332
- opts = new_evt['options']
333
- prov, m, s, d, i_w, i_l = refresh_ui(uid)
334
-
335
- return (
336
- new_evt,
337
- f"### 📅 Día {d}\n\nConsequence: {log}\n\n---\n\n**{new_evt['text']}**",
338
- gr.update(value=opts[0]['text']),
339
- gr.update(value=opts[1]['text']),
340
- gr.update(value=opts[2]['text'])
341
  )
342
-
343
- btn_opt1.click(lambda u, e: on_option(u, e, 0), [state_uid, state_event], [state_event, event_display, btn_opt1, btn_opt2, btn_opt3])
344
- btn_opt2.click(lambda u, e: on_option(u, e, 1), [state_uid, state_event], [state_event, event_display, btn_opt1, btn_opt2, btn_opt3])
345
- btn_opt3.click(lambda u, e: on_option(u, e, 2), [state_uid, state_event], [state_event, event_display, btn_opt1, btn_opt2, btn_opt3])
346
-
347
- def on_travel(uid, dest):
348
- if not dest: return "Elegí destino."
349
- log, success = game.travel(uid, dest)
350
- if success:
351
- prov, m, s, d, i_w, i_l = refresh_ui(uid)
352
- return log, prov, m, s, d
353
- return log, gr.update(), gr.update(), gr.update(), gr.update()
354
-
355
- btn_travel.click(on_travel, [state_uid, dest_dropdown], [travel_log, cur_prov_disp, inv_money, inv_sanity, inv_days])
356
-
357
- def on_trade(uid, action, item):
358
- log = game.trade_item(uid, action, item)
359
- prov, m, s, d, i_w, i_l = refresh_ui(uid)
360
- return log, m, i_w, i_l
361
-
362
- btn_buy_wine.click(lambda u: on_trade(u, "buy", "Vino"), state_uid, [market_log, inv_money, inv_wine_disp, inv_lith_disp])
363
- btn_sell_wine.click(lambda u: on_trade(u, "sell", "Vino"), state_uid, [market_log, inv_money, inv_wine_disp, inv_lith_disp])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
365
  if __name__ == "__main__":
366
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
1
  import gradio as gr
 
 
 
 
 
2
  import os
 
 
 
 
3
  import random
4
+ import json
5
+ from groq import Groq
 
6
 
7
+ # CONFIG
8
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
9
+ GROQ_CLIENT = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
10
+
11
+ # Custom HTML to load Phaser and the Game
12
+ GAME_HTML = """
13
+ <div id="game-container" style="width: 800px; height: 400px; margin: 0 auto; border: 2px solid cyan;"></div>
14
+ <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
15
+ <script type="module" src="/file=static/game.js"></script>
16
+ <script>
17
+ // BRIDGE: Gradio -> Phaser
18
+ function sendToGame(text) {
19
+ if (window.handleAIResponse) {
20
+ window.handleAIResponse(text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ // BRIDGE: Phaser -> Gradio
25
+ // We listen for the custom event dispatched by game.js
26
+ window.addEventListener('gameEventTriggered', function(e) {
27
+ console.log("Event from Game:", e.detail);
28
+ // Find the hidden button and click it
29
+ const btn = document.getElementById("bridge-btn");
30
+ if (btn) btn.click();
31
+ });
32
+ </script>
33
+ """
34
+
35
+ def ai_generate_script():
36
+ if not GROQ_CLIENT:
37
+ return "ERROR: AI DISCONNECTED. CHECK NEURAL LINK."
38
+
39
+ scenarios = [
40
+ "You arrive at a shattered subway station. A holographic poster of Perón flickers.",
41
+ "A stray Cyber-Dog blocks your path. It demands 500 Credits.",
42
+ "You find a stash of illegal yerba mate. It glows radioactive green.",
43
+ "The Dolar Blue exchange rate flashes on a billboard: 1 USD = INFINITY ARS."
44
+ ]
45
+
46
+ seed = random.choice(scenarios)
47
+
48
+ try:
49
+ completion = GROQ_CLIENT.chat.completions.create(
50
+ messages=[
51
+ {"role": "system", "content": "You are the AI Director of a cyberpunk adventure game set in Argentina 2077. Write a short, atmospheric description of an encounter (max 2 sentences). High contrast, noir style."},
52
+ {"role": "user", "content": f"Context: {seed}"}
53
+ ],
54
+ model="llama-3.1-70b-versatile"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  )
56
+ return completion.choices[0].message.content
57
+ except:
58
+ return seed
59
+
60
+ def game_bridge_fn():
61
+ # This is called when Phaser triggers an event
62
+ response = ai_generate_script()
63
+ # We return Javascript code to execute in the browser to update the game
64
+ # Gradio's js output can execute code.
65
+ js_call = f"window.handleAIResponse('{response.replace("'", "\\'")}')"
66
+ return js_call
67
+
68
+ with gr.Blocks(title="VIDA 4.0", theme=gr.themes.Monochrome()) as demo:
69
+ gr.Markdown("# 🇦🇷 VIDA 4.0: Graphic Protocol")
70
+
71
+ # Game Canvas
72
+ gr.HTML(GAME_HTML)
73
+
74
+ # Bridge Controls (Hidden)
75
+ bridge_btn = gr.Button("Bridge", elem_id="bridge-btn", visible=False)
76
+ # Allows sending JS back to client
77
+ js_output = gr.Label(visible=False)
78
+
79
+ bridge_btn.click(fn=game_bridge_fn, inputs=None, outputs=None, js="(x) => x")
80
+ # Logic: The click triggers Python `game_bridge_fn`.
81
+ # But how to send data back to JS?
82
+ # Gradio allows returning a string as the 'js' argument for the output event, but that's for the *next* event.
83
+ # Actually, simpler: Use `gr.Textbox` to pass data.
84
+
85
+ hidden_exchange = gr.Textbox(label="AI Output", visible=False, elem_id="ai-output")
86
+
87
+ bridge_btn.click(
88
+ fn=game_bridge_fn,
89
+ inputs=None,
90
+ outputs=None
91
+ ).then(
92
+ fn=None,
93
+ inputs=None,
94
+ outputs=None,
95
+ js="(r) => { window.handleAIResponse('" + "AI PROCESSING..." + "'); }"
96
+ # Wait, this is tricky. Let's do: Python updates Textbox -> JS change listener on Textbox updates Game.
97
+ )
98
+
99
+ # BETTER BRIDGE:
100
+ # 1. JS clicks Bridge Btn.
101
+ # 2. Python determines text, returns it to `hidden_exchange`.
102
+ # 3. `hidden_exchange.change` event triggers JS to read the value and call `handleAIResponse`.
103
+
104
+ bridge_btn.click(fn=ai_generate_script, outputs=hidden_exchange)
105
+
106
+ hidden_exchange.change(
107
+ fn=None,
108
+ js="(val) => window.handleAIResponse(val)",
109
+ inputs=hidden_exchange
110
+ )
111
 
112
  if __name__ == "__main__":
113
+ # Mount static files
114
+ app = demo.app
115
+ # Gradio mounts 'file=' routes automatically for absolute paths?
116
+ # Actually, let's use `allowed_paths`
117
+ demo.launch(server_name="0.0.0.0", server_port=7860, allowed_paths=["static"])
static/assets/bg_obelisco_1764982469642.png ADDED

Git LFS Details

  • SHA256: abb6814c47aed3ff92e8d0f1432683eaa0fbd9fcb69f3b92fe5d4fd63ea26e05
  • Pointer size: 131 Bytes
  • Size of remote file: 982 kB
static/assets/icon_wine_1764982832885.png ADDED

Git LFS Details

  • SHA256: 507c193aa28581e55cd49782e5dbed2cf55d625fefc31477e1291382da24d942
  • Pointer size: 131 Bytes
  • Size of remote file: 521 kB
static/assets/item_dolar_1764982497080.png ADDED

Git LFS Details

  • SHA256: a676423715c781049fa828968a248477856528ccbe3c366e0bb870d9552bf0e7
  • Pointer size: 131 Bytes
  • Size of remote file: 539 kB
static/assets/item_mate_1764982483347.png ADDED

Git LFS Details

  • SHA256: bf5a4ff164a0e870d0e02fede2acbd2b3c91dac213830c0c51f49b4c62a79cc4
  • Pointer size: 131 Bytes
  • Size of remote file: 469 kB
static/assets/map_argentina_cyber_1764982818748.png ADDED

Git LFS Details

  • SHA256: 49a62be95356fb33e44c3a6988076e6ce9f3455b618d97175e0d736dfdcf30da
  • Pointer size: 131 Bytes
  • Size of remote file: 842 kB
static/assets/vida_logo_1764982455490.png ADDED

Git LFS Details

  • SHA256: c901a3ff718a6154ae57bc0aea2fce96cc29d5d6b4a9c9245e1aa5bdd9a348db
  • Pointer size: 131 Bytes
  • Size of remote file: 776 kB
static/game.js ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // VIDA 4.0 - Cyber-Gaucho Engine
3
+ // Inspired by Flashback (1992)
4
+
5
+ const config = {
6
+ type: Phaser.AUTO,
7
+ width: 800,
8
+ height: 400,
9
+ parent: 'game-container',
10
+ physics: {
11
+ default: 'arcade',
12
+ arcade: {
13
+ gravity: { y: 300 },
14
+ debug: false
15
+ }
16
+ },
17
+ scene: {
18
+ preload: preload,
19
+ create: create,
20
+ update: update
21
+ }
22
+ };
23
+
24
+ const game = new Phaser.Game(config);
25
+
26
+ let player;
27
+ let cursors;
28
+ let bg;
29
+ let cinematicText;
30
+ let dialogueBox;
31
+ let isCinematic = false;
32
+
33
+ function preload() {
34
+ this.load.image('bg_city', '/file=static/assets/bg_obelisco.png');
35
+ this.load.image('item_mate', '/file=static/assets/item_mate.png');
36
+ // Placeholder for player if sprite gen failed
37
+ this.load.image('player_placeholder', '/file=static/assets/item_dolar.png');
38
+ }
39
+
40
+ function create() {
41
+ // 1. Background (Parallax feel)
42
+ bg = this.add.tileSprite(400, 200, 800, 400, 'bg_city');
43
+
44
+ // 2. Player (Cyber-Gaucho)
45
+ player = this.physics.add.sprite(100, 300, 'player_placeholder');
46
+ player.setBounce(0.2);
47
+ player.setCollideWorldBounds(true);
48
+ player.setScale(0.5); // Adjust based on icon size
49
+
50
+ // 3. Ground (Invisible)
51
+ const platforms = this.physics.add.staticGroup();
52
+ const ground = platforms.create(400, 390, 'ground').setScale(2).refreshBody();
53
+ // Hack: Just invisible floor
54
+ player.body.setGravityY(300);
55
+ this.physics.add.collider(player, platforms);
56
+
57
+ // Invisible floor rect
58
+ let floor = this.add.rectangle(400, 400, 800, 20, 0x000000);
59
+ this.physics.add.existing(floor, true);
60
+ this.physics.add.collider(player, floor);
61
+
62
+ // 4. Input
63
+ cursors = this.input.keyboard.createCursorKeys();
64
+
65
+ // 5. UI / Cinematic Layer
66
+ dialogueBox = this.add.rectangle(400, 350, 700, 80, 0x000000, 0.8);
67
+ dialogueBox.setVisible(false);
68
+
69
+ cinematicText = this.add.text(60, 320, '', {
70
+ fontSize: '16px',
71
+ fill: '#00ff00',
72
+ fontFamily: 'Courier'
73
+ });
74
+ cinematicText.setWordWrapWidth(680);
75
+ cinematicText.setVisible(false);
76
+
77
+ // Initial Event
78
+ triggerCinematic("SYSTEM BOOT... CYBER-GAUCHO ONLINE.\nUse Arrow Keys to move. Press SPACE to Interact.");
79
+ }
80
+
81
+ function update() {
82
+ if (isCinematic) {
83
+ player.setVelocityX(0);
84
+ player.anims.stop();
85
+
86
+ if (Phaser.Input.Keyboard.JustDown(cursors.space)) {
87
+ endCinematic();
88
+ }
89
+ return;
90
+ }
91
+
92
+ // Scroll Background
93
+ if (player.x > 700) {
94
+ player.x = 100;
95
+ bg.tilePositionX += 400; // Travel effect
96
+ requestAIEvent(); // Trigger AI on screen transition
97
+ } else if (player.x < 50) {
98
+ player.x = 50;
99
+ }
100
+
101
+ // Movement
102
+ if (cursors.left.isDown) {
103
+ player.setVelocityX(-160);
104
+ // player.anims.play('left', true);
105
+ }
106
+ else if (cursors.right.isDown) {
107
+ player.setVelocityX(160);
108
+ // player.anims.play('right', true);
109
+ }
110
+ else {
111
+ player.setVelocityX(0);
112
+ // player.anims.play('turn');
113
+ }
114
+
115
+ if (cursors.up.isDown && player.body.touching.down) {
116
+ player.setVelocityY(-330);
117
+ }
118
+ }
119
+
120
+ // --- INTELLIGENCE ---
121
+
122
+ function triggerCinematic(text) {
123
+ isCinematic = true;
124
+ dialogueBox.setVisible(true);
125
+ cinematicText.setText(text);
126
+ cinematicText.setVisible(true);
127
+ }
128
+
129
+ function endCinematic() {
130
+ isCinematic = false;
131
+ dialogueBox.setVisible(false);
132
+ cinematicText.setVisible(false);
133
+ }
134
+
135
+ function requestAIEvent() {
136
+ // This function calls the Python backend via a hidden button in the HTML
137
+ // We can't call Python directly from Phaser easily without a bridge.
138
+ // BRIDGE: Dispatch event to DOM -> Gradio's JS captures it -> Clicks hidden button
139
+
140
+ triggerCinematic("ANALYZING ENVIRONMENT... (Contacting AI)");
141
+
142
+ // Custom Event for Gradio
143
+ const event = new CustomEvent('gameEventTriggered', { detail: { type: 'travel' } });
144
+ window.dispatchEvent(event);
145
+ }
146
+
147
+ // Listen for response from Python (updated via Gradio output)
148
+ window.handleAIResponse = function (responseText) {
149
+ triggerCinematic(responseText);
150
+ }