게시판장기로 이해하는 규칙 엔진과 평가 함수 탐색예제 소스

예제 소스

장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개

BoardWidget.cpp

#include "BoardWidget.h"

#include "Game.h"

#include <QLineF>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QResizeEvent>
#include <algorithm>
#include <cmath>

namespace {

QColor hanColor() {
    return QColor(176, 28, 36);
}

QColor choColor() {
    return QColor(18, 102, 68);
}

QPainterPath octagon(QPointF center, qreal radius) {
    QPainterPath path;
    for (int i = 0; i < 8; ++i) {
        const qreal angle = (22.5 + 45.0 * i) * 3.14159265358979323846 / 180.0;
        const QPointF pt(center.x() + radius * std::cos(angle), center.y() + radius * std::sin(angle));
        if (i == 0) {
            path.moveTo(pt);
        } else {
            path.lineTo(pt);
        }
    }
    path.closeSubpath();
    return path;
}

}  // namespace

BoardWidget::BoardWidget(QWidget* parent) : QWidget(parent) {
    setMouseTracking(true);
    setMinimumSize(520, 620);
    setAutoFillBackground(false);
}

void BoardWidget::setGame(Game* game) {
    game_ = game;
    clearSelection();
    update();
}

void BoardWidget::setFlipped(bool flipped) {
    flipped_ = flipped;
    update();
}

void BoardWidget::setInteractive(bool interactive) {
    interactive_ = interactive;
}

void BoardWidget::clearSelection() {
    selected_.reset();
    targets_.clear();
    update();
}

QSize BoardWidget::sizeHint() const {
    return {640, 760};
}

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

void BoardWidget::resizeEvent(QResizeEvent*) {
    const qreal left = 42;
    const qreal right = 28;
    const qreal top = 40;
    const qreal bottom = 40;
    cellW_ = (width() - left - right) / 8.0;
    cellH_ = (height() - top - bottom) / 9.0;
    originX_ = left;
    originY_ = top;
}

QPointF BoardWidget::pointFor(Pos pos) const {
    int file = pos.file;
    int rank = pos.rank;
    if (flipped_) {
        file = 8 - file;
        rank = 9 - rank;
    }
    return {originX_ + file * cellW_, originY_ + rank * cellH_};
}

std::optional<Pos> BoardWidget::posFromPoint(QPointF pt) const {
    std::optional<Pos> best;
    qreal bestDist = std::min(cellW_, cellH_) * 0.48;
    for (int rank = 0; rank < 10; ++rank) {
        for (int file = 0; file < 9; ++file) {
            const Pos pos{file, rank};
            const qreal d = QLineF(pt, pointFor(pos)).length();
            if (d < bestDist) {
                bestDist = d;
                best = pos;
            }
        }
    }
    return best;
}

qreal BoardWidget::pieceRadius(PieceType type) const {
    const qreal base = std::min(cellW_, cellH_);
    switch (type) {
    case PieceType::King: return base * 0.42;
    case PieceType::Chariot:
    case PieceType::Cannon: return base * 0.38;
    case PieceType::Horse:
    case PieceType::Elephant: return base * 0.36;
    case PieceType::Advisor: return base * 0.33;
    case PieceType::Pawn: return base * 0.29;
    default: return base * 0.32;
    }
}

QFont BoardWidget::pieceFont(qreal radius) const {
    QFont font;
    font.setFamilies({QStringLiteral("Songti SC"), QStringLiteral("STSong"),
                      QStringLiteral("Apple SD Gothic Neo"), QStringLiteral("Noto Serif CJK KR"),
                      QStringLiteral("Serif")});
    font.setBold(true);
    font.setPixelSize(static_cast<int>(radius * 1.15));
    return font;
}

