순수 규칙 게임을 인공지능과 연결하기 위한 관찰, 행동, 통신 설계
스페이스 인베이더류 게임은 규칙이 단순하다. 플레이어는 좌우로 움직이고, 총알을 쏘고, 적의 총알을 피한다. 적은 천천히 내려오고, 벙커는 총알에 맞아 부서진다. 이 단순함은 학습 예제에서 큰 장점이다. 복잡한 3D 물리나 스토리 없이도 관찰, 행동, 보상, 정책이라는 인공지능의 핵심 요소를 모두 설명할 수 있기 때문이다.
이 프로젝트의 게임 엔진은 Qt/C++로 작성되어 있고, 기본적으로 사람 조작만으로도 돌아간다. 다시 말해 게임 본체는 AI가 아니다. AI는 게임 밖에서 들어온다. 이 구분을 분명히 해야 한다. 게임은 세계이고, 휴리스틱과 VLM은 그 세계를 바라보며 행동을 고르는 플레이어다.
파일 | 역할 |
|---|---|
src/gameengine.h/.cpp | 게임 규칙, 플레이어, 에일리언, 총알, 벙커, 충돌, 승패를 담당한다. |
src/aiplayer.cpp | 총알 회피, 적 정렬, 발사 규칙으로 만든 로컬 휴리스틱 플레이어다. |
src/mainwindow.cpp | 사람, 로컬 AI, VLM 모드를 선택하고 매 틱 행동을 게임에 넣는다. |
src/tcpvlmclient.cpp | 게임 화면 PNG와 14x11 격자를 외부 VLM 서버로 보내고 액션을 받는다. |
invader_vlm_server.py | TCP 서버로 INV3 추론 요청과 INVL 로그 요청을 처리한다. |
dataset_invader.py | JSONL 로그를 읽어 이미지와 액션 라벨 학습 데이터로 바꾼다. |
download_vlm_model.py | Qwen3-VL 2B 모델을 vlm_models 아래에 내려받는다. |
인공지능을 붙이려면 먼저 게임을 기계가 읽을 수 있는 형태로 정해야 한다. 이 프로젝트는 화면 전체를 PNG로 보낼 수도 있고, 동시에 14행 11열의 작은 격자 상태도 보낸다. 격자는 픽셀보다 정보가 거칠지만, 학습과 디버깅에는 훨씬 쉽다.
요소 | 값 |
|---|---|
격자 크기 | 14 x 11 |
Cell 0 | 빈칸 |
Cell 1 | 에일리언 또는 UFO |
Cell 2 | 플레이어 |
Cell 3 | 플레이어 총알 |
Cell 4 | 적 총알 |
행동 0 | 왼쪽 이동 |
행동 1 | 오른쪽 이동 |
행동 2 | 발사 |
행동 3 | 대기 |
관찰 = 화면 PNG + 14x11 격자
행동 = 0:left, 1:right, 2:fire, 3:none
게임 루프 = tick(action)휴리스틱은 학습된 모델이 아니라 사람이 직접 만든 경험적 규칙이다. 정답을 수학적으로 보장하지는 않지만, 문제를 빨리 풀기 위해 쓸 만한 판단 기준을 적어 둔 것이다. 인베이더에서는 '적 총알이 가까우면 피한다', '적과 x좌표를 맞춘다', '위에 적이 있으면 쏜다' 같은 규칙이 휴리스틱이다.
휴리스틱의 장점은 이해하기 쉽고 빠르다는 점이다. 단점은 새로운 상황에 약하다는 점이다. 예를 들어 적 총알이 두 방향에서 동시에 오거나, 벙커가 애매하게 막고 있거나, UFO를 노릴지 일반 적을 노릴지 선택해야 하는 상황에서는 규칙이 금방 복잡해진다.
aiplayer.cpp의 로컬 AI는 매우 짧고 좋은 예제다. 먼저 플레이어 근처로 내려오는 적 총알을 찾는다. 위험하면 왼쪽이나 오른쪽 중 더 안전한 쪽으로 이동한다. 위험이 없으면 아래쪽에 있고 x좌표가 가까운 에일리언을 목표로 잡고 그쪽으로 움직인다. 목표 x좌표에 거의 맞으면, 위에 적이 있고 플레이어 총알 수가 한도보다 적을 때 발사한다.
규칙 | 의미 |
|---|---|
bulletThreatNearX | 플레이어 위쪽 가까운 x좌표에 적 총알이 내려오면 위험으로 판단한다. |
bestAlienX | 아래쪽에 있고 플레이어 x좌표와 가까운 적을 우선 목표로 잡는다. |
alienAboveX | 현재 x좌표 위에 적이 있으면 발사할 가치가 있다고 본다. |
countPlayerBullets | 동시에 쏠 수 있는 플레이어 총알 수 제한을 지킨다. |
suggestAction | 회피 -> 정렬 -> 발사 -> 대기 순서로 행동을 고른다. |
if enemy bullet is near:
move to safer side
else if target alien is left:
move left
else if target alien is right:
move right
else if alien is above and bullet limit allows:
fire
else:
none이 장에서 휴리스틱은 단순한 임시방편이 아니다. 휴리스틱은 첫 번째 선생님이다. VLM을 학습시키려면 처음에는 누군가의 행동 데이터가 필요하다. 사람의 플레이를 모아도 되고, 로컬 휴리스틱의 플레이를 모아도 된다. 로컬 휴리스틱은 완벽하지 않지만, 모델이 '무엇을 보면 무엇을 해야 하는가'를 처음 익히는 데 충분한 출발점이 된다.
관점 | 휴리스틱 | 학습 모델 |
|---|---|---|
만드는 방법 | 사람이 규칙을 직접 적는다. | 데이터에서 패턴을 배운다. |
장점 | 빠르고 설명 가능하다. | 복잡한 상황을 더 유연하게 처리할 수 있다. |
단점 | 상황이 늘면 규칙이 복잡해진다. | 데이터가 부족하면 엉뚱한 행동을 한다. |
역할 | 초기 교사, 기준선, 폴백 | 최종 정책 또는 보조 판단자 |
게임은 C++ Qt 프로그램이고, VLM은 Python 서버에서 돈다. 둘은 언어도 다르고 실행 환경도 다르다. 브리지는 이 둘 사이의 번역기다. 게임 쪽은 현재 화면과 격자 상태를 보낸다. AI 쪽은 0, 1, 2, 3 중 하나의 행동만 돌려준다. 이 단순한 계약이 있어야 게임을 고치지 않고도 여러 AI 모델을 갈아 끼울 수 있다.
Qt Game
-> packGrid()
-> grab screen as PNG
-> TCP INV3 frame
-> Python VLM server
-> action digit 0..3
-> GameEngine.tick(action)프로토콜 | 용도 |
|---|---|
PING | 서버 연결 확인. 응답은 PONG. |
INV3 | PNG와 격자를 보내고 VLM 액션을 받는 추론 요청. |
RPLY | 서버가 돌려주는 액션 응답. 성공 시 action 바이트 포함. |
INVL | 사람 또는 로컬 AI의 한 틱 행동을 서버에 기록하는 로그 요청. |
LACK | 로그를 받았다는 확인 응답. |
invader_vlm_server.py는 외부 플레이어다. INV3 요청을 받으면 PNG와 격자를 읽고, Qwen3-VL 같은 비전 언어 모델에게 '이 화면에서 어떤 행동을 할 것인가'를 묻는다. 모델은 반드시 한 글자 0, 1, 2, 3으로 답해야 한다. 파싱이 실패하면 재시도하고, 그래도 실패하면 mock 또는 로컬 폴백 행동으로 안전하게 빠질 수 있다.
이 설계에서 중요한 점은 VLM이 게임 규칙을 직접 실행하지 않는다는 것이다. VLM은 행동만 고른다. 충돌, 점수, 총알 이동, 적 이동은 여전히 GameEngine이 책임진다. 이렇게 해야 AI가 이상한 답을 해도 게임 세계의 규칙은 깨지지 않는다.
python invader_vlm_server.py --host 127.0.0.1 --port 9300 --device mps
# 모델 없이 통신만 확인할 때
python invader_vlm_server.py --port 9300 --mockMainWindow는 사람, 로컬 AI, VLM의 행동을 모두 로그로 남길 수 있다. 각 로그에는 게임 이름, 시간, 액션, 액터, 모드, 상태, 순번, 14x11 보드가 들어간다. 이 로그는 그대로 행동 복제 학습의 재료가 된다. dataset_invader.py는 JSONL 로그를 읽고, 보드를 작은 이미지로 렌더링한 뒤, action 라벨과 함께 train.jsonl을 만든다.
python dataset_invader.py \
--jsonl logs/invader_train.jsonl \
--out-dir datasets/invader_sft \
--save-images데이터 원천 | 장점 | 주의점 |
|---|---|---|
사람 플레이 | 자연스럽고 창의적인 행동이 들어간다. | 실수와 습관도 함께 들어간다. |
로컬 휴리스틱 | 일관된 기준선 데이터를 빠르게 모을 수 있다. | 휴리스틱의 한계를 그대로 배운다. |
VLM 플레이 | 모델이 스스로 만든 행동을 다시 분석할 수 있다. | 오답이 반복 학습되지 않도록 필터링해야 한다. |
이 프로젝트는 세 단계로 발전시킬 수 있다. 첫 단계는 휴리스틱 자동 플레이어다. 두 번째 단계는 VLM 서버가 화면을 보고 행동을 고르는 단계다. 세 번째 단계는 사람과 휴리스틱의 로그를 모아 VLM을 미세조정하거나 작은 정책망을 학습하는 단계다. 이때 게임은 그대로 두고 브리지와 모델만 바꾸면 된다.
단계 | 설명 |
|---|---|
1. 휴리스틱 | 총알 회피, 적 정렬, 발사 규칙으로 자동 플레이 기준선을 만든다. |
2. 브리지 | Qt 게임과 Python VLM 서버를 TCP 프로토콜로 연결한다. |
3. 로깅 | 사람과 휴리스틱의 행동을 JSONL로 저장한다. |
4. 데이터셋 | JSONL을 이미지와 액션 라벨로 변환한다. |
5. 학습 | VLM 또는 작은 시각 정책 모델이 행동을 모방하게 학습한다. |
6. 검증 | 점수, 생존 시간, 웨이브 클리어 수, 위험 회피율을 비교한다. |
평가 항목 | 질문 |
|---|---|
생존 시간 | 모델이 얼마나 오래 버티는가? |
점수 | 적을 얼마나 많이 맞히는가? |
웨이브 진행 | 한 웨이브를 클리어하고 다음 웨이브로 넘어가는가? |
총알 회피 | 적 총알이 가까울 때 피하는 행동을 하는가? |
발사 효율 | 적이 위에 있을 때 쏘고, 의미 없는 발사를 줄이는가? |
응답 안정성 | VLM 응답이 0~3 형식을 잘 지키는가? |
폴백 빈도 | 서버 실패나 파싱 실패로 휴리스틱 대체가 얼마나 자주 일어나는가? |
이 장의 주인공은 거대한 모델이 아니다. 주인공은 연결 방식이다. 인공지능이 없는 고전 게임도 관찰과 행동의 인터페이스를 만들면 학습 환경이 된다. 휴리스틱은 첫 번째 교사이고, 브리지는 게임과 모델 사이의 언어다. 이 둘을 만들 수 있으면 어떤 오래된 프로그램도 AI 실험장으로 바꿀 수 있다.
댓글 0
아직 댓글이 없습니다.