예제 소스
인베이더 게임으로 배우는 휴리스틱과 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