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

예제 소스

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

gamecanvas.cpp

#include "gamecanvas.h"

#include <QFont>
#include <QKeyEvent>
#include <QPainter>
#include <QPaintEvent>

using namespace Invader;

namespace {

void drawAlienSprite(QPainter &p, const QRect &a, int phase)
{
    const int ox = a.x();
    const int oy = a.y();
    const QColor body(78, 220, 125);
    const QColor dark(38, 130, 72);
    const QColor eye(20, 60, 35);

    p.setPen(Qt::NoPen);
    p.setCompositionMode(QPainter::CompositionMode_SourceOver);
    p.fillRect(a, body);

    // 상단 안테나
    p.fillRect(a.center().x() - 1, oy - 4, 2, 5, QColor(200, 240, 210));
    p.fillRect(a.center().x() - 2, oy - 5, 4, 2, dark);

    // 눈
    p.fillRect(ox + 4, oy + 3, 5, 5, eye);
    p.fillRect(a.right() - 8, oy + 3, 5, 5, eye);

    // 입/하관 홈
    p.setCompositionMode(QPainter::CompositionMode_Clear);
    p.fillRect(QRect(ox + 7, oy + 9, a.width() - 14, 3), QColor(0, 0, 0, 0));
    p.setCompositionMode(QPainter::CompositionMode_SourceOver);

    // 다리(2프레임: 좌우 교대)
    const int skew = (phase == 0) ? 0 : 3;
    p.fillRect(ox + 2 + skew, a.bottom() - 4, 6, 4, dark);
    p.fillRect(a.right() - 7 - skew, a.bottom() - 4, 6, 4, dark);
}

void drawUfoSprite(QPainter &p, const QRect &u)
{
    p.setPen(Qt::NoPen);
    p.setBrush(QColor(210, 70, 120));
    p.drawEllipse(u.adjusted(4, 10, -4, -2));
    p.setBrush(QColor(170, 55, 100));
    p.drawEllipse(u.adjusted(1, 4, -1, -8));
    p.setBrush(QColor(255, 255, 210));
    p.drawEllipse(u.adjusted(10, 5, -10, -11));
    // 창 불빛
    p.setBrush(QColor(255, 250, 180));
    for (int i = 0; i < 3; ++i) {
        const int x = u.left() + 9 + i * 11;
        p.drawEllipse(QRect(x, u.top() + 6, 6, 6));
    }
}

} // namespace

GameCanvas::GameCanvas(QWidget *parent)
    : QWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);
    const int w = kWorldW * m_scale;
    const int h = kWorldH * m_scale;
    setMinimumSize(w, h);
    setMaximumSize(w, h);
}

void GameCanvas::refreshView()
{
    update();
}

void GameCanvas::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing, false);
    p.fillRect(rect(), QColor(10, 12, 28));

    if (!m_engine) {
        p.setPen(Qt::white);
        p.drawText(rect(), Qt::AlignCenter, QStringLiteral("no engine"));
        return;
    }

    // 별(장식)
    p.setPen(QColor(80, 90, 130, 90));
    for (int i = 0; i < 40; ++i) {
        const int x = (i * 97 + m_engine->tickCount()) % kWorldW;
        const int y = kHudH + 8 + (i * 53) % (kPlayBottom - kHudH - 40);
        p.drawPoint(x, y);
    }

    p.setPen(QPen(QColor(40, 50, 90), 1));
    p.drawLine(0, kHudH, kWorldW, kHudH);

    if (m_engine->ufoActive())
        drawUfoSprite(p, m_engine->ufoRect());

    for (const Bunker &b : m_engine->bunkers())
        p.drawImage(b.pos, b.mask);

    const int aphase = m_engine->alienAnimPhase();
    for (const QRect &a : m_engine->alienRects())
        drawAlienSprite(p, a, aphase);

    for (const Bullet &b : m_engine->bullets()) {
        p.setPen(Qt::NoPen);
        p.setBrush(b.fromPlayer ? QColor(255, 230, 80) : QColor(120, 200, 255));
        p.drawRect(b.rect);
    }

    for (const ExplosionParticle &ex : m_engine->particles()) {
        QColor c = ex.color;
        c.setAlphaF(qBound(0.15, ex.life / 14.0, 1.0));
        p.fillRect(QRectF(ex.pos.x() - 1, ex.pos.y() - 1, 4, 4), c);
    }

    const QRect pr = m_engine->playerRect();
    p.setBrush(QColor(220, 220, 240));
    p.setPen(Qt::NoPen);
    p.drawRect(pr);
    p.setBrush(QColor(180, 180, 220));
    p.drawRect(QRect(pr.center().x() - 4, pr.y() - 6, 8, 6));

    if (m_engine->status() == GameStatus::Intermission) {
        const int sec = m_engine->intermissionSecondsDisplay();
        if (sec > 0) {
            const int phase = (m_engine->tickCount() / 6) % 2; // 2컷 애니메이션
            QFont f;
            f.setPixelSize(phase == 0 ? 120 : 136);
            f.setBold(true);
            f.setStyleHint(QFont::SansSerif);
            p.setFont(f);
            const QRect area(0, kHudH, kWorldW, kPlayBottom - kHudH);
            const QString text = QString::number(sec);

            // 2컷: "팝" 느낌 (외곽/빛/오프셋이 번갈아 변함)
            p.setPen(QPen(QColor(0, 0, 0), phase == 0 ? 6 : 8));
            p.drawText(area.translated(phase == 0 ? 3 : 2, phase == 0 ? 3 : 4), Qt::AlignCenter, text);

            p.setPen(phase == 0 ? QColor(255, 235, 60) : QColor(255, 252, 210));
            p.drawText(area, Qt::AlignCenter, text);
        }
    }

    if (m_paused) {
        // 게임 진행은 멈추고, 중앙에 "PAUSE"만 표시.
        const QRect area(0, kHudH, kWorldW, kPlayBottom - kHudH);
        const int phase = (m_engine ? (m_engine->tickCount() / 10) % 2 : 0);
        QFont f;
        f.setPixelSize(phase == 0 ? 86 : 92);
        f.setBold(true);
        f.setStyleHint(QFont::SansSerif);
        p.setFont(f);
        p.setPen(QPen(QColor(0, 0, 0), 8));
        p.drawText(area.translated(3, 3), Qt::AlignCenter, QStringLiteral("PAUSE"));
        p.setPen(QColor(230, 240, 255));
        p.drawText(area, Qt::AlignCenter, QStringLiteral("PAUSE"));
    }
}

void GameCanvas::keyPressEvent(QKeyEvent *event)
{
    QWidget::keyPressEvent(event);
}