게시판SGF 기보로 학습하는 바둑 LLM 인공지능예제 소스

예제 소스

SGF 기보로 학습하는 바둑 LLM 인공지능 · 파일 169개

boardwidget.cpp

#include "boardwidget.h"

#include <QMouseEvent>
#include <QPainter>
#include <QRadialGradient>
#include <QSizePolicy>
#include <QTimer>
#include <QtMath>

BoardWidget::BoardWidget(QWidget *parent)
    : QWidget(parent)
{
    setMouseTracking(false);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    m_blinkTimer = new QTimer(this);
    m_blinkTimer->setInterval(550);
    connect(m_blinkTimer, &QTimer::timeout, this, [this]() {
        m_blinkOn = !m_blinkOn;
        if (m_lastRow >= 0 && m_lastCol >= 0)
            update();
    });
}

void BoardWidget::refreshFromBoard(const QVector<Stone> &cells, int size)
{
    m_cells = cells;
    m_size = (size > 1) ? size : kBoardSize;
    if (m_cells.isEmpty())
        m_cells.assign(m_size * m_size, Stone::Empty);
    update();
}

void BoardWidget::setScoringOverlay(const QSet<int> &deadStones,
                                    const QSet<int> &territoryBlack,
                                    const QSet<int> &territoryWhite,
                                    const QSet<int> &dame)
{
    m_deadSet = deadStones;
    m_territoryBlk = territoryBlack;
    m_territoryWht = territoryWhite;
    m_dameSet = dame;
    update();
}

void BoardWidget::clearScoringOverlay()
{
    m_deadSet.clear();
    m_territoryBlk.clear();
    m_territoryWht.clear();
    m_dameSet.clear();
    update();
}

void BoardWidget::setLastMove(int row, int col, bool blink)
{
    m_lastRow = row;
    m_lastCol = col;
    m_blinkOn = true;
    if (row >= 0 && col >= 0 && blink) {
        if (!m_blinkTimer->isActive())
            m_blinkTimer->start();
    } else {
        m_blinkTimer->stop();
    }
    update();
}

void BoardWidget::gridLayout(qreal *outSpacing, QPointF *outOrigin) const
{
    Q_ASSERT(outSpacing && outOrigin);
    const qreal aw = qreal(width());
    const qreal ah = qreal(height());
    const qreal pad = qMax(qreal(14.0), qMin(aw, ah) * qreal(0.045));
    const qreal availW = qMax(qreal(1.0), aw - 2.0 * pad);
    const qreal availH = qMax(qreal(1.0), ah - 2.0 * pad);
    const qreal side = qMax(qreal(1.0), qMin(availW, availH));
    const int n = qMax(2, m_size);
    *outSpacing = side / qreal(n - 1);
    *outOrigin = QPointF(pad + (availW - side) * 0.5, pad + (availH - side) * 0.5);
}

QPointF BoardWidget::pointForIntersection(int row, int col) const
{
    qreal sp = 1;
    QPointF origin;
    gridLayout(&sp, &origin);
    return QPointF(origin.x() + qreal(col) * sp, origin.y() + qreal(row) * sp);
}

QSize BoardWidget::sizeHint() const { return QSize(620, 620); }
QSize BoardWidget::minimumSizeHint() const { return QSize(280, 280); }

