# 프로 운영 레시피

> 고수의 강점은 대부분 수십 가지 "작은 습관"에서 나옵니다. 이 페이지는 흔히 쓰는 프로 운영을 하나씩 SDK 코드로 옮겼으며, 각 코드는 그대로 on_tick에 붙여 넣을 수 있습니다.

출처: https://war3ai.com/ko/docs/cookbook/

약속: `g`는 `Game`, `home`은 아군 본진(`g.my_buildings({"htow", "hkee", "hcas"})[0]`), `now = g.clock()`입니다. API 세부 사항은 [API 목록](https://war3ai.com/ko/api/)을, 완전히 실행 가능한 예제는 [예제 Bot](https://war3ai.com/ko/docs/examples/)을 참고하세요.

> **팁**
>
> LLM에게 어떤 운영을 추가하게 할 때는 "좀 더 프로처럼 해 줘"라고 설명하는 것보다, 해당 항목을 코드와 함께 붙여 넣는 편이 훨씬 효과적입니다.

## 가. 운영

### 1. 농부는 절대 놀지 않고, 금광당 5명

```python
for w in g.idle_workers():                                   # 놀고 있는 일꾼에게만(일하는 일꾼에게 다시 명령하면 채집이 끊김)
    mine = g.nearest([m for m in g.gold_mines() if crew[m.addr] < 5], w)
    g.gather(w, mine) if mine else g.gather(w, g.trees(w.x, w.y, limit=1)[0])
```

금광마다 몇 명을 보냈는지(`crew`)는 직접 기록하세요. 금광에 들어간 일꾼은 스냅샷에 없습니다. 전체 예제는 `hello_bot.py`에 있습니다.

### 2. 대기열에는 1개만, 돈이 묶이지 않게

```python
for b in g.my_buildings({"hbar"}):
    if not g.queue(b):                                       # 비었을 때만 다음 것을 넣음
        g.train(b, "hfoo")
```

### 3. 인구가 절대 막히지 않게

```python
stuck = any(p.blocked for _b, p in g.all_production("me"))  # 대기열은 있는데 시작 못 함 = 인구 부족
res = g.resources()
if stuck or res["food_cap"] - res["food_used"] <= 6:
    g.build_near(builder, "hhou", home.x, home.y)
```

`blocked`는 "거의 찼다"보다 한발 빠릅니다. 교전에서 병력을 한꺼번에 잃고 다시 보충할 때 대기열이 막히는 순간 바로 알 수 있습니다.

### 4. 빌드 순서 + 다 지으면 알아서 채광 복귀(Shift 복귀)

```python
spot = g.build_near(w, "hbar", home.x, home.y)
if spot:
    g.gather(w, mine, queue="after")                         # 다 지으면 채광으로 복귀, 다음 틱에 다시 찾을 필요 없음
```

농부 한 명이 여러 채를 연달아 짓기: `g.build_queue(w, [("hhou", x1, y1), ("hhou", x2, y2)])`. 비용은 착공할 때 차감됩니다.

### 5. 테크 업 타이밍, 공격/방어 업그레이드

```python
if not g.queue(hall) and g.can_do(hall, "hkee") in (0, 220):   # 본진 건물 대기열이 비어야 업그레이드 가능(아니면 185)
    g.upgrade(hall, "hkee")
p = g.production(hall)                                          # 테크 업 진행도
if p and p.kind == "upgrade":
    print(f"성채까지 {p.remaining:.0f}초 남음")
for sm in g.my_buildings({"hbla"}):
    if not g.queue(sm):
        ok = [u for u, v in zip(UPS, g.can_do_many([(sm, u) for u in UPS])) if v in (0, 220)]
        if ok:
            g.research(sm, ok[0])
```

### 6. 확장: 걸어가는 거리로 가장 가까운 금광 고르기

```python
mines = [m for m in g.gold_mines() if g.dist(m, home) > 1500 and not taken(m)]
best = min(mines, key=lambda m: g.path_distance(home, m) or 1e9)   # 섬의 금광은 None 반환 -> 맨 뒤로
```

## 나. 정찰과 정보

### 7. 상대가 뭘 하는지 보기

```python
for b, p in g.all_production("enemy"):          # 보이는 상대 건물이 무엇을 훈련 / 연구 / 업그레이드하는지
    print(b.type, p.kind, p.queue, f"{p.progress:.0%}" if p.progress is not None else "막힘")
```

이벤트와 함께 쓰기: `ev.kind == "production.done" and ev.owner != g.me()` — 상대가 방금 무엇을 뽑았는지.

### 8. 본 것을 기억하기(전장의 안개)

```python
for u, t, age in g.last_seen(max_age=60):       # 60 게임 초 안에 본 적(마지막 위치와 체력)
    ...
hero_seen = [r for r in g.last_seen() if r[0].is_hero]   # 상대 영웅을 마지막으로 본 곳
```

공정 모드에서는 이것이 상대에 대한 유일한 정보원입니다. 사람 플레이어와 똑같습니다.

### 9. 컴퓨터 상대가 어디를 치려는지(컴퓨터 AI에만 유효)

```python
plan = g.enemy_ai_plan(some_enemy_soldier)      # 이 유닛의 컴퓨터 대장이 가려는 곳
```

컴퓨터는 출발하기 전에 목표 지점을 정해 둡니다 — 미리 그곳으로 병력을 데려가세요.

## 다. 사냥

### 10. 밤 사냥

```python
if g.is_night():                                 # 18시 ~ 6시: 크립이 잠듦(먼저 쳐도 포위당하지 않음), 모든 유닛의 시야가 짧아짐
    ...
wait = g.seconds_until(18)                       # 해가 지기까지 남은 게임 초(하루 480초)
```

### 11. 이길 수 있는 캠프만 치기

```python
from openwar3 import combat
mine = [g.stats(u) for u in army]
def ttk(target): return combat.time_to_kill(mine, g.stats(target), target_hp=target.hp) or 1e9
camp = [c for c in g.creeps() if g.dist(c, center) < 600]
ours = max(ttk(c) for c in camp)                                 # 이 캠프를 전멸시키는 데 걸리는 시간(대략)
theirs = g.time_to_kill(camp, weakest_of_mine) or 1e9            # 크립들이 아군의 가장 약한 유닛을 죽이는 데 걸리는 시간
if ours < theirs and g.reachable(center, camp[0]):
    g.attack_move(army, camp[0].x, camp[0].y)
```

전체 예제: `micro_bot.py`의 `_maybe_creep`.

## 라. 마이크로 컨트롤

### 12. 점사: 가장 가까운 적이 아니라 "가장 빨리 죽일 수 있는" 적을

```python
target = min(visible_enemies, key=lambda e: g.time_to_kill(fighters, e) or 1e9)
g.attack([u for u in fighters if (g.current_target(u) or target).handle != target.handle], target)
```

"그 대상을 공격하고 있지 않은" 유닛에게만 명령하고(`current_target`), 이미 공격 중인 유닛은 방해하지 마세요.

### 13. 체력 낮은 유닛 빼기

```python
for u in army:
    if u.hp < u.hp_max * 0.35:
        g.move(u, *toward(home, u, 500))       # 본진 방향으로 500 후퇴, 3초 안에 다시 빼지 말 것
```

점사당하고 있는지 판단하기: `damage` 이벤트에서 같은 유닛이 짧은 시간에 여러 공격자에게 맞으면 = 포위된 것입니다.

### 14. 영웅 생존, 경험치 헌납 금지

```python
for h in g.my_heroes():
    if h.hp < h.hp_max * 0.4:
        g.move(h, home.x, home.y)
        g.use_item(h, slot_of(h, "phea"))       # 치유 물약: inventory(h)로 칸 번호 찾기
```

### 15. 상성: 맞는 유닛이 맞는 대상을 치게

```python
s = g.stats(u)
best = max(enemies, key=lambda e: s.dps_vs(g.stats(e)))   # 소총병은 그리폰 라이더를(관통 → 소형 갑옷 ×2), 그리폰 라이더는 보병을(마법 → 대형 갑옷 ×2)
```

상성표는 게임 데이터에서 가져옵니다: `combat.damage_multiplier("pierce", "small") == 2.0`.

### 16. 협공과 경로: 타워를 돌아서 가기

```python
route = g.walk_path(army_center, target)        # 지상 최단 경로의 꺾이는 지점
g.path(army, route, attack=True)                # 각 지점을 순서대로 공격 이동
```

타워를 피하려면 경로 탐색 그리드에서 타워 주변을 이동 불가로 표시한 뒤 계산합니다:

```python
grid = g.grid().copy()
for t in towers:
    grid.block_area(t.x, t.y, 800)             # 타워 사거리 700 + 여유분
route = grid.path((army_x, army_y), (target.x, target.y))
```

### 17. 한 틱의 명령은 한 배치로

```python
with g.batch():
    g.attack(melee, target_a)
    g.attack(ranged, target_b)
    g.move(wounded, *home_xy)
    g.cast(hero, "thunderclap")
```

명령 수십 개도 게임 스레드를 한 번만 기다립니다(실측: 이동 명령 8개 68 ms → 6.5 ms).

### 18. 공성: 포로 지면 공격

```python
g.attack_ground(mortars, tower.x, tower.y)       # 박격포 부대 / 투석기가 한 지점에 포격(숲 뒤, 은신 유닛)
```

## 마. 영웅

### 19. 스킬 찍는 순서

```python
SKILLS = {"Hamg": ["AHwe", "AHbz", "AHwe", "AHbz", "AHwe", "AHmt"]}   # 물의 정령, 눈보라…… 6레벨 궁극기 대규모 순간이동
info = g.hero_info(h)
if info and info["skill_points"]:
    g.learn(h, SKILLS[h.type][learned_count])  # 거부되면(궁극기를 배울 레벨이 아님) 다음 레벨까지 기다림
```

### 20. 스킬이 실제로 나갔는지

```python
r = g.cast(h, "thunderbolt", target=enemy_hero)
# 다음 틱:
if g.cooldown(h, "AHtb"):                         # 쿨다운 진입 = 실제로 시전됨. 수락 ≠ 시전
    ...
```

### 21. 물약 구매, 귀환

```python
g.buy(shop, "phea")                               # 영웅이 상점 옆에 서 있어야 함
g.use_item(hero, slot, x=home.x, y=home.y)        # 마을 귀환 주문서(지점 대상 아이템 사용)
```

## 바. 리뷰

- 매 틱 결정을 로그로 남기고(`print`는 실행 창에 출력됩니다), `g.say(유닛, "후퇴")`와 함께 게임 안에서 확인하세요.
- `production.done` 이벤트에는 "걸린 시간(초)"이 들어 있습니다 — 자신의 빌드 타임라인(첫 영웅이 몇 초에 나왔는지, 몇 초에 테크 업을 했는지)을 집계해 고수와 비교하세요.
- Agent가 스스로 리뷰하게 하기: [Agent 자율 반복](https://war3ai.com/ko/docs/agent-loop/)의 게임 리포트를 참고하세요.
