ONCE AI PUBLISHING디지털 핸드북 시리즈 #01
CHAPTER 04

테트리스를 배우는 강화학습 에이전트 만들기

C++ 게임 엔진을 Gymnasium 환경으로 감싸고 DQN으로 학습하는 실전 프로젝트

학습 목표

  • 외부 테트리스 Gym 패키지 없이 직접 만든 게임 엔진을 강화학습 환경으로 감싸는 방법을 이해한다.
  • C++ 엔진과 Python 학습 코드 사이를 C API와 ctypes로 연결하는 구조를 익힌다.
  • 관측, 행동, 보상, 종료 조건을 Gymnasium Env 인터페이스에 맞게 설계한다.
  • DQN 학습 루프를 구성하고 모델을 저장하거나 이어 학습하는 흐름을 만든다.
  • 매크로 행동과 LLM/VLM 보상 보조처럼 학습 안정성을 높이는 확장 아이디어를 살펴본다.

프로젝트 한눈에 보기

이 프로젝트의 핵심은 테트리스를 단순한 화면 게임으로 보지 않고, 에이전트가 반복해서 시행착오를 겪을 수 있는 학습 환경으로 바꾸는 데 있다. 게임 규칙은 C++ 엔진이 책임지고, 파이썬은 그 엔진을 호출해 관측과 보상을 받아 DQN 모델을 학습한다.

항목

내용

게임 엔진

레포 내부 tetris/src/tetris_engine.* 사용

학습 환경

local_tetris_env.py의 Gymnasium Env

행동 공간

기본 Discrete(7), 매크로 모드 Discrete(4 * board_width)

관측

hidden rows를 제외한 visible grid, shape=(22, 10, 1), dtype=uint8

학습 알고리즘

Stable-Baselines3 DQN

모델 저장

model/live_latest.zip 또는 지정한 이름의 zip 파일

왜 직접 엔진을 감싸는가

강화학습 튜토리얼에서는 이미 만들어진 Gym 환경을 자주 사용한다. 그러나 실제 프로젝트에서는 환경 자체가 제품의 일부인 경우가 많다. 이 장의 테트리스 프로젝트도 그렇다. 외부 ROM이나 gym-tetris에 의존하지 않고, 우리가 소유한 C++ 엔진을 학습 대상으로 삼는다. 이 방식은 처음에는 조금 번거롭지만 세 가지 장점이 있다.

  • 게임 규칙, 보상, 난이도, 랜덤 시드를 우리가 직접 통제할 수 있다.
  • Qt GUI와 헤드리스 학습을 같은 엔진 위에 올릴 수 있어 학습과 시각화가 분리되지 않는다.
  • 나중에 로컬 LLM/VLM, 휴먼 플레이 데이터, 매크로 플래닝 같은 실험을 같은 환경 위에서 이어갈 수 있다.

전체 아키텍처

코드 흐름은 아래처럼 네 층으로 나뉜다.

C++ TetrisEngine
  -> extern C bridge: tetris_c_api.cpp
  -> Python ctypes wrapper: local_tetris_env.py
  -> DQN training loop: train_tetris_rl.py

이 구조에서 가장 중요한 경계는 C++ 엔진과 Python 환경 사이의 경계다. 엔진은 게임을 한 스텝 진행하고, 파이썬은 그 결과를 강화학습 라이브러리가 이해할 수 있는 형태로 바꾼다.

파일

챕터에서 설명할 역할

tetris/src/tetris_engine.*

테트리스 규칙, 조각 이동, 라인 삭제, 보상 계산을 담당하는 C++ 코어 엔진

tetris/src/tetris_c_api.cpp

C++ 엔진을 파이썬에서 부를 수 있도록 extern C 함수로 감싸는 브릿지

local_tetris_env.py

C API를 ctypes로 호출해 Gymnasium Env 형태로 제공하는 핵심 환경

train_tetris_rl.py

Stable-Baselines3 DQN 모델을 만들고 학습, 저장, 이어학습을 처리하는 실행 스크립트

macro_planning_env.py

저수준 키 입력 대신 한 조각의 목표 회전과 목표 열을 고르는 매크로 행동 환경

llm_reward_wrapper.py

로컬 LLM/VLM이 보드 상태를 평가해 보상에 보조 신호를 더하는 래퍼

qt_rl_client.py / train_qt_live.py

학습된 모델을 Qt GUI와 연결하거나 학습 과정을 시각화하는 클라이언트

1단계: C++ 테트리스 엔진 이해하기

