게시판인베이더 게임으로 배우는 휴리스틱과 AI 브리지예제 소스

예제 소스

인베이더 게임으로 배우는 휴리스틱과 AI 브리지 · 파일 17개

dataset_invader.py

#!/usr/bin/env python3
"""invader_vlm_server.py / INVL 클라이언트 로그(JSONL) → 행동 레이블 학습 샘플.

예:
  python dataset_invader.py --jsonl logs/invader.jsonl --out-dir datasets/invader_sft --save-images

--won-only: 마지막 틱이 Won 인 세션만 사용(레거시). 무한 웨이브 빌드에선 Won 이 안 나오므로 거의 비어 있을 수 있음.
"""

from __future__ import annotations

import argparse
import json
import os
from typing import Iterator, List

from PIL import Image, ImageDraw

INV_ROWS = 14
INV_COLS = 11

ACTION_NAMES = ("left", "right", "fire", "none")


def _read_jsonl(path: str) -> Iterator[dict]:
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                yield json.loads(line)
            except json.JSONDecodeError:
                continue


def group_by_game(records: List[dict]) -> Iterator[List[dict]]:
    """상태가 없는 레코드(서버 predict 전용 JSONL 등)는 한 줄이 한 샘플이 되게 그대로 내보냄."""
    cur: List[dict] = []
    for rec in records:
        if "status" not in rec:
            if cur:
                yield cur
                cur = []
            yield [rec]
            continue
        cur.append(rec)
        st = rec.get("status")
        if st in ("Won", "Lost"):
            yield cur
            cur = []
    if cur:
        yield cur


def render_board(board: List[int], cell: int = 22, pad: int = 8) -> Image.Image:
    w = pad * 2 + cell * INV_COLS
    h = pad * 2 + cell * INV_ROWS
    img = Image.new("RGB", (w, h), (10, 12, 28))
    draw = ImageDraw.Draw(img)
    colors = {
        0: (10, 12, 28),
        1: (80, 220, 120),
        2: (220, 220, 240),
        3: (255, 230, 80),
        4: (120, 200, 255),
    }
    for r in range(INV_ROWS):
        for c in range(INV_COLS):
            v = board[r * INV_COLS + c]
            x0 = pad + c * cell
            y0 = pad + r * cell
            draw.rectangle([x0, y0, x0 + cell - 1, y0 + cell - 1], outline=(40, 50, 90))
            if v:
                draw.rectangle(
                    [x0 + 2, y0 + 2, x0 + cell - 3, y0 + cell - 3],
                    fill=colors.get(v, (128, 128, 128)),
                )
    return img


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--jsonl", required=True)
    ap.add_argument("--out-dir", required=True)
    ap.add_argument("--save-images", action="store_true")
    ap.add_argument("--won-only", action="store_true")
    args = ap.parse_args()

    def is_sample(r: dict) -> bool:
        if r.get("game") != "invader" or "action" not in r or "board" not in r:
            return False
        k = r.get("kind")
        if k is not None and k not in ("move", "predict"):
            return False
        return True

    moves = [r for r in _read_jsonl(args.jsonl) if is_sample(r)]
    os.makedirs(args.out_dir, exist_ok=True)
    items: List[dict] = []
    for game in group_by_game(moves):
        if not game:
            continue
        if args.won_only and game[-1].get("status") != "Won":
            continue
        items.extend(game)

    train_path = os.path.join(args.out_dir, "train.jsonl")
    n = 0
    with open(train_path, "w", encoding="utf-8") as out:
        for rec in items:
            board = rec.get("board")
            if not board or len(board) != INV_ROWS * INV_COLS:
                continue
            act = rec.get("action")
            if act is None or not (0 <= int(act) <= 3):
                continue
            prompt = (
                "Space Invaders grid 14x11. Symbols: A=alien P=you b=your bullet B=enemy bullet. "
                "Output exactly one digit: 0=left 1=right 2=fire 3=wait."
            )
            row = {"messages": [{"role": "user", "content": prompt + "\n(see image)"}], "action": int(act)}
            if args.save_images:
                img = render_board(board)
                img_path = os.path.join(args.out_dir, f"frame_{n:06d}.png")
                img.save(img_path)
                row["image"] = os.path.basename(img_path)
            out.write(json.dumps(row, ensure_ascii=False) + "\n")
            n += 1
    print(f"wrote {n} examples → {train_path}")


if __name__ == "__main__":
    main()