├── 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 └── server ├── README.md ├── server └── server.go /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 | PERSON_ONLINE 13 | }; 14 | 15 | // 游戏状态 16 | enum GameStatus 17 | { 18 | PLAYING, 19 | WIN, 20 | DEAD 21 | }; 22 | 23 | // 棋盘尺寸 24 | const int kBoardSizeNum = 15; 25 | 26 | class GameModel 27 | { 28 | public: 29 | GameModel(); 30 | 31 | public: 32 | std::vector> gameMapVec; // 存储当前游戏棋盘和棋子的情况,空白为0,白子1,黑子-1 33 | std::vector> scoreMapVec; // 存储各个点位的评分情况,作为AI下棋依据 34 | bool playerFlag; // 标示下棋方 35 | GameType gameType; // 游戏模式 36 | GameStatus gameStatus; // 游戏状态 37 | 38 | void startGame(GameType type); // 开始游戏 39 | void calculateScore(); // 计算评分 40 | void actionByPerson(int row, int col); // 人执行下棋 41 | void actionByAI(int &clickRow, int &clickCol); // 机器执行下棋 42 | void updateGameMap(int row, int col); // 每次落子后更新游戏棋盘 43 | bool isWin(int row, int col); // 判断游戏是否胜利 44 | bool isDeadGame(); // 判断是否和棋 45 | }; 46 | 47 | #endif // GAMEMODEL_H 48 | -------------------------------------------------------------------------------- /QtWuziqi.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-12-22T10:50:43 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui multimedia 8 | QT += network 9 | 10 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 11 | 12 | TARGET = QtWuziqi 13 | TEMPLATE = app 14 | 15 | 16 | SOURCES += main.cpp\ 17 | mainwindow.cpp \ 18 | GameModel.cpp 19 | 20 | HEADERS += mainwindow.h \ 21 | GameModel.h 22 | 23 | RESOURCES += \ 24 | resource.qrc 25 | -------------------------------------------------------------------------------- /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 | QAction *actionPVPOL = new QAction("Person VS Person Online", this); 50 | connect(actionPVPOL, SIGNAL(triggered()), this, SLOT(initPVPOLGame())); 51 | gameMenu->addAction(actionPVPOL); 52 | 53 | // 开始游戏 54 | initGame(); 55 | } 56 | 57 | MainWindow::~MainWindow() 58 | { 59 | if (game) 60 | { 61 | delete game; 62 | game = nullptr; 63 | } 64 | } 65 | 66 | void MainWindow::initGame() 67 | { 68 | // 初始化游戏模型 69 | game = new GameModel; 70 | initPVPGame(); 71 | } 72 | 73 | void MainWindow::initPVPGame() 74 | { 75 | game_type = PERSON; 76 | game->gameStatus = PLAYING; 77 | game->startGame(game_type); 78 | update(); 79 | } 80 | 81 | void MainWindow::initPVEGame() 82 | { 83 | game_type = BOT; 84 | game->gameStatus = PLAYING; 85 | game->startGame(game_type); 86 | update(); 87 | } 88 | 89 | void MainWindow::initPVPOLGame() 90 | { 91 | // 初始化套接字 92 | tcpSocket = new QTcpSocket(this); 93 | 94 | //连接服务器 95 | port = 12345; 96 | serverIP = new QHostAddress(); 97 | QString ip = "127.0.0.1"; 98 | serverIP->setAddress(ip); 99 | tcpSocket->connectToHost(*serverIP, port); 100 | 101 | connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(dataReceived())); 102 | game_type = PERSON_ONLINE; 103 | game->gameStatus = PLAYING; 104 | game->startGame(game_type); 105 | 106 | setMouseTracking(false); 107 | 108 | update(); 109 | } 110 | 111 | void MainWindow::paintEvent(QPaintEvent *event) 112 | { 113 | QPainter painter(this); 114 | // 绘制棋盘 115 | painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿 116 | // QPen pen; // 调整线条宽度 117 | // pen.setWidth(2); 118 | // painter.setPen(pen); 119 | for (int i = 0; i < kBoardSizeNum + 1; i++) 120 | { 121 | painter.drawLine(kBoardMargin + kBlockSize * i, kBoardMargin, kBoardMargin + kBlockSize * i, size().height() - kBoardMargin); 122 | painter.drawLine(kBoardMargin, kBoardMargin + kBlockSize * i, size().width() - kBoardMargin, kBoardMargin + kBlockSize * i); 123 | } 124 | 125 | QBrush brush; 126 | brush.setStyle(Qt::SolidPattern); 127 | // 绘制落子标记(防止鼠标出框越界) 128 | if (clickPosRow > 0 && clickPosRow < kBoardSizeNum && 129 | clickPosCol > 0 && clickPosCol < kBoardSizeNum && 130 | game->gameMapVec[clickPosRow][clickPosCol] == 0) 131 | { 132 | if (game->playerFlag) 133 | brush.setColor(Qt::white); 134 | else 135 | brush.setColor(Qt::black); 136 | painter.setBrush(brush); 137 | painter.drawRect(kBoardMargin + kBlockSize * clickPosCol - kMarkSize / 2, kBoardMargin + kBlockSize * clickPosRow - kMarkSize / 2, kMarkSize, kMarkSize); 138 | } 139 | 140 | // 绘制棋子 141 | for (int i = 0; i < kBoardSizeNum; i++) 142 | for (int j = 0; j < kBoardSizeNum; j++) 143 | { 144 | if (game->gameMapVec[i][j] == 1) 145 | { 146 | brush.setColor(Qt::white); 147 | painter.setBrush(brush); 148 | painter.drawEllipse(kBoardMargin + kBlockSize * j - kRadius, kBoardMargin + kBlockSize * i - kRadius, kRadius * 2, kRadius * 2); 149 | } 150 | else if (game->gameMapVec[i][j] == -1) 151 | { 152 | brush.setColor(Qt::black); 153 | painter.setBrush(brush); 154 | painter.drawEllipse(kBoardMargin + kBlockSize * j - kRadius, kBoardMargin + kBlockSize * i - kRadius, kRadius * 2, kRadius * 2); 155 | } 156 | } 157 | 158 | // 判断输赢 159 | if (clickPosRow > 0 && clickPosRow < kBoardSizeNum && 160 | clickPosCol > 0 && clickPosCol < kBoardSizeNum && 161 | (game->gameMapVec[clickPosRow][clickPosCol] == 1 || 162 | game->gameMapVec[clickPosRow][clickPosCol] == -1)) 163 | { 164 | if (game->isWin(clickPosRow, clickPosCol) && game->gameStatus == PLAYING) 165 | { 166 | qDebug() << "win"; 167 | game->gameStatus = WIN; 168 | QSound::play(WIN_SOUND); 169 | QString str; 170 | if (game->gameMapVec[clickPosRow][clickPosCol] == 1) 171 | str = "white player"; 172 | else if (game->gameMapVec[clickPosRow][clickPosCol] == -1) 173 | str = "black player"; 174 | QMessageBox::StandardButton btnValue = QMessageBox::information(this, "congratulations", str + " win!"); 175 | 176 | // 重置游戏状态,否则容易死循环 177 | if (btnValue == QMessageBox::Ok) 178 | { 179 | game->startGame(game_type); 180 | game->gameStatus = PLAYING; 181 | } 182 | } 183 | } 184 | 185 | 186 | // 判断死局 187 | if (game->isDeadGame()) 188 | { 189 | QSound::play(LOSE_SOUND); 190 | QMessageBox::StandardButton btnValue = QMessageBox::information(this, "oops", "dead game!"); 191 | if (btnValue == QMessageBox::Ok) 192 | { 193 | game->startGame(game_type); 194 | game->gameStatus = PLAYING; 195 | } 196 | 197 | } 198 | } 199 | 200 | void MainWindow::mouseMoveEvent(QMouseEvent *event) 201 | { 202 | // 通过鼠标的hover确定落子的标记 203 | int x = event->x(); 204 | int y = event->y(); 205 | 206 | // 棋盘边缘不能落子 207 | if (x >= kBoardMargin + kBlockSize / 2 && 208 | x < size().width() - kBoardMargin && 209 | y >= kBoardMargin + kBlockSize / 2 && 210 | y < size().height()- kBoardMargin) 211 | { 212 | // 获取最近的左上角的点 213 | int col = x / kBlockSize; 214 | int row = y / kBlockSize; 215 | 216 | int leftTopPosX = kBoardMargin + kBlockSize * col; 217 | int leftTopPosY = kBoardMargin + kBlockSize * row; 218 | 219 | // 根据距离算出合适的点击位置,一共四个点,根据半径距离选最近的 220 | clickPosRow = -1; // 初始化最终的值 221 | clickPosCol = -1; 222 | int len = 0; // 计算完后取整就可以了 223 | 224 | // 确定一个误差在范围内的点,且只可能确定一个出来 225 | len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY) * (y - leftTopPosY)); 226 | if (len < kPosDelta) 227 | { 228 | clickPosRow = row; 229 | clickPosCol = col; 230 | } 231 | len = sqrt((x - leftTopPosX - kBlockSize) * (x - leftTopPosX - kBlockSize) + (y - leftTopPosY) * (y - leftTopPosY)); 232 | if (len < kPosDelta) 233 | { 234 | clickPosRow = row; 235 | clickPosCol = col + 1; 236 | } 237 | len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY - kBlockSize) * (y - leftTopPosY - kBlockSize)); 238 | if (len < kPosDelta) 239 | { 240 | clickPosRow = row + 1; 241 | clickPosCol = col; 242 | } 243 | len = sqrt((x - leftTopPosX - kBlockSize) * (x - leftTopPosX - kBlockSize) + (y - leftTopPosY - kBlockSize) * (y - leftTopPosY - kBlockSize)); 244 | if (len < kPosDelta) 245 | { 246 | clickPosRow = row + 1; 247 | clickPosCol = col + 1; 248 | } 249 | } 250 | 251 | // 存了坐标后也要重绘 252 | update(); 253 | } 254 | 255 | void MainWindow::mouseReleaseEvent(QMouseEvent *event) 256 | { 257 | if(game_type == PERSON_ONLINE && !game->playerFlag) 258 | { 259 | chessOneByPersonOnline(); 260 | } 261 | // 人下棋,并且不能抢机器的棋 262 | if (!(game_type == BOT && !game->playerFlag)) 263 | { 264 | chessOneByPerson(); 265 | // 如果是人机模式,需要调用AI下棋 266 | if (game->gameType == BOT && !game->playerFlag) 267 | { 268 | // 用定时器做一个延迟 269 | QTimer::singleShot(kAIDelay, this, SLOT(chessOneByAI())); 270 | } 271 | } 272 | 273 | } 274 | 275 | void MainWindow::chessOneByPerson() 276 | { 277 | // 根据当前存储的坐标下子 278 | // 只有有效点击才下子,并且该处没有子 279 | if (clickPosRow != -1 && clickPosCol != -1 && game->gameMapVec[clickPosRow][clickPosCol] == 0) 280 | { 281 | game->actionByPerson(clickPosRow, clickPosCol); 282 | QSound::play(CHESS_ONE_SOUND); 283 | 284 | // 重绘 285 | update(); 286 | } 287 | } 288 | 289 | void MainWindow::chessOneByAI() 290 | { 291 | game->actionByAI(clickPosRow, clickPosCol); 292 | QSound::play(CHESS_ONE_SOUND); 293 | update(); 294 | } 295 | 296 | void MainWindow::chessOneByPersonOnline() 297 | { 298 | // 根据当前存储的坐标下子 299 | // 只有有效点击才下子,并且该处没有子 300 | if (clickPosRow != -1 && clickPosCol != -1 && game->gameMapVec[clickPosRow][clickPosCol] == 0) 301 | { 302 | game->actionByPerson(clickPosRow, clickPosCol); 303 | QSound::play(CHESS_ONE_SOUND); 304 | 305 | // 将位置通过套接字发给对手 306 | QString scol = QString::number(clickPosCol, 10); 307 | QString srow = QString::number(clickPosRow, 10); 308 | QString msg = srow + "," + scol + "\n"; // 注意:要加上换行符 309 | //qDebug() << msg; 310 | int length = tcpSocket->write(msg.toLatin1(), msg.length()); 311 | tcpSocket->flush(); 312 | if(length != msg.length()) 313 | { 314 | return; 315 | } 316 | // 重绘 317 | update(); 318 | } 319 | } 320 | 321 | void MainWindow::dataReceived() 322 | { 323 | QByteArray datagram; 324 | datagram.resize(tcpSocket->bytesAvailable()); 325 | tcpSocket->read(datagram.data(), datagram.size()); 326 | QString msg = datagram.data(); 327 | // 只有第一个开始游戏的玩家才会收到这条消息 328 | if(msg.contains("has")) 329 | { 330 | setMouseTracking(true); 331 | game->playerFlag = false; 332 | //qDebug() << "You are the first one"; 333 | return; 334 | } 335 | 336 | 337 | int row = msg.section(',', 0, 0).toInt(); 338 | int col = msg.section(',', 1, 1).toInt(); 339 | //qDebug() << "recv:" << row << col; 340 | setMouseTracking(false); 341 | 342 | if (row != -1 && col != -1 && game->gameMapVec[row][col] == 0) 343 | { 344 | game->actionByPerson(row, col); 345 | QSound::play(CHESS_ONE_SOUND); 346 | setMouseTracking(true); 347 | } 348 | update(); 349 | } 350 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "GameModel.h" 8 | 9 | class MainWindow : public QMainWindow 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | MainWindow(QWidget *parent = 0); 15 | ~MainWindow(); 16 | 17 | protected: 18 | // 绘制 19 | void paintEvent(QPaintEvent *event); 20 | // 监听鼠标移动情况,方便落子 21 | void mouseMoveEvent(QMouseEvent *event); 22 | // 实际落子 23 | void mouseReleaseEvent(QMouseEvent *event); 24 | 25 | private: 26 | GameModel *game; // 游戏指针 27 | GameType game_type; // 存储游戏类型 28 | int clickPosRow, clickPosCol; // 存储将点击的位置 29 | void initGame(); 30 | void checkGame(int y, int x); 31 | 32 | int port; 33 | QHostAddress *serverIP; 34 | QTcpSocket *tcpSocket; 35 | 36 | private slots: 37 | void chessOneByPerson(); // 人执行 38 | void chessOneByAI(); // AI下棋 39 | 40 | void chessOneByPersonOnline(); // 通过网络与好友下棋 41 | 42 | void initPVPGame(); 43 | void initPVEGame(); 44 | 45 | void initPVPOLGame(); 46 | 47 | void dataReceived(); 48 | }; 49 | 50 | #endif // MAINWINDOW_H 51 | -------------------------------------------------------------------------------- /pic/wuziqi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liu-jianhao/QtWuziqi/7db0251ea40921e3a3c05fc1735ac7f3ae3ac108/pic/wuziqi.gif -------------------------------------------------------------------------------- /res/sound/chessone.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liu-jianhao/QtWuziqi/7db0251ea40921e3a3c05fc1735ac7f3ae3ac108/res/sound/chessone.wav -------------------------------------------------------------------------------- /res/sound/lose.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liu-jianhao/QtWuziqi/7db0251ea40921e3a3c05fc1735ac7f3ae3ac108/res/sound/lose.wav -------------------------------------------------------------------------------- /res/sound/win.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liu-jianhao/QtWuziqi/7db0251ea40921e3a3c05fc1735ac7f3ae3ac108/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 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 在Ubuntu环境下可以直接运行: 3 | ```shell 4 | ./server 5 | ``` 6 | 运行之后就可以开始下网络五子棋了! 7 | 若是在其他环境则需要编译server.go 8 | -------------------------------------------------------------------------------- /server/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liu-jianhao/QtWuziqi/7db0251ea40921e3a3c05fc1735ac7f3ae3ac108/server/server -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. 2 | // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 4 | // See page 254. 5 | //!+ 6 | 7 | // Chat is a server that lets clients chat with each other. 8 | package main 9 | 10 | import ( 11 | "bufio" 12 | "fmt" 13 | "log" 14 | "net" 15 | ) 16 | 17 | //!+broadcaster 18 | type client chan<- string // an outgoing message channel 19 | 20 | var ( 21 | entering = make(chan client) 22 | leaving = make(chan client) 23 | messages = make(chan string) // all incoming client messages 24 | ) 25 | 26 | func broadcaster() { 27 | clients := make(map[client]bool) // all connected clients 28 | for { 29 | select { 30 | case msg := <-messages: 31 | // Broadcast incoming message to all 32 | // clients' outgoing message channels. 33 | for cli := range clients { 34 | cli <- msg 35 | } 36 | 37 | case cli := <-entering: 38 | clients[cli] = true 39 | 40 | case cli := <-leaving: 41 | delete(clients, cli) 42 | close(cli) 43 | } 44 | } 45 | } 46 | 47 | //!-broadcaster 48 | 49 | //!+handleConn 50 | func handleConn(conn net.Conn) { 51 | ch := make(chan string) // outgoing client messages 52 | go clientWriter(conn, ch) 53 | 54 | who := conn.RemoteAddr().String() 55 | messages <- who + " has arrived" 56 | entering <- ch 57 | 58 | input := bufio.NewScanner(conn) 59 | for input.Scan() { 60 | messages <- input.Text() 61 | } 62 | // NOTE: ignoring potential errors from input.Err() 63 | 64 | leaving <- ch 65 | conn.Close() 66 | } 67 | 68 | func clientWriter(conn net.Conn, ch <-chan string) { 69 | for msg := range ch { 70 | fmt.Fprintln(conn, msg) // NOTE: ignoring network errors 71 | } 72 | } 73 | 74 | //!-handleConn 75 | 76 | //!+main 77 | func main() { 78 | listener, err := net.Listen("tcp", "localhost:12345") 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | go broadcaster() 84 | for { 85 | conn, err := listener.Accept() 86 | if err != nil { 87 | log.Print(err) 88 | continue 89 | } 90 | go handleConn(conn) 91 | } 92 | } 93 | 94 | //!-main 95 | --------------------------------------------------------------------------------