엔진의 중심은 TetrisEngine 클래스다. 이 클래스는 보드 크기, 숨겨진 스폰 영역, 미리보기 조각 수, 라인 삭제 보상, 높이와 구멍 패널티 같은 설정을 가진다. 강화학습 입장에서 엔진은 reset과 step을 제공하는 시뮬레이터다.

enum class TetrisAction : int8_t {
    Noop = 0, Left = 1, Right = 2,
    RotateCW = 3, RotateCCW = 4,
    SoftDrop = 5, HardDrop = 6, Count = 7
};

행동은 총 7개다. 왼쪽, 오른쪽, 회전, 소프트 드롭, 하드 드롭처럼 사람이 키보드로 누르는 조작을 거의 그대로 모델의 행동 공간으로 삼는다. 이렇게 하면 환경은 직관적이지만 학습은 쉽지 않다. 모델은 한 조각을 어디에 둘지 장기적으로 판단해야 하는데, 실제 행동은 한 칸씩 이동하는 미세 조작이기 때문이다.

2단계: C API 브릿지 만들기

파이썬이 C++ 클래스를 직접 다루기는 어렵다. 그래서 tetris_c_api.cpp는 TetrisEngine을 TetrisHandle이라는 불투명 핸들로 감싸고, 파이썬에서 호출하기 쉬운 C 스타일 함수들을 제공한다.

TetrisHandle* tetris_create(...);
void tetris_reset(TetrisHandle* h);
int tetris_step(TetrisHandle* h, int action);
double tetris_last_reward(const TetrisHandle* h);
int tetris_fill_render_grid(TetrisHandle* h, uint8_t* out, int out_len);

여기서 tetris_fill_render_grid가 관측을 만드는 핵심 함수다. 잠긴 블록과 현재 떨어지는 블록을 합친 보드를 row-major uint8 배열로 채워 준다. 강화학습 모델은 이 배열을 보고 다음 행동을 고른다.

3단계: Gymnasium 환경으로 감싸기

local_tetris_env.py는 ctypes로 동적 라이브러리를 열고, C API 함수들의 인자 타입과 반환 타입을 선언한다. 그 다음 LocalTetrisEnv가 Gymnasium의 reset과 step 규약에 맞춰 엔진을 호출한다.

self.action_space = spaces.Discrete(7)
self.observation_space = spaces.Box(
    low=0,
    high=7,
    shape=(self._visible_h, self._w, 1),
    dtype=np.uint8,
)

관측에서 숨겨진 행을 제거하는 것도 중요하다. C++ 엔진은 총 26행을 갖지만 상단 4행은 조각 스폰을 위한 숨겨진 영역이다. 학습 입력은 실제 화면에 보이는 22행만 사용한다.

4단계: 보상 설계하기

강화학습에서 보상은 모델에게 주는 유일한 방향 신호다. 테트리스에서 단순히 오래 살아남기만 보상하면 모델은 라인을 지우기보다 버티는 행동을 배울 수 있다. 반대로 라인 삭제만 크게 보상하면 높은 탑을 쌓다가 갑자기 게임이 끝나는 전략을 배울 수도 있다.

항목

내용

라인 삭제 보상

1줄=1.0, 2줄=3.0, 3줄=10.0, 4줄=30.0

스택 높이 패널티

새 블록이 쌓인 뒤 높이가 증가하면 패널티

울퉁불퉁함 패널티

인접 열 높이 차이가 클수록 패널티

절대 높이 패널티

보드가 높게 쌓일수록 패널티

구멍 패널티

블록 아래 빈칸이 생기면 패널티

config/rl_config.py는 이 보상 스케일을 한곳에서 관리한다. 장의 실습에서는 먼저 기본 설정으로 학습하고, 이후 패널티 값을 바꿔가며 플레이 스타일이 어떻게 달라지는지 비교한다.

5단계: DQN 학습 루프 만들기

train_tetris_rl.py는 설정 파일을 읽고, 환경을 만들고, DQN 모델을 학습한 뒤 model 디렉토리에 저장한다. Stable-Baselines3는 관측 공간을 보고 CNN 정책을 쓸지 MLP 정책을 쓸지 선택할 수 있는데, 이 프로젝트는 이미지형 보드 관측이므로 CnnPolicy가 자연스럽다.

python train_tetris_rl.py

# 설정 파일을 바꿔 실행하고 싶다면
python train_tetris_rl.py --config config/rl_config.py --save-name dqn_first_run

