Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- .gitattributes +2 -0
- app.py +168 -160
- icon_wine_1764982832885.png +3 -0
- map_argentina_cyber_1764982818748.png +3 -0
.gitattributes
CHANGED
|
@@ -2,6 +2,8 @@ packages.txt text eol=lf
|
|
| 2 |
requirements.txt text eol=lf
|
| 3 |
*.md text eol=lf
|
| 4 |
bg_obelisco_1764982469642.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 5 |
item_dolar_1764982497080.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
item_mate_1764982483347.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 7 |
vida_logo_1764982455490.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 2 |
requirements.txt text eol=lf
|
| 3 |
*.md text eol=lf
|
| 4 |
bg_obelisco_1764982469642.png filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
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
|
app.py
CHANGED
|
@@ -24,6 +24,27 @@ IMAGE_PATH = os.path.dirname(os.path.abspath(__file__))
|
|
| 24 |
GAME_DATA_FILE = "vida_game_data.json"
|
| 25 |
USER_SAVES_FILE = "vida_user_saves.json"
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
class VidaGameEngine:
|
| 28 |
def __init__(self):
|
| 29 |
self.groq_client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
|
|
@@ -44,7 +65,6 @@ class VidaGameEngine:
|
|
| 44 |
json.dump(self.users, f, ensure_ascii=False, indent=2)
|
| 45 |
|
| 46 |
def update_news(self):
|
| 47 |
-
# Noticias satíricas por defecto (Fallback)
|
| 48 |
self.news_cache = [
|
| 49 |
"El Presidente declara feriado nacional porque ganó Boca",
|
| 50 |
"La inflación bajó 0.1% y el gobierno celebra con asado de polenta",
|
|
@@ -59,7 +79,6 @@ class VidaGameEngine:
|
|
| 59 |
if res.status_code == 200:
|
| 60 |
articles = res.json().get('articles', [])
|
| 61 |
if articles:
|
| 62 |
-
# Mezclar noticias reales para más realismo trágico
|
| 63 |
self.news_cache = [a['title'] for a in articles]
|
| 64 |
except: pass
|
| 65 |
|
|
@@ -67,10 +86,10 @@ class VidaGameEngine:
|
|
| 67 |
user_id = hashlib.md5(name.encode()).hexdigest()[:8]
|
| 68 |
|
| 69 |
base_stats = {
|
| 70 |
-
"Estudiante": {"salary": 150000, "sanity": 100, "savings": 20000
|
| 71 |
-
"Docente": {"salary": 450000, "sanity": 80, "savings": 40000
|
| 72 |
-
"Freelancer IT": {"salary": 1500000, "sanity": 60, "savings": 5000
|
| 73 |
-
"Jubilado": {"salary": 200000, "sanity": 50, "savings": 100000
|
| 74 |
}
|
| 75 |
|
| 76 |
stats = base_stats.get(profession, base_stats["Estudiante"])
|
|
@@ -82,7 +101,8 @@ class VidaGameEngine:
|
|
| 82 |
"money": stats["savings"],
|
| 83 |
"salary": stats["salary"],
|
| 84 |
"sanity": stats["sanity"],
|
| 85 |
-
"inventory": {"Mate": 0, "Dolar": 0},
|
|
|
|
| 86 |
"status": "ALIVE"
|
| 87 |
}
|
| 88 |
self.save_users()
|
|
@@ -91,25 +111,34 @@ class VidaGameEngine:
|
|
| 91 |
def get_user(self, user_id):
|
| 92 |
return self.users.get(user_id)
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
def generate_daily_event(self, user_id):
|
| 95 |
user = self.get_user(user_id)
|
| 96 |
if not user or user["status"] != "ALIVE":
|
| 97 |
return None
|
| 98 |
|
| 99 |
-
# Asegurar noticias
|
| 100 |
if not self.news_cache: self.update_news()
|
| 101 |
news_item = random.choice(self.news_cache)
|
|
|
|
| 102 |
|
| 103 |
-
# Prompt
|
| 104 |
-
system_prompt = """
|
| 105 |
-
Eres el narrador de un juego
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
3. La absurda/desesperada (alto riesgo).
|
| 112 |
-
JSON output: {text: str, options: [{text: str, cost_money: int, cost_sanity: int}]}
|
| 113 |
"""
|
| 114 |
user_context = f"Rol: {user['profession']}, Plata: ${user['money']}, Cordura: {user['sanity']}%. Noticia: {news_item}"
|
| 115 |
|
|
@@ -128,11 +157,11 @@ class VidaGameEngine:
|
|
| 128 |
raise Exception("No AI")
|
| 129 |
except:
|
| 130 |
event_data = {
|
| 131 |
-
"text": f"
|
| 132 |
"options": [
|
| 133 |
-
{"text": "
|
| 134 |
-
{"text": "
|
| 135 |
-
{"text": "
|
| 136 |
]
|
| 137 |
}
|
| 138 |
|
|
@@ -142,68 +171,60 @@ class VidaGameEngine:
|
|
| 142 |
user = self.get_user(user_id)
|
| 143 |
selected_option = event_data["options"][option_idx]
|
| 144 |
|
| 145 |
-
# Efectos
|
| 146 |
user["money"] += selected_option.get("cost_money", 0)
|
| 147 |
user["sanity"] += selected_option.get("cost_sanity", 0)
|
| 148 |
user["days_survived"] += 1
|
| 149 |
|
| 150 |
-
# Inflación y Cobro (cada 10 turnos para más ritmo)
|
| 151 |
-
msg_extra = ""
|
| 152 |
-
if user["days_survived"] % 10 == 0:
|
| 153 |
-
inflation = random.uniform(0.10, 0.25)
|
| 154 |
-
user["salary"] = int(user["salary"] * (1 + inflation * 0.4))
|
| 155 |
-
renta = int(user["salary"] * 0.5)
|
| 156 |
-
user["money"] -= renta
|
| 157 |
-
user["money"] += user["salary"]
|
| 158 |
-
msg_extra = f"\n\n¡COBRASTE! Pero el alquiler subió {int(inflation*100)}%. Pagaste ${renta}."
|
| 159 |
-
|
| 160 |
# Game Over Checks
|
| 161 |
game_over = False
|
| 162 |
death_reason = ""
|
| 163 |
|
| 164 |
-
if user["money"] < -50000:
|
| 165 |
user["status"] = "BANKRUPT"
|
| 166 |
-
death_reason = "
|
| 167 |
game_over = True
|
| 168 |
elif user["sanity"] <= 0:
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
user["sanity"] = 20
|
| 173 |
-
msg_extra += "\n\n¡Casi te volvés loco, pero un buen MATE te salvó!"
|
| 174 |
-
else:
|
| 175 |
-
user["status"] = "BURNED_OUT"
|
| 176 |
-
death_reason = "Brote psicótico. Terminaste corriendo desnudo por la 9 de Julio."
|
| 177 |
-
game_over = True
|
| 178 |
|
| 179 |
self.save_users()
|
| 180 |
-
return user, f"{selected_option['text']}
|
| 181 |
|
| 182 |
-
def
|
| 183 |
user = self.get_user(user_id)
|
| 184 |
-
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
|
|
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
user["inventory"][item] -= 1
|
| 201 |
-
user["
|
| 202 |
self.save_users()
|
| 203 |
-
return "
|
| 204 |
-
|
| 205 |
-
return "Los dólares no se usan, se guardan bajo el colchón."
|
| 206 |
-
return "No tenés eso."
|
| 207 |
|
| 208 |
# --- ASSETS ---
|
| 209 |
def get_img(name):
|
|
@@ -213,146 +234,133 @@ def get_img(name):
|
|
| 213 |
# --- UI LOGIC ---
|
| 214 |
game = VidaGameEngine()
|
| 215 |
|
| 216 |
-
|
| 217 |
-
if not user: return 0, 0, 0, 0, 0
|
| 218 |
-
return (
|
| 219 |
-
user['money'],
|
| 220 |
-
user['sanity'],
|
| 221 |
-
user['days_survived'],
|
| 222 |
-
user['inventory'].get('Mate', 0),
|
| 223 |
-
user['inventory'].get('Dolar', 0)
|
| 224 |
-
)
|
| 225 |
-
|
| 226 |
-
with gr.Blocks(theme=gr.themes.Monochrome(primary_hue="cyan", radius_size=gr.themes.sizes.radius_none)) as demo:
|
| 227 |
state_uid = gr.State()
|
| 228 |
state_event = gr.State()
|
| 229 |
|
| 230 |
# HEADER
|
| 231 |
with gr.Row():
|
| 232 |
logo_img = get_img("vida_logo.png")
|
| 233 |
-
if logo_img: gr.Image(logo_img, show_label=False, show_download_button=False, container=False, height=
|
| 234 |
-
gr.Markdown("# 🇦🇷 VIDA
|
| 235 |
|
| 236 |
# CREATION
|
| 237 |
with gr.Group(visible=True) as screen_creation:
|
| 238 |
-
gr.Markdown("### Elegí tu destino")
|
| 239 |
with gr.Row():
|
| 240 |
-
name_input = gr.Textbox(label="
|
| 241 |
-
prof_input = gr.Radio(["Estudiante", "Docente", "Freelancer IT", "Jubilado"], label="
|
| 242 |
-
start_btn = gr.Button("
|
| 243 |
|
| 244 |
-
#
|
| 245 |
with gr.Group(visible=False) as screen_game:
|
|
|
|
| 246 |
with gr.Row(variant="panel"):
|
| 247 |
with gr.Column(scale=1):
|
| 248 |
-
gr.
|
| 249 |
-
stat_money = gr.Number(label="Pesos", value=0)
|
| 250 |
with gr.Column(scale=1):
|
| 251 |
-
|
| 252 |
with gr.Column(scale=1):
|
| 253 |
-
|
| 254 |
with gr.Column(scale=1):
|
| 255 |
-
gr.
|
| 256 |
-
inv_mate = gr.Number(label="Mates", value=0)
|
| 257 |
-
btn_drink_mate = gr.Button("Tomar Mate", size="sm")
|
| 258 |
|
| 259 |
-
#
|
| 260 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
with gr.Column(scale=2):
|
| 262 |
-
bg_img = get_img("bg_obelisco.png")
|
| 263 |
-
if bg_img: gr.Image(bg_img, show_label=False, container=False)
|
| 264 |
-
with gr.Column(scale=3):
|
| 265 |
event_display = gr.Markdown("...")
|
| 266 |
-
btn_opt1 = gr.Button("
|
| 267 |
-
btn_opt2 = gr.Button("
|
| 268 |
-
btn_opt3 = gr.Button("
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
-
#
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
-
# --- HANDLERS ---
|
| 284 |
def on_start(n, p):
|
| 285 |
if not n: return (None,)*15
|
| 286 |
uid, u = game.create_character(n, p)
|
| 287 |
evt = game.generate_daily_event(uid)
|
| 288 |
-
|
| 289 |
-
m, s, d, i_m, i_d = ui_refresh_hud(u)
|
| 290 |
opts = evt['options']
|
| 291 |
-
|
| 292 |
return (
|
| 293 |
-
uid, evt,
|
| 294 |
-
gr.update(visible=False), gr.update(visible=True),
|
| 295 |
-
m, s, d,
|
| 296 |
-
f"###
|
| 297 |
-
gr.update(value=
|
| 298 |
-
gr.update(value=
|
| 299 |
-
gr.update(value=
|
| 300 |
)
|
| 301 |
|
| 302 |
start_btn.click(on_start, [name_input, prof_input],
|
| 303 |
-
[state_uid, state_event, screen_creation, screen_game,
|
| 304 |
-
|
| 305 |
event_display, btn_opt1, btn_opt2, btn_opt3])
|
| 306 |
|
| 307 |
def on_option(uid, evt, idx):
|
| 308 |
-
|
|
|
|
| 309 |
|
| 310 |
-
if dead:
|
| 311 |
-
return (
|
| 312 |
-
evt,
|
| 313 |
-
gr.update(visible=False), gr.update(visible=True),
|
| 314 |
-
f"# 💀 {reason}\n\nSobreviviste {user['days_survived']} días.",
|
| 315 |
-
0,0,0,0,0,
|
| 316 |
-
"", gr.update(), gr.update(), gr.update()
|
| 317 |
-
)
|
| 318 |
-
|
| 319 |
new_evt = game.generate_daily_event(uid)
|
| 320 |
-
m, s, d, i_m, i_d = ui_refresh_hud(user)
|
| 321 |
opts = new_evt['options']
|
|
|
|
| 322 |
|
| 323 |
return (
|
| 324 |
new_evt,
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
gr.update(value=f"{opts[0]['text']}"),
|
| 330 |
-
gr.update(value=f"{opts[1]['text']}"),
|
| 331 |
-
gr.update(value=f"{opts[2]['text']}")
|
| 332 |
)
|
| 333 |
|
| 334 |
-
btn_opt1.click(lambda u, e: on_option(u, e, 0), [state_uid, state_event], [state_event,
|
| 335 |
-
btn_opt2.click(lambda u, e: on_option(u, e, 1), [state_uid, state_event], [state_event,
|
| 336 |
-
btn_opt3.click(lambda u, e: on_option(u, e, 2), [state_uid, state_event], [state_event,
|
| 337 |
|
| 338 |
-
def
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
btn_buy_dolar.click(lambda u: on_buy(u, "Dolar"), state_uid, [shop_log, stat_money, stat_sanity, stat_days, inv_mate, gr.Dummy()])
|
| 346 |
-
|
| 347 |
-
def on_drink(uid):
|
| 348 |
-
res = game.use_item(uid, "Mate")
|
| 349 |
-
u = game.get_user(uid)
|
| 350 |
-
m, s, d, i_m, i_d = ui_refresh_hud(u)
|
| 351 |
-
return m, s, inv_mate, res
|
| 352 |
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
|
| 357 |
if __name__ == "__main__":
|
| 358 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 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
|
|
|
|
| 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",
|
|
|
|
| 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 |
|
|
|
|
| 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"])
|
|
|
|
| 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()
|
|
|
|
| 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 |
|
|
|
|
| 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 |
|
|
|
|
| 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):
|
|
|
|
| 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)
|
icon_wine_1764982832885.png
ADDED
|
|
Git LFS Details
|
map_argentina_cyber_1764982818748.png
ADDED
|
Git LFS Details
|