게시판테트리스를 배우는 강화학습 에이전트 만들기예제 소스

예제 소스

테트리스를 배우는 강화학습 에이전트 만들기 · 파일 31개

tetris_c_api.cpp

#include "tetris_engine.h"

#include <cstdint>
#include <cstring>
#include <vector>

extern "C" {

struct TetrisHandle {
    TetrisEngine engine;
    std::vector<uint8_t> render_grid;
    explicit TetrisHandle(const TetrisEngineConfig& cfg) : engine(cfg) {}
};

// Create/destroy
TetrisHandle* tetris_create(int board_width, int board_height, int hidden_rows, int preview_count,
                            double line_reward_1, double line_reward_2, double line_reward_3, double line_reward_4,
                            double height_reward_scale, double stack_height_penalty_scale,
                            double bumpiness_penalty_scale, double max_height_penalty_scale,
                            double hole_penalty_scale, double step_penalty) {
    TetrisEngineConfig cfg;
    cfg.board_width = board_width;
    cfg.board_height = board_height;
    cfg.hidden_rows = hidden_rows;
    cfg.preview_count = preview_count;
    cfg.line_reward_1 = line_reward_1;
    cfg.line_reward_2 = line_reward_2;
    cfg.line_reward_3 = line_reward_3;
    cfg.line_reward_4 = line_reward_4;
    cfg.height_reward_scale = height_reward_scale;
    cfg.stack_height_penalty_scale = stack_height_penalty_scale;
    cfg.bumpiness_penalty_scale = bumpiness_penalty_scale;
    cfg.max_height_penalty_scale = max_height_penalty_scale;
    cfg.hole_penalty_scale = hole_penalty_scale;
    cfg.step_penalty = step_penalty;
    return new TetrisHandle(cfg);
}

void tetris_destroy(TetrisHandle* h) { delete h; }

// Control
void tetris_set_seed(TetrisHandle* h, uint64_t seed) { h->engine.set_seed(seed); }

void tetris_reset(TetrisHandle* h) { h->engine.reset(); }

// Step returns 1 if alive, 0 if game over
int tetris_step(TetrisHandle* h, int action) {
    const auto a = static_cast<TetrisAction>(static_cast<int8_t>(action));
    const bool alive = h->engine.step(a);
    return alive ? 1 : 0;
}

double tetris_last_reward(const TetrisHandle* h) { return h->engine.last_reward(); }

int tetris_game_over(const TetrisHandle* h) { return h->engine.game_over() ? 1 : 0; }

int tetris_lines_cleared_total(const TetrisHandle* h) { return h->engine.lines_cleared_total(); }

int tetris_piece_index(const TetrisHandle* h) { return h->engine.piece_index(); }

int tetris_stack_height(const TetrisHandle* h) { return h->engine.stack_height_metric(); }
int tetris_bumpiness(const TetrisHandle* h) { return h->engine.bumpiness_metric(); }
int tetris_holes(const TetrisHandle* h) { return h->engine.holes_metric(); }

int tetris_current_type(const TetrisHandle* h) {
    return static_cast<int>(h->engine.current_type());
}
int tetris_current_x(const TetrisHandle* h) { return h->engine.current_x(); }
int tetris_current_y(const TetrisHandle* h) { return h->engine.current_y(); }
int tetris_current_rotation(const TetrisHandle* h) { return h->engine.current_rotation(); }

// Dimensions
int tetris_board_width(const TetrisHandle* h) { return h->engine.config().board_width; }
int tetris_board_height(const TetrisHandle* h) { return h->engine.config().board_height; }
int tetris_hidden_rows(const TetrisHandle* h) { return h->engine.config().hidden_rows; }

// ── Macro candidate evaluation (MoE/heuristics) ───────────────────────────────
// out layout per candidate i:
//   out[i*6 + 0] = cleared_lines (int)
//   out[i*6 + 1] = holes_after (int)
//   out[i*6 + 2] = bumpiness_after (int)
//   out[i*6 + 3] = stack_height_after (int)
//   out[i*6 + 4] = game_over (0/1)
//   out[i*6 + 5] = steps_used (int)
//
// returns number of candidates written (<= out_candidates)
static int next_micro_action(int piece_type, int cur_rot, int cur_x, int target_rot, int target_x) {
    if (piece_type == 0) {
        return static_cast<int>(TetrisAction::Noop);
    }
    const int dr = (target_rot - cur_rot) & 3;
    if (dr == 0) {
        // pass
    } else if (dr <= 2) {
        return static_cast<int>(TetrisAction::RotateCW);
    } else {
        return static_cast<int>(TetrisAction::RotateCCW);
    }
    if (cur_x < target_x) return static_cast<int>(TetrisAction::Right);
    if (cur_x > target_x) return static_cast<int>(TetrisAction::Left);
    return static_cast<int>(TetrisAction::HardDrop);
}

int tetris_eval_macro_candidates(const TetrisHandle* h, int* out, int out_candidates) {
    if (!h || !out || out_candidates <= 0) {
        return 0;
    }
    const int w = h->engine.config().board_width;
    const int candidates = 4 * w;
    const int n = std::min(out_candidates, candidates);

    const int base_lines = h->engine.lines_cleared_total();
    const int base_holes = h->engine.holes_metric();

    for (int a = 0; a < n; ++a) {
        const int target_rot = a & 3;
        const int target_col = a >> 2;

        TetrisEngine sim = h->engine;  // copy current state
        const int start_pi = sim.piece_index();
        int steps = 0;
        while (!sim.game_over() && sim.piece_index() == start_pi && steps < 600) {
            const int ptype = static_cast<int>(sim.current_type());
            const int cur_rot = sim.current_rotation();
            const int cur_x = sim.current_x();
            const int micro = next_micro_action(ptype, cur_rot, cur_x, target_rot, target_col);
            sim.step(static_cast<TetrisAction>(static_cast<int8_t>(micro)));
            steps += 1;
        }

        const int cleared = sim.lines_cleared_total() - base_lines;
        const int holes_after = sim.holes_metric();
        const int bump_after = sim.bumpiness_metric();
        const int height_after = sim.stack_height_metric();
        const int go = sim.game_over() ? 1 : 0;

        const int off = a * 6;
        out[off + 0] = cleared;
        out[off + 1] = holes_after;
        out[off + 2] = bump_after;
        out[off + 3] = height_after;
        out[off + 4] = go;
        out[off + 5] = steps;
        (void)base_holes;
    }
    return n;
}

// Observation: render grid (locked + active), row-major uint8, size = w*h
// Returns number of bytes written (w*h), or 0 on error.
int tetris_fill_render_grid(TetrisHandle* h, uint8_t* out, int out_len) {
    const int w = h->engine.config().board_width;
    const int hgt = h->engine.config().board_height;
    const int need = w * hgt;
    if (!out || out_len < need) {
        return 0;
    }
    h->engine.fill_render_grid(h->render_grid);
    if (static_cast<int>(h->render_grid.size()) != need) {
        return 0;
    }
    std::memcpy(out, h->render_grid.data(), static_cast<size_t>(need));
    return need;
}

}  // extern "C"