예제 소스
테트리스를 배우는 강화학습 에이전트 만들기 · 파일 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;
};