void BoardWidget::drawBoard(QPainter& p) const {
    QLinearGradient bg(rect().topLeft(), rect().bottomRight());
    bg.setColorAt(0, QColor(214, 168, 106));
    bg.setColorAt(1, QColor(186, 132, 74));
    p.fillRect(rect(), bg);

    const QRectF board(
        originX_ - cellW_ * 0.55,
        originY_ - cellH_ * 0.55,
        cellW_ * 8 + cellW_ * 1.1,
        cellH_ * 9 + cellH_ * 1.1);
    p.setPen(QPen(QColor(92, 54, 24, 180), 2));
    p.setBrush(QColor(232, 193, 132));
    p.drawRoundedRect(board, 10, 10);

    p.setPen(QPen(QColor(62, 36, 18), 1.6));
    for (int file = 0; file < 9; ++file) {
        p.drawLine(pointFor({file, 0}), pointFor({file, 9}));
    }
    for (int rank = 0; rank < 10; ++rank) {
        p.drawLine(pointFor({0, rank}), pointFor({8, rank}));
    }

    p.setPen(QPen(QColor(62, 36, 18), 2.1));
    auto palaceX = [&](Pos a, Pos b) { p.drawLine(pointFor(a), pointFor(b)); };
    palaceX({3, 0}, {5, 2});
    palaceX({5, 0}, {3, 2});
    palaceX({3, 7}, {5, 9});
    palaceX({5, 7}, {3, 9});
    p.setPen(QPen(QColor(62, 36, 18), 2.4));
    p.setBrush(Qt::NoBrush);
    p.drawRect(QRectF(pointFor({3, 0}), pointFor({5, 2})));
    p.drawRect(QRectF(pointFor({3, 7}), pointFor({5, 9})));

    QFont labelFont = font();
    labelFont.setPointSize(11);
    labelFont.setBold(true);
    p.setFont(labelFont);
    p.setPen(QColor(78, 46, 22));
    const QString files = QStringLiteral("123456789");
    for (int file = 0; file < 9; ++file) {
        const int shown = flipped_ ? 8 - file : file;
        const QPointF top = pointFor({file, 0}) - QPointF(0, cellH_ * 0.42);
        const QPointF bot = pointFor({file, 9}) + QPointF(0, cellH_ * 0.42);
        p.drawText(QRectF(top.x() - 12, top.y() - 14, 24, 18), Qt::AlignCenter, files.mid(shown, 1));
        p.drawText(QRectF(bot.x() - 12, bot.y() - 4, 24, 18), Qt::AlignCenter, files.mid(shown, 1));
    }
    for (int rank = 0; rank < 10; ++rank) {
        const int shown = flipped_ ? 9 - rank : rank;
        const QPointF left = pointFor({0, rank}) - QPointF(cellW_ * 0.46, 0);
        p.drawText(QRectF(left.x() - 18, left.y() - 10, 28, 20), Qt::AlignCenter,
                   QString::number(shown + 1));
    }

    p.setPen(choColor());
    const QPointF choLabel = pointFor({8, flipped_ ? 0 : 9}) + QPointF(cellW_ * 0.42, 0);
    p.drawText(QRectF(choLabel.x() - 8, choLabel.y() - 12, 36, 24), Qt::AlignLeft, QStringLiteral("초"));
    p.setPen(hanColor());
    const QPointF hanLabel = pointFor({8, flipped_ ? 9 : 0}) + QPointF(cellW_ * 0.42, 0);
    p.drawText(QRectF(hanLabel.x() - 8, hanLabel.y() - 12, 36, 24), Qt::AlignLeft, QStringLiteral("한"));
}

