From be80990f93f6efb0a82432c3a0335055dbfd7151 Mon Sep 17 00:00:00 2001 From: CalciteM Team Date: Fri, 28 Jun 2019 23:05:28 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NineChess/src/aithread.cpp | 4 + NineChess/src/boarditem.cpp | 83 +++++++++++---------- NineChess/src/boarditem.h | 9 ++- NineChess/src/config.h | 5 +- NineChess/src/gamecontroller.cpp | 26 +++++-- NineChess/src/gamescene.cpp | 11 +-- NineChess/src/gameview.cpp | 3 + NineChess/src/manuallistview.h | 9 ++- NineChess/src/ninechess.cpp | 47 +++++++++--- NineChess/src/ninechess.h | 5 +- NineChess/src/ninechesswindow.cpp | 119 +++++++++++++++++++++++------- NineChess/src/pieceitem.h | 8 ++ 12 files changed, 231 insertions(+), 98 deletions(-) diff --git a/NineChess/src/aithread.cpp b/NineChess/src/aithread.cpp index c351bbcb..e8d0190d 100644 --- a/NineChess/src/aithread.cpp +++ b/NineChess/src/aithread.cpp @@ -54,6 +54,7 @@ void AiThread::run() while (!isInterruptionRequested()) { mutex.lock(); + if (chess_->whosTurn() == NineChess::PLAYER1) i = 1; else if (chess_->whosTurn() == NineChess::PLAYER2) @@ -74,11 +75,14 @@ void AiThread::run() ai_ab.alphaBetaPruning(aiDepth); const char *str = ai_ab.bestMove(); qDebug() << "Computer:" << str << "\n"; + if (strcmp(str, "error!")) emit command(str); + #ifdef DEBUG qDebug() << "Thread" << id << "run" << ++iTemp << "times"; #endif + emit calcFinished(); // 执行完毕后继续判断 diff --git a/NineChess/src/boarditem.cpp b/NineChess/src/boarditem.cpp index 728773b9..2a0130f8 100644 --- a/NineChess/src/boarditem.cpp +++ b/NineChess/src/boarditem.cpp @@ -3,40 +3,41 @@ #include BoardItem::BoardItem(QGraphicsItem *parent) : QGraphicsItem(), -size(BOARD_SIZE), -sizeShadow(5.0), -hasObliqueLine(false) + size(BOARD_SIZE), + sizeShadow(5.0), + hasObliqueLine(false) { Q_UNUSED(parent) + // 棋盘中心放在场景中心 setPos(0, 0); + // 初始化24个落子点 - for (int i = 0; i < RING; i++) { + for (int i = 0; i < N_RINGS; i++) { // 内圈的12点钟方向为第一个位置,按顺时针方向排序 // 然后是中圈和外圈 qreal a = (i + 1) * LINE_INTERVAL; - position[i * SEAT + 0].rx() = 0; - position[i * SEAT + 0].ry() = -a; - position[i * SEAT + 1].rx() = a; - position[i * SEAT + 1].ry() = -a; - position[i * SEAT + 2].rx() = a; - position[i * SEAT + 2].ry() = 0; - position[i * SEAT + 3].rx() = a; - position[i * SEAT + 3].ry() = a; - position[i * SEAT + 4].rx() = 0; - position[i * SEAT + 4].ry() = a; - position[i * SEAT + 5].rx() = -a; - position[i * SEAT + 5].ry() = a; - position[i * SEAT + 6].rx() = -a; - position[i * SEAT + 6].ry() = 0; - position[i * SEAT + 7].rx() = -a; - position[i * SEAT + 7].ry() = -a; + position[i * N_SEATS + 0].rx() = 0; + position[i * N_SEATS + 0].ry() = -a; + position[i * N_SEATS + 1].rx() = a; + position[i * N_SEATS + 1].ry() = -a; + position[i * N_SEATS + 2].rx() = a; + position[i * N_SEATS + 2].ry() = 0; + position[i * N_SEATS + 3].rx() = a; + position[i * N_SEATS + 3].ry() = a; + position[i * N_SEATS + 4].rx() = 0; + position[i * N_SEATS + 4].ry() = a; + position[i * N_SEATS + 5].rx() = -a; + position[i * N_SEATS + 5].ry() = a; + position[i * N_SEATS + 6].rx() = -a; + position[i * N_SEATS + 6].ry() = 0; + position[i * N_SEATS + 7].rx() = -a; + position[i * N_SEATS + 7].ry() = -a; } } BoardItem::~BoardItem() { - } QRectF BoardItem::boundingRect() const @@ -57,19 +58,19 @@ void BoardItem::setDiagonal(bool arg) update(boundingRect()); } - void BoardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option) - Q_UNUSED(widget) + Q_UNUSED(widget) // 填充阴影 painter->fillRect(boundingRect(), QBrush(QColor(64, 64, 64))); // 填充图片 - painter->drawPixmap(-size / 2, -size / 2, size, size, QPixmap(":/image/resources/image/board.png")); + painter->drawPixmap(-size / 2, -size / 2, size, size, + QPixmap(":/image/resources/image/board.png")); // 黑色实线画笔 QPen pen(QBrush(Qt::black), LINE_WEIGHT, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin); @@ -78,25 +79,26 @@ void BoardItem::paint(QPainter *painter, // 空画刷 painter->setBrush(Qt::NoBrush); - for (int i = 0; i < RING; i++) { + for (uint8_t i = 0; i < N_RINGS; i++) { // 画3个方框 - painter->drawPolygon(position + i * SEAT, SEAT); + painter->drawPolygon(position + i * N_SEATS, N_SEATS); } // 画4条纵横线 - painter->drawLine(position[0], position[(RING - 1) * SEAT]); - painter->drawLine(position[2], position[(RING - 1) * SEAT + 2]); - painter->drawLine(position[4], position[(RING - 1) * SEAT + 4]); - painter->drawLine(position[6], position[(RING - 1) * SEAT + 6]); + painter->drawLine(position[0], position[(N_RINGS - 1) * N_SEATS]); + painter->drawLine(position[2], position[(N_RINGS - 1) * N_SEATS + 2]); + painter->drawLine(position[4], position[(N_RINGS - 1) * N_SEATS + 4]); + painter->drawLine(position[6], position[(N_RINGS - 1) * N_SEATS + 6]); if (hasObliqueLine) { // 画4条斜线 - painter->drawLine(position[1], position[(RING - 1) * SEAT + 1]); - painter->drawLine(position[3], position[(RING - 1) * SEAT + 3]); - painter->drawLine(position[5], position[(RING - 1) * SEAT + 5]); - painter->drawLine(position[7], position[(RING - 1) * SEAT + 7]); + painter->drawLine(position[1], position[(N_RINGS - 1) * N_SEATS + 1]); + painter->drawLine(position[3], position[(N_RINGS - 1) * N_SEATS + 3]); + painter->drawLine(position[5], position[(N_RINGS - 1) * N_SEATS + 5]); + painter->drawLine(position[7], position[(N_RINGS - 1) * N_SEATS + 7]); } +#ifdef DRAW_SEAT_NUMBER // 画 Seat 编号 QPen fontPen(QBrush(Qt::white), LINE_WEIGHT, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin); painter->setPen(fontPen); @@ -109,8 +111,9 @@ void BoardItem::paint(QPainter *painter, for (int i = 0; i < 8; i++) { char cSeat = '1' + i; QString strSeat(cSeat); - painter->drawText(position[(RING - 1) * SEAT + i], strSeat); + painter->drawText(position[(N_RINGS - 1) * N_SEATS + i], strSeat); } +#endif // DRAW_SEAT_NUMBER } QPointF BoardItem::nearestPosition(QPointF const pos) @@ -119,7 +122,7 @@ QPointF BoardItem::nearestPosition(QPointF const pos) QPointF nearestPos = QPointF(0, 0); // 寻找最近的落子点 - for (int i = 0; i < RING * SEAT; i++) { + for (int i = 0; i < N_RINGS * N_SEATS; i++) { // 如果鼠标点距离落子点在棋子半径内 if (QLineF(pos, position[i]).length() < PIECE_SIZE / 2) { nearestPos = position[i]; @@ -131,17 +134,17 @@ QPointF BoardItem::nearestPosition(QPointF const pos) QPointF BoardItem::cp2pos(int c, int p) { - return position[(c - 1) * SEAT + p - 1]; + return position[(c - 1) * N_SEATS + p - 1]; } bool BoardItem::pos2cp(QPointF pos, int &c, int &p) { // 寻找最近的落子点 - for (int i = 0; i < RING * SEAT; i++) { + for (int i = 0; i < N_RINGS * N_SEATS; i++) { // 如果pos点在落子点附近 if (QLineF(pos, position[i]).length() < PIECE_SIZE / 6) { - c = i / SEAT + 1; - p = i % SEAT + 1; + c = i / N_SEATS + 1; + p = i % N_SEATS + 1; return true; } } diff --git a/NineChess/src/boarditem.h b/NineChess/src/boarditem.h index fdaeebdb..a8b22b0b 100644 --- a/NineChess/src/boarditem.h +++ b/NineChess/src/boarditem.h @@ -10,8 +10,11 @@ class BoardItem : public QGraphicsItem public: explicit BoardItem(QGraphicsItem *parent = nullptr); ~BoardItem(); + QRectF boundingRect() const; + QPainterPath shape() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr); @@ -40,10 +43,10 @@ public: bool pos2cp(QPointF pos, int &c, int &p); // 3圈,禁止修改! - static const int RING = 3; + static const uint8_t N_RINGS = 3; // 8位,禁止修改! - static const int SEAT = 8; + static const uint8_t N_SEATS = 8; private: // 棋盘尺寸 @@ -53,7 +56,7 @@ private: qreal sizeShadow; // 24个落子点 - QPointF position[RING * SEAT]; + QPointF position[N_RINGS * N_SEATS]; // 是否有斜线 bool hasObliqueLine; diff --git a/NineChess/src/config.h b/NineChess/src/config.h index 89edeb37..f6bd8a30 100644 --- a/NineChess/src/config.h +++ b/NineChess/src/config.h @@ -11,6 +11,9 @@ //#define AB_RANDOM_SORT_CHILDREN // Բ (ķѴڴ) -#define DEBUG_AB_TREE +//#define DEBUG_AB_TREE + +// SEAT +#define DRAW_SEAT_NUMBER #endif // CONFIG_H \ No newline at end of file diff --git a/NineChess/src/gamecontroller.cpp b/NineChess/src/gamecontroller.cpp index 16000637..f6dc8f0e 100644 --- a/NineChess/src/gamecontroller.cpp +++ b/NineChess/src/gamecontroller.cpp @@ -48,7 +48,8 @@ GameController::GameController(GameScene & scene, QObject * parent) : connect(&ai2, SIGNAL(command(const QString &, bool)), this, SLOT(command(const QString &, bool))); - // 安装事件过滤器监视scene的各个事件,由于我重载了QGraphicsScene,相关事件在重载函数中已设定,不必安装监视器。 + // 安装事件过滤器监视scene的各个事件, + // 由于我重载了QGraphicsScene,相关事件在重载函数中已设定,不必安装监视器。 //scene.installEventFilter(this); } @@ -70,6 +71,7 @@ const QMap GameController::getActions() // 主窗口更新菜单栏 // 之所以不用信号和槽的模式,是因为发信号的时候槽还来不及关联 QMap actions; + for (int i = 0; i < NineChess::N_RULES; i++) { // QMap的key存放int索引值,value存放规则名称和规则提示 QStringList strlist; @@ -77,6 +79,7 @@ const QMap GameController::getActions() strlist.append(tr(NineChess::RULES[i].description)); actions.insert(i, strlist); } + return actions; } @@ -223,6 +226,7 @@ void GameController::setRule(int ruleNo, int stepLimited /*= -1*/, int timeLimit stepsLimit = stepLimited; timeLimit = timeLimited; } + // 设置模型规则,重置游戏 chess_.setContext(&NineChess::RULES[ruleNo], stepsLimit, timeLimit); chessTemp = chess_; @@ -368,6 +372,7 @@ void GameController::mirror() // 更新棋谱 int row = 0; + for (auto str : *(chess_.getCmdList())) { manualListModel.setData(manualListModel.index(row++), str.c_str()); } @@ -382,9 +387,11 @@ void GameController::mirror() ai1.setAi(chess_); ai2.setAi(chess_); + if (isEngine1) { ai1.start(); } + if (isEngine2) { ai2.start(); } @@ -407,6 +414,7 @@ void GameController::turnRight() // 更新棋谱 int row = 0; + for (auto str : *(chess_.getCmdList())) { manualListModel.setData(manualListModel.index(row++), str.c_str()); } @@ -419,9 +427,11 @@ void GameController::turnRight() ai1.setAi(chess_); ai2.setAi(chess_); + if (isEngine1) { ai1.start(); } + if (isEngine2) { ai2.start(); } @@ -478,6 +488,7 @@ void GameController::timerEvent(QTimerEvent *event) qt1 = QTime(0, 0, 0, 0).addMSecs(remainingTime1); qt2 = QTime(0, 0, 0, 0).addMSecs(remainingTime2); + emit time1Changed(qt1.toString("mm:ss.zzz")); emit time2Changed(qt2.toString("mm:ss.zzz")); @@ -557,6 +568,7 @@ bool GameController::actionPiece(QPointF pos) // 如果再决出胜负后悔棋,则重新启动计时 if (chess_.whoWin() == NineChess::NOBODY) { + // 重新启动计时 timeID = startTimer(100); @@ -591,7 +603,7 @@ bool GameController::actionPiece(QPointF pos) break; } - // 如果移子不成功,尝试重新选子,这里不break + // 如果移子不成功,尝试重新选子,这里不break case NineChess::ACTION_CHOOSE: piece = qgraphicsitem_cast(item); @@ -687,6 +699,7 @@ bool GameController::giveUp() // 将新增的棋谱行插入到ListModel currentRow = manualListModel.rowCount() - 1; int k = 0; + // 输出命令行 for (auto i = (chess_.getCmdList())->begin(); i != (chess_.getCmdList())->end(); ++i) { // 跳过已添加的,因标准list容器没有下标 @@ -698,6 +711,7 @@ bool GameController::giveUp() if (chess_.whoWin() != NineChess::NOBODY) playSound(":/sound/resources/sound/loss.wav"); } + return result; } @@ -706,14 +720,16 @@ bool GameController::command(const QString &cmd, bool update /*= true*/) { Q_UNUSED(hasSound) - // 防止接收滞后结束的线程发送的指令 - if (sender() == &ai1 && !isEngine1) - return false; + // 防止接收滞后结束的线程发送的指令 + if (sender() == &ai1 && !isEngine1) + return false; + if (sender() == &ai2 && !isEngine2) return false; // 声音 QString sound; + switch (chess_.getAction()) { case NineChess::ACTION_CHOOSE: case NineChess::ACTION_PLACE: diff --git a/NineChess/src/gamescene.cpp b/NineChess/src/gamescene.cpp index 65450fdd..9f0d3ca6 100644 --- a/NineChess/src/gamescene.cpp +++ b/NineChess/src/gamescene.cpp @@ -29,14 +29,14 @@ GameScene::~GameScene() } // 屏蔽掉Shift和Control按键,事实证明没用,按键事件未必由视图类处理 -/* +#if 0 void GameScene::keyPressEvent(QKeyEvent *keyEvent) { if(keyEvent->key() == Qt::Key_Shift || keyEvent->key() == Qt::Key_Control) return; QGraphicsScene::keyPressEvent(keyEvent); } -*/ +#endif void GameScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) { @@ -47,9 +47,10 @@ void GameScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) void GameScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { - //屏蔽鼠标按下事件 + // 屏蔽鼠标按下事件 mouseEvent->accept(); - /* + +#if 0 // 只处理左键事件 if(mouseEvent->button() != Qt::LeftButton) return; @@ -62,7 +63,7 @@ void GameScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) // 调用默认事件处理函数 //QGraphicsScene::mousePressEvent(mouseEvent); - */ +#endif } void GameScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) diff --git a/NineChess/src/gameview.cpp b/NineChess/src/gameview.cpp index d193a3a3..4b1fdd3f 100644 --- a/NineChess/src/gameview.cpp +++ b/NineChess/src/gameview.cpp @@ -31,9 +31,11 @@ void GameView::flip() * │0 -1 0│ * └0 0 1┘ */ + // 方法一: 直接在原变换矩阵基础上乘以上面的矩阵 // QMatrix只对变换矩阵前两列赋值 setMatrix(matrix() * QMatrix(1, 0, 0, -1, 0, 0)); + /* 方法二: 人工计算好新的变换矩阵后再对场景赋值 * 这个方法的效率未必高,还需要人工计算 QMatrix mt = matrix(); @@ -98,6 +100,7 @@ void GameView::resizeEvent(QResizeEvent *event) scale(sx, sy); //qDebug() << "scale :" << sx; */ + // 使用如下形式,更简洁 QGraphicsView::resizeEvent(event); fitInView(sceneRect(), Qt::KeepAspectRatio); diff --git a/NineChess/src/manuallistview.h b/NineChess/src/manuallistview.h index 520816fc..86417dda 100644 --- a/NineChess/src/manuallistview.h +++ b/NineChess/src/manuallistview.h @@ -1,4 +1,5 @@ -/* QListView派生类 +/* + * QListView派生类 * 之所以要派生这个类,重载sizeHint函数 * 只是为了让停靠栏(父窗口)在初始时不至于过宽难看 * QDockWidget没有很好的控制初始大小的方法,resize函数没效果 @@ -25,6 +26,7 @@ public: { Q_UNUSED(parent) } + QSize sizeHint() const { QSize size = QListView::sizeHint(); @@ -55,10 +57,11 @@ protected slots: newEmptyRow = true; } +#if 0 /* 本来重载rowsInserted函数用于在插入新行后自动选中最后一行, 但是,在关联Model的insertRow执行后rowsInserted会被立即执行, 此时,Model的setData还未被执行,会选中一个空行。 - 所以不再采用这种方式,而是在控制模块中指定。 + 所以不再采用这种方式,而是在控制模块中指定。*/ void rowsInserted(const QModelIndex &parent, int start, int end) { // 调用父类函数,为使滚动条更新,否则scrollToBottom不能正确执行。 QListView::rowsInserted(parent, start, end); @@ -66,7 +69,7 @@ protected slots: setCurrentIndex(id); scrollToBottom(); } - */ +#endif // 采用判断最后一个元素是否改变来选中之 void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, diff --git a/NineChess/src/ninechess.cpp b/NineChess/src/ninechess.cpp index c2cd85bf..2bc9db34 100644 --- a/NineChess/src/ninechess.cpp +++ b/NineChess/src/ninechess.cpp @@ -194,14 +194,17 @@ void NineChess::createMoveTable() for (int s = 0; s < N_SEATS; s++) { // 顺时针走一步的位置 moveTable[r * N_SEATS + s][0] = r * N_SEATS + (s + 1) % N_SEATS; + // 逆时针走一步的位置 moveTable[r * N_SEATS + s][1] = r * N_SEATS + (s + N_SEATS - 1) % N_SEATS; + // 如果是 0、2、4、6位(偶数位)或是有斜线 if (!(s & 1) || this->currentRule.hasObliqueLines) { if (r > 1) { // 向内走一步的位置 moveTable[r * N_SEATS + s][2] = (r - 1) * N_SEATS + s; } + if (r < N_RINGS) { // 向外走一步的位置 moveTable[r * N_SEATS + s][3] = (r + 1) * N_SEATS + s; @@ -1210,15 +1213,15 @@ bool NineChess::choose(int pos) uint64_t NineChess::chessHash() { /* - hash各数据位详解(名为hash,但实际并无冲突,是算法用到的棋局数据的完全表示) - 57-64位:空白不用,全为0 - 56位:轮流标识,0为先手,1为后手 - 55位:动作标识,落子(选子移动)为0,1为去子 - 7-54位(共48位):从棋盘第一个位置点到最后一个位置点的棋子,每个点用2个二进制位表示,共24个位置点,即48位。 - 0b00表示空白,0b01表示先手棋子,0b10表示后手棋子,0b11表示禁点 - 5-6位(共2位):待去子数,最大为3,用2个二进制位表示即可 - 1-4位:player1的手棋数,不需要player2的(可计算出) - */ + * hash各数据位详解(名为hash,但实际并无冲突,是算法用到的棋局数据的完全表示) + * 57-64位:空白不用,全为0 + * 56位:轮流标识,0为先手,1为后手 + * 55位:动作标识,落子(选子移动)为0,1为去子 + * 7-54位(共48位):从棋盘第一个位置点到最后一个位置点的棋子,每个点用2个二进制位表示,共24个位置点,即48位。 + * 0b00表示空白,0b01表示先手棋子,0b10表示后手棋子,0b11表示禁点 + * 5-6位(共2位):待去子数,最大为3,用2个二进制位表示即可 + * 1-4位:player1的手棋数,不需要player2的(可计算出) + */ uint64_t hash = 0ull; for (int i = N_SEATS; i < (N_RINGS + 1) * N_SEATS; i++) { @@ -1332,6 +1335,7 @@ bool NineChess::command(int move) } else { return place(move & 0x00ff); } + return false; } @@ -1346,6 +1350,7 @@ inline long NineChess::update(long time_p /*= -1*/) case NineChess::GAME_PLACING: case NineChess::GAME_MOVING: ftime(¤tTimeb); + // 更新时间 if (time_p >= *player_ms) { *player_ms = ret = time_p; @@ -1361,14 +1366,19 @@ inline long NineChess::update(long time_p /*= -1*/) *player_ms = ret = (long)(currentTimeb.time - startTimeb.time) * 1000 + (currentTimeb.millitm - startTimeb.millitm) - playerNext_ms; } + // 有限时要求则判断胜负 if (currentRule.maxTimeLedToLose > 0) win(); + return ret; + case NineChess::GAME_NOTSTARTED: return ret; + case NineChess::GAME_OVER: return ret; + default: return ret; } @@ -1640,20 +1650,23 @@ bool NineChess::isAllSurrounded(char ch) bool NineChess::isAllSurrounded(enum Player ply) { char t = '\x30'; + if (ply == PLAYER1) t &= '\x10'; else if (ply == PLAYER2) t &= '\x20'; + return isAllSurrounded(t); } void NineChess::cleanForbiddenPoints() { - for (int i = 1; i <= N_RINGS; i++) + for (int i = 1; i <= N_RINGS; i++) { for (int j = 0; j < N_SEATS; j++) { if (board_[i * N_SEATS + j] == '\x0f') board_[i * N_SEATS + j] = '\x00'; } + } } enum NineChess::Player NineChess::changeTurn() @@ -1669,6 +1682,7 @@ void NineChess::setTips() case NineChess::GAME_NOTSTARTED: tips = "轮到黑方落子,剩余" + std::to_string(context.nPiecesInHand_1) + "子"; break; + case NineChess::GAME_PLACING: if (context.action == ACTION_PLACE) { if (context.turn == PLAYER1) { @@ -1684,6 +1698,7 @@ void NineChess::setTips() } } break; + case NineChess::GAME_MOVING: if (context.action == ACTION_PLACE || context.action == ACTION_CHOOSE) { if (context.turn == PLAYER1) { @@ -1699,6 +1714,7 @@ void NineChess::setTips() } } break; + case NineChess::GAME_OVER: if (winner == DRAW) tips = "超出限定步数,双方平局"; @@ -1714,6 +1730,7 @@ void NineChess::setTips() tips = "白方获胜!"; } break; + default: break; } @@ -1956,7 +1973,9 @@ void NineChess::turn(bool cmdChange /*= true*/) int args = 0; int mm = 0, ss = 0, mss = 0; - args = sscanf(cmdline, "(%1u,%1u)->(%1u,%1u) %2u:%2u.%3u", &c1, &p1, &c2, &p2, &mm, &ss, &mss); + args = sscanf(cmdline, "(%1u,%1u)->(%1u,%1u) %2u:%2u.%3u", + &c1, &p1, &c2, &p2, &mm, &ss, &mss); + if (args >= 4) { if (c1 == 1) c1 = N_RINGS; @@ -1989,7 +2008,10 @@ void NineChess::turn(bool cmdChange /*= true*/) } for (auto itor = cmdlist.begin(); itor != cmdlist.end(); itor++) { - args = sscanf((*itor).c_str(), "(%1u,%1u)->(%1u,%1u) %2u:%2u.%3u", &c1, &p1, &c2, &p2, &mm, &ss, &mss); + args = sscanf((*itor).c_str(), + "(%1u,%1u)->(%1u,%1u) %2u:%2u.%3u", + &c1, &p1, &c2, &p2, &mm, &ss, &mss); + if (args >= 4) { if (c1 == 1) c1 = N_RINGS; @@ -2028,6 +2050,7 @@ void NineChess::rotate(int degrees, bool cmdChange /*= true*/) { // 将degrees转化为0~359之间的数 degrees = degrees % 360; + if (degrees < 0) degrees += 360; diff --git a/NineChess/src/ninechess.h b/NineChess/src/ninechess.h index 757d77f4..9e0cb1f1 100644 --- a/NineChess/src/ninechess.h +++ b/NineChess/src/ninechess.h @@ -171,7 +171,7 @@ public: // 尚待去除的子数 int nPiecesNeedRemove; - /* +#if 0 本打算用如下的结构体来表示“三连” struct Mill { char piece1; // “三连”中最小的棋子 @@ -184,7 +184,8 @@ public: 但为了提高执行效率改用一个64位整数了,规则如下 0x 00 00 00 00 00 00 00 00 unused unused piece1 pos1 piece2 pos2 piece3 pos3 - */ +#endif + // “三连列表” list millList; }; diff --git a/NineChess/src/ninechesswindow.cpp b/NineChess/src/ninechesswindow.cpp index 732d9435..b61879db 100644 --- a/NineChess/src/ninechesswindow.cpp +++ b/NineChess/src/ninechesswindow.cpp @@ -24,6 +24,7 @@ #include #include #include + #include "ninechesswindow.h" #include "gamecontroller.h" #include "gamescene.h" @@ -37,17 +38,22 @@ NineChessWindow::NineChessWindow(QWidget * parent) : autoRunTimer(this) { ui.setupUi(this); - //去掉标题栏 + + // 去掉标题栏 //setWindowFlags(Qt::FramelessWindowHint); - //设置透明(窗体标题栏不透明,背景透明,如果不去掉标题栏,背景就变为黑色) + + // 设置透明(窗体标题栏不透明,背景透明,如果不去掉标题栏,背景就变为黑色) //setAttribute(Qt::WA_TranslucentBackground); - //设置全体透明度系数 + + // 设置全体透明度系数 //setWindowOpacity(0.7); // 设置场景 scene = new GameScene(this); + // 设置场景尺寸大小为棋盘大小的1.08倍 - scene->setSceneRect(-BOARD_SIZE * 0.54, -BOARD_SIZE * 0.54, BOARD_SIZE * 1.08, BOARD_SIZE * 1.08); + scene->setSceneRect(-BOARD_SIZE * 0.54, -BOARD_SIZE * 0.54, + BOARD_SIZE * 1.08, BOARD_SIZE * 1.08); // 初始化各个控件 @@ -87,6 +93,7 @@ NineChessWindow::~NineChessWindow() game->disconnect(); game->deleteLater(); } + qDeleteAll(ruleActionList); } @@ -97,7 +104,9 @@ void NineChessWindow::closeEvent(QCloseEvent *event) // 取消自动运行 ui.actionAutoRun_A->setChecked(false); + //qDebug() << "closed"; + QMainWindow::closeEvent(event); } @@ -116,6 +125,7 @@ bool NineChessWindow::eventFilter(QObject *watched, QEvent *event) break; } } + return QMainWindow::eventFilter(watched, event); } @@ -130,6 +140,7 @@ void NineChessWindow::initialize() // 添加新菜单栏动作 QMap actions = game->getActions(); + for (auto i = actions.constBegin(); i != actions.constEnd(); i++) { // qDebug() << i.key() << i.value(); // QMap的key存放int索引值,value存放规则名称和规则提示 @@ -151,31 +162,40 @@ void NineChessWindow::initialize() } // 关联主窗口动作信号和控制器的槽 + connect(ui.actionGiveUp_G, SIGNAL(triggered()), game, SLOT(giveUp())); + connect(ui.actionEngine1_T, SIGNAL(toggled(bool)), game, SLOT(setEngine1(bool))); + connect(ui.actionEngine2_R, SIGNAL(toggled(bool)), game, SLOT(setEngine2(bool))); + connect(ui.actionSound_S, SIGNAL(toggled(bool)), game, SLOT(setSound(bool))); + connect(ui.actionAnimation_A, SIGNAL(toggled(bool)), game, SLOT(setAnimation(bool))); // 视图上下翻转 connect(ui.actionFlip_F, &QAction::triggered, game, &GameController::flip); + // 视图左右镜像 connect(ui.actionMirror_M, &QAction::triggered, game, &GameController::mirror); + // 视图须时针旋转90° connect(ui.actionTurnRight_R, &QAction::triggered, game, &GameController::turnRight); + // 视图逆时针旋转90° connect(ui.actionTurnLeftt_L, &QAction::triggered, game, &GameController::turnLeft); // 关联控制器的信号和主窗口控件的槽 + // 更新LCD1,显示玩家1用时 connect(game, SIGNAL(time1Changed(QString)), ui.lcdNumber_1, SLOT(display(QString))); @@ -211,22 +231,30 @@ void NineChessWindow::initialize() // 关联列表视图和字符串列表模型 ui.listView->setModel(game->getManualListModel()); + // 因为QListView的rowsInserted在setModel之后才能启动, // 第一次需手动初始化选中listView第一项 //qDebug() << ui.listView->model(); ui.listView->setCurrentIndex(ui.listView->model()->index(0, 0)); + // 初始局面、前一步、后一步、最终局面的槽 + connect(ui.actionBegin_S, &QAction::triggered, this, &NineChessWindow::on_actionRowChange); + connect(ui.actionPrevious_B, &QAction::triggered, this, &NineChessWindow::on_actionRowChange); + connect(ui.actionNext_F, &QAction::triggered, this, &NineChessWindow::on_actionRowChange); + connect(ui.actionEnd_E, &QAction::triggered, this, &NineChessWindow::on_actionRowChange); + // 手动在listView里选择招法后更新的槽 connect(ui.listView, &ManualListView::currentChangedSignal, this, &NineChessWindow::on_actionRowChange); + // 更新四个键的状态 on_actionRowChange(); } @@ -235,8 +263,10 @@ void NineChessWindow::ruleInfo() { int s = game->getStepsLimit(); int t = game->getTimeLimit(); + QString tl(" 不限时"); QString sl(" 不限步"); + if (s > 0) sl = " 限" + QString::number(s) + "步"; if (t > 0) @@ -244,22 +274,27 @@ void NineChessWindow::ruleInfo() // 规则显示 ui.labelRule->setText(tl + sl); + // 规则提示 ui.labelInfo->setToolTip(QString(NineChess::RULES[ruleNo].name) + "\n" + NineChess::RULES[ruleNo].description); + ui.labelRule->setToolTip(ui.labelInfo->toolTip()); - //QString tip_Rule = QString("%1\n%2").arg(tr(NineChess::RULES[ruleNo].name)) - // .arg(tr(NineChess::RULES[ruleNo].info)); +#if 0 + QString tip_Rule = QString("%1\n%2").arg(tr(NineChess::RULES[ruleNo].name)) + .arg(tr(NineChess::RULES[ruleNo].info)); +#endif } void NineChessWindow::on_actionLimited_T_triggered() { - /* 其实本来可以用设计器做个ui,然后从QDialog派生个自己的对话框 - * 但我不想再派生新类了,又要多出一个类和两个文件 - * 还要写与主窗口的接口,费劲 - * 于是手写QDialog界面 - */ + /* + * 其实本来可以用设计器做个ui,然后从QDialog派生个自己的对话框 + * 但我不想再派生新类了,又要多出一个类和两个文件 + * 还要写与主窗口的接口,费劲 + * 于是手写QDialog界面 + */ int gStep = game->getStepsLimit(); int gTime = game->getTimeLimit(); @@ -270,6 +305,7 @@ void NineChessWindow::on_actionLimited_T_triggered() dialog->setWindowTitle(tr("步数和时间限制")); dialog->resize(256, 108); dialog->setModal(true); + // 生成各个控件 QFormLayout *formLayout = new QFormLayout(dialog); QLabel *label_step = new QLabel(dialog); @@ -277,13 +313,15 @@ void NineChessWindow::on_actionLimited_T_triggered() QComboBox *comboBox_step = new QComboBox(dialog); QComboBox *comboBox_time = new QComboBox(dialog); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); +#if 0 // 设置各个控件ObjectName,不设也没关系 - /*formLayout->setObjectName(QStringLiteral("formLayout")); + 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"));*/ + buttonBox->setObjectName(QStringLiteral("buttonBox")); +#endif // 设置各个控件数据 label_step->setText(tr("超出限制步数判和:")); label_time->setText(tr("任意一方超时判负:")); @@ -357,6 +395,7 @@ void NineChessWindow::actionRules_triggered() // 重置游戏规则 game->setRule(ruleNo); + // 更新规则显示 ruleInfo(); } @@ -365,11 +404,14 @@ void NineChessWindow::on_actionNew_N_triggered() { if (file.isOpen()) file.close(); + // 取消自动运行 ui.actionAutoRun_A->setChecked(false); + // 取消AI设定 ui.actionEngine1_T->setChecked(false); ui.actionEngine2_R->setChecked(false); + // 重置游戏规则 game->gameReset(); } @@ -380,26 +422,32 @@ void NineChessWindow::on_actionOpen_O_triggered() if (path.isEmpty() == false) { if (file.isOpen()) file.close(); - //文件对象 + + // 文件对象 file.setFileName(path); + // 不支持1MB以上的文件 if (file.size() > 0x100000) { // 定义新对话框 - QMessageBox msgBox(QMessageBox::Warning, tr("文件过大"), tr("不支持1MB以上文件"), QMessageBox::Ok); + QMessageBox msgBox(QMessageBox::Warning, + tr("文件过大"), tr("不支持1MB以上文件"), QMessageBox::Ok); msgBox.exec(); return; } - //打开文件,只读方式打开 + // 打开文件,只读方式打开 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(); + // 读取并显示棋谱时,不必刷新棋局场景 if (!(game->command(cmd, false))) { // 定义新对话框 @@ -407,10 +455,12 @@ void NineChessWindow::on_actionOpen_O_triggered() msgBox.exec(); return; } + while (!textStream.atEnd()) { cmd = textStream.readLine(); game->command(cmd, false); } + // 最后刷新棋局场景 game->updateScence(); } @@ -421,23 +471,28 @@ void NineChessWindow::on_actionSave_S_triggered() { if (file.isOpen()) { file.close(); - //打开文件,只写方式打开 + + // 打开文件,只写方式打开 bool isok = file.open(QFileDevice::WriteOnly | QFileDevice::Text); + if (isok) { - //写文件 + // 写文件 QTextStream textStream(&file); QStringListModel *strlist = qobject_cast(ui.listView->model()); for (QString cmd : strlist->stringList()) textStream << cmd << endl; file.flush(); } - } else + } else { on_actionSaveAs_A_triggered(); + } } void NineChessWindow::on_actionSaveAs_A_triggered() { - QString path = QFileDialog::getSaveFileName(this, tr("打开棋谱文件"), QDir::currentPath() + tr("棋谱.txt"), "TXT(*.txt)"); + QString path = QFileDialog::getSaveFileName(this, + tr("打开棋谱文件"), QDir::currentPath() + tr("棋谱.txt"), "TXT(*.txt)"); + if (path.isEmpty() == false) { if (file.isOpen()) file.close(); @@ -447,8 +502,9 @@ void NineChessWindow::on_actionSaveAs_A_triggered() // 打开文件,只写方式打开 bool isok = file.open(QFileDevice::WriteOnly | QFileDevice::Text); + if (isok) { - //写文件 + // 写文件 QTextStream textStream(&file); QStringListModel *strlist = qobject_cast(ui.listView->model()); for (QString cmd : strlist->stringList()) @@ -456,7 +512,6 @@ void NineChessWindow::on_actionSaveAs_A_triggered() file.flush(); } } - } void NineChessWindow::on_actionEdit_E_toggled(bool arg1) @@ -480,6 +535,7 @@ void NineChessWindow::on_actionInvert_I_toggled(bool arg1) ui.picLabel1->setPixmap(QPixmap(":/icon/Resources/icon/Black.png")); ui.picLabel2->setPixmap(QPixmap(":/icon/Resources/icon/White.png")); } + // 让控制器改变棋子颜色 game->setInvert(arg1); } @@ -506,6 +562,7 @@ void NineChessWindow::on_actionRowChange() } else if (obsender == ui.actionEnd_E) { ui.listView->setCurrentIndex(model->index(rows - 1, 0)); } + currentRow = ui.listView->currentIndex().row(); } @@ -541,7 +598,8 @@ void NineChessWindow::on_actionRowChange() // 更新局面 game->phaseChange(currentRow); - /* 下面的代码全部取消,改用QTimer的方式实现 +#if 0 + // 下面的代码全部取消,改用QTimer的方式实现 // 更新局面 bool changed = game->phaseChange(currentRow); // 处理自动播放时的动画 @@ -559,7 +617,7 @@ void NineChessWindow::on_actionRowChange() QTimer::singleShot(waitTime, &loop, SLOT(quit())); loop.exec(); } - */ +#endif // 0 } void NineChessWindow::onAutoRunTimeOut(QPrivateSignal signal) @@ -578,7 +636,9 @@ void NineChessWindow::onAutoRunTimeOut(QPrivateSignal signal) if (currentRow < rows - 1) { ui.listView->setCurrentIndex(ui.listView->model()->index(currentRow + 1, 0)); } + currentRow = ui.listView->currentIndex().row(); + // 更新动作状态 if (currentRow <= 0) { ui.actionBegin_S->setEnabled(false); @@ -614,11 +674,13 @@ void NineChessWindow::on_actionAutoRun_A_toggled(bool arg1) // 自动运行前禁用控件 ui.dockWidget->setEnabled(false); ui.gameView->setEnabled(false); + // 启动定时器 autoRunTimer.start(game->getDurationTime() + 50); } else { // 关闭定时器 autoRunTimer.stop(); + // 自动运行结束后启用控件 ui.dockWidget->setEnabled(true); ui.gameView->setEnabled(true); @@ -723,7 +785,10 @@ void NineChessWindow::on_actionEngine_E_triggered() time1_new = spinBox_time1->value(); time2_new = spinBox_time2->value(); - if (depth1 != depth1_new || depth2 != depth2_new || time1 != time1_new || time2 != time2_new) { + if (depth1 != depth1_new || + depth2 != depth2_new || + time1 != time1_new || + time2 != time2_new) { // 重置AI game->setAiDepthTime(depth1_new, time1_new, depth2_new, time2_new); } @@ -750,7 +815,7 @@ void NineChessWindow::on_actionAbout_A_triggered() dialog->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint); dialog->setObjectName(QStringLiteral("aboutDialog")); - dialog->setWindowTitle(tr("九连棋")); + dialog->setWindowTitle(tr("三棋")); dialog->setModal(true); // 生成各个控件 @@ -771,7 +836,7 @@ void NineChessWindow::on_actionAbout_A_triggered() label_icon1->setScaledContents(true); label_icon2->setScaledContents(true); - label_text->setText(tr("支持开源,捐助作者,诚接软件开发项目 —— liuweilhy")); + label_text->setText(tr("Donate")); label_text->setAlignment(Qt::AlignCenter); label_image->setPixmap(QPixmap(QString::fromUtf8(":/image/resources/image/donate.png"))); label_image->setAlignment(Qt::AlignCenter); diff --git a/NineChess/src/pieceitem.h b/NineChess/src/pieceitem.h index 81e30d45..e507fb29 100644 --- a/NineChess/src/pieceitem.h +++ b/NineChess/src/pieceitem.h @@ -10,6 +10,7 @@ class PieceItem : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) + // 位置属性 Q_PROPERTY(QPointF pos READ pos WRITE setPos) @@ -27,6 +28,7 @@ public: { Type = UserType + 2 }; + int type() const { return Type; @@ -44,22 +46,27 @@ public: { return model_; } + void setModel(enum Models model) { this->model_ = model; } + int getNum() { return num; } + void setNum(int n) { num = n; } + bool isDeleted() { return deleted_; } + void setDeleted(bool deleted = true) { this->deleted_ = deleted; @@ -67,6 +74,7 @@ public: this->model_ = noPiece; update(boundingRect()); } + void setShowNum(bool show = true) { this->showNum = show;