File size: 8,036 Bytes
9bc99d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""AUBIN FPS ölçümü — ViZDoom (Kempka et al., 2016) "defend_the_center": canavarlar her yönden gelir, ajan döner ve ateş eder.
Ölçü = bölüm başına öldürme (kill). Algı: oyun motorunun etiket tamponu (nesne algılayıcısı rolü) → metin gözlem
("düşman, nişangâhın 12° solunda, yakın"); KARAR: AUBIN (AubinController, her hamle tek ileri geçiş). Karşılaştırma:
rastgele ajan ve kural ajanı (en yakın düşmana dön, nişandaysa ateş et) aynı tohumlarla.

    python fps_vizdoom.py --episodes 5 --baseline_episodes 20 --adapter emrevrg/AUBIN-12B --out fps.json
"""
import argparse, json, os, random, subprocess, sys, time


def ensure_vizdoom():
    try:
        import vizdoom  # noqa
    except Exception:
        subprocess.run([sys.executable, "-m", "pip", "-q", "install", "vizdoom"], check=False)
    import vizdoom
    return vizdoom


ACTIONS = {"turn_left": "turn the view left", "turn_right": "turn the view right", "attack": "fire the weapon at the crosshair"}


def make_game(vzd, seed):
    g = vzd.DoomGame()
    g.load_config(os.path.join(vzd.scenarios_path, "defend_the_center.cfg"))
    g.set_screen_resolution(vzd.ScreenResolution.RES_320X240)
    g.set_labels_buffer_enabled(True); g.set_window_visible(False); g.set_seed(seed)
    g.set_mode(vzd.Mode.PLAYER); g.init()
    btn = [str(b).split(".")[-1] for b in g.get_available_buttons()]
    return g, btn


def observe(state, W=320, fov=90.0):
    """Etiketlerden düşmanlar: nişangâha açısal uzaklık (derece, -sol/+sağ), boy (yakınlık), nişanda mı."""
    enemies = []
    for l in state.labels:
        if l.object_name in ("DoomPlayer",) or "Puff" in l.object_name or "Blood" in l.object_name:
            continue
        cx = l.x + l.width / 2
        ang = (cx - W / 2) / W * fov
        enemies.append({"ang": round(ang, 1), "h": int(l.height), "on": l.x <= W / 2 <= l.x + l.width})
    enemies.sort(key=lambda e: (-e["h"], abs(e["ang"])))
    return enemies


def describe(enemies, ammo, health, coarse=False):
    if not enemies:
        s = "No enemy visible."
    else:
        parts = []
        for e in enemies[:4]:
            deg = (round(abs(e["ang"]) / 10) * 10) if coarse else round(abs(e["ang"]))   # kaba açı: bellek benzerliği için
            side = "ON the crosshair" if e["on"] else (f"{deg:.0f} degrees to the {'left' if e['ang'] < 0 else 'right'}")
            dist = "very close" if e["h"] > 90 else ("near" if e["h"] > 45 else "far")
            parts.append(f"enemy {side}, {dist}")
        s = "; ".join(parts) + "."
    return f"{s} Ammo {ammo}, health {health}."


def run(vzd, policy, seed, max_steps=700, mem=None):
    g, btn = make_game(vzd, seed)
    idx = {"turn_left": btn.index("TURN_LEFT"), "turn_right": btn.index("TURN_RIGHT"), "attack": btn.index("ATTACK")}
    g.new_episode(); steps = 0; ms = []
    if mem is not None:
        mem["kills"] = lambda: int(g.get_game_variable(vzd.GameVariable.KILLCOUNT))
    while not g.is_episode_finished() and steps < max_steps:
        st = g.get_state()
        en = observe(st)
        ammo = int(g.get_game_variable(vzd.GameVariable.AMMO2)); hp = int(g.get_game_variable(vzd.GameVariable.HEALTH))
        t0 = time.perf_counter(); a = policy(en, ammo, hp); ms.append((time.perf_counter() - t0) * 1e3)
        act = [0] * len(btn); act[idx[a]] = 1
        g.make_action(act, 4); steps += 1
    kills = int(g.get_game_variable(vzd.GameVariable.KILLCOUNT))
    g.close()
    return {"seed": seed, "kills": kills, "steps": steps, "median_ms": round(sorted(ms)[len(ms) // 2], 1) if ms else 0}


def rule_policy(en, ammo, hp):
    if not en:
        return "turn_left"
    e = en[0]
    if e["on"]:
        return "attack"
    return "turn_left" if e["ang"] < 0 else "turn_right"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--episodes", type=int, default=5); ap.add_argument("--baseline_episodes", type=int, default=20)
    ap.add_argument("--adapter", default="emrevrg/AUBIN-12B"); ap.add_argument("--out", default="fps.json")
    ap.add_argument("--work", default=""); ap.add_argument("--learn", action="store_true")
    a = ap.parse_args()
    vzd = ensure_vizdoom()
    R = {"protocol": __doc__.split("    python")[0].strip(), "scenario": "defend_the_center", "rows": {}}
    rng = random.Random(0)
    for name, pol in () if a.baseline_episodes <= 0 else (("random", lambda e, am, hp: rng.choice(list(ACTIONS))), ("rule: turn to nearest, fire when aimed", rule_policy)):
        eps = [run(vzd, pol, 1000 + i) for i in range(a.baseline_episodes)]
        R["rows"][name] = {"episodes": len(eps), "mean_kills": round(sum(e["kills"] for e in eps) / len(eps), 2),
                           "max_kills": max(e["kills"] for e in eps), "per_episode": eps}
        print(name, R["rows"][name]["mean_kills"], flush=True)
        json.dump(R, open(a.out, "w"), indent=1)
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from aubin import Aubin, AubinController
    ctl = AubinController(Aubin(a.adapter), actions=ACTIONS,
                          instructions="You are playing a first-person shooter. Enemies approach from all sides. "
                                       "Turn toward the closest enemy and fire only when an enemy is ON the crosshair; do not waste ammo.")

    def aubin_policy(en, ammo, hp):
        return ctl.act(describe(en, ammo, hp), command="kill the enemies and survive")["action"]
    tag = f"AUBIN ({a.adapter})"
    if a.learn:                                        # deneyerek öğrenme: ilerleme getiren hamleler anında belleğe
        from aubin import AubinLearning
        smart = AubinLearning(ctl.m, min_sim=0.95)        # yalnız aynı oyun durumu oy verir
        # bellek anahtarı = oyunun ayrık durumu (metnin karma vektörü sol/sağ ayrımını kaçırıyordu → tek hamleye çöküyordu)
        smart._text = lambda state, q: state.get("situation", "")
        Q = {"action": {"type": "choice", "criteria": ACTIONS, "instructions": "Command: kill the enemies and survive. " + ctl.instructions}}
        mem = {"prev": None}

        def aubin_policy(en, ammo, hp):
            e0 = en[0] if en else None
            sit = ("none" if e0 is None else ("on_crosshair" if e0["on"] else ("left" if e0["ang"] < 0 else "right"))
                   + "_" + ("far" if e0["h"] <= 45 else "near")) + ("_noammo" if ammo <= 0 else "")
            st = {"observation": describe(en, ammo, hp), "situation": f"situation {sit}"}
            if mem["prev"] is not None:                   # önceki hamlenin sonucu: ilerleme varsa o hamle doğrudur
                pst, pa, pen, pk = mem["prev"]
                kills = mem["kills"]()
                near0 = abs(pen[0]["ang"]) if pen else None; near1 = abs(en[0]["ang"]) if en else None
                good = (pa == "attack" and kills > pk) or (pa != "attack" and near0 is not None and near1 is not None
                                                         and (near1 < near0 - 1 or (en and en[0]["on"])))
                if good:
                    smart.learn(pst, Q, {"action": pa})
            out = smart.decide(st, Q)["action"]["answer"]
            mem["prev"] = (st, out, en, mem["kills"]())
            return out
        tag += " + AUBIN-Learn (online, memory persists across episodes)"
    eps = []
    for i in range(a.episodes):
        ctl.reset(); mem_reset = locals().get("mem")
        if mem_reset is not None:
            mem_reset["prev"] = None
        eps.append(run(vzd, aubin_policy, 1000 + i, mem=locals().get("mem"))); print("AUBIN bölüm", eps[-1], flush=True)
        R["rows"][tag] = {"episodes": len(eps), "mean_kills": round(sum(e["kills"] for e in eps) / len(eps), 2),
                                             "max_kills": max(e["kills"] for e in eps), "per_episode": eps}
        json.dump(R, open(a.out, "w"), indent=1)
    print("SONUC", json.dumps({k: v["mean_kills"] for k, v in R["rows"].items()}), flush=True)


if __name__ == "__main__":
    main()