void BoardWidget::drawHighlights(QPainter& p) const {
    if (!game_) {
        return;
    }
    if (const auto last = game_->lastMove()) {
        if (!last->pass) {
            p.setPen(Qt::NoPen);
            p.setBrush(QColor(255, 170, 40, 90));
            const qreal r = std::min(cellW_, cellH_) * 0.46;
            p.drawEllipse(pointFor(last->from), r, r);
            p.setBrush(QColor(255, 140, 20, 120));
            p.drawEllipse(pointFor(last->to), r, r);
        }
    }
    if (game_->isCheck(Side::Cho) || game_->isCheck(Side::Han)) {
        const Side checked = game_->isCheck(game_->sideToMove()) ? game_->sideToMove()
                                                                 : opposite(game_->sideToMove());
        const Pos king = game_->kingPos(checked);
        if (inBoard(king)) {
            p.setPen(QPen(QColor(220, 40, 40, 200), 3));
            p.setBrush(QColor(220, 40, 40, 50));
            const qreal r = pieceRadius(PieceType::King) + 6;
            p.drawEllipse(pointFor(king), r, r);
        }
    }
    if (selected_) {
        p.setPen(QPen(QColor(40, 90, 200), 3));
        p.setBrush(QColor(80, 140, 255, 70));
        const qreal r = std::min(cellW_, cellH_) * 0.46;
        p.drawEllipse(pointFor(*selected_), r, r);
        for (Pos t : targets_) {
            const bool capture = static_cast<bool>(game_->at(t));
            if (capture) {
                p.setPen(QPen(QColor(200, 40, 40, 220), 2.5));
                p.setBrush(Qt::NoBrush);
                p.drawEllipse(pointFor(t), r * 0.92, r * 0.92);
            } else {
                p.setPen(Qt::NoPen);
                p.setBrush(QColor(30, 90, 40, 160));
                p.drawEllipse(pointFor(t), 7, 7);
            }
        }
    }
}

void BoardWidget::drawPiece(QPainter& p, Pos pos, Piece piece) const {
    const QPointF c = pointFor(pos);
    const qreal r = pieceRadius(piece.type);
    const QColor ink = piece.side == Side::Han ? hanColor() : choColor();

    p.setPen(Qt::NoPen);
    p.setBrush(QColor(40, 24, 10, 70));
    p.drawEllipse(c + QPointF(2.2, 3.2), r, r);

    QLinearGradient fill(c - QPointF(r, r), c + QPointF(r, r));
    fill.setColorAt(0, QColor(255, 244, 214));
    fill.setColorAt(1, QColor(232, 204, 150));
    p.setBrush(fill);
    p.setPen(QPen(QColor(92, 58, 24), 1.8));
    p.drawPath(octagon(c, r));

    p.setPen(QPen(ink.lighter(130), 1.0));
    p.setBrush(Qt::NoBrush);
    p.drawPath(octagon(c, r * 0.78));

    p.setFont(pieceFont(r));
    p.setPen(ink);
    p.drawText(QRectF(c.x() - r, c.y() - r, r * 2, r * 2), Qt::AlignCenter,
               QString::fromUtf8(pieceGlyph(piece)));
}

void BoardWidget::paintEvent(QPaintEvent*) {
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing, true);
    p.setRenderHint(QPainter::TextAntialiasing, true);
    drawBoard(p);
    if (!game_) {
        return;
    }
    drawHighlights(p);
    for (int rank = 0; rank < 10; ++rank) {
        for (int file = 0; file < 9; ++file) {
            const Pos pos{file, rank};
            const Piece piece = game_->at(pos);
            if (piece) {
                drawPiece(p, pos, piece);
            }
        }
    }
}

void BoardWidget::mousePressEvent(QMouseEvent* event) {
    if (!game_ || !interactive_ || game_->result() != GameResult::Playing) {
        return;
    }
    const auto hit = posFromPoint(event->position());
    if (!hit) {
        clearSelection();
        emit statusNeedsUpdate();
        return;
    }
    const Piece piece = game_->at(*hit);
    if (selected_) {
        if (game_->tryMove(*selected_, *hit)) {
            clearSelection();
            emit moveCommitted();
            return;
        }
    }
    if (piece && piece.side == game_->sideToMove()) {
        selected_ = *hit;
        targets_ = game_->legalTargets(*hit);
        update();
        emit statusNeedsUpdate();
        return;
    }
    clearSelection();
    emit statusNeedsUpdate();
}