Update app.py
Browse files
app.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
| 1 |
-
# app.py
|
| 2 |
import gradio as gr
|
| 3 |
import random
|
| 4 |
import time
|
| 5 |
|
| 6 |
-
# ----------------
|
| 7 |
MONSTERS = [
|
| 8 |
{"id":"slime","name":"Slime","base_hp":30,"xp":10,"gold":5, "arise":0.7},
|
| 9 |
{"id":"goblin","name":"Goblin","base_hp":45,"xp":18,"gold":12, "arise":0.5},
|
|
@@ -27,13 +26,11 @@ CARDS_POOL = [
|
|
| 27 |
{"name":"Mana Bloom","desc":"Tăng mana regen","apply": lambda s: s.update({"mana_regen": s.get("mana_regen",0.0)+0.5})},
|
| 28 |
]
|
| 29 |
|
| 30 |
-
# ----------------
|
| 31 |
def choose_monster_for_floor(floor):
|
| 32 |
-
"""Scale monster by floor: higher floor -> rarer monsters"""
|
| 33 |
pool = []
|
| 34 |
for m in MONSTERS:
|
| 35 |
weight = 1.0
|
| 36 |
-
# basic scaling: dragons/kings rarer but appear at high floors
|
| 37 |
if m["id"] in ("slime","goblin"):
|
| 38 |
weight = max(1.0, 6 - floor//2)
|
| 39 |
else:
|
|
@@ -50,35 +47,29 @@ def choose_monster_for_floor(floor):
|
|
| 50 |
|
| 51 |
def fmt_status(state):
|
| 52 |
p = state["player"]
|
| 53 |
-
|
| 54 |
-
return s
|
| 55 |
|
| 56 |
def add_log(state, text):
|
| 57 |
state["log"].append(f"[{time.strftime('%H:%M:%S')}] {text}")
|
| 58 |
-
if len(state["log"])>200:
|
|
|
|
| 59 |
|
| 60 |
-
# ---------------- Initialize state ----------------
|
| 61 |
def default_state():
|
| 62 |
return {
|
| 63 |
-
"player":{
|
| 64 |
"level":1, "xp":0, "hp_max":100, "hp":100, "attack":10, "gold":50,
|
| 65 |
"hp_regen":0.0, "mana":50, "mana_max":50,
|
| 66 |
"xp_needed":100, "skills":[], "pet": None
|
| 67 |
},
|
| 68 |
-
"dungeon": {
|
| 69 |
-
|
| 70 |
-
"dungeon_name": None,
|
| 71 |
-
"floor": 0,
|
| 72 |
-
"monster": None,
|
| 73 |
-
},
|
| 74 |
-
"farm": {
|
| 75 |
-
"plots": [], # each: {"seed_id":..., "ready_at":time_offset, "mutated":False, "quality":...}
|
| 76 |
-
},
|
| 77 |
"shop": {"seeds": SEEDS},
|
| 78 |
"log": [],
|
|
|
|
|
|
|
| 79 |
}
|
| 80 |
|
| 81 |
-
# ---------------- Game
|
| 82 |
def start_dungeon(state, dungeon_name):
|
| 83 |
state["dungeon"]["in_dungeon"] = True
|
| 84 |
state["dungeon"]["dungeon_name"] = dungeon_name
|
|
@@ -91,48 +82,34 @@ def start_dungeon(state, dungeon_name):
|
|
| 91 |
|
| 92 |
def attack(state):
|
| 93 |
if not state["dungeon"]["in_dungeon"]:
|
| 94 |
-
add_log(state, "Bạn chưa vào phó bản.
|
| 95 |
return fmt_status(state), "\n".join(state["log"][-10:])
|
| 96 |
m = state["dungeon"]["monster"]
|
| 97 |
p = state["player"]
|
| 98 |
-
|
| 99 |
-
pet_bonus = 0
|
| 100 |
-
if p["pet"]:
|
| 101 |
-
pet_bonus = p["pet"]["attack"]//2
|
| 102 |
dmg = random.randint(max(1,p["attack"]-3), p["attack"]+3) + pet_bonus
|
| 103 |
m["hp"] -= dmg
|
| 104 |
-
add_log(state, f"
|
| 105 |
if m["hp"] <= 0:
|
| 106 |
xp = m["xp"] + state["dungeon"]["floor"]*2
|
| 107 |
gold = m["gold"] + state["dungeon"]["floor"]
|
| 108 |
p["xp"] += xp
|
| 109 |
p["gold"] += gold
|
| 110 |
-
add_log(state, f"{m['name']} bị hạ! +{xp} XP, +{gold}
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
pet = {"name": f"Pet {m['name']}", "attack": max(3, m["base_hp"]//10), "tier":"common"}
|
| 116 |
-
p["pet"] = pet
|
| 117 |
-
add_log(state, f"✨ {m['name']} đã arise! Trở thành pet của bạn: {pet['name']}")
|
| 118 |
-
# next floor
|
| 119 |
if state["dungeon"]["floor"] >= 10:
|
| 120 |
-
# finished dungeon
|
| 121 |
add_log(state, f"Hoàn thành phó bản {state['dungeon']['dungeon_name']}! Về thị trấn.")
|
| 122 |
-
state["dungeon"]
|
| 123 |
-
state["dungeon"]["monster"] = None
|
| 124 |
-
state["dungeon"]["floor"] = 0
|
| 125 |
-
# back to town: minor heal
|
| 126 |
p["hp"] = min(p["hp_max"], p["hp"] + 20)
|
| 127 |
else:
|
| 128 |
-
# go to next floor
|
| 129 |
state["dungeon"]["floor"] += 1
|
| 130 |
m2 = choose_monster_for_floor(state["dungeon"]["floor"])
|
| 131 |
-
m2["hp"] = m2["base_hp"] +
|
| 132 |
state["dungeon"]["monster"] = m2
|
| 133 |
-
add_log(state, f"
|
| 134 |
-
# check level up
|
| 135 |
-
level_up_msgs = ""
|
| 136 |
while p["xp"] >= p["xp_needed"]:
|
| 137 |
p["xp"] -= p["xp_needed"]
|
| 138 |
p["level"] += 1
|
|
@@ -140,189 +117,20 @@ def attack(state):
|
|
| 140 |
p["attack"] += 2
|
| 141 |
p["hp"] = p["hp_max"]
|
| 142 |
p["xp_needed"] = int(p["xp_needed"] * 1.4)
|
| 143 |
-
add_log(state, f"⭐
|
| 144 |
-
level_up_msgs += "LEVEL_UP\n"
|
| 145 |
return fmt_status(state), "\n".join(state["log"][-12:])
|
| 146 |
else:
|
| 147 |
-
|
| 148 |
-
mdmg = random.randint(max(1, m["xp"]//6), max(1, m["xp"]//4))
|
| 149 |
-
# small scale with floor
|
| 150 |
-
mdmg = int(mdmg + state["dungeon"]["floor"]/2)
|
| 151 |
p["hp"] -= mdmg
|
| 152 |
add_log(state, f"{m['name']} phản công gây {mdmg} sát thương!")
|
| 153 |
if p["hp"] <= 0:
|
| 154 |
-
add_log(state, "⚠️ Bạn đã tử vong!
|
| 155 |
p["hp"] = int(p["hp_max"]*0.6)
|
| 156 |
p["gold"] = max(0, int(p["gold"]*0.9))
|
| 157 |
-
state["dungeon"]
|
| 158 |
-
state["dungeon"]["monster"] = None
|
| 159 |
-
state["dungeon"]["floor"] = 0
|
| 160 |
return fmt_status(state), "\n".join(state["log"][-12:])
|
| 161 |
|
| 162 |
def choose_card_flow(state):
|
| 163 |
-
# produce 3 random cards (copies)
|
| 164 |
pick = random.sample(CARDS_POOL, 3)
|
| 165 |
state["pending_cards"] = pick
|
| 166 |
-
add_log(state, "
|
| 167 |
-
return [f"{c['name']}: {c['desc']}" for c in pick], "\n".join(state["log"][-8:])
|
| 168 |
-
|
| 169 |
-
def apply_card(state, idx):
|
| 170 |
-
pick = state.get("pending_cards")
|
| 171 |
-
if not pick or idx<0 or idx>=len(pick):
|
| 172 |
-
return "Không có thẻ để chọn.", "\n".join(state["log"][-6:])
|
| 173 |
-
card = pick[idx]
|
| 174 |
-
# apply card effect by calling its lambda (we store callable in pool)
|
| 175 |
-
# Note: CARDS_POOL entries have apply: lambda s: s.update(...)
|
| 176 |
-
# Here pick's item is same object
|
| 177 |
-
try:
|
| 178 |
-
card["apply"](state["player"])
|
| 179 |
-
state["player"]["skills"].append(card["name"])
|
| 180 |
-
except Exception as e:
|
| 181 |
-
add_log(state, f"Lỗi khi áp dụng thẻ: {e}")
|
| 182 |
-
add_log(state, f"Bạn chọn thẻ: {card['name']}. Hiệu ứng đã áp dụng.")
|
| 183 |
-
state["pending_cards"] = []
|
| 184 |
-
return fmt_status(state), "\n".join(state["log"][-8:])
|
| 185 |
-
|
| 186 |
-
# ---------------- Farming ----------------
|
| 187 |
-
def plant_seed(state, seed_id):
|
| 188 |
-
seeds = state["shop"]["seeds"]
|
| 189 |
-
s = next((x for x in seeds if x["id"]==seed_id), None)
|
| 190 |
-
if not s:
|
| 191 |
-
return "Hạt giống không hợp lệ.", "\n".join(state["log"][-8:])
|
| 192 |
-
player = state["player"]
|
| 193 |
-
if player["gold"] < s["cost"]:
|
| 194 |
-
return "Không đủ vàng để mua hạt.", "\n".join(state["log"][-8:])
|
| 195 |
-
player["gold"] -= s["cost"]
|
| 196 |
-
# simulate grow_time by storing a counter (we'll use integer tick simulation)
|
| 197 |
-
plot = {"seed_id": seed_id, "seed_name": s["name"], "grow_time": s["grow_time"], "remaining": s["grow_time"], "mutated": False, "quality":1}
|
| 198 |
-
state["farm"]["plots"].append(plot)
|
| 199 |
-
add_log(state, f"Mua và trồng {s['name']}. Thời gian chờ: {s['grow_time']} tick.")
|
| 200 |
-
return f"Trồng {s['name']} thành công.", "\n".join(state["log"][-8:])
|
| 201 |
-
|
| 202 |
-
def tick_grow(state):
|
| 203 |
-
# called when user 'waits' or acts: reduce remaining for each plot
|
| 204 |
-
for p in state["farm"]["plots"]:
|
| 205 |
-
if p["remaining"]>0:
|
| 206 |
-
p["remaining"] -= 1
|
| 207 |
-
if p["remaining"]<=0:
|
| 208 |
-
# finish, determine mutation by seed base mut_rate
|
| 209 |
-
seed = next((x for x in SEEDS if x["name"]==p["seed_name"] or x["id"]==p["seed_id"]), None)
|
| 210 |
-
base = seed["mut_rate"] if seed else 0.05
|
| 211 |
-
if random.random() < base:
|
| 212 |
-
p["mutated"] = True
|
| 213 |
-
p["quality"] = random.randint(2,4)
|
| 214 |
-
add_log(state, f"🌱 Cây {p['seed_name']} biến dị! Chất lượng {p['quality']}.")
|
| 215 |
-
else:
|
| 216 |
-
p["mutated"] = False
|
| 217 |
-
p["quality"] = 1
|
| 218 |
-
add_log(state, f"🌿 Cây {p['seed_name']} thu hoạch bình thường.")
|
| 219 |
-
return "Đã tiến hành 1 tick thời gian.", "\n".join(state["log"][-8:])
|
| 220 |
-
|
| 221 |
-
def harvest_plot(state, idx):
|
| 222 |
-
if idx<0 or idx>=len(state["farm"]["plots"]):
|
| 223 |
-
return "Không có ô trồng này.", "\n".join(state["log"][-8:])
|
| 224 |
-
plot = state["farm"]["plots"].pop(idx)
|
| 225 |
-
reward_xp = 5 * plot["quality"]
|
| 226 |
-
# mutated gives special item (mutation fruit)
|
| 227 |
-
if plot["mutated"]:
|
| 228 |
-
item = {"type":"mutation","power":plot["quality"], "name":f"Mutation {plot['seed_name']}"}
|
| 229 |
-
state.setdefault("inventory", []).append(item)
|
| 230 |
-
add_log(state, f"Thu hoạch: {plot['seed_name']} (Mutation!). Đã nhận 1 Mutation.")
|
| 231 |
-
else:
|
| 232 |
-
state["player"]["gold"] += 8*plot["quality"]
|
| 233 |
-
add_log(state, f"Thu hoạch: {plot['seed_name']}. Nhận {8*plot['quality']} vàng.")
|
| 234 |
-
state["player"]["xp"] += reward_xp
|
| 235 |
-
add_log(state, f"Nhận thêm +{reward_xp} XP từ thu hoạch.")
|
| 236 |
-
return fmt_status(state), "\n".join(state["log"][-10:])
|
| 237 |
-
|
| 238 |
-
def feed_mutation(state, inv_idx):
|
| 239 |
-
inv = state.get("inventory", [])
|
| 240 |
-
if inv_idx<0 or inv_idx>=len(inv):
|
| 241 |
-
return "Không có item này.", "\n".join(state["log"][-8:])
|
| 242 |
-
item = inv.pop(inv_idx)
|
| 243 |
-
if item["type"]!="mutation":
|
| 244 |
-
return "Chỉ có Mutation mới cho ăn.", "\n".join(state["log"][-8:])
|
| 245 |
-
# feeding: add XP scaled by power and chance to learn skill
|
| 246 |
-
gain_xp = item["power"] * 25
|
| 247 |
-
state["player"]["xp"] += gain_xp
|
| 248 |
-
learned = False
|
| 249 |
-
if random.random() < 0.25 * item["power"]:
|
| 250 |
-
new_skill = f"MutSkill_{random.randint(100,999)}"
|
| 251 |
-
state["player"]["skills"].append(new_skill)
|
| 252 |
-
learned = True
|
| 253 |
-
add_log(state, f"🎉 Nhân vật học được kỹ năng mới từ mutation: {new_skill}!")
|
| 254 |
-
add_log(state, f"Bạn cho nhân vật ăn {item['name']} -> +{gain_xp} XP. {'(Learned skill!)' if learned else ''}")
|
| 255 |
-
return fmt_status(state), "\n".join(state["log"][-8:])
|
| 256 |
-
|
| 257 |
-
# ---------------- Shop ----------------
|
| 258 |
-
def buy_seed(state, seed_id):
|
| 259 |
-
s = next((x for x in SEEDS if x["id"]==seed_id), None)
|
| 260 |
-
if not s:
|
| 261 |
-
return "Hạt không hợp lệ.", "\n".join(state["log"][-6:])
|
| 262 |
-
if state["player"]["gold"] < s["cost"]:
|
| 263 |
-
return "Không đủ vàng.", "\n".join(state["log"][-6:])
|
| 264 |
-
state["player"]["gold"] -= s["cost"]
|
| 265 |
-
state.setdefault("seed_bag", []).append(s)
|
| 266 |
-
add_log(state, f"Mua {s['name']} x1.")
|
| 267 |
-
return f"Mua {s['name']} thành công.", "\n".join(state["log"][-6:])
|
| 268 |
-
|
| 269 |
-
# ---------------- UI / Gradio ----------------
|
| 270 |
-
with gr.Blocks(title="Adias: Arise & Mutation (Demo)") as demo:
|
| 271 |
-
gr.Markdown("## 🏞️ Adias — Arise & Mutation (Mini demo)")
|
| 272 |
-
# state
|
| 273 |
-
state = gr.State(default_state())
|
| 274 |
-
|
| 275 |
-
col1 = gr.Column(scale=2)
|
| 276 |
-
col2 = gr.Column(scale=1)
|
| 277 |
-
|
| 278 |
-
with gr.Row():
|
| 279 |
-
with col1:
|
| 280 |
-
gr.Markdown("### 🔹 Thị Trấn (Menu)")
|
| 281 |
-
dungeon_select = gr.Dropdown([ "Slime Cave","Goblin Den","Elf Forest","Dragon Lair","King's Trial" ], label="Chọn phó bản")
|
| 282 |
-
start_btn = gr.Button("🗡️ Vào phó bản")
|
| 283 |
-
attack_btn = gr.Button("⚔️ Tấn công (Trong dungeon)")
|
| 284 |
-
tick_btn = gr.Button("⏳ Thời gian trôi 1 tick (farm)")
|
| 285 |
-
harvest_btn = gr.Button("🌾 Thu hoạch ô đầu")
|
| 286 |
-
cards_btn = gr.Button("🃏 Lấy 3 thẻ bài (nếu có lvl up)")
|
| 287 |
-
buy_seed_btn = gr.Button("🛒 Mua Hạt Thường (10 gold)")
|
| 288 |
-
buy_spark_btn = gr.Button("🛒 Mua Hạt Sét (30 gold)")
|
| 289 |
-
buy_glow_btn = gr.Button("🛒 Mua Hạt Lấp Lánh (60 gold)")
|
| 290 |
-
feed_btn = gr.Button("🍎 Cho ăn Mutation (ô 0 trong inventory)")
|
| 291 |
-
|
| 292 |
-
status_box = gr.Textbox(label="Trạng thái", lines=1, interactive=False)
|
| 293 |
-
log_box = gr.Textbox(label="Log", lines=12, interactive=False)
|
| 294 |
-
inv_box = gr.Textbox(label="Inventory (index: item)", lines=3, interactive=False)
|
| 295 |
-
|
| 296 |
-
with col2:
|
| 297 |
-
gr.Markdown("### 🧾 Hồ sơ nhân vật")
|
| 298 |
-
panel = gr.HTML("<div id='panel'></div>")
|
| 299 |
-
stats_box = gr.Textbox(label="Thông tin nhân vật", lines=8, interactive=False)
|
| 300 |
-
|
| 301 |
-
# callbacks
|
| 302 |
-
def refresh_ui(state):
|
| 303 |
-
st = fmt_status(state)
|
| 304 |
-
status = st
|
| 305 |
-
logs = "\n".join(state["log"][-12:])
|
| 306 |
-
inv = state.get("inventory", [])
|
| 307 |
-
inv_text = "\n".join([f"{i}. {it['name']} (power {it.get('power')})" for i,it in enumerate(inv)]) if inv else "Rỗng"
|
| 308 |
-
stats = f"Cấp: {state['player']['level']}\nHP: {state['player']['hp']}/{state['player']['hp_max']}\nATK: {state['player']['attack']}\nGold: {state['player']['gold']}\nSkills: {', '.join(state['player']['skills']) if state['player']['skills'] else 'None'}\nPet: {state['player']['pet']['name'] if state['player']['pet'] else 'Không'}"
|
| 309 |
-
return status, logs, inv_text, stats
|
| 310 |
-
|
| 311 |
-
start_btn.click(lambda s, d: start_dungeon(s, d), inputs=[state, dungeon_select], outputs=[status_box, log_box])
|
| 312 |
-
attack_btn.click(lambda s: attack(s), inputs=[state], outputs=[status_box, log_box])
|
| 313 |
-
tick_btn.click(lambda s: tick_grow(s), inputs=[state], outputs=[status_box, log_box])
|
| 314 |
-
harvest_btn.click(lambda s: harvest_plot(s, 0), inputs=[state], outputs=[status_box, log_box])
|
| 315 |
-
cards_btn.click(lambda s: choose_card_flow(s), inputs=[state], outputs=[gr.Dropdown.update(choices=[]), log_box]) # placeholder
|
| 316 |
-
buy_seed_btn.click(lambda s: buy_seed(s, "basic"), inputs=[state], outputs=[log_box, status_box])
|
| 317 |
-
buy_spark_btn.click(lambda s: buy_seed(s, "spark"), inputs=[state], outputs=[log_box, status_box])
|
| 318 |
-
buy_glow_btn.click(lambda s: buy_seed(s, "glow"), inputs=[state], outputs=[log_box, status_box])
|
| 319 |
-
feed_btn.click(lambda s: feed_mutation(s, 0), inputs=[state], outputs=[status_box, log_box])
|
| 320 |
-
|
| 321 |
-
# small refresh button to show state
|
| 322 |
-
refresh = gr.Button("🔄 Refresh UI")
|
| 323 |
-
refresh.click(lambda s: refresh_ui(s), inputs=[state], outputs=[status_box, log_box, inv_box, stats_box])
|
| 324 |
-
|
| 325 |
-
# initial fill
|
| 326 |
-
demo.load(lambda s: refresh_ui(s), inputs=[state], outputs=[status_box, log_box, inv_box, stats_box])
|
| 327 |
-
|
| 328 |
-
demo.launch()
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import random
|
| 3 |
import time
|
| 4 |
|
| 5 |
+
# ---------------- Cấu hình dữ liệu ----------------
|
| 6 |
MONSTERS = [
|
| 7 |
{"id":"slime","name":"Slime","base_hp":30,"xp":10,"gold":5, "arise":0.7},
|
| 8 |
{"id":"goblin","name":"Goblin","base_hp":45,"xp":18,"gold":12, "arise":0.5},
|
|
|
|
| 26 |
{"name":"Mana Bloom","desc":"Tăng mana regen","apply": lambda s: s.update({"mana_regen": s.get("mana_regen",0.0)+0.5})},
|
| 27 |
]
|
| 28 |
|
| 29 |
+
# ---------------- Hàm phụ trợ ----------------
|
| 30 |
def choose_monster_for_floor(floor):
|
|
|
|
| 31 |
pool = []
|
| 32 |
for m in MONSTERS:
|
| 33 |
weight = 1.0
|
|
|
|
| 34 |
if m["id"] in ("slime","goblin"):
|
| 35 |
weight = max(1.0, 6 - floor//2)
|
| 36 |
else:
|
|
|
|
| 47 |
|
| 48 |
def fmt_status(state):
|
| 49 |
p = state["player"]
|
| 50 |
+
return f"Cấp {p['level']} · HP {p['hp']}/{p['hp_max']} · XP {p['xp']}/{p['xp_needed']} · Vàng {p['gold']} · Pet: {p['pet']['name'] if p['pet'] else 'Không'}"
|
|
|
|
| 51 |
|
| 52 |
def add_log(state, text):
|
| 53 |
state["log"].append(f"[{time.strftime('%H:%M:%S')}] {text}")
|
| 54 |
+
if len(state["log"]) > 200:
|
| 55 |
+
state["log"].pop(0)
|
| 56 |
|
|
|
|
| 57 |
def default_state():
|
| 58 |
return {
|
| 59 |
+
"player": {
|
| 60 |
"level":1, "xp":0, "hp_max":100, "hp":100, "attack":10, "gold":50,
|
| 61 |
"hp_regen":0.0, "mana":50, "mana_max":50,
|
| 62 |
"xp_needed":100, "skills":[], "pet": None
|
| 63 |
},
|
| 64 |
+
"dungeon": {"in_dungeon": False, "dungeon_name": None, "floor": 0, "monster": None},
|
| 65 |
+
"farm": {"plots": []},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
"shop": {"seeds": SEEDS},
|
| 67 |
"log": [],
|
| 68 |
+
"inventory": [],
|
| 69 |
+
"pending_cards": []
|
| 70 |
}
|
| 71 |
|
| 72 |
+
# ---------------- Game logic ----------------
|
| 73 |
def start_dungeon(state, dungeon_name):
|
| 74 |
state["dungeon"]["in_dungeon"] = True
|
| 75 |
state["dungeon"]["dungeon_name"] = dungeon_name
|
|
|
|
| 82 |
|
| 83 |
def attack(state):
|
| 84 |
if not state["dungeon"]["in_dungeon"]:
|
| 85 |
+
add_log(state, "Bạn chưa vào phó bản.")
|
| 86 |
return fmt_status(state), "\n".join(state["log"][-10:])
|
| 87 |
m = state["dungeon"]["monster"]
|
| 88 |
p = state["player"]
|
| 89 |
+
pet_bonus = p["pet"]["attack"]//2 if p["pet"] else 0
|
|
|
|
|
|
|
|
|
|
| 90 |
dmg = random.randint(max(1,p["attack"]-3), p["attack"]+3) + pet_bonus
|
| 91 |
m["hp"] -= dmg
|
| 92 |
+
add_log(state, f"Tấn công {m['name']} gây {dmg} sát thương. (Pet +{pet_bonus})")
|
| 93 |
if m["hp"] <= 0:
|
| 94 |
xp = m["xp"] + state["dungeon"]["floor"]*2
|
| 95 |
gold = m["gold"] + state["dungeon"]["floor"]
|
| 96 |
p["xp"] += xp
|
| 97 |
p["gold"] += gold
|
| 98 |
+
add_log(state, f"{m['name']} bị hạ! +{xp} XP, +{gold} vàng.")
|
| 99 |
+
if state["dungeon"]["floor"] == 10 and random.random() < m.get("arise", 0.0):
|
| 100 |
+
pet = {"name": f"Pet {m['name']}", "attack": max(3, m["base_hp"]//10), "tier":"common"}
|
| 101 |
+
p["pet"] = pet
|
| 102 |
+
add_log(state, f"✨ {m['name']} đã arise! Trở thành pet: {pet['name']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
if state["dungeon"]["floor"] >= 10:
|
|
|
|
| 104 |
add_log(state, f"Hoàn thành phó bản {state['dungeon']['dungeon_name']}! Về thị trấn.")
|
| 105 |
+
state["dungeon"] = {"in_dungeon": False, "dungeon_name": None, "floor": 0, "monster": None}
|
|
|
|
|
|
|
|
|
|
| 106 |
p["hp"] = min(p["hp_max"], p["hp"] + 20)
|
| 107 |
else:
|
|
|
|
| 108 |
state["dungeon"]["floor"] += 1
|
| 109 |
m2 = choose_monster_for_floor(state["dungeon"]["floor"])
|
| 110 |
+
m2["hp"] = m2["base_hp"] + p["level"]*3 + state["dungeon"]["floor"]*2
|
| 111 |
state["dungeon"]["monster"] = m2
|
| 112 |
+
add_log(state, f"Tầng {state['dungeon']['floor']}: Quái {m2['name']}")
|
|
|
|
|
|
|
| 113 |
while p["xp"] >= p["xp_needed"]:
|
| 114 |
p["xp"] -= p["xp_needed"]
|
| 115 |
p["level"] += 1
|
|
|
|
| 117 |
p["attack"] += 2
|
| 118 |
p["hp"] = p["hp_max"]
|
| 119 |
p["xp_needed"] = int(p["xp_needed"] * 1.4)
|
| 120 |
+
add_log(state, f"⭐ Lên cấp {p['level']}! Chọn 1 thẻ bài.")
|
|
|
|
| 121 |
return fmt_status(state), "\n".join(state["log"][-12:])
|
| 122 |
else:
|
| 123 |
+
mdmg = random.randint(max(1, m["xp"]//6), max(1, m["xp"]//4)) + int(state["dungeon"]["floor"]/2)
|
|
|
|
|
|
|
|
|
|
| 124 |
p["hp"] -= mdmg
|
| 125 |
add_log(state, f"{m['name']} phản công gây {mdmg} sát thương!")
|
| 126 |
if p["hp"] <= 0:
|
| 127 |
+
add_log(state, "⚠️ Bạn đã tử vong! Mất 10% vàng.")
|
| 128 |
p["hp"] = int(p["hp_max"]*0.6)
|
| 129 |
p["gold"] = max(0, int(p["gold"]*0.9))
|
| 130 |
+
state["dungeon"] = {"in_dungeon": False, "dungeon_name": None, "floor": 0, "monster": None}
|
|
|
|
|
|
|
| 131 |
return fmt_status(state), "\n".join(state["log"][-12:])
|
| 132 |
|
| 133 |
def choose_card_flow(state):
|
|
|
|
| 134 |
pick = random.sample(CARDS_POOL, 3)
|
| 135 |
state["pending_cards"] = pick
|
| 136 |
+
add_log(state, "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|