void BoardWidget::drawBoard(QPainter &p, int wpx, int hpx, qreal sp, QPointF off) const
{
    Q_UNUSED(wpx);
    Q_UNUSED(hpx);

    const QColor wood(QColor(220, 184, 116));
    p.fillRect(QRectF(0, 0, wpx, hpx), wood);

    const int N = m_size;
    const qreal gridSpan = qreal(N - 1) * sp;

    p.setPen(QPen(QColor(80, 50, 20), qMax(qreal(0.9), sp * 0.045)));
    p.setBrush(Qt::NoBrush);

    for (int i = 0; i < N; ++i) {
        const qreal y = off.y() + qreal(i) * sp;
        p.drawLine(QLineF(off.x(), y, off.x() + gridSpan, y));
    }
    for (int j = 0; j < N; ++j) {
        const qreal x = off.x() + qreal(j) * sp;
        p.drawLine(QLineF(x, off.y(), x, off.y() + gridSpan));
    }

    // 화점 (19×19: 9개) — 천원/네 모서리/네 변 중앙
    {
        const int mid = N / 2;
        const int corner = (N >= 13) ? 3 : 2;
        const int far = N - 1 - corner;
        QVector<QPoint> hoshi;
        hoshi.append(QPoint(mid, mid));
        if (corner < far) {
            hoshi.append(QPoint(corner, corner));
            hoshi.append(QPoint(corner, far));
            hoshi.append(QPoint(far, corner));
            hoshi.append(QPoint(far, far));
            if (N >= 19) {
                hoshi.append(QPoint(mid, corner));
                hoshi.append(QPoint(mid, far));
                hoshi.append(QPoint(corner, mid));
                hoshi.append(QPoint(far, mid));
            }
        }
        const qreal hoshiR = qMax(qreal(2.0), sp * 0.11);
        p.setPen(Qt::NoPen);
        p.setBrush(QColor(40, 25, 10));
        for (const QPoint &h : hoshi) {
            const qreal x = off.x() + qreal(h.x()) * sp;
            const qreal y = off.y() + qreal(h.y()) * sp;
            p.drawEllipse(QPointF(x, y), hoshiR, hoshiR);
        }
    }

    const qreal rr = qMax(qreal(2.0), sp * kStoneRadiusFactor);
    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < N; ++c) {
            const Stone v = (r * N + c < m_cells.size()) ? m_cells[r * N + c] : Stone::Empty;
            if (v == Stone::Empty)
                continue;
            const QPointF cen(off.x() + qreal(c) * sp, off.y() + qreal(r) * sp);
            QRadialGradient rg(cen - QPointF(rr * 0.3, rr * 0.3), rr * 1.6);
            if (v == Stone::Black) {
                rg.setColorAt(0.0, QColor(80, 80, 84));
                rg.setColorAt(1.0, QColor(8, 8, 10));
                p.setPen(QPen(Qt::black, 1));
            } else {
                rg.setColorAt(0.0, QColor(255, 255, 255));
                rg.setColorAt(1.0, QColor(210, 210, 215));
                p.setPen(QPen(QColor(80, 80, 84), 1));
            }
            p.setBrush(QBrush(rg));
            p.drawEllipse(cen, rr, rr);
        }
    }
}

