예제 소스
장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개
MainWindow.cpp
#include "MainWindow.h"
#include "BoardWidget.h"
#include "SetupDialog.h"
#include <QApplication>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QMenuBar>
#include <QMessageBox>
#include <QPushButton>
#include <QStatusBar>
#include <QTimer>
#include <QVBoxLayout>
#include <cstdlib>
#include <ctime>
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
std::srand(static_cast<unsigned>(std::time(nullptr)));
setWindowTitle(QStringLiteral("장기"));
resize(1080, 820);
auto* central = new QWidget;
auto* root = new QHBoxLayout(central);
root->setContentsMargins(16, 16, 16, 16);
root->setSpacing(16);
board_ = new BoardWidget;
board_->setGame(&game_);
root->addWidget(board_, 1);
auto* side = new QFrame;
side->setObjectName(QStringLiteral("sidePanel"));
side->setFixedWidth(280);
auto* sideLayout = new QVBoxLayout(side);
sideLayout->setSpacing(10);
auto* title = new QLabel(QStringLiteral("장기"));
title->setObjectName(QStringLiteral("titleLabel"));
sideLayout->addWidget(title);
turnLabel_ = new QLabel;
turnLabel_->setObjectName(QStringLiteral("turnLabel"));
turnLabel_->setWordWrap(true);
sideLayout->addWidget(turnLabel_);
checkLabel_ = new QLabel;
checkLabel_->setObjectName(QStringLiteral("checkLabel"));
checkLabel_->setWordWrap(true);
sideLayout->addWidget(checkLabel_);
scoreLabel_ = new QLabel;
scoreLabel_->setWordWrap(true);
sideLayout->addWidget(scoreLabel_);
auto* capTitle = new QLabel(QStringLiteral("잡은 말"));
capTitle->setObjectName(QStringLiteral("sectionLabel"));
sideLayout->addWidget(capTitle);
capturedChoLabel_ = new QLabel;
capturedHanLabel_ = new QLabel;
capturedChoLabel_->setWordWrap(true);
capturedHanLabel_->setWordWrap(true);
sideLayout->addWidget(capturedChoLabel_);
sideLayout->addWidget(capturedHanLabel_);
auto* histTitle = new QLabel(QStringLiteral("기보"));
histTitle->setObjectName(QStringLiteral("sectionLabel"));
sideLayout->addWidget(histTitle);
historyList_ = new QListWidget;
historyList_->setAlternatingRowColors(true);
sideLayout->addWidget(historyList_, 1);
undoButton_ = new QPushButton(QStringLiteral("무르기"));
passButton_ = new QPushButton(QStringLiteral("한수쉼"));
bikjangButton_ = new QPushButton(QStringLiteral("빅장 선언"));
resignButton_ = new QPushButton(QStringLiteral("기권"));
auto* newButton = new QPushButton(QStringLiteral("새 대국"));
newButton->setObjectName(QStringLiteral("primaryButton"));
sideLayout->addWidget(undoButton_);
sideLayout->addWidget(passButton_);
sideLayout->addWidget(bikjangButton_);
sideLayout->addWidget(resignButton_);
sideLayout->addWidget(newButton);
root->addWidget(side);
setCentralWidget(central);
auto* gameMenu = menuBar()->addMenu(QStringLiteral("대국"));
gameMenu->addAction(QStringLiteral("새 대국"), this, &MainWindow::newGame);
gameMenu->addAction(QStringLiteral("무르기"), this, &MainWindow::undoMove);
gameMenu->addSeparator();
gameMenu->addAction(QStringLiteral("종료"), qApp, &QApplication::quit);
connect(board_, &BoardWidget::moveCommitted, this, &MainWindow::onHumanMoved);
connect(board_, &BoardWidget::statusNeedsUpdate, this, &MainWindow::updateStatus);
connect(undoButton_, &QPushButton::clicked, this, &MainWindow::undoMove);
connect(passButton_, &QPushButton::clicked, this, &MainWindow::passMove);
connect(bikjangButton_, &QPushButton::clicked, this, &MainWindow::claimBikjang);
connect(resignButton_, &QPushButton::clicked, this, &MainWindow::resignGame);
connect(newButton, &QPushButton::clicked, this, &MainWindow::newGame);
setStyleSheet(QStringLiteral(R"(
QMainWindow { background: #2b1d12; }
QWidget#sidePanel {
background: #3a2718;
border: 1px solid #6a4a2c;
border-radius: 12px;
}
QLabel { color: #f3e2c4; }
QLabel#titleLabel { font-size: 28px; font-weight: 700; color: #f6d7a4; }
QLabel#turnLabel { font-size: 16px; font-weight: 600; }
QLabel#checkLabel { font-size: 15px; font-weight: 700; color: #ffb4a8; }
QLabel#sectionLabel { font-size: 13px; color: #c9a36a; font-weight: 600; }
QListWidget {
background: #2b1d12;
color: #f3e2c4;
border: 1px solid #6a4a2c;
border-radius: 8px;
}
QPushButton {
background: #5a3b22;
color: #f8ead0;
border: 1px solid #8a6238;
border-radius: 8px;
padding: 8px 12px;
font-weight: 600;
}
QPushButton:hover { background: #6b4729; }
QPushButton:disabled { color: #9a8166; background: #3d2a1b; }
QPushButton#primaryButton { background: #8a4b1f; }
QMenuBar { background: #24180f; color: #f3e2c4; }
QMenuBar::item:selected { background: #5a3b22; }
QStatusBar { color: #f3e2c4; background: #24180f; }
)"));
startGame(settings_);
}
void MainWindow::startGame(const GameSettings& settings) {
settings_ = settings;
ai_.setDepth(settings_.aiDepth);
game_.newGame(settings_.choFormation, settings_.hanFormation);
game_.setBikjangEnabled(settings_.bikjangEnabled);
board_->setFlipped(settings_.mode == GameSettings::Mode::HumanHan);
board_->setInteractive(true);
board_->clearSelection();
aiBusy_ = false;
updateStatus();
QTimer::singleShot(120, this, &MainWindow::thinkIfAiTurn);
}
void MainWindow::newGame() {
SetupDialog dialog(this);
if (dialog.exec() != QDialog::Accepted) {
return;
}
startGame(dialog.settings());
}
bool MainWindow::isHumanTurn() const {
if (settings_.mode == GameSettings::Mode::HumanVsHuman) {
return true;
}
if (settings_.mode == GameSettings::Mode::HumanCho) {
return game_.sideToMove() == Side::Cho;
}
return game_.sideToMove() == Side::Han;
}
void MainWindow::onHumanMoved() {
updateStatus();
thinkIfAiTurn();
}
void MainWindow::thinkIfAiTurn() {
if (game_.result() != GameResult::Playing || isHumanTurn() || aiBusy_) {
board_->setInteractive(isHumanTurn() && game_.result() == GameResult::Playing);
return;
}
board_->setInteractive(false);
aiBusy_ = true;
statusBar()->showMessage(QStringLiteral("컴퓨터가 수를 고르는 중..."));
QTimer::singleShot(80, this, &MainWindow::playAiMove);
}
void MainWindow::playAiMove() {
const Move move = ai_.chooseMove(game_);
if (move.pass) {
game_.passTurn();
} else {
game_.tryMove(move.from, move.to);
}
aiBusy_ = false;
board_->clearSelection();
updateStatus();
thinkIfAiTurn();
}
QString MainWindow::resultText() const {
switch (game_.result()) {
case GameResult::ChoWin: return QStringLiteral("초 승리 (외통)");
case GameResult::HanWin: return QStringLiteral("한 승리 (외통)");
case GameResult::Draw: return QStringLiteral("무승부");
case GameResult::Playing: return {};
}
return {};
}
void MainWindow::refreshCaptures() {
auto text = [](Side side, const std::vector<Piece>& pieces) {
QString line = (side == Side::Cho) ? QStringLiteral("초: ") : QStringLiteral("한: ");
if (pieces.empty()) {
line += QStringLiteral("없음");
return line;
}
for (Piece p : pieces) {
line += QString::fromUtf8(pieceGlyph(p));
line += QLatin1Char(' ');
}
return line;
};
capturedChoLabel_->setText(text(Side::Cho, game_.capturedBy(Side::Cho)));
capturedHanLabel_->setText(text(Side::Han, game_.capturedBy(Side::Han)));
}
void MainWindow::refreshHistory() {
historyList_->clear();
int ply = 1;
for (const Move& move : game_.history()) {
const QString row = QStringLiteral("%1. %2")
.arg(ply)
.arg(QString::fromStdString(game_.moveText(move)));
historyList_->addItem(row);
++ply;
}
if (historyList_->count() > 0) {
historyList_->scrollToBottom();
}
}
void MainWindow::updateStatus() {
const QString sideName = (game_.sideToMove() == Side::Cho) ? QStringLiteral("초 (楚)")
: QStringLiteral("한 (漢)");
if (game_.result() != GameResult::Playing) {
turnLabel_->setText(resultText());
checkLabel_->setText({});
statusBar()->showMessage(resultText());
board_->setInteractive(false);
} else {
const QString who = isHumanTurn() ? QStringLiteral("당신") : QStringLiteral("컴퓨터");
turnLabel_->setText(QStringLiteral("%1 차례 · %2").arg(sideName, who));
if (game_.isCheck(game_.sideToMove())) {
checkLabel_->setText(QStringLiteral("장군!"));
} else if (game_.isBikjang()) {
checkLabel_->setText(QStringLiteral("빅장 (궁이 마주 봄)"));
} else {
checkLabel_->setText({});
}
statusBar()->showMessage(QStringLiteral("수 %1").arg(game_.history().size()));
board_->setInteractive(isHumanTurn() && !aiBusy_);
}
const double cho = game_.pieceScore(Side::Cho) / 10.0;
const double han = game_.pieceScore(Side::Han) / 10.0;
scoreLabel_->setText(QStringLiteral("점수 초 %1 · 한 %2")
.arg(cho, 0, 'f', 1)
.arg(han, 0, 'f', 1));
bikjangButton_->setEnabled(game_.result() == GameResult::Playing && game_.bikjangEnabled()
&& game_.isBikjang() && !game_.isCheck(game_.sideToMove()) && !aiBusy_
&& isHumanTurn());
passButton_->setEnabled(game_.result() == GameResult::Playing && !game_.isCheck(game_.sideToMove())
&& !aiBusy_ && isHumanTurn());
undoButton_->setEnabled(!game_.history().empty() && !aiBusy_);
resignButton_->setEnabled(game_.result() == GameResult::Playing && !aiBusy_ && isHumanTurn());
refreshCaptures();
refreshHistory();
board_->update();
}
void MainWindow::undoMove() {
if (aiBusy_) {
return;
}
if (!game_.undo()) {
return;
}
if (settings_.mode != GameSettings::Mode::HumanVsHuman) {
game_.undo();
}
board_->clearSelection();
updateStatus();
}
void MainWindow::passMove() {
if (!isHumanTurn() || !game_.passTurn()) {
return;
}
board_->clearSelection();
updateStatus();
thinkIfAiTurn();
}
void MainWindow::resignGame() {
if (!isHumanTurn()) {
return;
}
const auto answer = QMessageBox::question(this, QStringLiteral("기권"),
QStringLiteral("이번 대국을 기권할까요?"));
if (answer != QMessageBox::Yes) {
return;
}
game_.resign(game_.sideToMove());
updateStatus();
}
void MainWindow::claimBikjang() {
if (!game_.claimBikjang()) {
return;
}
updateStatus();
QMessageBox::information(this, QStringLiteral("빅장"), QStringLiteral("빅장으로 무승부입니다."));
}