Files
fortschritts-quest/src/engine.py
T
arch_agent cc172d71c5 fix: Log füllt volle Fläche + Quest-Abschluss mit Reise
- max_log 15→30, alle Einträge werden gerendert (nicht nur 12)
- Quest-Abschluss: Belohnung → Reise → Ankunft → neue Quest
  - 'Du machst dich auf den Weg...' → 'Ankunft in: X' → 'Neue Quest'
- 2 Sekunden Pause nach Quest-Abschluss zum Lesen
2026-07-25 20:21:25 +02:00

247 lines
10 KiB
Python

"""Game-Engine: Auto-Progress, Kämpfe, Loot, Quests, Shop."""
import random
import time
from character import Character, Item, RACES, CLASSES
from llm import NameCache
class Monster:
def __init__(self, name: str, level: int, hp: int, attack: int, defense: int, xp: int, gold: int):
self.name = name
self.level = level
self.hp = hp
self.attack = attack
self.defense = defense
self.xp = xp
self.gold = gold
def is_alive(self):
return self.hp > 0
class GameEngine:
def __init__(self, character: Character):
self.character = character
self.cache = NameCache()
self.cache.refill_async()
self.current_location = self.cache.get_location()
self.current_quest = self.cache.get_quest(character.level)
self.quest_progress = 0
self.quest_target = random.randint(3, 8)
self.log = []
self.max_log = 30
def _add_log(self, msg: str):
self.log.insert(0, msg)
if len(self.log) > self.max_log:
self.log = self.log[:self.max_log]
def _spawn_monster(self) -> Monster:
name = self.cache.get_monster(self.character.level, self.current_location)
level = max(1, self.character.level + random.randint(-1, 1))
hp = 10 + level * 5 + random.randint(0, 10)
attack = level * 2 + random.randint(0, 3)
defense = level + random.randint(0, 2)
xp = 5 + level * 3 + random.randint(0, 5)
gold = 1 + level + random.randint(0, 3)
return Monster(name, level, hp, attack, defense, xp, gold)
def _generate_loot(self, monster: Monster) -> Item:
item_types = ["weapon", "armor", "helmet", "boots", "accessory", "junk"]
item_type = random.choices(item_types, weights=[15, 15, 10, 10, 8, 42])[0]
# Rarity based on luck and level
roll = random.randint(1, 100) + self.character.lck
if roll > 95:
rarity = "legendär"
elif roll > 80:
rarity = "episch"
elif roll > 60:
rarity = "selten"
else:
rarity = "gewöhnlich"
name = self.cache.get_item(item_type, self.character.level, rarity)
rarity_mult = {"gewöhnlich": 1, "selten": 1.5, "episch": 2.5, "legendär": 4}
mult = rarity_mult.get(rarity, 1)
base_power = self.character.level + random.randint(0, 3)
power = max(1, int(base_power * mult))
value = max(1, int((monster.gold + 2) * mult * random.uniform(0.8, 1.2)))
return Item(name, item_type, power, value, rarity)
def _combat(self, monster: Monster) -> dict:
"""Führt einen automatischen Kampf durch. Gibt Ergebnis-Dict zurück."""
rounds = 0
while monster.is_alive() and self.character.is_alive():
rounds += 1
# Character attacks
char_dmg = max(1, self.character.attack - monster.defense + random.randint(-2, 2))
char_dmg = min(char_dmg, monster.hp)
monster.hp -= char_dmg
if not monster.is_alive():
break
# Monster attacks
mon_dmg = max(1, monster.attack - self.character.defense + random.randint(-2, 2))
actual = self.character.take_damage(mon_dmg)
if not self.character.is_alive():
return {"result": "death", "rounds": rounds, "monster": monster.name}
return {"result": "victory", "rounds": rounds, "monster": monster.name}
def _complete_quest(self):
self.character.quests_done += 1
quest_gold = 5 + self.character.level * 3 + random.randint(0, 10)
quest_xp = 10 + self.character.level * 5 + random.randint(0, 10)
self.character.gold += quest_gold
leveled = self.character.gain_xp(quest_xp)
self._add_log(f"[bold green]✅ Quest abgeschlossen: {self.current_quest}[/]")
self._add_log(f"[dim]Belohnung: +{quest_gold} Gold, +{quest_xp} XP[/]")
if leveled:
self._add_log(f"[bold yellow]⬆️ Stufe {self.character.level} erreicht![/]")
self.character.heal()
# Travel to new location before new quest
self._add_log(f"[blue]🌍 Du machst dich auf den Weg...[/]")
self.current_location = self.cache.get_location()
self._add_log(f"[blue]🌍 Ankunft in: {self.current_location}[/]")
# New quest
self.current_quest = self.cache.get_quest(self.character.level)
self.quest_progress = 0
self.quest_target = random.randint(3, 8)
self._add_log(f"[cyan]📜 Neue Quest: {self.current_quest}[/]")
self._add_log(f"[dim]Ziel: Besiege {self.quest_target} Gegner[/]")
def step(self) -> dict:
"""Ein Schritt des Auto-Progress. Gibt Event-Info zurück."""
self.character.steps += 1
events = []
# Random: sometimes travel
if random.randint(1, 50) == 1:
self.current_location = self.cache.get_location()
events.append(("travel", f"[blue]🌍 Du reist nach: {self.current_location}[/]"))
self._add_log(events[-1][1])
# Spawn monster
monster = self._spawn_monster()
events.append(("encounter", f"[red]⚔️ {monster.name} (Stufe {monster.level}) erscheint![/]"))
self._add_log(events[-1][1])
# Fight
result = self._combat(monster)
if result["result"] == "victory":
self.character.kills += 1
events.append(("victory", f"[green]⚔️ Besiegst {monster.name} ({result['rounds']} Runden)[/]"))
self._add_log(events[-1][1])
# XP + Gold
self.character.gold += monster.gold
leveled = self.character.gain_xp(monster.xp)
events.append(("loot_xp", f"[dim]+{monster.xp} XP, +{monster.gold} Gold[/]"))
self._add_log(events[-1][1])
if leveled:
self._add_log(f"[bold yellow]⬆️ Stufe {self.character.level} erreicht![/]")
self.character.heal()
events.append(("levelup", f"[bold yellow]⬆️ STUFE {self.character.level}![/]"))
# Loot drop
if random.randint(1, 100) <= 70: # 70% drop chance
item = self._generate_loot(monster)
self._add_log(f"[magenta]📦 Loot: {item}[/]")
events.append(("loot", f"[magenta]📦 {item}[/]", item))
self._auto_equip(item)
else:
# Always some gold
extra_gold = random.randint(1, 3)
self.character.gold += extra_gold
# Quest progress
self.quest_progress += 1
if self.quest_progress >= self.quest_target:
self._complete_quest()
events.append(("quest_done", f"[green]✅ Quest erfüllt![/]", True)) # True = pause flag
elif result["result"] == "death":
self.character.deaths += 1
events.append(("death", f"[bold red]💀 Von {monster.name} besiegt! Respawn...[/]"))
self._add_log(events[-1][1])
# Respawn: lose 10% gold, heal
lost_gold = max(1, self.character.gold // 10)
self.character.gold -= lost_gold
self.character.heal()
self._add_log(f"[red]Verlierst {lost_gold} Gold beim Sterben.[/]")
return {"events": events}
def _auto_equip(self, item: Item):
"""Prüft ob Item besser ist und rüstet es automatisch aus."""
slot = None
if item.item_type in ("weapon", "armor", "helmet", "boots", "accessory"):
slot = item.item_type
if slot:
current = self.character.equipped[slot]
if current is None or item.power > current.power:
if current:
self.character.inventory.append(current)
self.character.equipped[slot] = item
self._add_log(f"[cyan]🎽 Ausgerüstet: {item}[/]")
else:
self.character.inventory.append(item)
else:
self.character.inventory.append(item)
# Auto-sell junk to keep inventory clean
if item.item_type == "junk":
self.character.gold += item.value
if item in self.character.inventory:
self.character.inventory.remove(item)
self._add_log(f"[dim]💰 Verkauft: {item.name} (+{item.value} Gold)[/]")
def _sell_junk(self):
"""Verkauft alle Schrott-Items."""
sold = 0
total_gold = 0
for item in self.character.inventory[:]:
if item.item_type == "junk":
self.character.inventory.remove(item)
self.character.gold += item.value
total_gold += item.value
sold += 1
if sold > 0:
self._add_log(f"[dim]💰 {sold} Schrott verkauft (+{total_gold} Gold)[/]")
def get_state(self) -> dict:
return {
"character": self.character.to_dict(),
"current_location": self.current_location,
"current_quest": self.current_quest,
"quest_progress": self.quest_progress,
"quest_target": self.quest_target,
"log": self.log,
}
def save(self, path: str):
import json
with open(path, "w") as f:
json.dump(self.get_state(), f, ensure_ascii=False, indent=2)
@classmethod
def load(cls, path: str) -> "GameEngine":
import json
with open(path) as f:
d = json.load(f)
char = Character.from_dict(d["character"])
engine = cls(char)
engine.current_location = d.get("current_location", engine.current_location)
engine.current_quest = d.get("current_quest", engine.current_quest)
engine.quest_progress = d.get("quest_progress", 0)
engine.quest_target = d.get("quest_target", 5)
engine.log = d.get("log", [])
return engine