게시판오목으로 이해하는 자기대국과 모방 학습예제 소스

예제 소스

오목으로 이해하는 자기대국과 모방 학습 · 파일 59개

boardwidget.cpp

#include "boardwidget.h"

#include <QMouseEvent>
#include <QPainter>
#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(500);
    connect(m_blinkTimer, &QTimer::timeout, this, [this]() {
        m_blinkOn = !m_blinkOn;
        if (m_lastRow >= 0 && m_lastCol >= 0)
            update();
    });
}

void BoardWidget::refreshFromBoard(const QVector<QVector<Cell>> &cells)
{
    m_cells = cells;
    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(12.0), qMin(aw, ah) * qreal(0.04));
    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, currentBoardSize());
    *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(480, 480);
}

QSize BoardWidget::minimumSizeHint() const
{
    return QSize(120, 120);
}

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

    const QColor wood(QColor(222, 191, 130));
    p.fillRect(rect(), wood);

    qreal sp = 1;
    QPointF off;
    gridLayout(&sp, &off);
    const int N = currentBoardSize();
    const qreal gridSpan = qreal(N - 1) * sp;

    p.setPen(QPen(QColor(120, 80, 30), qMax(qreal(0.8), sp * 0.035)));
    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));
    }

    // 화점 (중앙 + 네 모서리). 13/15/19 공통: 모서리 좌표는 (3, N-4).
    {
        const int mid = N / 2;
        const int corner = 3;
        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));
        }
        const qreal hoshiR = qMax(qreal(1.6), sp * 0.10);
        p.setPen(Qt::NoPen);
        p.setBrush(QColor(60, 40, 18));
        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 Cell stone = (!m_cells.isEmpty()) ? m_cells[r][c] : Cell::None;
            if (stone == Cell::None)
                continue;
            const QPointF cen = pointForIntersection(r, c);
            QRadialGradient rg(cen - QPointF(rr * 0.25, rr * 0.25), rr * 1.4);
            if (stone == Cell::Black) {
                rg.setColorAt(0.0, QColor(70, 70, 74));
                rg.setColorAt(1.0, QColor(10, 10, 10));
                p.setPen(QPen(Qt::black, 1));
            } else {
                rg.setColorAt(0.0, QColor(255, 255, 255));
                rg.setColorAt(1.0, QColor(215, 215, 219));
                p.setPen(QPen(QColor(90, 90, 94), 1));
            }
            p.setBrush(QBrush(rg));
            p.drawEllipse(cen, rr, rr);
        }
    }

    // 마지막 수 깜빡임 마커 (붉은 링).
    if (m_blinkOn && m_lastRow >= 0 && m_lastRow < N && m_lastCol >= 0 && m_lastCol < N
        && !m_cells.isEmpty() && m_cells[m_lastRow][m_lastCol] != Cell::None) {
        const QPointF cen = pointForIntersection(m_lastRow, m_lastCol);
        const qreal markerR = qMax(qreal(2.0), rr * 0.55);
        QPen markerPen(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);
    }

    QWidget::paintEvent(event);
}

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.52;
    int bestR = -1;
    int bestC = -1;

    const int N = currentBoardSize();
    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);
}