├── GameModel.cpp ├── GameModel.h ├── QtWuziqi.pro ├── README.md ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── pic └── wuziqi.gif ├── res └── sound │ ├── chessone.wav │ ├── lose.wav │ └── win.wav └── resource.qrc /GameModel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "GameModel.h" 5 | 6 | GameModel::GameModel() 7 | { 8 | 9 | } 10 | 11 | void GameModel::startGame(GameType type) 12 | { 13 | gameType = type; 14 | // 初始棋盘 15 | gameMapVec.clear(); 16 | for (int i = 0; i < kBoardSizeNum; i++) 17 | { 18 | std::vector lineBoard; 19 | for (int j = 0; j < kBoardSizeNum; j++) 20 | lineBoard.push_back(0); 21 | gameMapVec.push_back(lineBoard); 22 | } 23 | 24 | // 如果是AI模式,需要初始化评分数组 25 | if (gameType == BOT) 26 | { 27 | scoreMapVec.clear(); 28 | for (int i = 0; i < kBoardSizeNum; i++) 29 | { 30 | std::vector lineScores; 31 | for (int j = 0; j < kBoardSizeNum; j++) 32 | lineScores.push_back(0); 33 | scoreMapVec.push_back(lineScores); 34 | } 35 | } 36 | 37 | // 己方下为true,对方下位false 38 | playerFlag = true; 39 | 40 | 41 | } 42 | 43 | void GameModel::updateGameMap(int row, int col) 44 | { 45 | if (playerFlag) 46 | gameMapVec[row][col] = 1; 47 | else 48 | gameMapVec[row][col] = -1; 49 | 50 | // 换手 51 | playerFlag = !playerFlag; 52 | } 53 | 54 | void GameModel::actionByPerson(int row, int col) 55 | { 56 | updateGameMap(row, col); 57 | } 58 | 59 | void GameModel::actionByAI(int &clickRow, int &clickCol) 60 | { 61 | // 计算评分 62 | calculateScore(); 63 | 64 | // 从评分中找出最大分数的位置 65 | int maxScore = 0; 66 | std::vector> maxPoints; 67 | 68 | for (int row = 1; row < kBoardSizeNum; row++) 69 | for (int col = 1; col < kBoardSizeNum; col++) 70 | { 71 | // 前提是这个坐标是空的 72 | if (gameMapVec[row][col] == 0) 73 | { 74 | if (scoreMapVec[row][col] > maxScore) // 找最大的数和坐标 75 | { 76 | maxPoints.clear(); 77 | maxScore = scoreMapVec[row][col]; 78 | maxPoints.push_back(std::make_pair(row, col)); 79 | } 80 | else if (scoreMapVec[row][col] == maxScore) // 如果有多个最大的数,都存起来 81 | maxPoints.push_back(std::make_pair(row, col)); 82 | } 83 | } 84 | 85 | // 随机落子,如果有多个点的话 86 | srand((unsigned)time(0)); 87 | int index = rand() % maxPoints.size(); 88 | 89 | std::pair pointPair = maxPoints.at(index); 90 | clickRow = pointPair.first; // 记录落子点 91 | clickCol = pointPair.second; 92 | updateGameMap(clickRow, clickCol); 93 | } 94 | 95 | // 最关键的计算评分函数 96 | void GameModel::calculateScore() 97 | { 98 | // 统计玩家或者电脑连成的子 99 | int personNum = 0; // 玩家连成子的个数 100 | int botNum = 0; // AI连成子的个数 101 | int emptyNum = 0; // 各方向空白位的个数 102 | 103 | // 清空评分数组 104 | scoreMapVec.clear(); 105 | for (int i = 0; i < kBoardSizeNum; i++) 106 | { 107 | std::vector lineScores; 108 | for (int j = 0; j < kBoardSizeNum; j++) 109 | lineScores.push_back(0); 110 | scoreMapVec.push_back(lineScores); 111 | } 112 | 113 | // 计分(此处是完全遍历,其实可以用bfs或者dfs加减枝降低复杂度,通过调整权重值,调整AI智能程度以及攻守风格) 114 | for (int row = 0; row < kBoardSizeNum; row++) 115 | for (int col = 0; col < kBoardSizeNum; col++) 116 | { 117 | // 空白点就算 118 | if (row > 0 && col > 0 && 119 | gameMapVec[row][col] == 0) 120 | { 121 | // 遍历周围八个方向 122 | for (int y = -1; y <= 1; y++) 123 | for (int x = -1; x <= 1; x++) 124 | { 125 | // 重置 126 | personNum = 0; 127 | botNum = 0; 128 | emptyNum = 0; 129 | 130 | // 原坐标不算 131 | if (!(y == 0 && x == 0)) 132 | { 133 | // 每个方向延伸4个子 134 | 135 | // 对玩家白子评分(正反两个方向) 136 | for (int i = 1; i <= 4; i++) 137 | { 138 | if (row + i * y > 0 && row + i * y < kBoardSizeNum && 139 | col + i * x > 0 && col + i * x < kBoardSizeNum && 140 | gameMapVec[row + i * y][col + i * x] == 1) // 玩家的子 141 | { 142 | personNum++; 143 | } 144 | else if (row + i * y > 0 && row + i * y < kBoardSizeNum && 145 | col + i * x > 0 && col + i * x < kBoardSizeNum && 146 | gameMapVec[row + i * y][col + i * x] == 0) // 空白位 147 | { 148 | emptyNum++; 149 | break; 150 | } 151 | else // 出边界 152 | break; 153 | } 154 | 155 | for (int i = 1; i <= 4; i++) 156 | { 157 | if (row - i * y > 0 && row - i * y < kBoardSizeNum && 158 | col - i * x > 0 && col - i * x < kBoardSizeNum && 159 | gameMapVec[row - i * y][col - i * x] == 1) // 玩家的子 160 | { 161 | personNum++; 162 | } 163 | else if (row - i * y > 0 && row - i * y < kBoardSizeNum && 164 | col - i * x > 0 && col - i * x < kBoardSizeNum && 165 | gameMapVec[row - i * y][col - i * x] == 0) // 空白位 166 | { 167 | emptyNum++; 168 | break; 169 | } 170 | else // 出边界 171 | break; 172 | } 173 | 174 | if (personNum == 1) // 杀二 175 | scoreMapVec[row][col] += 10; 176 | else if (personNum == 2) // 杀三 177 | { 178 | if (emptyNum == 1) 179 | scoreMapVec[row][col] += 30; 180 | else if (emptyNum == 2) 181 | scoreMapVec[row][col] += 40; 182 | } 183 | else if (personNum == 3) // 杀四 184 | { 185 | // 量变空位不一样,优先级不一样 186 | if (emptyNum == 1) 187 | scoreMapVec[row][col] += 60; 188 | else if (emptyNum == 2) 189 | scoreMapVec[row][col] += 110; 190 | } 191 | else if (personNum == 4) // 杀五 192 | scoreMapVec[row][col] += 10100; 193 | 194 | // 进行一次清空 195 | emptyNum = 0; 196 | 197 | // 对AI黑子评分 198 | for (int i = 1; i <= 4; i++) 199 | { 200 | if (row + i * y > 0 && row + i * y < kBoardSizeNum && 201 | col + i * x > 0 && col + i * x < kBoardSizeNum && 202 | gameMapVec[row + i * y][col + i * x] == 1) // 玩家的子 203 | { 204 | botNum++; 205 | } 206 | else if (row + i * y > 0 && row + i * y < kBoardSizeNum && 207 | col + i * x > 0 && col + i * x < kBoardSizeNum && 208 | gameMapVec[row +i * y][col + i * x] == 0) // 空白位 209 | { 210 | emptyNum++; 211 | break; 212 | } 213 | else // 出边界 214 | break; 215 | } 216 | 217 | for (int i = 1; i <= 4; i++) 218 | { 219 | if (row - i * y > 0 && row - i * y < kBoardSizeNum && 220 | col - i * x > 0 && col - i * x < kBoardSizeNum && 221 | gameMapVec[row - i * y][col - i * x] == -1) // AI的子 222 | { 223 | botNum++; 224 | } 225 | else if (row - i * y > 0 && row - i * y < kBoardSizeNum && 226 | col - i * x > 0 && col - i * x < kBoardSizeNum && 227 | gameMapVec[row - i * y][col - i * x] == 0) // 空白位 228 | { 229 | emptyNum++; 230 | break; 231 | } 232 | else // 出边界 233 | break; 234 | } 235 | 236 | if (botNum == 0) // 普通下子 237 | scoreMapVec[row][col] += 5; 238 | else if (botNum == 1) // 活二 239 | scoreMapVec[row][col] += 10; 240 | else if (botNum == 2) 241 | { 242 | if (emptyNum == 1) // 死三 243 | scoreMapVec[row][col] += 25; 244 | else if (emptyNum == 2) 245 | scoreMapVec[row][col] += 50; // 活三 246 | } 247 | else if (botNum == 3) 248 | { 249 | if (emptyNum == 1) // 死四 250 | scoreMapVec[row][col] += 55; 251 | else if (emptyNum == 2) 252 | scoreMapVec[row][col] += 100; // 活四 253 | } 254 | else if (botNum >= 4) 255 | scoreMapVec[row][col] += 10000; // 活五 256 | 257 | } 258 | } 259 | 260 | } 261 | } 262 | } 263 | 264 | bool GameModel::isWin(int row, int col) 265 | { 266 | // 横竖斜四种大情况,每种情况都根据当前落子往后遍历5个棋子,有一种符合就算赢 267 | // 水平方向 268 | for (int i = 0; i < 5; i++) 269 | { 270 | // 往左5个,往右匹配4个子,20种情况 271 | if (col - i > 0 && 272 | col - i + 4 < kBoardSizeNum && 273 | gameMapVec[row][col - i] == gameMapVec[row][col - i + 1] && 274 | gameMapVec[row][col - i] == gameMapVec[row][col - i + 2] && 275 | gameMapVec[row][col - i] == gameMapVec[row][col - i + 3] && 276 | gameMapVec[row][col - i] == gameMapVec[row][col - i + 4]) 277 | return true; 278 | } 279 | 280 | // 竖直方向(上下延伸4个) 281 | for (int i = 0; i < 5; i++) 282 | { 283 | if (row - i > 0 && 284 | row - i + 4 < kBoardSizeNum && 285 | gameMapVec[row - i][col] == gameMapVec[row - i + 1][col] && 286 | gameMapVec[row - i][col] == gameMapVec[row - i + 2][col] && 287 | gameMapVec[row - i][col] == gameMapVec[row - i + 3][col] && 288 | gameMapVec[row - i][col] == gameMapVec[row - i + 4][col]) 289 | return true; 290 | } 291 | 292 | // 左斜方向 293 | for (int i = 0; i < 5; i++) 294 | { 295 | if (row + i < kBoardSizeNum && 296 | row + i - 4 > 0 && 297 | col - i > 0 && 298 | col - i + 4 < kBoardSizeNum && 299 | gameMapVec[row + i][col - i] == gameMapVec[row + i - 1][col - i + 1] && 300 | gameMapVec[row + i][col - i] == gameMapVec[row + i - 2][col - i + 2] && 301 | gameMapVec[row + i][col - i] == gameMapVec[row + i - 3][col - i + 3] && 302 | gameMapVec[row + i][col - i] == gameMapVec[row + i - 4][col - i + 4]) 303 | return true; 304 | } 305 | 306 | // 右斜方向 307 | for (int i = 0; i < 5; i++) 308 | { 309 | if (row - i > 0 && 310 | row - i + 4 < kBoardSizeNum && 311 | col - i > 0 && 312 | col - i + 4 < kBoardSizeNum && 313 | gameMapVec[row - i][col - i] == gameMapVec[row - i + 1][col - i + 1] && 314 | gameMapVec[row - i][col - i] == gameMapVec[row - i + 2][col - i + 2] && 315 | gameMapVec[row - i][col - i] == gameMapVec[row - i + 3][col - i + 3] && 316 | gameMapVec[row - i][col - i] == gameMapVec[row - i + 4][col - i + 4]) 317 | return true; 318 | } 319 | 320 | return false; 321 | } 322 | 323 | bool GameModel::isDeadGame() 324 | { 325 | // 所有空格全部填满 326 | for (int i = 1; i < kBoardSizeNum; i++) 327 | for (int j = 1; j < kBoardSizeNum; j++) 328 | { 329 | if (!(gameMapVec[i][j] == 1 || gameMapVec[i][j] == -1)) 330 | return false; 331 | } 332 | return true; 333 | } 334 | 335 | -------------------------------------------------------------------------------- /GameModel.h: -------------------------------------------------------------------------------- 1 | #ifndef GAMEMODEL_H 2 | #define GAMEMODEL_H 3 | 4 | // ---- 五子棋游戏模型类 ---- // 5 | #include 6 | 7 | // 游戏类型,双人还是AI(目前固定让AI下黑子) 8 | enum GameType 9 | { 10 | PERSON, 11 | BOT 12 | }; 13 | 14 | // 游戏状态 15 | enum GameStatus 16 | { 17 | PLAYING, 18 | WIN, 19 | DEAD 20 | }; 21 | 22 | // 棋盘尺寸 23 | const int kBoardSizeNum = 15; 24 | 25 | class GameModel 26 | { 27 | public: 28 | GameModel(); 29 | 30 | public: 31 | std::vector> gameMapVec; // 存储当前游戏棋盘和棋子的情况,空白为0,白子1,黑子-1 32 | std::vector> scoreMapVec; // 存储各个点位的评分情况,作为AI下棋依据 33 | bool playerFlag; // 标示下棋方 34 | GameType gameType; // 游戏模式 35 | GameStatus gameStatus; // 游戏状态 36 | 37 | void startGame(GameType type); // 开始游戏 38 | void calculateScore(); // 计算评分 39 | void actionByPerson(int row, int col); // 人执行下棋 40 | void actionByAI(int &clickRow, int &clickCol); // 机器执行下棋 41 | void updateGameMap(int row, int col); // 每次落子后更新游戏棋盘 42 | bool isWin(int row, int col); // 判断游戏是否胜利 43 | bool isDeadGame(); // 判断是否和棋 44 | }; 45 | 46 | #endif // GAMEMODEL_H 47 | -------------------------------------------------------------------------------- /QtWuziqi.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-12-22T10:50:43 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui multimedia 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = QtWuziqi 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += main.cpp\ 16 | mainwindow.cpp \ 17 | GameModel.cpp 18 | 19 | HEADERS += mainwindow.h \ 20 | GameModel.h 21 | 22 | RESOURCES += \ 23 | resource.qrc 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtWuziqi 2 | Qt 写的五子棋小游戏,带AI和双人对战 3 | # ScreenShot 4 | ![](https://github.com/tashaxing/QtWuziqi/raw/master/pic/wuziqi.gif)
5 | # BlogAddress 6 | http://blog.csdn.net/u012234115/article/details/53871009 7 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "mainwindow.h" 12 | 13 | // -------全局遍历-------// 14 | #define CHESS_ONE_SOUND ":/res/sound/chessone.wav" 15 | #define WIN_SOUND ":/res/sound/win.wav" 16 | #define LOSE_SOUND ":/res/sound/lose.wav" 17 | 18 | const int kBoardMargin = 30; // 棋盘边缘空隙 19 | const int kRadius = 15; // 棋子半径 20 | const int kMarkSize = 6; // 落子标记边长 21 | const int kBlockSize = 40; // 格子的大小 22 | const int kPosDelta = 20; // 鼠标点击的模糊距离上限 23 | 24 | const int kAIDelay = 700; // AI下棋的思考时间 25 | 26 | // -------------------- // 27 | 28 | MainWindow::MainWindow(QWidget *parent) 29 | : QMainWindow(parent) 30 | { 31 | // 设置棋盘大小 32 | setFixedSize(kBoardMargin * 2 + kBlockSize * kBoardSizeNum, kBoardMargin * 2 + kBlockSize * kBoardSizeNum); 33 | // setStyleSheet("background-color:yellow;"); 34 | 35 | // 开启鼠标hover功能,这两句一般要设置window的 36 | setMouseTracking(true); 37 | // centralWidget()->setMouseTracking(true); 38 | 39 | // 添加菜单 40 | QMenu *gameMenu = menuBar()->addMenu(tr("Game")); // menuBar默认是存在的,直接加菜单就可以了 41 | QAction *actionPVP = new QAction("Person VS Person", this); 42 | connect(actionPVP, SIGNAL(triggered()), this, SLOT(initPVPGame())); 43 | gameMenu->addAction(actionPVP); 44 | 45 | QAction *actionPVE = new QAction("Person VS Computer", this); 46 | connect(actionPVE, SIGNAL(triggered()), this, SLOT(initPVEGame())); 47 | gameMenu->addAction(actionPVE); 48 | 49 | // 开始游戏 50 | initGame(); 51 | } 52 | 53 | MainWindow::~MainWindow() 54 | { 55 | if (game) 56 | { 57 | delete game; 58 | game = nullptr; 59 | } 60 | } 61 | 62 | void MainWindow::initGame() 63 | { 64 | // 初始化游戏模型 65 | game = new GameModel; 66 | initPVPGame(); 67 | } 68 | 69 | void MainWindow::initPVPGame() 70 | { 71 | game_type = PERSON; 72 | game->gameStatus = PLAYING; 73 | game->startGame(game_type); 74 | update(); 75 | } 76 | 77 | void MainWindow::initPVEGame() 78 | { 79 | game_type = BOT; 80 | game->gameStatus = PLAYING; 81 | game->startGame(game_type); 82 | update(); 83 | } 84 | 85 | void MainWindow::paintEvent(QPaintEvent *event) 86 | { 87 | QPainter painter(this); 88 | // 绘制棋盘 89 | painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿 90 | // QPen pen; // 调整线条宽度 91 | // pen.setWidth(2); 92 | // painter.setPen(pen); 93 | for (int i = 0; i < kBoardSizeNum + 1; i++) 94 | { 95 | painter.drawLine(kBoardMargin + kBlockSize * i, kBoardMargin, kBoardMargin + kBlockSize * i, size().height() - kBoardMargin); 96 | painter.drawLine(kBoardMargin, kBoardMargin + kBlockSize * i, size().width() - kBoardMargin, kBoardMargin + kBlockSize * i); 97 | } 98 | 99 | QBrush brush; 100 | brush.setStyle(Qt::SolidPattern); 101 | // 绘制落子标记(防止鼠标出框越界) 102 | if (clickPosRow > 0 && clickPosRow < kBoardSizeNum && 103 | clickPosCol > 0 && clickPosCol < kBoardSizeNum && 104 | game->gameMapVec[clickPosRow][clickPosCol] == 0) 105 | { 106 | if (game->playerFlag) 107 | brush.setColor(Qt::white); 108 | else 109 | brush.setColor(Qt::black); 110 | painter.setBrush(brush); 111 | painter.drawRect(kBoardMargin + kBlockSize * clickPosCol - kMarkSize / 2, kBoardMargin + kBlockSize * clickPosRow - kMarkSize / 2, kMarkSize, kMarkSize); 112 | } 113 | 114 | // 绘制棋子 115 | for (int i = 0; i < kBoardSizeNum; i++) 116 | for (int j = 0; j < kBoardSizeNum; j++) 117 | { 118 | if (game->gameMapVec[i][j] == 1) 119 | { 120 | brush.setColor(Qt::white); 121 | painter.setBrush(brush); 122 | painter.drawEllipse(kBoardMargin + kBlockSize * j - kRadius, kBoardMargin + kBlockSize * i - kRadius, kRadius * 2, kRadius * 2); 123 | } 124 | else if (game->gameMapVec[i][j] == -1) 125 | { 126 | brush.setColor(Qt::black); 127 | painter.setBrush(brush); 128 | painter.drawEllipse(kBoardMargin + kBlockSize * j - kRadius, kBoardMargin + kBlockSize * i - kRadius, kRadius * 2, kRadius * 2); 129 | } 130 | } 131 | 132 | // 判断输赢 133 | if (clickPosRow > 0 && clickPosRow < kBoardSizeNum && 134 | clickPosCol > 0 && clickPosCol < kBoardSizeNum && 135 | (game->gameMapVec[clickPosRow][clickPosCol] == 1 || 136 | game->gameMapVec[clickPosRow][clickPosCol] == -1)) 137 | { 138 | if (game->isWin(clickPosRow, clickPosCol) && game->gameStatus == PLAYING) 139 | { 140 | qDebug() << "win"; 141 | game->gameStatus = WIN; 142 | QSound::play(WIN_SOUND); 143 | QString str; 144 | if (game->gameMapVec[clickPosRow][clickPosCol] == 1) 145 | str = "white player"; 146 | else if (game->gameMapVec[clickPosRow][clickPosCol] == -1) 147 | str = "black player"; 148 | QMessageBox::StandardButton btnValue = QMessageBox::information(this, "congratulations", str + " win!"); 149 | 150 | // 重置游戏状态,否则容易死循环 151 | if (btnValue == QMessageBox::Ok) 152 | { 153 | game->startGame(game_type); 154 | game->gameStatus = PLAYING; 155 | } 156 | } 157 | } 158 | 159 | 160 | // 判断死局 161 | if (game->isDeadGame()) 162 | { 163 | QSound::play(LOSE_SOUND); 164 | QMessageBox::StandardButton btnValue = QMessageBox::information(this, "oops", "dead game!"); 165 | if (btnValue == QMessageBox::Ok) 166 | { 167 | game->startGame(game_type); 168 | game->gameStatus = PLAYING; 169 | } 170 | 171 | } 172 | } 173 | 174 | void MainWindow::mouseMoveEvent(QMouseEvent *event) 175 | { 176 | // 通过鼠标的hover确定落子的标记 177 | int x = event->x(); 178 | int y = event->y(); 179 | 180 | // 棋盘边缘不能落子 181 | if (x >= kBoardMargin + kBlockSize / 2 && 182 | x < size().width() - kBoardMargin && 183 | y >= kBoardMargin + kBlockSize / 2 && 184 | y < size().height()- kBoardMargin) 185 | { 186 | // 获取最近的左上角的点 187 | int col = x / kBlockSize; 188 | int row = y / kBlockSize; 189 | 190 | int leftTopPosX = kBoardMargin + kBlockSize * col; 191 | int leftTopPosY = kBoardMargin + kBlockSize * row; 192 | 193 | // 根据距离算出合适的点击位置,一共四个点,根据半径距离选最近的 194 | clickPosRow = -1; // 初始化最终的值 195 | clickPosCol = -1; 196 | int len = 0; // 计算完后取整就可以了 197 | 198 | // 确定一个误差在范围内的点,且只可能确定一个出来 199 | len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY) * (y - leftTopPosY)); 200 | if (len < kPosDelta) 201 | { 202 | clickPosRow = row; 203 | clickPosCol = col; 204 | } 205 | len = sqrt((x - leftTopPosX - kBlockSize) * (x - leftTopPosX - kBlockSize) + (y - leftTopPosY) * (y - leftTopPosY)); 206 | if (len < kPosDelta) 207 | { 208 | clickPosRow = row; 209 | clickPosCol = col + 1; 210 | } 211 | len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY - kBlockSize) * (y - leftTopPosY - kBlockSize)); 212 | if (len < kPosDelta) 213 | { 214 | clickPosRow = row + 1; 215 | clickPosCol = col; 216 | } 217 | len = sqrt((x - leftTopPosX - kBlockSize) * (x - leftTopPosX - kBlockSize) + (y - leftTopPosY - kBlockSize) * (y - leftTopPosY - kBlockSize)); 218 | if (len < kPosDelta) 219 | { 220 | clickPosRow = row + 1; 221 | clickPosCol = col + 1; 222 | } 223 | } 224 | 225 | // 存了坐标后也要重绘 226 | update(); 227 | } 228 | 229 | void MainWindow::mouseReleaseEvent(QMouseEvent *event) 230 | { 231 | // 人下棋,并且不能抢机器的棋 232 | if (!(game_type == BOT && !game->playerFlag)) 233 | { 234 | chessOneByPerson(); 235 | // 如果是人机模式,需要调用AI下棋 236 | if (game->gameType == BOT && !game->playerFlag) 237 | { 238 | // 用定时器做一个延迟 239 | QTimer::singleShot(kAIDelay, this, SLOT(chessOneByAI())); 240 | } 241 | } 242 | 243 | } 244 | 245 | void MainWindow::chessOneByPerson() 246 | { 247 | // 根据当前存储的坐标下子 248 | // 只有有效点击才下子,并且该处没有子 249 | if (clickPosRow != -1 && clickPosCol != -1 && game->gameMapVec[clickPosRow][clickPosCol] == 0) 250 | { 251 | game->actionByPerson(clickPosRow, clickPosCol); 252 | QSound::play(CHESS_ONE_SOUND); 253 | 254 | // 重绘 255 | update(); 256 | } 257 | } 258 | 259 | void MainWindow::chessOneByAI() 260 | { 261 | game->actionByAI(clickPosRow, clickPosCol); 262 | QSound::play(CHESS_ONE_SOUND); 263 | update(); 264 | } 265 | 266 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include "GameModel.h" 6 | 7 | class MainWindow : public QMainWindow 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | MainWindow(QWidget *parent = 0); 13 | ~MainWindow(); 14 | 15 | protected: 16 | // 绘制 17 | void paintEvent(QPaintEvent *event); 18 | // 监听鼠标移动情况,方便落子 19 | void mouseMoveEvent(QMouseEvent *event); 20 | // 实际落子 21 | void mouseReleaseEvent(QMouseEvent *event); 22 | 23 | private: 24 | GameModel *game; // 游戏指针 25 | GameType game_type; // 存储游戏类型 26 | int clickPosRow, clickPosCol; // 存储将点击的位置 27 | void initGame(); 28 | void checkGame(int y, int x); 29 | 30 | private slots: 31 | void chessOneByPerson(); // 人执行 32 | void chessOneByAI(); // AI下棋 33 | 34 | void initPVPGame(); 35 | void initPVEGame(); 36 | }; 37 | 38 | #endif // MAINWINDOW_H 39 | -------------------------------------------------------------------------------- /pic/wuziqi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashaxing/QtWuziqi/f4a8700699fce1b4d4a9d07e4618045c57ad3666/pic/wuziqi.gif -------------------------------------------------------------------------------- /res/sound/chessone.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashaxing/QtWuziqi/f4a8700699fce1b4d4a9d07e4618045c57ad3666/res/sound/chessone.wav -------------------------------------------------------------------------------- /res/sound/lose.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashaxing/QtWuziqi/f4a8700699fce1b4d4a9d07e4618045c57ad3666/res/sound/lose.wav -------------------------------------------------------------------------------- /res/sound/win.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tashaxing/QtWuziqi/f4a8700699fce1b4d4a9d07e4618045c57ad3666/res/sound/win.wav -------------------------------------------------------------------------------- /resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | res/sound/chessone.wav 4 | res/sound/lose.wav 5 | res/sound/win.wav 6 | 7 | 8 | --------------------------------------------------------------------------------