게시판인베이더 게임으로 배우는 휴리스틱과 AI 브리지예제 소스

예제 소스

인베이더 게임으로 배우는 휴리스틱과 AI 브리지 · 파일 17개

gameengine.h

#pragma once

#include <optional>

#include <QByteArray>
#include <QColor>
#include <QImage>
#include <QPoint>
#include <QRect>
#include <QVector>

namespace Invader {

inline constexpr int kGridRows = 14;
inline constexpr int kGridCols = 11;

// ---- 픽셀 기반 월드 (화면 좌표) ----
inline constexpr int kWorldW = 480;
inline constexpr int kWorldH = 640;
inline constexpr int kHudH = 54;
inline constexpr int kPlayTop = kHudH;
inline constexpr int kPlayBottom = kWorldH - 16;
/** 새 게임 시작 시 플레이어 동시 탄 상한 (연사 기본값). UFO 격추 시 +2. */
inline constexpr int kMaxPlayerBullets = 5;
/** MainWindow 게임 타이머 주기(ms) — 카운트다운 길이 계산과 맞출 것. */
inline constexpr int kGameTimerIntervalMs = 48;
inline constexpr int kIntermissionDurationSec = 10;
inline constexpr int kIntermissionTicks =
    (kIntermissionDurationSec * 1000 + kGameTimerIntervalMs - 1) / kGameTimerIntervalMs;

enum class Cell : uint8_t {
    Empty = 0,
    Alien = 1,
    Player = 2,
    PlayerBullet = 3,
    AlienBullet = 4,
};

enum class PlayMode : uint8_t {
    Human = 0,
    Ai = 1,
    Vlm = 2,
};

enum class GameStatus : uint8_t {
    Playing = 0,
    Won = 1,        // 로그 호환용(미사용)
    Lost = 2,
    Intermission = 3, // 웨이브 클리어 후 카운트다운(이동만, 발사 불가)
};

enum class PlayerAction : uint8_t {
    Left = 0,
    Right = 1,
    Fire = 2,
    None = 3,
};

struct Bullet {
    QRect rect;      // world coords
    int vy = 0;      // px per tick
    bool fromPlayer; // true=player, false=alien
};

struct Bunker {
    QPoint pos; // top-left world coords
    QImage mask; // ARGB, alpha=1 이 남은 픽셀
    QRect rect() const { return QRect(pos, mask.size()); }
};

struct ExplosionParticle {
    QPointF pos;
    QPointF vel;
    QColor color;
    int life = 0;
};

/** 간단한 스페이스 인베이더 규칙: 격자 스냅 상태 + 틱 시뮬레이션. */
class GameEngine {
public:
    GameEngine();

    void reset();
    /** 한 프레임 진행. a 는 플레이어 조작 1회분. */
    void tick(PlayerAction a);

    /** 승/패 후에도 폭발 파티클이 남아 있을 때 매 프레임 호출. */
    void advanceParticles();

    GameStatus status() const { return m_status; }
    PlayMode playMode() const { return m_playMode; }
    void setPlayMode(PlayMode m) { m_playMode = m; }

    // --- 렌더링/관측용 ---
    int worldWidth() const { return kWorldW; }
    int worldHeight() const { return kWorldH; }
    QRect playerRect() const { return m_player; }
    const QVector<QRect> &alienRects() const { return m_aliens; }
    const QVector<Bullet> &bullets() const { return m_bullets; }
    const QVector<Bunker> &bunkers() const { return m_bunkers; }
    const QVector<ExplosionParticle> &particles() const { return m_particles; }

    bool ufoActive() const { return m_ufoActive; }
    QRect ufoRect() const { return m_ufo; }

    /** 에일리언 2프레임 애니메이션 위상 (0 또는 1). */
    int alienAnimPhase() const { return (m_tick / 8) % 2; }

    // (호환) VLM/학습용: 기존 14×11 격자 직렬화.
    void packGrid(QByteArray *out) const;

    // AI/휴리스틱용 보조 정보
    int alienDir() const { return m_alienDir; }
    int tickCount() const { return m_tick; }
    int score() const { return m_score; }
    /** 현재 틱에서 허용되는 동시 플레이어 탄 개수 상한. */
    int maxPlayerBullets() const { return m_maxPlayerBullets; }
    /** Intermission 중 화면용 남은 초(올림). */
    int intermissionSecondsDisplay() const;

private:
    void applyPlayer(PlayerAction a);
    void applyPlayerIntermission(PlayerAction a);
    void prepareNextWave();
    void stepAliens();
    void stepBullets();
    void tryAlienShoot();
    void resolveCollisions();
    void damageBunkersFromAliens();
    void damageBunkerAt(const QPoint &worldHit);
    void spawnHitBurst(const QPoint &worldCenter, int strength);
    void stepParticles();
    void stepUfo();
    void checkWinLose();
    void rebuildCoarseGrid(QByteArray *out) const;

    PlayMode m_playMode = PlayMode::Human;
    GameStatus m_status = GameStatus::Playing;

    int m_tick = 0;

    QRect m_player;
    int m_playerVx = 0;

    QVector<QRect> m_aliens;
    int m_alienDir = 1;          // -1 or +1
    int m_alienStepEvery = 10;   // ticks
    int m_alienStepPx = 6;       // px per step
    int m_alienDropPx = 14;      // px when hit wall
    int m_alienShootCooldown = 0;

    QVector<Bullet> m_bullets;
    QVector<Bunker> m_bunkers;
    QVector<ExplosionParticle> m_particles;

    bool m_ufoActive = false;
    QRect m_ufo;
    int m_ufoVx = 3;
    int m_ufoSpawnIn = 500;
    int m_score = 0;
    int m_maxPlayerBullets = kMaxPlayerBullets;
    int m_intermissionTicksLeft = 0;
};

} // namespace Invader