diff --git a/NineChess/src/aithread.cpp b/NineChess/src/aithread.cpp index 207e527a..72709978 100644 --- a/NineChess/src/aithread.cpp +++ b/NineChess/src/aithread.cpp @@ -1,9 +1,10 @@ #include #include "aithread.h" -AiThread::AiThread(QObject *parent) : QThread(parent), +AiThread::AiThread(int id, QObject *parent) : QThread(parent), waiting_(false) { + this->id = id; } AiThread::~AiThread() @@ -22,27 +23,46 @@ void AiThread::setAi(const NineChess &chess) void AiThread::run() { + // 测试用数据 int iTemp = 0; + // 设一个标识,1号线程只管玩家1,2号线程只管玩家2 + int i = 0; - forever{ - if (isInterruptionRequested()) - return; + while (!isInterruptionRequested()) { mutex.lock(); - if (waiting_) - pauseCondition.wait(&mutex); - mutex.unlock(); + if (chess->whosTurn() == NineChess::PLAYER1) + i = 1; + else if (chess->whosTurn() == NineChess::PLAYER2) + i = 2; + else + i = 0; - ai_ab.setChess(*chess); - ai_ab.alphaBetaPruning(5); + if (i != id || waiting_) { + pauseCondition.wait(&mutex); + mutex.unlock(); + continue; + } + else { + ai_ab.setChess(*chess); + mutex.unlock(); + } + + ai_ab.alphaBetaPruning(3); const char * str = ai_ab.bestMove(); qDebug() << str; if (strcmp(str, "error!")) emit command(str); + qDebug() << "Thread" << id << " run " << ++iTemp << "times"; - // 测试用 - qDebug() << "thread running " << iTemp++ << "times"; + // 执行完毕后继续判断 + if (!isInterruptionRequested()) { + mutex.lock(); + pauseCondition.wait(&mutex); + mutex.unlock(); + } } + qDebug() << "Thread" << id << " quit."; } void AiThread::pause() @@ -62,14 +82,13 @@ void AiThread::resume() void AiThread::stop() { - if(isFinished()) + if (isFinished() || !isRunning()) return; + if(!isInterruptionRequested()) + requestInterruption(); mutex.lock(); - requestInterruption(); - if (waiting_) { - waiting_ = false; - pauseCondition.wakeAll(); - } + waiting_ = false; + pauseCondition.wakeAll(); mutex.unlock(); } diff --git a/NineChess/src/aithread.h b/NineChess/src/aithread.h index db972cbf..caa1ad26 100644 --- a/NineChess/src/aithread.h +++ b/NineChess/src/aithread.h @@ -12,7 +12,7 @@ class AiThread : public QThread Q_OBJECT public: - explicit AiThread(QObject *parent = nullptr); + explicit AiThread(int id, QObject *parent = nullptr); ~AiThread(); signals: @@ -29,6 +29,8 @@ public slots: void stop(); private: + // 玩家ID + int id; // 互斥锁 QMutex mutex; // 线程等待标识,这里没用到,留着以后扩展用 diff --git a/NineChess/src/gamecontroller.cpp b/NineChess/src/gamecontroller.cpp index a686e169..d8b81154 100644 --- a/NineChess/src/gamecontroller.cpp +++ b/NineChess/src/gamecontroller.cpp @@ -30,7 +30,9 @@ GameController::GameController(GameScene &scene, QObject *parent) : QObject(pare timeID(0), ruleNo(-1), timeLimit(0), - stepsLimit(0) + stepsLimit(0), + ai1(1), + ai2(2) { // 已在view的样式表中添加背景,scene中不用添加背景 // 区别在于,view中的背景不随视图变换而变换,scene中的背景随视图变换而变换 @@ -53,6 +55,11 @@ GameController::~GameController() // 停止计时器 if (timeID != 0) killTimer(timeID); + // 停掉线程 + ai1.stop(); + ai2.stop(); + ai1.wait(); + ai2.wait(); } const QMap GameController::getActions() @@ -94,11 +101,9 @@ void GameController::gameReset() // 停掉线程 ai1.stop(); - ai1.quit(); - ai1.wait(); ai2.stop(); - ai2.quit(); - ai2.wait(); + isEngine1 = false; + isEngine2 = false; // 清除棋子 qDeleteAll(pieceList); @@ -331,29 +336,55 @@ void GameController::timerEvent(QTimerEvent *event) bool GameController::command(const QString &cmd, bool update /*= true*/) { - if (chess.command(cmd.toStdString().c_str())) { - if (chess.getPhase() == NineChess::GAME_NOTSTARTED) { - gameReset(); - gameStart(); - } - if (update) - updateScence(chess); - // 将新增的棋谱行插入到ListModel - currentRow = manualListModel.rowCount() - 1; - int k = 0; - // 输出命令行 - for (auto i = (chess.getCmdList())->begin(); i != (chess.getCmdList())->end(); ++i) { - // 跳过已添加的,因标准list容器没有下标 - if (k++ <= currentRow) - continue; - manualListModel.insertRow(++currentRow); - manualListModel.setData(manualListModel.index(currentRow), (*i).c_str()); - } - return true; - } - else { + // 防止接收滞后结束的线程发送的指令 + if (sender() == &ai1 && !isEngine1) return false; + if (sender() == &ai2 && !isEngine2) + return false; + + if (!chess.command(cmd.toStdString().c_str())) + return false; + + if (chess.getPhase() == NineChess::GAME_NOTSTARTED) { + gameReset(); + gameStart(); } + + if (update) + updateScence(chess); + + // 将新增的棋谱行插入到ListModel + currentRow = manualListModel.rowCount() - 1; + int k = 0; + // 输出命令行 + for (auto i = (chess.getCmdList())->begin(); i != (chess.getCmdList())->end(); ++i) { + // 跳过已添加的,因标准list容器没有下标 + if (k++ <= currentRow) + continue; + manualListModel.insertRow(++currentRow); + manualListModel.setData(manualListModel.index(currentRow), (*i).c_str()); + } + + // AI设置 + if (&chess == &(this->chess)) { + // 如果还未决出胜负 + if (chess.whoWin() == NineChess::NOBODY) { + if (chess.whosTurn() == NineChess::PLAYER1) { + if (isEngine1) + ai1.resume(); + } + else { + if (isEngine2) + ai2.resume(); + } + } + // 如果已经决出胜负 + else { + ai1.stop(); + ai2.stop(); + } + } + return true; } // 历史局面 @@ -724,27 +755,5 @@ bool GameController::updateScence(NineChess &chess) animationGroup->start(QAbstractAnimation::DeleteWhenStopped); - // AI设置 - if (&chess == &(this->chess)) { - // 如果还未决出胜负 - if (chess.whoWin() == NineChess::NOBODY) { - if (chess.whosTurn() == NineChess::PLAYER1) { - if(isEngine1) - ai1.resume(); - ai2.pause(); - } - else { - if(isEngine2) - ai2.resume(); - ai1.pause(); - } - } - // 如果已经决出胜负 - else { - ai1.stop(); - ai2.stop(); - } - } - return true; } diff --git a/NineChess/src/ninechess.cpp b/NineChess/src/ninechess.cpp index 32645eab..d3782f44 100644 --- a/NineChess/src/ninechess.cpp +++ b/NineChess/src/ninechess.cpp @@ -1165,13 +1165,13 @@ bool NineChess::command(int16_t move) if (move < 0) { return capture(-move); } - else if (move & 0x00ff) { - return place(move & 0x00ff); - } - else { + else if (move & 0x1f00) { if (choose(move >> 8)) return place(move & 0x00ff); } + else { + return place(move & 0x00ff); + } return false; } diff --git a/NineChess/src/ninechess.h b/NineChess/src/ninechess.h index 74488dea..e6eb7bd4 100644 --- a/NineChess/src/ninechess.h +++ b/NineChess/src/ninechess.h @@ -173,50 +173,50 @@ public: // 获取棋局状态和棋盘数据 void getData(struct Rule &rule, int &step, int &flags, const char *&boardsource, int &p1_InHand, int &p2_InHand, int &num_NeedRemove); // 获取当前规则 - const struct Rule *getRule() { return &rule; } + const struct Rule *getRule() const { return &rule; } // 获取棋盘数据 - const char *getBoard() { return data.board; } + const char *getBoard() const { return data.board; } // 获取棋子位置(c, p) bool getPieceCP(const Players &player, const int &number, int &c, int &p); // 获取当前棋子 bool getCurrentPiece(Players &player, int &number); // 获取当前棋子位置点 - int getCurrentPos() { return currentPos; } + int getCurrentPos() const { return currentPos; } // 获取当前步数 - int getStep() { return data.step; } + int getStep() const { return data.step; } // 获取局面阶段标识 - enum Phases getPhase() { return data.phase; } + enum Phases getPhase() const { return data.phase; } // 获取轮流状态标识 - enum Players whosTurn() { return data.turn; } + enum Players whosTurn() const { return data.turn; } // 获取动作状态标识 - enum Actions getAction() { return data.action; } + enum Actions getAction() const { return data.action; } // 判断胜负 - enum Players whoWin() { return winner; } + enum Players whoWin() const { return winner; } // 玩家1和玩家2的用时 void getPlayer_TimeMS(int &p1_ms, int &p2_ms); // 获取棋局的字符提示 - const string getTip() { return tip; } + const string getTip() const { return tip; } // 获取位置点棋子的归属人 enum Players getWhosPiece(int c, int p); // 获取当前招法 - const char *getCmdLine() { return cmdline; } + const char *getCmdLine() const { return cmdline; } // 获得棋谱 - const list * getCmdList() { return &cmdlist; } + const list * getCmdList() const { return &cmdlist; } // 获取开局时间 - timeb getStartTimeb() { return startTimeb; } + timeb getStartTimeb() const { return startTimeb; } // 重新设置开局时间 void setStartTimeb(timeb stimeb) { startTimeb = stimeb; } // 玩家1剩余未放置子数 - int getPlayer1_InHand() { return data.player1_InHand; } + int getPlayer1_InHand() const { return data.player1_InHand; } // 玩家2剩余未放置子数 - int getPlayer2_InHand() { return data.player2_InHand; } + int getPlayer2_InHand() const { return data.player2_InHand; } // 玩家1盘面剩余子数 - int getPlayer1_Remain() { return data.player1_Remain; } + int getPlayer1_Remain() const { return data.player1_Remain; } // 玩家1盘面剩余子数 - int getPlayer2_Remain() { return data.player2_Remain; } + int getPlayer2_Remain() const { return data.player2_Remain; } // 尚待去除的子数 - int getNum_NeedRemove() { return data.num_NeedRemove; } + int getNum_NeedRemove() const { return data.num_NeedRemove; } // 游戏重置 bool reset(); diff --git a/NineChess/src/ninechessai_ab.cpp b/NineChess/src/ninechessai_ab.cpp index a7c173d1..6e807564 100644 --- a/NineChess/src/ninechessai_ab.cpp +++ b/NineChess/src/ninechessai_ab.cpp @@ -7,6 +7,7 @@ #include "ninechessai_ab.h" #include #include +#include NineChessAi_ab::NineChessAi_ab(): rootNode(nullptr), @@ -125,7 +126,10 @@ void NineChessAi_ab::sortChildren(Node *node) i->value = evaluate(node); } // 排序 - node->children.sort([](Node *n1, Node *n2) { return n1->value > n2->value; }); + if(chessTemp.whosTurn() == NineChess::PLAYER1) + node->children.sort([](Node *n1, Node *n2) { return n1->value > n2->value; }); + else + node->children.sort([](Node *n1, Node *n2) { return n1->value < n2->value; }); } void NineChessAi_ab::deleteTree(Node *node) @@ -157,21 +161,21 @@ void NineChessAi_ab::setChess(const NineChess &chess) { // 对于0、2、4、6位(偶数位) if (!(j & 1)) { - boardScore[1 * NineChess::SEAT + j] = 80; + boardScore[1 * NineChess::SEAT + j] = 90; boardScore[2 * NineChess::SEAT + j] = 100; - boardScore[3 * NineChess::SEAT + j] = 80; + boardScore[3 * NineChess::SEAT + j] = 90; } // 对于有斜线情况下的1、3、5、7位(奇数位) else if(chessTemp.rule.hasObliqueLine) { - boardScore[1 * NineChess::SEAT + j] = 70; - boardScore[2 * NineChess::SEAT + j] = 90; - boardScore[3 * NineChess::SEAT + j] = 70; + boardScore[1 * NineChess::SEAT + j] = 85; + boardScore[2 * NineChess::SEAT + j] = 95; + boardScore[3 * NineChess::SEAT + j] = 85; } // 对于无斜线情况下的1、3、5、7位(奇数位) else { - boardScore[1 * NineChess::SEAT + j] = 60; - boardScore[2 * NineChess::SEAT + j] = 80; - boardScore[3 * NineChess::SEAT + j] = 60; + boardScore[1 * NineChess::SEAT + j] = 80; + boardScore[2 * NineChess::SEAT + j] = 85; + boardScore[3 * NineChess::SEAT + j] = 80; } } } @@ -203,9 +207,9 @@ int NineChessAi_ab::evaluate(Node *node) case NineChess::ACTION_CHOOSE: case NineChess::ACTION_PLACE: break; - // 如果形成去子状态,每有一个可去的子,算500分 + // 如果形成去子状态,每有一个可去的子,算1000分 case NineChess::ACTION_CAPTURE: - value += (chessData->turn == NineChess::PLAYER1) ? chessData->num_NeedRemove * 500 : -chessData->num_NeedRemove * 500; + value += (chessData->turn == NineChess::PLAYER1) ? chessData->num_NeedRemove * 1000 : -chessData->num_NeedRemove * 1000; break; default: break; @@ -253,7 +257,8 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) if (chessTemp.whosTurn() == NineChess::PLAYER1) { for (auto child : node->children) { dataStack.push(chessTemp.data); - chessTemp.command(child->move); + if(!chessTemp.command(child->move)) + qDebug() << child->move; value = alphaBetaPruning(depth - 1, alpha, beta, child); chessTemp.data = dataStack.top(); dataStack.pop(); @@ -262,6 +267,7 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) alpha = value; // 剪枝返回 if (alpha >= beta) { + node->value = alpha; return value; } } @@ -272,7 +278,8 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) else { for (auto child : node->children) { dataStack.push(chessTemp.data); - chessTemp.command(child->move); + if(!chessTemp.command(child->move)) + qDebug() << child->move; value = alphaBetaPruning(depth - 1, alpha, beta, child); chessTemp.data = dataStack.top(); dataStack.pop(); @@ -281,6 +288,7 @@ int NineChessAi_ab::alphaBetaPruning(int depth, int alpha, int beta, Node *node) beta = value; // 剪枝返回 if (alpha >= beta) { + node->value = beta; return value; } } diff --git a/NineChess/src/ninechesswindow.cpp b/NineChess/src/ninechesswindow.cpp index 819d3a18..d41ebdbf 100644 --- a/NineChess/src/ninechesswindow.cpp +++ b/NineChess/src/ninechesswindow.cpp @@ -27,7 +27,7 @@ NineChessWindow::NineChessWindow(QWidget *parent) : QMainWindow(parent), scene(nullptr), game(nullptr), - ruleNo(0) + ruleNo(-1) { ui.setupUi(this); //去掉标题栏 @@ -181,7 +181,6 @@ void NineChessWindow::initialize() // 默认第2号规则 ruleNo = 2; - // 设置选中当前规则的菜单项 ruleActionList.at(ruleNo)->setChecked(true); // 重置游戏规则 game->setRule(ruleNo); @@ -232,6 +231,83 @@ void NineChessWindow::ruleInfo() // .arg(tr(NineChess::RULES[ruleNo].info)); } +void NineChessWindow::on_actionLimited_T_triggered() +{ + /* 其实本来可以用设计器做个ui,然后从QDialog派生个自己的对话框 + * 但我不想再派生新类了,又要多出一个类和两个文件 + * 还要写与主窗口的接口,费劲 + * 于是手写QDialog界面 + */ + int gStep = game->getStepsLimit(); + int gTime = game->getTimeLimit(); + + // 定义新对话框 + QDialog *dialog = new QDialog(this); + dialog->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint); + dialog->setObjectName(QStringLiteral("Dialog")); + dialog->setWindowTitle(tr("步数和时间限制")); + dialog->resize(256, 108); + dialog->setModal(true); + // 生成各个控件 + QFormLayout *formLayout = new QFormLayout(dialog); + QLabel *label_step = new QLabel(dialog); + QLabel *label_time = new QLabel(dialog); + QComboBox *comboBox_step = new QComboBox(dialog); + QComboBox *comboBox_time = new QComboBox(dialog); + QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); + // 设置各个控件ObjectName,不设也没关系 + /*formLayout->setObjectName(QStringLiteral("formLayout")); + label_step->setObjectName(QStringLiteral("label_step")); + label_time->setObjectName(QStringLiteral("label_time")); + comboBox_step->setObjectName(QStringLiteral("comboBox_step")); + comboBox_time->setObjectName(QStringLiteral("comboBox_time")); + buttonBox->setObjectName(QStringLiteral("buttonBox"));*/ + // 设置各个控件数据 + label_step->setText(tr("超出限制步数判和:")); + label_time->setText(tr("任意一方超时判负:")); + comboBox_step->addItem(tr("无限制"), 0); + comboBox_step->addItem(tr("50步"), 50); + comboBox_step->addItem(tr("100步"), 100); + comboBox_step->addItem(tr("200步"), 200); + comboBox_time->addItem(tr("无限制"), 0); + comboBox_time->addItem(tr("5分钟"), 5); + comboBox_time->addItem(tr("10分钟"), 10); + comboBox_time->addItem(tr("20分钟"), 20); + comboBox_step->setCurrentIndex(comboBox_step->findData(gStep)); + comboBox_time->setCurrentIndex(comboBox_time->findData(gTime)); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + buttonBox->setCenterButtons(true); + buttonBox->button(QDialogButtonBox::Ok)->setText(tr("确定")); + buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("取消")); + // 布局 + formLayout->setSpacing(6); + formLayout->setContentsMargins(11, 11, 11, 11); + formLayout->setWidget(0, QFormLayout::LabelRole, label_step); + formLayout->setWidget(0, QFormLayout::FieldRole, comboBox_step); + formLayout->setWidget(1, QFormLayout::LabelRole, label_time); + formLayout->setWidget(1, QFormLayout::FieldRole, comboBox_time); + formLayout->setWidget(2, QFormLayout::SpanningRole, buttonBox); + // 关联信号和槽函数 + connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); + // 收集数据 + if (dialog->exec() == QDialog::Accepted) { + int dStep = comboBox_step->currentData().toInt(); + int dTime = comboBox_time->currentData().toInt(); + if (gStep != dStep || gTime != dTime) { + // 重置游戏规则 + game->setRule(ruleNo, dStep, dTime); + } + } + + // 删除对话框,子控件会一并删除 + dialog->disconnect(); + delete dialog; + + // 更新规则显示 + ruleInfo(); +} + void NineChessWindow::actionRules_triggered() { // 取消其它规则的选择 @@ -242,9 +318,14 @@ void NineChessWindow::actionRules_triggered() action->setChecked(true); ruleNo = action->data().toInt(); + // 如果游戏规则没变化,则返回 + if (ruleNo == game->getRuleNo()) + return; + // 取消AI设定 ui.actionEngine1_T->setChecked(false); ui.actionEngine2_R->setChecked(false); + // 重置游戏规则 game->setRule(ruleNo); // 更新规则显示 @@ -255,6 +336,10 @@ void NineChessWindow::on_actionNew_N_triggered() { if (file.isOpen()) file.close(); + // 取消AI设定 + ui.actionEngine1_T->setChecked(false); + ui.actionEngine2_R->setChecked(false); + // 重置游戏规则 game->gameReset(); } @@ -280,7 +365,10 @@ void NineChessWindow::on_actionOpen_O_triggered() bool isok = file.open(QFileDevice::ReadOnly | QFileDevice::Text); if (isok) { - //读文件 + // 取消AI设定 + ui.actionEngine1_T->setChecked(false); + ui.actionEngine2_R->setChecked(false); + // 读文件 QTextStream textStream(&file); QString cmd; cmd = textStream.readLine(); @@ -520,84 +608,6 @@ void NineChessWindow::on_actionAutoRun_A_toggled(bool arg1) ui.actionAutoRun_A->setChecked(false); } -void NineChessWindow::on_actionLimited_T_triggered() -{ - /* 其实本来可以用设计器做个ui,然后从QDialog派生个自己的对话框 - * 但我不想再派生新类了,又要多出一个类和两个文件 - * 还要写与主窗口的接口,费劲 - * 于是手写QDialog界面 - */ - int stepLimited = game->getStepsLimit(); - int timeLimited = game->getTimeLimit(); - - // 定义新对话框 - QDialog *dialog = new QDialog(this); - dialog->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint); - dialog->setObjectName(QStringLiteral("Dialog")); - dialog->setWindowTitle(tr("步数和时间限制")); - dialog->resize(256, 108); - dialog->setModal(true); - // 生成各个控件 - QFormLayout *formLayout = new QFormLayout(dialog); - QLabel *label_step = new QLabel(dialog); - QLabel *label_time = new QLabel(dialog); - QComboBox *comboBox_step = new QComboBox(dialog); - QComboBox *comboBox_time = new QComboBox(dialog); - QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); - // 设置各个控件ObjectName,不设也没关系 - /*formLayout->setObjectName(QStringLiteral("formLayout")); - label_step->setObjectName(QStringLiteral("label_step")); - label_time->setObjectName(QStringLiteral("label_time")); - comboBox_step->setObjectName(QStringLiteral("comboBox_step")); - comboBox_time->setObjectName(QStringLiteral("comboBox_time")); - buttonBox->setObjectName(QStringLiteral("buttonBox"));*/ - // 设置各个控件数据 - label_step->setText(tr("超出限制步数判和:")); - label_time->setText(tr("任意一方超时判负:")); - comboBox_step->addItem(tr("无限制"), 0); - comboBox_step->addItem(tr("50步"), 50); - comboBox_step->addItem(tr("100步"), 100); - comboBox_step->addItem(tr("200步"), 200); - comboBox_time->addItem(tr("无限制"), 0); - comboBox_time->addItem(tr("5分钟"), 5); - comboBox_time->addItem(tr("10分钟"), 10); - comboBox_time->addItem(tr("20分钟"), 20); - comboBox_step->setCurrentIndex(comboBox_step->findData(stepLimited)); - comboBox_time->setCurrentIndex(comboBox_time->findData(timeLimited)); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - buttonBox->setCenterButtons(true); - buttonBox->button(QDialogButtonBox::Ok)->setText(tr("确定")); - buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("取消")); - // 布局 - formLayout->setSpacing(6); - formLayout->setContentsMargins(11, 11, 11, 11); - formLayout->setWidget(0, QFormLayout::LabelRole, label_step); - formLayout->setWidget(0, QFormLayout::FieldRole, comboBox_step); - formLayout->setWidget(1, QFormLayout::LabelRole, label_time); - formLayout->setWidget(1, QFormLayout::FieldRole, comboBox_time); - formLayout->setWidget(2, QFormLayout::SpanningRole, buttonBox); - // 关联信号和槽函数 - connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); - // 收集数据 - if (dialog->exec() == QDialog::Accepted) { - stepLimited = comboBox_step->currentData().toInt(); - timeLimited = comboBox_time->currentData().toInt(); - // 选择当前规则 - QAction *action = dynamic_cast(sender()); - action->setChecked(true); - int ruleNo = action->data().toInt(); - // 重置游戏规则 - game->setRule(ruleNo, stepLimited, timeLimited); - } - - // 删除对话框,子控件会一并删除 - delete dialog; - - // 更新规则显示 - ruleInfo(); -} - void NineChessWindow::on_actionLocal_L_triggered() { ui.actionLocal_L->setChecked(true);