├── .gitignore ├── Board.cpp ├── Board.h ├── ChineseChess.pro ├── ChooseIP.cpp ├── ChooseIP.h ├── ChooseSerOrCli.cpp ├── ChooseSerOrCli.h ├── ChooseSerOrCli.ui ├── Fonts └── 华文行楷.ttf ├── InputIP.cpp ├── InputIP.h ├── NetGame.cpp ├── NetGame.h ├── README.md ├── SingleGame.cpp ├── SingleGame.h ├── Step.h ├── Stone.cpp ├── Stone.h ├── WinWidget.cpp ├── WinWidget.h ├── chess ├── 99bOOOPIC52.jpg └── bg.jpeg ├── chessrec.qrc ├── main.cpp ├── openWidget.cpp ├── openWidget.h ├── openWidget.ui └── screenshot ├── netGame.png ├── open.png └── singleGame.png /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore .pro.user by Qt Creator 2 | ChineseChess.pro.user 3 | ChineseChess.pro.user* 4 | 5 | # ignore object file 6 | *.o 7 | *.obj 8 | chinaChess 9 | 10 | # Qt-es 11 | moc_*.cpp 12 | moc_*.h 13 | qrc_*.cpp 14 | ui_*.h 15 | Makefile* 16 | /.qmake.cache 17 | /.qmake.stash 18 | -------------------------------------------------------------------------------- /Board.cpp: -------------------------------------------------------------------------------- 1 | #include "Board.h" 2 | 3 | Board::Board(QWidget *parent) 4 | : QWidget(parent) 5 | { 6 | this->resize(720, 660); 7 | this->setMinimumSize(720, 660); 8 | this->setMaximumSize(720, 660); 9 | initStone(false); 10 | aboutButton = new QPushButton(this); 11 | aboutButton->setText("关于"); 12 | aboutButton->setGeometry(600, 66, 80, 40); 13 | backButton = new QPushButton(this); 14 | backButton->setText("悔棋"); 15 | backButton->setGeometry(600, 310, 80, 40); 16 | returnButton = new QPushButton(this); 17 | returnButton->setText("返回"); 18 | returnButton->setGeometry(600, 554, 80, 40); 19 | connect(aboutButton, SIGNAL(clicked(bool)), this, SLOT(aboutSlot())); 20 | connect(backButton, SIGNAL(clicked(bool)), this, SLOT(backSlot())); 21 | connect(returnButton, SIGNAL(clicked(bool)), this, SLOT(returnSlot())); 22 | selectedID = -1; 23 | isRedTurn = true; 24 | 25 | // 按钮样式 26 | setStyleSheet("QPushButton" 27 | "{" 28 | "color: white;" 29 | "background-color: #ab88ed;" 30 | "border:none;" 31 | "padding: 3px;" 32 | "font-family: 'Verdana';" 33 | "font-size: 15px;" 34 | "text-align: center;" 35 | "border-radius:8px;" 36 | "}"); 37 | QString style = "QPushButton:hover" 38 | "{" 39 | "font-size: 20px;" 40 | "color: #6900de;" 41 | "background-color: #8d59dd;" 42 | "}"; 43 | aboutButton->setStyleSheet(style); 44 | backButton->setStyleSheet(style); 45 | returnButton->setStyleSheet(style); 46 | 47 | // 设置背景图 48 | this->setAutoFillBackground(true); 49 | QPalette palette = this->palette(); 50 | QBrush brush(QPixmap(":/new/prefix1/chess/bg.jpeg").scaled(this->size(), 51 | Qt::IgnoreAspectRatio, 52 | Qt::SmoothTransformation)); 53 | palette.setBrush(QPalette::Window, brush); 54 | this->setPalette(palette); 55 | } 56 | 57 | Board::~Board() 58 | { 59 | } 60 | 61 | // init stones 62 | // 默认(isReverse == false): 红方在下, 黑方在上 63 | // isReverse =- true : 黑方在下, 红方在上 64 | void Board::initStone(bool isReverse) 65 | { 66 | for (int i = 0; i < 32; i++) 67 | this->stone[i].init(i); 68 | 69 | if (isReverse) { 70 | for (int i = 0; i < 32; i++) { 71 | this->stone[i].row = 9 - stone[i].row; 72 | this->stone[i].col = 8 - stone[i].col; 73 | } 74 | //update(); 75 | } 76 | isRedSide = !isReverse; 77 | } 78 | 79 | void Board::paintEvent(QPaintEvent *) 80 | { 81 | // paint board 82 | QPainter *painter = new QPainter(this); 83 | painter->setBrush(QBrush("brown")); 84 | painter->setPen(QColor("red")); 85 | int d = 60; // 直径 86 | this->r = d / 2; 87 | for (int i = 1; i <= 8; i++) { 88 | for (int j = 1; j <= 4; j++) { 89 | painter->drawRect(i * d, j * d, d, d); 90 | } 91 | } 92 | 93 | painter->drawRect(d, 5 * d, 8 * d, d); 94 | for (int i = 1; i <= 8; i++) { 95 | for (int j = 6; j <= 9; j++) { 96 | painter->drawRect(i * d, j * d, d, d); 97 | } 98 | } 99 | 100 | painter->drawLine(QPoint(4 * d, d), QPoint(6 * d,3 * d)); 101 | painter->drawLine(QPoint(6 * d, d), QPoint(4 * d, 3 * d)); 102 | painter->drawLine(QPoint(4 * d, 8 * d), QPoint(6 * d, 10 * d)); 103 | painter->drawLine(QPoint(6 * d, 8 * d), QPoint(4 * d, 10 * d)); 104 | 105 | QRect rect = QRect(d, 5 * d, 8 * d, d); 106 | 107 | painter->setFont(QFont("华文行楷", 40, 50)); 108 | painter->setPen(Qt::white); 109 | painter->drawText(rect, tr(" 楚河 汉界"), QTextOption(Qt::AlignCenter)); 110 | // 胜负判断 111 | if (stone[4].isDead || stone[20].isDead) { 112 | this->close(); 113 | WinWidget *win = new WinWidget(stone[4].isDead ? 1 : 0); 114 | win->show(); 115 | } 116 | 117 | // paint stone 118 | for (int i = 0; i < 32; i++) 119 | this->drawStone(painter, i); 120 | 121 | delete painter; 122 | painter = nullptr; 123 | } 124 | 125 | void Board::mouseReleaseEvent(QMouseEvent *ev) 126 | { 127 | // 若不是左键, 则不处理 128 | if (ev->button() != Qt::LeftButton) { 129 | QWidget::mouseReleaseEvent(ev); 130 | return; 131 | } 132 | QPoint point = ev->pos(); 133 | int row, col; 134 | // click on the outside of the board 135 | if (getRowCol(point, row ,col) == false) 136 | return; 137 | 138 | //qDebug() << "row,col = "<setRenderHints(QPainter::Antialiasing); // 消除棋子锯齿 148 | if (selectedID == ID) { 149 | painter->setBrush(QColor(64, 224, 205)); 150 | } else { 151 | QBrush brush(stone[ID].isRed ? QColor(Qt::yellow) : QColor(135, 206, 250)); 152 | painter->setBrush(brush); 153 | } 154 | 155 | QPen pen(Qt::white, 2); 156 | painter->setPen(pen); 157 | painter->drawEllipse(translate(ID), r, r); 158 | painter->setPen(Qt::black); 159 | 160 | if (stone[ID].isRed) 161 | painter->setPen(Qt::red); 162 | 163 | QPoint point = translate(ID); 164 | QRect rect = QRect(point.x() - r, point.y() - r, r * 2, r * 2); 165 | painter->drawText(rect, stone[ID].name(), QTextOption(Qt::AlignCenter)); 166 | } 167 | 168 | // 获取棋子坐标 169 | QPoint Board::translate(int ID) 170 | { 171 | QPoint point; 172 | point.rx() = (this->stone[ID].col + 1) * r * 2; // 注意坐标与行列的关系,x对应列,y对应行 173 | point.ry() = (this->stone[ID].row + 1) * r * 2; 174 | return point; 175 | } 176 | 177 | // 获取棋子坐标的函数重载 178 | QPoint Board::translate(int row, int col) 179 | { 180 | QPoint point; 181 | point.rx() = (col + 1) * r *2; // 注意坐标与行列的关系,x对应列,y对应行 182 | point.ry() = (row + 1) * r * 2; 183 | return point; 184 | } 185 | // 获取鼠标点击的坐标,并判断是否点击在棋盘内 186 | bool Board::getRowCol(QPoint point, int &row, int &col) 187 | { 188 | // 简单直接,易于理解,但效率不高 189 | #if 0 190 | for (row = 0; row < 10; row++) { 191 | for (col = 0; col < 9; col++) { 192 | QPoint p = translate(row, col); 193 | int dx = p.x() - point.x(); 194 | int dy = p.y() - point.y(); 195 | int distance = dx * dx + dy * dy; 196 | if (distance <= r * r) 197 | return true; 198 | } 199 | } 200 | return false; // 点击在棋盘外 201 | #endif 202 | 203 | // 注意将像素坐标先+r再转换为行列值 204 | //(注:+r再除是类比了四舍五入的想法,圆心在行列交叉处,点击在圆心上方应该向大于它的数取整) 205 | //( 而int类型默认向比它的小的数取整) 206 | row = (point.y() + r) / (2 * r) - 1; 207 | col = (point.x() + r) / (2 * r) - 1; 208 | return row <= 9 && col <= 8; 209 | } 210 | 211 | bool Board::canMove(int moveID, int row, int col, int killID) 212 | { 213 | if (stone[moveID].isRed == stone[killID].isRed) { // 如果要吃掉的棋子不是敌方的 214 | selectedID = killID; // 换选择 215 | update(); 216 | return false; 217 | } else { 218 | switch (stone[moveID].type) { 219 | case Stone::JIANG: 220 | return canMoveJIANG(moveID, row, col); 221 | case Stone::SI: 222 | return canMoveSI(moveID, row, col); 223 | case Stone::BING: 224 | return canMoveBING(moveID, row, col); 225 | case Stone::CHE: 226 | return canMoveCHE(moveID, row, col); 227 | case Stone::XIANG: 228 | return canMoveXIANG(moveID, row, col); 229 | case Stone::MA: 230 | return canMoveMA(moveID, row, col); 231 | case Stone::PAO: 232 | return canMovePAO(moveID, row, col, killID); 233 | } 234 | return false; 235 | } 236 | } 237 | bool Board::canMovePAO(int moveID, int row, int col, int killID) 238 | { 239 | if ( !(stone[moveID].row == row || stone[moveID].col == col) ) 240 | return false; 241 | 242 | if (killID == -1) 243 | return isLineNull(moveID, row, col); 244 | 245 | int count1 = 0; // 用来计算要移动的棋子和要被吃的棋子之间棋子的个数 246 | int count2 = 0; 247 | int count3 = 0; 248 | int count4 = 0; 249 | #if 0 250 | for (int i = 0; i < 32; i++) { 251 | if (stone[i].isDead) 252 | continue; 253 | 254 | if (stone[moveID].row == stone[killID].row ) { 255 | if(stone[killID].row > stone[moveID].col) { 256 | if(stone[i].row == stone[killID].row 257 | && stone[moveID].col < stone[i].col 258 | && stone[i].col < stone[killID].col) 259 | count1++; 260 | } else { 261 | if(stone[i].row == row 262 | && stone[moveID].col > stone[i].col 263 | && stone[i].col > stone[killID].col) 264 | count2++; 265 | } 266 | } 267 | if (stone[moveID].col == stone[killID].col ) { 268 | if(row > stone[moveID].row) { 269 | if(stone[i].col == stone[killID].col 270 | && stone[moveID].row < stone[i].row 271 | && stone[i].row < stone[killID].row) 272 | count3++; 273 | } else { 274 | if (stone[i].col == stone[killID].col 275 | && stone[moveID].row > stone[i].row 276 | && stone[i].row > stone[killID].row) 277 | count4++; 278 | } 279 | } 280 | } 281 | #endif 282 | if (stone[moveID].row == row ) { 283 | if (col > stone[moveID].col) { 284 | for (int i = stone[moveID].col + 1; i < col; i++) { 285 | if (getStoneID(row, i) != -1) 286 | count1++; 287 | if (count1 > 1) 288 | break; 289 | } 290 | } else { 291 | for (int i = col + 1; i < stone[moveID].col; i++) { 292 | if (getStoneID(row, i) != -1) 293 | count2++; 294 | if (count2 > 1) 295 | break; 296 | } 297 | } 298 | } else if (stone[moveID].col == col ) { 299 | if (row > stone[moveID].row) { 300 | for (int i = stone[moveID].row + 1; i < row; i++) { 301 | if (getStoneID(i, col) != -1) 302 | count3++; 303 | if (count3 > 1) 304 | break; 305 | } 306 | } else { 307 | for (int i = row + 1; i < stone[moveID].row; i++) { 308 | if (getStoneID(i, col) != -1) 309 | count4++; 310 | if (count4 > 1) 311 | break; 312 | } 313 | } 314 | } 315 | return count1 == 1 || count2 == 1 || count3 == 1 || count4 == 1; 316 | } 317 | 318 | bool Board::canMoveMA(int moveID, int row, int col) 319 | { 320 | int rowDistance = abs(stone[moveID].row - row); 321 | int colDistance = abs(stone[moveID].col - col); 322 | if (rowDistance == 2) { // 在行上别马腿 323 | int centerRow = (stone[moveID].row + row) / 2; 324 | int centerCol = stone[moveID].col; 325 | if (getStoneID(centerRow, centerCol) != -1) 326 | return false; 327 | } 328 | if (colDistance == 2) { // 在列上别马腿 329 | int centerCol = (stone[moveID].col + col) / 2; 330 | int centerRow = stone[moveID].row; 331 | if (getStoneID(centerRow, centerCol) != -1) 332 | return false; 333 | } 334 | int dr = stone[moveID].row - row; 335 | int dc = stone[moveID].col - col; 336 | int d = abs(dr) * 10 + abs(dc); 337 | return d == 21 || d == 12; 338 | } 339 | 340 | bool Board::canMoveXIANG(int moveID, int row, int col) 341 | { 342 | // 去了对方位置 343 | if (stone[moveID].isRed == isRedSide) { 344 | if (row < 5) 345 | return false; 346 | } else { 347 | if (row > 4) 348 | return false; 349 | } 350 | // 判断是否田字中心有棋 351 | int centerRow = (stone[moveID].row + row) / 2; 352 | int centerCol = (stone[moveID].col + col) / 2; 353 | if (getStoneID(centerRow, centerCol) != -1) 354 | return false; 355 | 356 | int dr = stone[moveID].row - row; 357 | int dc = stone[moveID].col - col; 358 | int d = abs(dr) * 10 + abs(dc); 359 | return d == 22; 360 | } 361 | 362 | bool Board::canMoveCHE(int moveID, int row, int col) 363 | { 364 | if ( !(stone[moveID].row == row || stone[moveID].col == col) ) 365 | return false; 366 | 367 | // 判断移动后和移动前的位置之间是否有棋子 368 | return isLineNull(moveID, row, col); 369 | } 370 | 371 | bool Board::canMoveBING(int moveID, int row, int col) 372 | { 373 | if (stone[moveID].isRed == isRedSide) { 374 | if (row > stone[moveID].row) // 往回走 375 | return false; 376 | 377 | if (stone[moveID].row > 4) { 378 | if (stone[moveID].col != col) // 限定只能直走 379 | return false; 380 | } 381 | } else { 382 | if (row < stone[moveID].row) // back off 383 | return false; 384 | 385 | if (stone[moveID].row < 5) { 386 | if (stone[moveID].col != col) // 限定只能直走 387 | return false; 388 | } 389 | } 390 | int dr = stone[moveID].row - row; 391 | int dc = stone[moveID].col - col; 392 | int d = abs(dr) * 10 + abs(dc); 393 | return d == 1 || d == 10; 394 | } 395 | 396 | bool Board::canMoveJIANG(int moveID, int row, int col) 397 | { 398 | if (stone[moveID].isRed == isRedSide) { 399 | if (stone[4].row == row && stone[4].col == col && stone[4].col == stone[20].col) { 400 | for (int i = stone[4].row + 1; i < stone[20].row; i++) { 401 | if (getStoneID(i, col) != -1) 402 | return false; 403 | } 404 | return true; 405 | } 406 | } else { 407 | if (stone[20].row == row && stone[20].col == col && stone[4].col == stone[20].col) { 408 | for (int i = stone[4].row + 1; i < stone[20].row; i++) { 409 | if (getStoneID(i, col) != -1) 410 | return false; 411 | } 412 | return true; 413 | } 414 | } 415 | if (stone[moveID].isRed == isRedSide) { 416 | if (row < 7 || row > 9) 417 | return false; 418 | } else { 419 | if (row > 2 || row < 0) 420 | return false; 421 | } 422 | 423 | if (col < 3 || col > 5) 424 | return false; 425 | 426 | int dr = stone[moveID].row - row; 427 | int dc = stone[moveID].col - col; 428 | int d = abs(dr) * 10 + abs(dc); 429 | return d == 1 || d == 10; 430 | } 431 | 432 | bool Board::canMoveSI(int moveID, int row, int col) 433 | { 434 | if (stone[moveID].isRed == isRedSide) { 435 | if (row < 7 || row > 9) 436 | return false; 437 | } else { 438 | if (row > 2 || row < 0) 439 | return false; 440 | } 441 | if (col < 3 || col > 5) 442 | return false; 443 | 444 | int dr = stone[moveID].row - row; 445 | int dc = stone[moveID].col - col; 446 | int d = abs(dr) * 10 + abs(dc); 447 | return d == 11; 448 | } 449 | void Board::clicked(int clickedID, int row, int col) 450 | { 451 | if (selectedID == -1) { // 假设是第一次被点击,那么selectedID应该被重新赋值 452 | if (clickedID != -1) { // 在上一个假设后,这一次假设点击的地方是有棋子的 453 | if (stone[clickedID].isRed == isRedTurn) { // 再在上一次假设后,点击的棋子是轮到的一方的 454 | selectedID = clickedID; // 经上面全部假设后,那么该棋子被选中 455 | update(); 456 | } 457 | } 458 | } else { // 已经在上一次点击的时候选择了棋子,那么这时候应该移动棋子 459 | if (canMove(selectedID, row, col, clickedID)) { 460 | this->saveStep(selectedID, clickedID, row, col, this->backSteps); 461 | /* qDebug()<backSteps[backSteps.size()-1]->moveID].name()<<"form"<< 462 | this->backSteps[backSteps.size()-1]->rowFrom<<","<< 463 | this->backSteps[backSteps.size()-1]->colFrom<<"to"<< 464 | this->backSteps[backSteps.size()-1]->rowTo<<"," << 465 | this->backSteps[backSteps.size()-1]->colTo;*/ 466 | moveStone(selectedID, row, col); 467 | selectedID = -1; // clear selected flag 468 | killStone(clickedID); 469 | update(); 470 | } 471 | } 472 | } 473 | bool Board::isLineNull(int moveID, int row, int col) 474 | { 475 | if (stone[moveID].row == row ) { 476 | if (col > stone[moveID].col) { 477 | for (int i = stone[moveID].col + 1; i < col; i++) { 478 | if (getStoneID(row, i) != -1) 479 | return false; 480 | } 481 | return true; 482 | } else { 483 | for (int i = col + 1; i < stone[moveID].col; i++) 484 | { 485 | if (getStoneID(row, i) != -1) 486 | return false; 487 | } 488 | return true; 489 | } 490 | } else if (stone[moveID].col == col ) { 491 | if (row > stone[moveID].row) { 492 | for (int i = stone[moveID].row + 1; i < row; i++) { 493 | if (getStoneID(i, col) != -1) 494 | return false; 495 | } 496 | return true; 497 | } else { 498 | for (int i = row + 1; i < stone[moveID].row; i++) { 499 | if (getStoneID(i, col) != -1) 500 | return false; 501 | } 502 | return true; 503 | } 504 | } 505 | return true; 506 | } 507 | 508 | void Board::moveStone(int selectedID, int row, int col) 509 | { 510 | stone[selectedID].row = row; 511 | stone[selectedID].col = col; 512 | isRedTurn = !isRedTurn; // 轮到另一方下棋 513 | } 514 | 515 | void Board::moveStone(int selectedID, int killID, int row, int col) 516 | { 517 | stone[selectedID].row = row; 518 | stone[selectedID].col = col; 519 | killStone(killID); 520 | isRedTurn = !isRedTurn; // 轮到另一方下棋 521 | } 522 | 523 | void Board::killStone(int killID) 524 | { 525 | if (killID == -1) // 这次点击的地方没有棋子 526 | return; 527 | 528 | // 这次点击的地方有棋子 529 | stone[killID].isDead = true; // 上面的棋子被吃掉 530 | } 531 | 532 | void Board::reviveStone(int killID) 533 | { 534 | if (killID != -1 && stone[killID].isDead) 535 | stone[killID].isDead = false; 536 | } 537 | 538 | void Board::saveStep(int moveID, int killID, int rowTo, int colTo, 539 | QList &steps) 540 | { 541 | int rowFrom, colFrom; 542 | getRowCol(translate(moveID), rowFrom, colFrom); 543 | Step *step = new Step; 544 | 545 | step->moveID = moveID; 546 | step->killID = killID; 547 | step->rowFrom = rowFrom; 548 | step->colFrom = colFrom; 549 | step->rowTo = rowTo; 550 | step->colTo = colTo; 551 | 552 | steps.append(step); 553 | } 554 | int Board::getStoneID(int row, int col) 555 | { 556 | int i = 0; 557 | for ( ; i<32; i++) { // 用循环后i代表被点击的棋子的ID 558 | if (stone[i].row == row && stone[i].col == col && stone[i].isDead == false) 559 | break; 560 | } 561 | int clickedID = -1; // 每次点击棋子后clickedID会被重新赋值 562 | if (i < 32) // 表示点击的地方是有棋子的 563 | clickedID = i; 564 | 565 | return clickedID; 566 | } 567 | 568 | void Board::aboutSlot() 569 | { 570 | QMessageBox::information(this,"关于"," 作者:Ifan Tsai\n " 571 | " 版本:2.1\n" 572 | "Coder will change the world ! "); 573 | } 574 | 575 | void Board::backSlot() 576 | { 577 | if (this->backSteps.size() < 2) 578 | return; 579 | 580 | Step *step = this->backSteps.back(); 581 | this->backSteps.removeLast(); 582 | this->moveStone(step->moveID, step->rowFrom, step->colFrom); 583 | this->reviveStone(step->killID); 584 | delete step; 585 | 586 | step = this->backSteps.back(); 587 | this->backSteps.removeLast(); 588 | this->moveStone(step->moveID, step->rowFrom, step->colFrom); 589 | this->reviveStone(step->killID); 590 | delete step; 591 | 592 | this->selectedID = -1; 593 | update(); 594 | } 595 | 596 | void Board::returnSlot() 597 | { 598 | int ret = QMessageBox::warning(this, "返回", "确定要放弃当前棋局返回主界面?", 599 | QMessageBox::No | QMessageBox::Yes); 600 | if (ret == QMessageBox::Yes) { 601 | openWidget *w = new openWidget; 602 | w->show(); 603 | this->close(); 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /Board.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_H 2 | #define BOARD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "Stone.h" 18 | #include "WinWidget.h" 19 | #include "Step.h" 20 | #include "openWidget.h" 21 | #define FOR_ANDROID 22 | class Board : public QWidget 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | Board(QWidget *parent = 0); 28 | ~Board(); 29 | protected: 30 | void paintEvent(QPaintEvent *); // 绘图事件 31 | void mouseReleaseEvent(QMouseEvent *); // 鼠标释放事件 32 | public: 33 | QPushButton *backButton; // 悔棋按钮 34 | QPushButton *aboutButton; // 关于按钮 35 | QPushButton *returnButton; // 返回按钮 36 | int r; // 棋子半径 37 | Stone stone[32]; 38 | int selectedID; // 用来标记某个棋子被选择了 39 | bool isRedTurn; // 标记轮到哪一方 40 | QList backSteps; // 用来保存悔棋步骤 41 | bool isRedSide; // 用来表示是否红棋在下方 42 | public: 43 | QPoint translate(int ID); // 获取棋子的坐标 44 | QPoint translate(int row, int col); // 上面函数的重载 45 | void drawStone(QPainter *&painter, int ID); // 画棋子 46 | bool getRowCol(QPoint point, int &row, int &col); // 获取鼠标点击的坐标,并判断是否点击在棋盘内 47 | int getStoneID(int row, int col); 48 | void moveStone(int selectedID, int row, int col); 49 | void moveStone(int selectedID, int killID, int row, int col); 50 | void killStone(int killID); 51 | void reviveStone(int killID); // 复活棋子 52 | bool isLineNull(int moveID, int row, int col); // 用来判断车和炮移动时之间有无棋子 53 | void initStone(bool isReverse); // 初始化棋子 54 | 55 | bool canMove(int moveID, int row, int col, int killID); 56 | bool canMoveJIANG(int moveID, int row, int col); 57 | bool canMoveSI(int moveID, int row, int col); 58 | bool canMoveBING(int moveID, int row, int col); 59 | bool canMoveCHE(int moveID, int row, int col); 60 | bool canMoveXIANG(int moveID, int row, int col); 61 | bool canMoveMA(int moveID, int row, int col); 62 | bool canMovePAO(int moveID, int row, int col, int killID); 63 | 64 | void paintWin(); // 胜利窗口 65 | virtual void clicked(int clickedID, int row, int col); 66 | void saveStep(int moveID, int killID, int rowTo, 67 | int colTo, QList &steps); 68 | public slots: 69 | void aboutSlot(); 70 | void backSlot(); 71 | void returnSlot(); 72 | }; 73 | 74 | #endif // BOARD_H 75 | -------------------------------------------------------------------------------- /ChineseChess.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-05-22T21:04:16 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui network 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = chinaChess 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += main.cpp\ 16 | Board.cpp \ 17 | Stone.cpp \ 18 | WinWidget.cpp \ 19 | SingleGame.cpp \ 20 | openWidget.cpp \ 21 | NetGame.cpp \ 22 | ChooseSerOrCli.cpp \ 23 | ChooseIP.cpp \ 24 | InputIP.cpp 25 | 26 | HEADERS += Board.h \ 27 | Stone.h \ 28 | WinWidget.h \ 29 | Step.h \ 30 | SingleGame.h \ 31 | openWidget.h \ 32 | NetGame.h \ 33 | ChooseSerOrCli.h \ 34 | ChooseIP.h \ 35 | InputIP.h \ 36 | step.h 37 | CONFIG += c++11 38 | 39 | RESOURCES += \ 40 | chessrec.qrc 41 | 42 | FORMS += \ 43 | openWidget.ui \ 44 | ChooseSerOrCli.ui 45 | -------------------------------------------------------------------------------- /ChooseIP.cpp: -------------------------------------------------------------------------------- 1 | // 用来服务器选择IP的对话框 2 | #include "ChooseIP.h" 3 | 4 | ChooseIP::ChooseIP() 5 | { 6 | this->setWindowTitle("请选择一个IP作为服务器的IP"); 7 | this->setWindowFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint); 8 | box = new QComboBox(this); 9 | button = new QPushButton("确定"); 10 | layout = new QHBoxLayout(this); 11 | layout->addWidget(box); 12 | layout->addWidget(button); 13 | QList addrList = QNetworkInterface::allAddresses(); 14 | foreach (QHostAddress addr, addrList) { 15 | QString addrStr = QHostAddress(addr.toIPv4Address()).toString(); 16 | if(addrStr == "0.0.0.0" || addrStr == "127.0.0.1") 17 | continue; 18 | box->addItem(addrStr); 19 | } 20 | connect(button, SIGNAL(clicked(bool)), this, SLOT(chooseAddr())); 21 | } 22 | 23 | void ChooseIP::chooseAddr() 24 | { 25 | IPstr = box->currentText(); 26 | this->close(); 27 | } 28 | -------------------------------------------------------------------------------- /ChooseIP.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOOSEIP_H 2 | #define CHOOSEIP_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | class ChooseIP : public QDialog 10 | { 11 | Q_OBJECT 12 | public: 13 | ChooseIP(); 14 | public: 15 | QString IPstr; 16 | QHBoxLayout *layout; 17 | QComboBox *box; 18 | QPushButton *button; 19 | public slots: 20 | void chooseAddr(); 21 | }; 22 | 23 | #endif // CHOOSEIP_H 24 | -------------------------------------------------------------------------------- /ChooseSerOrCli.cpp: -------------------------------------------------------------------------------- 1 | // 用来选择是服务器还是客户端的对话框 2 | #include "ChooseSerOrCli.h" 3 | #include "ui_ChooseSerOrCli.h" 4 | 5 | ChooseSerOrCli::ChooseSerOrCli(QWidget *parent) : 6 | QDialog(parent), 7 | ui(new Ui::ChooseSerOrCli) 8 | { 9 | ui->setupUi(this); 10 | this->setWindowFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint | 11 | Qt::WindowMinimizeButtonHint | 12 | Qt::WindowMaximizeButtonHint); // 去掉对话框右上方关闭选项 13 | connect(ui->yseButton, SIGNAL(clicked(bool)), this, SLOT(yesSlot())); 14 | connect(ui->noButton, SIGNAL(clicked(bool)), this, SLOT(noSlot())); 15 | } 16 | 17 | ChooseSerOrCli::~ChooseSerOrCli() 18 | { 19 | delete ui; 20 | } 21 | 22 | void ChooseSerOrCli::yesSlot() 23 | { 24 | this->isServer = true; 25 | this->close(); 26 | } 27 | 28 | void ChooseSerOrCli::noSlot() 29 | { 30 | this->isServer = false; 31 | this->close(); 32 | } 33 | -------------------------------------------------------------------------------- /ChooseSerOrCli.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOOSESERORCLI_H 2 | #define CHOOSESERORCLI_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class ChooseSerOrCli; 8 | } 9 | 10 | class ChooseSerOrCli : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit ChooseSerOrCli(QWidget *parent = nullptr); 16 | ~ChooseSerOrCli(); 17 | 18 | private: 19 | Ui::ChooseSerOrCli *ui; 20 | 21 | public: 22 | bool isServer; 23 | public slots: 24 | void yesSlot(); 25 | void noSlot(); 26 | }; 27 | 28 | #endif // CHOOSESERORCLI_H 29 | -------------------------------------------------------------------------------- /ChooseSerOrCli.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ChooseSerOrCli 4 | 5 | 6 | 7 | 0 8 | 0 9 | 276 10 | 172 11 | 12 | 13 | 14 | 服务器or客户端 15 | 16 | 17 | 18 | 19 | 10 20 | 20 21 | 261 22 | 81 23 | 24 | 25 | 26 | 27 | 宋体 28 | 12 29 | 75 30 | false 31 | true 32 | 33 | 34 | 35 | <html><head/><body><p><span style=" font-size:14pt; font-weight:600; color:#aa0000;">是否选择以服务器来启动?</span></p></body></html> 36 | 37 | 38 | 39 | 40 | 41 | 50 42 | 100 43 | 181 44 | 61 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Fonts/华文行楷.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfanTsai/ChineseChess/a34d63efef7d0deb95a6e1cba3a5785012a56864/Fonts/华文行楷.ttf -------------------------------------------------------------------------------- /InputIP.cpp: -------------------------------------------------------------------------------- 1 | // 用来客户端输入IP的对话框 2 | #include "InputIP.h" 3 | 4 | InputIP::InputIP() 5 | { 6 | this->setWindowTitle("请输入一个IP用来连接服务器"); 7 | this->setWindowFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint); 8 | edit = new QLineEdit(this); 9 | button = new QPushButton("确定"); 10 | layout = new QHBoxLayout(this); 11 | layout->addWidget(edit); 12 | layout->addWidget(button); 13 | connect(button, SIGNAL(clicked(bool)), this, SLOT(chooseIP())); 14 | } 15 | void InputIP::chooseIP() 16 | { 17 | IPstr = edit->text(); 18 | this->close(); 19 | } 20 | -------------------------------------------------------------------------------- /InputIP.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUTIP_H 2 | #define INPUTIP_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | class InputIP : public QDialog 8 | { 9 | Q_OBJECT 10 | public: 11 | InputIP(); 12 | public: 13 | QString IPstr; 14 | QHBoxLayout *layout; 15 | QLineEdit *edit; 16 | QPushButton *button; 17 | public slots: 18 | void chooseIP(); 19 | }; 20 | 21 | #endif // INPUTIP_H 22 | -------------------------------------------------------------------------------- /NetGame.cpp: -------------------------------------------------------------------------------- 1 | #include "NetGame.h" 2 | static int isRedStart; // 用来表示谁先执红棋 3 | 4 | /* 5 | * 报文结构: 6 | * buf的第一个字节表示命令,0表示谁执红旗,1表示走棋,2表示聊天内容 7 | * 且0为第一次发送数据,同时在文本浏览器里显示连接成功... 8 | */ 9 | NetGame::NetGame() 10 | { 11 | /* 界面处理 */ 12 | this->resize(800, 660); 13 | this->setMinimumSize(800, 660); 14 | this->setMaximumSize(800, 660); 15 | this->aboutButton->setGeometry(630, 66, 110, 40); 16 | this->returnButton->setGeometry(630, 540, 110, 40); 17 | textBrowser = new QTextBrowser(this); 18 | textBrowser->setGeometry(580, 150, 200, 250); 19 | sendEdit = new QLineEdit(this); 20 | sendButton = new QPushButton(this); 21 | sendEdit->setGeometry(580, 420, 200, 30); 22 | sendButton->setGeometry(720, 470, 60, 30); 23 | sendButton->setText("发送"); 24 | connect(sendButton, SIGNAL(clicked(bool)), this, SLOT(chatSlot())); 25 | connect(sendEdit, SIGNAL(returnPressed()), this, SLOT(chatSlot())); 26 | this->backButton->hide(); // 不显示悔棋按钮 27 | /* 程序主逻辑 */ 28 | server = nullptr; 29 | socket = nullptr; 30 | choose = new ChooseSerOrCli; 31 | choose->exec(); 32 | if (choose->isServer) { 33 | setWindowTitle("ChineseChess: Server"); 34 | ChooseIP *choose = new ChooseIP; 35 | choose->exec(); 36 | qsrand(static_cast(QTime(0,0,0).secsTo(QTime::currentTime()) )); 37 | isRedStart = qrand() % 2; // 随机产生先手 (红方先手) 38 | if (!isRedStart) 39 | this->initStone(true); 40 | server = new QTcpServer(this); // 创建服务器socket 41 | server->listen(QHostAddress(choose->IPstr), 10101); // 监听 42 | textBrowser->append(QString("IP: %1").arg(choose->IPstr)); // 在聊天框里显示IP 43 | // 当有新的连接来的时候,触发信号,调用槽函数 44 | connect(server, SIGNAL(newConnection()), this, SLOT(newConnectionSlot())); 45 | } else { 46 | setWindowTitle("ChineseChess: Client"); 47 | InputIP *input = new InputIP; 48 | input->exec(); 49 | socket = new QTcpSocket(this); // 创建客户端socket 50 | socket->connectToHost(QHostAddress(input->IPstr), 10101); // 连接 51 | // 当有数据发送过来时,触发信号,调用槽函数 52 | connect(socket, SIGNAL(readyRead()), this, SLOT(recvSlot())); 53 | } 54 | textBrowser->append("等待连接..."); 55 | } 56 | 57 | void NetGame::clicked(int clickedID, int row, int col) 58 | { 59 | // 选择了对方的棋子 60 | if (selectedID == -1 && isRedTurn != isRedSide) 61 | return; 62 | 63 | Board::clicked(clickedID, row, col); 64 | char buf[4]; 65 | buf[0] = 1; 66 | buf[1] = static_cast(clickedID); 67 | buf[2] = static_cast(9 - row); 68 | buf[3] = static_cast(8 - col); 69 | socket->write(buf, 4); 70 | } 71 | 72 | void NetGame::newConnectionSlot() 73 | { 74 | if (socket) 75 | return; 76 | 77 | // 接收连接,等同于C语言里的accept,返回值类似于C语言里的文件描述符 78 | socket = server->nextPendingConnection(); 79 | textBrowser->append("连接成功..."); 80 | // 当有客户端数据发送过来时,触发信号,调用槽函数 81 | connect(socket, SIGNAL(readyRead()), this, SLOT(recvSlot())); 82 | char buf[2]; 83 | buf[0] = 0; 84 | buf[1] = static_cast(isRedStart); 85 | socket->write(buf, 2); 86 | } 87 | // 接收数据并处理 88 | void NetGame::recvSlot() 89 | { 90 | QByteArray buf = socket->readAll(); 91 | char cmd = buf[0]; 92 | switch (cmd) { 93 | case 0: { 94 | textBrowser->append("连接成功..."); 95 | if (buf[1]) 96 | this->initStone(true); 97 | break; 98 | } 99 | case 1: { 100 | int ID = buf[1]; 101 | int row = buf[2]; 102 | int col = buf[3]; 103 | Board::clicked(ID, row, col); 104 | break; 105 | } 106 | case 2: { 107 | QString str = buf; 108 | QString chatContent = str.remove(0, 1); 109 | textBrowser->append(chatContent); 110 | break; 111 | } 112 | } 113 | } 114 | 115 | void NetGame::chatSlot() 116 | { 117 | QString chatStr = sendEdit->text(); 118 | sendEdit->clear(); 119 | if (chatStr.isEmpty()) 120 | return; 121 | 122 | chatStr = (choose->isServer ? "Server: " : "Client: ") + chatStr; 123 | textBrowser->append(chatStr); 124 | chatStr = 2 + chatStr; 125 | //const char *buf = chatStr.toStdString().c_str(); 126 | //socket->write(buf, chatStr.length()); 127 | socket->write(chatStr.toUtf8().data()); 128 | } 129 | -------------------------------------------------------------------------------- /NetGame.h: -------------------------------------------------------------------------------- 1 | #ifndef NETGAME_H 2 | #define NETGAME_H 3 | #include "Board.h" 4 | #include "ChooseSerOrCli.h" 5 | #include "ChooseIP.h" 6 | #include "InputIP.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | class NetGame : public Board 17 | { 18 | Q_OBJECT 19 | public: 20 | NetGame(); 21 | 22 | private: 23 | QTcpServer *server; 24 | QTcpSocket *socket; 25 | ChooseSerOrCli *choose; 26 | QTextBrowser *textBrowser; 27 | QLineEdit *sendEdit; 28 | QPushButton *sendButton; 29 | public: 30 | void clicked(int clickedID, int row, int col); 31 | 32 | public slots: 33 | void newConnectionSlot(); 34 | void recvSlot(); 35 | void chatSlot(); 36 | }; 37 | 38 | #endif // NETGAME_H 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChineseChess 2 | 中国象棋 - 基于Qt Widgets ( C++ ) 3 | 4 | **字体采用了华文行楷,可在Fonts/中找到并安装** 5 | 6 | ## description 7 | 8 | 完成单机、网络对战以及人机对战,用极小极大算法、α-β剪枝和静态评估实现了一定程度的人机对弈,并实现了 9 | Windows、 Linux、 Android 等多平台的运行 10 | 11 | ## Screenshot 12 | 13 | ![open.png](https://img.caiyifan.cn/open.png) 14 | 15 | > 注: 下图中棋子边缘的锯齿已消除, 只是图片未更新 16 | 17 | ![singleGame.png](https://img.caiyifan.cn/singleGame.png) 18 | 19 | ![netGame.png](https://img.caiyifan.cn/netGame.png) 20 | 21 | -------------------------------------------------------------------------------- /SingleGame.cpp: -------------------------------------------------------------------------------- 1 | #include "SingleGame.h" 2 | 3 | SingleGame::SingleGame() 4 | { 5 | int ret = QMessageBox::question(nullptr, "友情提示", "是否需要悔棋功能?"); 6 | if (ret == QMessageBox::No) 7 | this->backButton->hide(); 8 | } 9 | void SingleGame::clicked(int clickedID, int row, int col) 10 | { 11 | if (!this->isRedTurn) // 如果不是玩家走,玩家移动了也没用 12 | return; 13 | 14 | if (this->isRedTurn) 15 | Board::clicked(clickedID, row, col); 16 | 17 | if (!this->isRedTurn) // 紧接着上面玩家走了之后,isRedTurn为false,该函数自动进入,轮到电脑走 18 | QTimer::singleShot(100, this, SLOT(computerMove())); 19 | } 20 | void SingleGame::computerMove() 21 | { 22 | QTime time; 23 | time.start(); 24 | Step *step = computerGetBestMove(); 25 | this->saveStep(step->moveID, step->killID, step->rowTo, step->colTo, this->backSteps); 26 | /*qDebug()<backSteps[backSteps.size()-1]->moveID].name()<<"form"<< 27 | this->backSteps[backSteps.size()-1]->rowFrom<<","<< 28 | this->backSteps[backSteps.size()-1]->colFrom<<"to"<< 29 | this->backSteps[backSteps.size()-1]->rowTo<<"," << 30 | this->backSteps[backSteps.size()-1]->colTo;*/ 31 | this->moveStone(step->moveID, step->killID, step->rowTo, step->colTo); 32 | update(); 33 | //int time_Diff = time.elapsed(); 34 | //qDebug()<<"电脑思考了"< &steps) 45 | { 46 | int min = 0; 47 | int max = 16; 48 | if (this->isRedTurn) { 49 | min = 16; 50 | max = 32; 51 | } 52 | for (int i = min; i < max; i++) { 53 | if (stone[i].isDead) 54 | continue; 55 | int eachRowBegin = 0, eachRowEnd = 9, eachColBegin = 0, eachColEnd = 8; 56 | if (stone[i].type == Stone::SI) { // 士优化 57 | eachRowBegin = stone[i].row -1 ; 58 | eachRowEnd = stone[i].row + 1; 59 | eachColBegin = stone[i].col - 1; 60 | eachColEnd = stone[i].col + 1; 61 | } else if (stone[i].type == Stone::JIANG) { // 将优化 62 | eachRowBegin = stone[i].row -1 ; 63 | eachRowEnd = stone[i].row + 1; 64 | eachColBegin = stone[i].col - 1; 65 | eachColEnd = stone[i].col + 1; 66 | int killID = ( (i - 16) > 0 ) ? (i - 16) : (i + 16); 67 | if (!stone[killID].isDead) { 68 | if (this->canMove(i, stone[killID].row, stone[killID].col, killID)) { 69 | this->saveStep(i, killID, stone[killID].row, stone[killID].col, steps); 70 | continue; 71 | } 72 | } 73 | } else if (stone[i].type == Stone::BING) { // 兵优化 74 | if (stone[i].isRed) { 75 | if (stone[i].row > 4) { 76 | eachRowEnd = eachRowBegin = stone[i].row - 1; 77 | eachColBegin = eachColEnd = stone[i].col; 78 | } else { 79 | eachRowBegin = eachRowEnd = stone[i].row - 1; 80 | eachColBegin = stone[i].col - 1; 81 | eachColEnd = stone[i].col + 1; 82 | } 83 | } else { 84 | if (stone[i].row < 5) { 85 | eachRowEnd = eachRowBegin = stone[i].row + 1; 86 | eachColBegin = eachColEnd = stone[i].col; 87 | } else { 88 | eachRowBegin = eachRowEnd = stone[i].row + 1; 89 | eachColBegin = stone[i].col - 1; 90 | eachColEnd = stone[i].col + 1; 91 | } 92 | } 93 | } else if (stone[i].type == Stone::XIANG) { // 象优化 94 | if (stone[i].isRed) { 95 | eachRowBegin = 5; 96 | eachRowEnd = 9; 97 | } else { 98 | eachRowBegin = 0; 99 | eachRowEnd = 4; 100 | } 101 | } 102 | for (int row = eachRowBegin; row <= eachRowEnd; row++) { 103 | for (int col = eachColBegin;col <= eachColEnd;col++) { 104 | int killID = this->getStoneID(row, col); 105 | if (killID != -1 && this->isRedTurn == stone[killID].isRed) 106 | continue; 107 | if (this->canMove(i, row, col, killID)) 108 | this->saveStep(i, killID, row, col , steps); 109 | } 110 | } 111 | } 112 | } 113 | void SingleGame::fakeMove(Step *&step) 114 | { 115 | this->killStone(step->killID); 116 | this->moveStone(step->moveID, step->rowTo, step->colTo); 117 | } 118 | 119 | void SingleGame::unfakeMove(Step *&step) 120 | { 121 | this->reviveStone(step->killID); 122 | this->moveStone(step->moveID, step->rowFrom, step->colFrom); 123 | } 124 | 125 | int SingleGame::calcScore() 126 | { 127 | //enum TYPE{CHE, MA, XIANG, PAO ,JIANG, BING, SI}; 128 | int chessScore[] = {52, 13, 6, 22, 208, 2, 6}; 129 | int blackScore = 0; 130 | int redScore = 0; 131 | for (int i = 0; i < 16; i++) { 132 | if (stone[i].isDead) 133 | continue; 134 | if (stone[i].type == Stone::BING && stone[i].row > 4) // 兵分值升级 135 | blackScore += (chessScore[Stone::BING] * 10); 136 | else 137 | blackScore += chessScore[stone[i].type]; 138 | } 139 | for (int i = 16; i < 32; i++) { 140 | if (stone[i].isDead) 141 | continue; 142 | if (stone[i].type == Stone::BING && stone[i].row < 5) // 兵分值升级 143 | redScore += (chessScore[Stone::BING] * 10); 144 | else 145 | redScore += chessScore[stone[i].type]; 146 | } 147 | int totalScore = blackScore - redScore; 148 | return totalScore; 149 | } 150 | int SingleGame::getMinScore(int deep, int currentMaxScore) 151 | { 152 | if (!deep) 153 | return calcScore(); 154 | 155 | QList steps; 156 | getAllPossibleMove(steps); 157 | int minScore = INT_MAX; 158 | while (steps.count()) { 159 | Step *step = steps.front(); 160 | steps.removeFirst(); 161 | fakeMove(step); 162 | int score = getMaxScore(deep - 1, minScore); 163 | unfakeMove(step); 164 | delete step; 165 | if (score <= currentMaxScore) { 166 | while (steps.count()) { 167 | Step *step = steps.front(); 168 | steps.removeFirst(); 169 | delete step; 170 | } 171 | return score; 172 | } 173 | if (minScore > score) 174 | minScore = score; 175 | } 176 | return minScore; 177 | } 178 | int SingleGame::getMaxScore(int deep, int currentMinScore) 179 | { 180 | if (!deep) 181 | return calcScore(); 182 | 183 | QList steps; 184 | getAllPossibleMove(steps); 185 | int maxScore = INT_MIN; 186 | while (steps.count()) { 187 | Step *step = steps.front(); 188 | steps.removeFirst(); 189 | fakeMove(step); 190 | int score = getMinScore(deep - 1, maxScore); 191 | unfakeMove(step); 192 | delete step; 193 | if (score >= currentMinScore) { 194 | while (steps.count()) { 195 | Step *step = steps.front(); 196 | steps.removeFirst(); 197 | delete step; 198 | } 199 | return score; 200 | } 201 | if (maxScore < score) 202 | maxScore = score; 203 | } 204 | return maxScore; 205 | } 206 | 207 | Step* SingleGame::computerGetBestMove() 208 | { 209 | QList steps; 210 | getAllPossibleMove(steps); 211 | int maxScore = INT_MIN; 212 | Step* bestStep = nullptr; 213 | while (steps.count()) { 214 | Step *step = steps.front(); 215 | steps.removeFirst(); 216 | fakeMove(step); 217 | int score = getMinScore(deep - 1, maxScore); // 获取第二步中分值最小的 218 | unfakeMove(step); 219 | if (score > maxScore) { 220 | maxScore = score; 221 | delete bestStep; 222 | bestStep = step; 223 | } else { 224 | delete step; 225 | } 226 | } 227 | return bestStep; 228 | } 229 | -------------------------------------------------------------------------------- /SingleGame.h: -------------------------------------------------------------------------------- 1 | #ifndef SINGLEGAME_H 2 | #define SINGLEGAME_H 3 | #include "Board.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class SingleGame : public Board 14 | { 15 | Q_OBJECT 16 | public: 17 | SingleGame(); 18 | public: 19 | int deep = 4; 20 | public: 21 | void clicked(int clickedID, int row, int col); 22 | Step* computerGetBestMove(); 23 | void getAllPossibleMove(QList &steps); 24 | void fakeMove(Step *&step); // 假走 25 | void unfakeMove(Step *&step); // 假走后移动回去 26 | int calcScore(); 27 | int getMinScore(int deep, int currentMinScore); 28 | int getMaxScore(int deep, int currentMaxScore); 29 | public slots: 30 | void computerMove(); 31 | }; 32 | 33 | #endif // SINGLEGAME_H 34 | -------------------------------------------------------------------------------- /Step.h: -------------------------------------------------------------------------------- 1 | #ifndef STEP_H 2 | #define STEP_H 3 | 4 | struct Step 5 | { 6 | int moveID; 7 | int killID; 8 | int rowFrom; 9 | int colFrom; 10 | int rowTo; 11 | int colTo; 12 | }; 13 | 14 | 15 | #endif // STEP_H 16 | -------------------------------------------------------------------------------- /Stone.cpp: -------------------------------------------------------------------------------- 1 | #include "Stone.h" 2 | 3 | struct { 4 | int row; 5 | int col; 6 | Stone::TYPE type; 7 | } pos[16] = { 8 | {0, 0, Stone::CHE}, 9 | {0, 1, Stone::MA}, 10 | {0, 2, Stone::XIANG}, 11 | {0, 3, Stone::SI}, 12 | {0, 4, Stone::JIANG}, 13 | {0, 5, Stone::SI}, 14 | {0, 6, Stone::XIANG}, 15 | {0, 7, Stone::MA}, 16 | {0, 8, Stone::CHE}, 17 | {2, 1, Stone::PAO}, 18 | {2, 7, Stone::PAO}, 19 | {3, 0, Stone::BING}, 20 | {3, 2, Stone::BING}, 21 | {3, 4, Stone::BING}, 22 | {3, 6, Stone::BING}, 23 | {3, 8, Stone::BING} }; 24 | 25 | Stone::Stone() 26 | { 27 | 28 | } 29 | 30 | QString Stone::name() 31 | { 32 | switch(this->type) { 33 | case Stone::PAO: 34 | return "炮"; 35 | case Stone::BING: 36 | return "兵"; 37 | case Stone::CHE: 38 | return "车"; 39 | case Stone::JIANG: 40 | return "将"; 41 | case Stone::MA: 42 | return "马"; 43 | case Stone::SI: 44 | return "士"; 45 | case Stone::XIANG: 46 | return "相"; 47 | } 48 | return "错误"; 49 | } 50 | 51 | void Stone::init(int ID) 52 | { 53 | 54 | if (ID < 16) { 55 | this->row = pos[ID].row; 56 | this->col = pos[ID].col; 57 | this->type = pos[ID].type; 58 | } else { 59 | this->col = 8 - pos[ID-16].col; 60 | this->row = 9 - pos[ID-16].row; 61 | this->type = pos[ID-16].type; 62 | } 63 | this->ID = ID; 64 | this->isDead = false; 65 | this->isRed = (ID >= 16); 66 | } 67 | -------------------------------------------------------------------------------- /Stone.h: -------------------------------------------------------------------------------- 1 | #ifndef STONE_H 2 | #define STONE_H 3 | #include 4 | 5 | class Stone 6 | { 7 | public: 8 | Stone(); 9 | public: 10 | enum TYPE{CHE, MA, XIANG, PAO ,JIANG, BING, SI}; 11 | int row; // 行 12 | int col; // 列 13 | int ID; 14 | bool isRed; 15 | bool isDead; 16 | TYPE type; 17 | 18 | public: 19 | QString name(); 20 | void init(int ID); 21 | }; 22 | 23 | #endif // STONE_H 24 | -------------------------------------------------------------------------------- /WinWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "WinWidget.h" 2 | 3 | WinWidget::WinWidget(QWidget *parent) : QWidget(parent) 4 | { 5 | } 6 | 7 | WinWidget::WinWidget(int i) 8 | { 9 | this->resize(480,320); 10 | this->color = i; 11 | } 12 | 13 | void WinWidget::paintEvent(QPaintEvent *) 14 | { 15 | QPainter *painter = new QPainter(this); 16 | if (this->color) { 17 | painter->setPen(Qt::red); 18 | this->setWindowTitle("红方胜出,黑方战败"); 19 | } else { 20 | painter->setPen(Qt::black); 21 | this->setWindowTitle("黑方胜出,红方战败"); 22 | } 23 | painter->setFont(QFont("华文行楷",100,50)); 24 | QRect rect = QRect(30,10,400,300); 25 | painter->drawText(rect,"Win!",QTextOption(Qt::AlignCenter)); 26 | } 27 | -------------------------------------------------------------------------------- /WinWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef WINWIDGET_H 2 | #define WINWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | class WinWidget : public QWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit WinWidget(QWidget *parent = nullptr); 12 | WinWidget(int i); 13 | private: 14 | int color; 15 | protected: 16 | void paintEvent(QPaintEvent *); 17 | }; 18 | 19 | #endif // WINWIDGET_H 20 | -------------------------------------------------------------------------------- /chess/99bOOOPIC52.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfanTsai/ChineseChess/a34d63efef7d0deb95a6e1cba3a5785012a56864/chess/99bOOOPIC52.jpg -------------------------------------------------------------------------------- /chess/bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfanTsai/ChineseChess/a34d63efef7d0deb95a6e1cba3a5785012a56864/chess/bg.jpeg -------------------------------------------------------------------------------- /chessrec.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | chess/99bOOOPIC52.jpg 4 | chess/bg.jpeg 5 | Fonts/华文行楷.ttf 6 | 7 | 8 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // 2016年6月8日19:36:49 2 | // 加了剪枝优化,走棋的优化 3 | // 目前深度为4 4 | // 2016年7月4日14:36:47 追加悔棋功能 5 | // 2016年7月5日15:32:20 实现网络版和不完善的网络聊天功能 6 | // 待解决:网络版聊天功能中文乱码,长度不能过长等问题还没解决 7 | // 2019年03月05日22:02:05 已解决远古问题 8 | #include "openWidget.h" 9 | #include 10 | #include 11 | #include 12 | int main(int argc, char *argv[]) 13 | { 14 | QApplication a(argc, argv); 15 | openWidget w; 16 | w.show(); 17 | 18 | /* 设置字体 */ 19 | int id = QFontDatabase::addApplicationFont(":/new/prefix1/Fonts/华文行楷.ttf"); 20 | QString str = QFontDatabase::applicationFontFamilies(id).at(0); 21 | QFont font(str,10); 22 | font.setPointSize(20); 23 | w.setFont(font); 24 | 25 | return a.exec(); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /openWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "openWidget.h" 2 | #include "ui_openWidget.h" 3 | #include "Board.h" 4 | #include "SingleGame.h" 5 | #include "NetGame.h" 6 | openWidget::openWidget(QWidget *parent) : 7 | QDialog(parent), 8 | ui(new Ui::openWidget) 9 | { 10 | ui->setupUi(this); 11 | this->setWindowTitle("中国象棋"); 12 | ui->button2->setFocusPolicy(Qt::TabFocus); 13 | this->setMinimumSize(409, 583); 14 | this->setMaximumSize(409, 583); 15 | connect(ui->button1,SIGNAL(clicked(bool)),this,SLOT(button1Slot())); 16 | connect(ui->button2,SIGNAL(clicked(bool)),this,SLOT(button2Slot())); 17 | connect(ui->button3,SIGNAL(clicked(bool)),this,SLOT(button3Slot())); 18 | } 19 | 20 | openWidget::~openWidget() 21 | { 22 | delete ui; 23 | } 24 | 25 | void openWidget::button1Slot() 26 | { 27 | this->close(); 28 | Board *board = new Board; 29 | board->show(); 30 | } 31 | 32 | void openWidget::button2Slot() 33 | { 34 | this->close(); 35 | SingleGame *singleGame = new SingleGame; 36 | singleGame->show(); 37 | } 38 | 39 | void openWidget::button3Slot() 40 | { 41 | this->close(); 42 | NetGame *netGame = new NetGame; 43 | netGame->show(); 44 | } 45 | -------------------------------------------------------------------------------- /openWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENWIDGET_H 2 | #define OPENWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class openWidget; 8 | } 9 | 10 | class openWidget : public QDialog 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit openWidget(QWidget *parent = nullptr); 16 | ~openWidget(); 17 | 18 | private: 19 | Ui::openWidget *ui; 20 | public slots: 21 | void button1Slot(); 22 | void button2Slot(); 23 | void button3Slot(); 24 | }; 25 | 26 | #endif // OPENWIDGET_H 27 | -------------------------------------------------------------------------------- /openWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | openWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 408 10 | 583 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | #openWidget { 18 | background-image: url(:/new/prefix1/chess/99bOOOPIC52.jpg); 19 | } 20 | 21 | 22 | 23 | 24 | 250 25 | 200 26 | 111 27 | 41 28 | 29 | 30 | 31 | 32 | 华文行楷 33 | 17 34 | 50 35 | true 36 | false 37 | 38 | 39 | 40 | false 41 | 42 | 43 | QPushButton { 44 | color: black; 45 | background-color: #c0c1bb; 46 | border: none; 47 | border-radius: 10px; 48 | 49 | } 50 | 51 | QPushButton:hover { 52 | color: black; 53 | background-color: #b99b7f; 54 | border: none; 55 | border-radius: 10px; 56 | 57 | } 58 | 59 | 60 | 自我对战 61 | 62 | 63 | 300 64 | 65 | 66 | 67 | 68 | 69 | 250 70 | 270 71 | 111 72 | 41 73 | 74 | 75 | 76 | 77 | 华文行楷 78 | 17 79 | true 80 | 81 | 82 | 83 | Qt::StrongFocus 84 | 85 | 86 | QPushButton { 87 | color: black; 88 | background-color: #c0c1bb; 89 | border: none; 90 | border-radius: 10px; 91 | 92 | } 93 | 94 | QPushButton:hover { 95 | color: black; 96 | background-color: #b99b7f; 97 | border: none; 98 | border-radius: 10px; 99 | 100 | } 101 | 102 | 103 | 人机对战 104 | 105 | 106 | 107 | 108 | 109 | 250 110 | 340 111 | 111 112 | 41 113 | 114 | 115 | 116 | 117 | 华文行楷 118 | 17 119 | true 120 | 121 | 122 | 123 | QPushButton { 124 | color: black; 125 | background-color: #c0c1bb; 126 | border: none; 127 | border-radius: 10px; 128 | 129 | } 130 | 131 | QPushButton:hover { 132 | color: black; 133 | background-color: #b99b7f; 134 | border: none; 135 | border-radius: 10px; 136 | 137 | } 138 | 139 | 140 | 连网对战 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /screenshot/netGame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfanTsai/ChineseChess/a34d63efef7d0deb95a6e1cba3a5785012a56864/screenshot/netGame.png -------------------------------------------------------------------------------- /screenshot/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfanTsai/ChineseChess/a34d63efef7d0deb95a6e1cba3a5785012a56864/screenshot/open.png -------------------------------------------------------------------------------- /screenshot/singleGame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfanTsai/ChineseChess/a34d63efef7d0deb95a6e1cba3a5785012a56864/screenshot/singleGame.png --------------------------------------------------------------------------------