학습 결과는 기본적으로 model/live_latest.zip에 저장된다. 기존 모델이 있으면 .bak 파일로 백업한 뒤 새 모델을 저장하므로, 실험을 반복해도 마지막 결과를 쉽게 찾을 수 있다.

6단계: 매크로 행동으로 난이도 낮추기

저수준 행동 공간은 사람이 보기에는 자연스럽지만 모델에게는 어렵다. 한 조각을 왼쪽으로 여러 번 옮기고 회전한 뒤 하드 드롭해야 하는데, 그 모든 중간 행동이 올바르게 이어져야 보상이 나온다. macro_planning_env.py는 이 문제를 줄이기 위해 행동을 '목표 열과 목표 회전'으로 바꾼다.

action = target_col * 4 + target_rot

보드 폭이 10이라면 매크로 행동은 40개다. 모델은 더 이상 매 프레임 왼쪽 키를 누를지 오른쪽 키를 누를지 고민하지 않는다. 대신 현재 조각을 몇 번째 열에 어떤 회전 상태로 놓을지만 고른다. 환경 내부는 그 목표를 향해 회전, 이동, 하드 드롭을 자동으로 실행한다.

7단계: 로컬 LLM/VLM으로 보상 보조하기

llm_reward_wrapper.py는 학습 환경 위에 한 겹 더 씌우는 래퍼다. 원래 환경 보상에 로컬 LLM 또는 VLM이 평가한 점수를 더한다. 예를 들어 보드가 평평한지, 구멍이 많은지, 다음 행동이 위험해 보이는지 같은 신호를 보조 보상으로 사용할 수 있다.

reward_shaped = env_reward + alpha * llm_score

8단계: Qt GUI로 결과 확인하기

헤드리스 학습은 빠르지만 모델이 실제로 어떻게 플레이하는지 감이 잘 오지 않는다. Qt 앱은 TCP 포트를 열고, 파이썬 클라이언트가 학습된 모델로 행동을 보내게 한다.

./tetris/build/tetris_rl --rl-port 5555
python qt_rl_client.py --port 5555 --model model/live_latest.zip --deterministic --auto-reset

이 장의 마지막 실습에서는 학습된 모델을 GUI에 연결해 실제 플레이를 관찰한다. 여기서 모델이 자주 만드는 실수를 메모하면 다음 실험의 보상 설계가 훨씬 명확해진다.

실습 순서

  1. 엔진 빌드: tetris 디렉토리에서 C API 브릿지를 빌드한다.
  2. 환경 확인: LocalTetrisEnv를 생성해 reset과 step이 정상 동작하는지 확인한다.
  3. 첫 학습: 기본 설정으로 DQN을 짧게 학습하고 모델 파일이 저장되는지 확인한다.
  4. 보상 조정: stack_height, bumpiness, max_height 패널티를 바꿔 플레이 성향을 비교한다.
  5. 매크로 모드: cfg.env.macro=True 설정으로 행동 공간을 바꿔 학습 효율을 비교한다.
  6. 시각화: Qt GUI와 모델 클라이언트를 연결해 결과를 눈으로 확인한다.

챕터 마무리

이번 장의 핵심은 강화학습 알고리즘 자체보다 '학습 가능한 환경을 만드는 힘'이다. 테트리스 엔진을 직접 만들고, C API로 공개하고, Gymnasium Env로 감싸고, DQN으로 학습시키는 전체 흐름을 경험하면 다른 게임이나 시뮬레이터에도 같은 패턴을 적용할 수 있다.

다음 장에서는 이 프로젝트를 더 발전시켜 학습 로그를 분석하고, 여러 보상 설정의 결과를 비교하며, 모델의 행동을 평가하는 자동 리포트를 만들어 본다.

연습 문제

  • line_reward_4 값을 30.0에서 10.0으로 낮추면 모델의 전략이 어떻게 달라질지 예상해 보자.
  • bumpiness_penalty_scale을 0.0, 0.02, 0.05로 바꿔 세 번 학습하고 GUI에서 플레이를 비교해 보자.
  • 매크로 행동 공간을 '목표 열과 회전'이 아니라 '후보 배치 점수 상위 K개'로 바꾼다면 어떤 장단점이 있을지 정리해 보자.
  • LLM 보상 보조를 사용할 때 alpha가 너무 크면 어떤 문제가 생길지 설명해 보자.
CHAPTER 04 · 테트리스를 배우는 강화학습 에이전트 만들기1 / 1
책장을 누르거나 좌우로 밀어 넘기기
CHAPTER 04

테트리스를 배우는 강화학습 에이전트 만들기

댓글 0

아직 댓글이 없습니다.