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

예제 소스

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

tetris_engine.h

#pragma once

#include <array>
#include <cstdint>
#include <random>
#include <vector>

/** Discrete actions for RL policies. */
enum class TetrisAction : int8_t {
    Noop = 0,
    Left = 1,
    Right = 2,
    RotateCW = 3,
    RotateCCW = 4,
    SoftDrop = 5,
    HardDrop = 6,
    Count = 7
};

enum class TetrominoType : uint8_t { None = 0, I, O, T, S, Z, J, L };

struct TetrisEngineConfig {
    int board_width = 10;
    /** Total rows including hidden spawn rows at the top. */
    int board_height = 26;
    /** Number of top rows hidden from rendering (spawn buffer). */
    int hidden_rows = 4;
    int preview_count = 5;
    /** Per-line-count clear rewards (indexed by number of lines cleared). */
    double line_reward_1 = 1.0;   // 1-line clear
    double line_reward_2 = 3.0;   // 2-line clear
    double line_reward_3 = 10.0;  // 3-line clear
    double line_reward_4 = 30.0;  // 4-line clear (Tetris)
    /** Reward per row of landing height — placing lower gives more reward. */
    double height_reward_scale = 0.1;
    /**
     * Penalty per unit increase of stack height (max filled height).
     * Only applies on lock: compares before vs after (post line-clear) and penalizes positive delta.
     */
    double stack_height_penalty_scale = 0.0;
    /** Penalty scale for surface bumpiness (sum of adjacent column height diffs). */
    double bumpiness_penalty_scale = 0.0;
    /** Penalty scale for absolute stack height (post line-clear). */
    double max_height_penalty_scale = 0.0;
    /** Penalty multiplier for newly created "holes" (empty cells with a filled cell above in the same column). */
    double hole_penalty_scale = 1.0;
    double step_penalty = 0.0;
};

/**
 * Headless-friendly Tetris ruleset (simplified SRS-like rotation with basic kicks).
 * Intended for RL: reset(), step(), observation accessors, reward bookkeeping.
 */
class TetrisEngine {
public:
    explicit TetrisEngine(TetrisEngineConfig cfg = {});

    void set_seed(uint64_t seed);
    void reset();

    /** One game tick: apply action, then gravity unless hard drop already locked. */
    TetrisAction last_action() const { return last_action_; }
    bool game_over() const { return game_over_; }
    int lines_cleared_total() const { return lines_cleared_total_; }
    double last_reward() const { return last_reward_; }
    int piece_index() const { return piece_index_; }

    const TetrisEngineConfig& config() const { return cfg_; }

    // Diagnostics (for tuning reward shaping)
    int stack_height_metric() const;
    int bumpiness_metric() const;
    int holes_metric() const;

    /** Locked cells only (0 empty, 1..7 type). Shape: height * width row-major, row 0 = top. */
    const std::vector<uint8_t>& locked_grid() const { return grid_; }

    TetrominoType current_type() const { return current_type_; }
    int current_x() const { return current_x_; }
    int current_y() const { return current_y_; }
    int current_rotation() const { return current_rotation_; }

    const std::vector<TetrominoType>& preview_queue() const { return preview_; }

    /** Stacked view: locked cells + active piece (0 empty, 1..7). */
    void fill_render_grid(std::vector<uint8_t>& out) const;

    bool step(TetrisAction action);

private:
    static constexpr int kRotations = 4;
    static constexpr int kCells = 16;

    using Shape = std::array<int8_t, kCells>;

    bool fits(int bx, int by, int rot, TetrominoType t) const;
    void lock_piece();
    void spawn_next();
    int clear_lines();
    void refill_preview();
    TetrominoType draw_from_bag();
    Shape shape_for(TetrominoType t, int rot) const;

    bool try_rotate(int new_rot);
    void try_shift(int dx, int dy);
    bool try_move_down();
    int count_holes() const;
    int stack_height() const;
    std::vector<int> column_heights() const;
    int bumpiness() const;

    TetrisEngineConfig cfg_;
    std::mt19937_64 rng_;
    std::vector<uint8_t> grid_;
    std::vector<TetrominoType> bag_;
    size_t bag_pos_ = 0;

    std::vector<TetrominoType> preview_;

    TetrominoType current_type_ = TetrominoType::None;
    int current_x_ = 0;
    int current_y_ = 0;
    int current_rotation_ = 0;

    int piece_index_ = 0;
    int lines_cleared_total_ = 0;
    bool game_over_ = false;
    double last_reward_ = 0.0;
    TetrisAction last_action_ = TetrisAction::Noop;
};