void BoardWidget::paintEvent(QPaintEvent *event)
{
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);

    qreal sp = 1;
    QPointF off;
    gridLayout(&sp, &off);

    drawBoard(p, width(), height(), sp, off);

    const int N = m_size;
    const qreal rr = qMax(qreal(2.0), sp * kStoneRadiusFactor);

    if (m_blinkOn && m_lastRow >= 0 && m_lastRow < N && m_lastCol >= 0 && m_lastCol < N) {
        const QPointF cen = pointForIntersection(m_lastRow, m_lastCol);
        const Stone v = (m_lastRow * N + m_lastCol < m_cells.size())
                            ? m_cells[m_lastRow * N + m_lastCol]
                            : Stone::Empty;
        if (v != Stone::Empty) {
            const qreal markerR = qMax(qreal(2.0), rr * 0.55);
            QPen markerPen((v == Stone::Black) ? QColor(255, 60, 60) : QColor(220, 30, 30));
            markerPen.setWidthF(qMax(qreal(1.6), rr * 0.18));
            p.setPen(markerPen);
            p.setBrush(Qt::NoBrush);
            p.drawEllipse(cen, markerR, markerR);
        }
    }

    // ── 계가 오버레이 ──
    if (!m_deadSet.isEmpty() || !m_territoryBlk.isEmpty()
        || !m_territoryWht.isEmpty() || !m_dameSet.isEmpty()) {
        // 사석 X (큰 빨강 X)
        const qreal xR = rr * 0.7;
        QPen xPen(QColor(220, 30, 30));
        xPen.setWidthF(qMax(qreal(2.0), rr * 0.22));
        xPen.setCapStyle(Qt::RoundCap);
        p.setPen(xPen);
        for (int idx : m_deadSet) {
            if (idx < 0 || idx >= N * N) continue;
            const int r = idx / N;
            const int c = idx % N;
            const QPointF cen = pointForIntersection(r, c);
            p.drawLine(QLineF(cen.x() - xR, cen.y() - xR,
                              cen.x() + xR, cen.y() + xR));
            p.drawLine(QLineF(cen.x() - xR, cen.y() + xR,
                              cen.x() + xR, cen.y() - xR));
        }

        // 흑/백 영토 O (작은 동그라미)
        const qreal tR = qMax(qreal(2.0), rr * 0.32);
        for (int idx : m_territoryBlk) {
            if (idx < 0 || idx >= N * N) continue;
            const int r = idx / N;
            const int c = idx % N;
            const QPointF cen = pointForIntersection(r, c);
            p.setPen(Qt::NoPen);
            p.setBrush(QColor(20, 20, 20));
            p.drawEllipse(cen, tR, tR);
        }
        for (int idx : m_territoryWht) {
            if (idx < 0 || idx >= N * N) continue;
            const int r = idx / N;
            const int c = idx % N;
            const QPointF cen = pointForIntersection(r, c);
            p.setBrush(QColor(255, 255, 255));
            p.setPen(QPen(QColor(80, 80, 84), qMax(qreal(1.0), rr * 0.08)));
            p.drawEllipse(cen, tR, tR);
        }
        // 공배 (회색 작은 점)
        const qreal dR = qMax(qreal(1.5), rr * 0.20);
        for (int idx : m_dameSet) {
            if (idx < 0 || idx >= N * N) continue;
            const int r = idx / N;
            const int c = idx % N;
            const QPointF cen = pointForIntersection(r, c);
            p.setPen(Qt::NoPen);
            p.setBrush(QColor(120, 120, 120));
            p.drawEllipse(cen, dR, dR);
        }
    }

    QWidget::paintEvent(event);
}

QImage BoardWidget::renderToImage(int pxSize) const
{
    const int s = qMax(240, pxSize);
    QImage img(s, s, QImage::Format_RGB32);
    img.fill(QColor(220, 184, 116));
    QPainter p(&img);
    p.setRenderHint(QPainter::Antialiasing);

    const qreal pad = s * 0.045;
    const qreal availW = s - 2.0 * pad;
    const qreal availH = s - 2.0 * pad;
    const qreal side = qMin(availW, availH);
    const int N = qMax(2, m_size);
    const qreal sp = side / qreal(N - 1);
    QPointF off(pad + (availW - side) * 0.5, pad + (availH - side) * 0.5);

    drawBoard(p, s, s, sp, off);
    p.end();
    return img;
}

void BoardWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() != Qt::LeftButton) {
        QWidget::mouseReleaseEvent(event);
        return;
    }

    qreal sp = 1;
    QPointF originScratch;
    gridLayout(&sp, &originScratch);
    Q_UNUSED(originScratch);

    const QPointF pos = event->position();
    qreal bestDist = sp * 0.55;
    int bestR = -1;
    int bestC = -1;

    const int N = m_size;
    for (int r = 0; r < N; ++r) {
        for (int c = 0; c < N; ++c) {
            const QPointF pt = pointForIntersection(r, c);
            const qreal d = QLineF(pt, pos).length();
            if (d <= bestDist) {
                bestDist = d;
                bestR = r;
                bestC = c;
            }
        }
    }

    if (bestR >= 0 && bestC >= 0)
        emit intersectionClicked(bestR, bestC);

    QWidget::mouseReleaseEvent(event);
}