Qt、C++实现五子棋人机对战与当地双人对战(高难度AI,少少代码)

[复制链接]
发表于 2025-12-23 14:03:23 | 显示全部楼层 |阅读模式
先容

本项目基于 Qt C++ 实现了一个完备的五子棋游戏,支持 人机对战 和 大家对战 模式,并提供了三种难度选择(简单、中等、困难)。界面雅观,逻辑清晰,是一个综合性很强的 Qt 小项目
标题项目核心功能


  • 棋盘绘制:通过 QPainter 实现网格棋盘和棋子的绘制,利用坐标映射鼠标点击位置到棋盘。
  • 落子规则:处理处罚玩家或 AI 的落子,并查抄是否得胜。
  • 人机对战:根据难度,AI 实现从简单的随机落子到基于评分体系的智能落子。
  • 模式切换:支持大家对战和人机对战模式,切换后自动重置棋盘。
  • 难度选择:通过下拉框提供“简单”、“中等”、“困难”三种难度。
本项目是一个范例的五子棋游戏,涵盖了 Qt 界面开辟、绘图、变乱处理处罚以及简单的 AI 逻辑实现。通过界面美化和模式选择功能,为玩家提供了精良的用户体验,非常适互助为 Qt 项目标学习和展示案例。

代码
  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3. #include <QMainWindow>
  4. #include <QPainter>
  5. #include <QMouseEvent>
  6. #include <QVector>
  7. #include <QPushButton>
  8. #include <QLabel>
  9. #include <QComboBox>
  10. QT_BEGIN_NAMESPACE
  11. namespace Ui { class MainWindow; }
  12. QT_END_NAMESPACE
  13. class MainWindow : public QMainWindow {
  14.     Q_OBJECT
  15. public:
  16.     explicit MainWindow(QWidget *parent = nullptr);
  17.     ~MainWindow();
  18. protected:
  19.     void paintEvent(QPaintEvent *event) override;  // 绘制棋盘和棋子
  20.     void mousePressEvent(QMouseEvent *event) override;  // 处理鼠标点击落子
  21. private slots:
  22.     void onNewGameClicked();   // 新游戏按钮事件
  23.     void onSwitchModeClicked(); // 切换模式按钮事件
  24.     void onDifficultyChanged(int index); // 选择难度下拉框事件
  25. private:
  26.     Ui::MainWindow *ui;
  27.     // 界面控件
  28.     QLabel *modeLabel;           // 显示当前模式
  29.     QLabel *statusLabel;         // 显示当前轮次
  30.     QComboBox *difficultyComboBox; // 难度选择下拉框
  31.     QPushButton *newGameButton;   // 新游戏按钮
  32.     QPushButton *switchModeButton; // 切换模式按钮
  33.     // 游戏状态
  34.     const int gridSize = 40;      // 棋盘格子大小
  35.     const int boardSize = 15;     // 棋盘行列数
  36.     QVector<QVector<int>> board; // 棋盘状态,0为空,1为黑棋,2为白棋
  37.     bool isBlackTurn = true;      // 当前是否轮到黑棋
  38.     bool isPlayerVsAI = false;    // 是否是人机对战模式
  39.     int difficultyLevel = 1;      // 默认难度为简单模式
  40.     // 内部方法
  41.     void checkWin(int x, int y);  // 检查胜负
  42.     void resetGame();             // 重置棋盘
  43.     void aiMove();                // AI 落子逻辑
  44.     int calculateScore(int x, int y, int player); // 计算分数
  45. };
  46. #endif // MAINWINDOW_H
复制代码
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include <QMessageBox>
  4. #include <cstdlib>  // 用于随机数生成(AI 落子)
  5. MainWindow::MainWindow(QWidget *parent)
  6.     : QMainWindow(parent), ui(new Ui::MainWindow), board(boardSize, QVector<int>(boardSize, 0)) {
  7.     ui->setupUi(this);
  8.     // 设置窗口大小
  9.     setFixedSize(gridSize * boardSize, gridSize * boardSize + 80);
  10.     // 初始化按钮
  11.     newGameButton = new QPushButton("新游戏", this);
  12.     switchModeButton = new QPushButton("切换模式", this);
  13.     // 初始化标签
  14.     statusLabel = new QLabel("当前轮到黑棋", this);
  15.     modeLabel = new QLabel("当前模式:人人对战", this);
  16.     // 初始化难度选择下拉框
  17.     difficultyComboBox = new QComboBox(this);
  18.     difficultyComboBox->addItem("简单");
  19.     difficultyComboBox->addItem("中等");
  20.     difficultyComboBox->addItem("困难");
  21.     difficultyComboBox->setCurrentIndex(difficultyLevel - 1); // 默认选中“简单”
  22.     // 设置控件位置
  23.     newGameButton->setGeometry(10, gridSize * boardSize + 10, 100, 30);
  24.     switchModeButton->setGeometry(120, gridSize * boardSize + 10, 100, 30);
  25.     difficultyComboBox->setGeometry(240, gridSize * boardSize + 10, 80, 30);
  26.     statusLabel->setGeometry(330, gridSize * boardSize + 10, 120, 30);
  27.     modeLabel->setGeometry(10, gridSize * boardSize + 50, 200, 30);
  28.     // 设置按钮样式
  29.     QString buttonStyle =
  30.         "QPushButton {"
  31.         "   background-color: #87CEFA;"      // 浅蓝色背景
  32.         "   color: white;"                  // 白色文字
  33.         "   border-radius: 10px;"           // 圆角
  34.         "   font-size: 14px;"               // 字体大小
  35.         "   padding: 5px 10px;"
  36.         "}"
  37.         "QPushButton:hover {"
  38.         "   background-color: #4682B4;"     // Hover时深蓝色
  39.         "}";
  40.     newGameButton->setStyleSheet(buttonStyle);
  41.     switchModeButton->setStyleSheet(buttonStyle);
  42.     // 设置下拉框样式
  43.     difficultyComboBox->setStyleSheet(
  44.         "QComboBox {"
  45.         "   border: 2px solid #4682B4;"     // 深蓝色边框
  46.         "   border-radius: 5px;"
  47.         "   padding: 3px 8px;"
  48.         "   font-size: 14px;"
  49.         "}"
  50.         "QComboBox::drop-down {"
  51.         "   border: none;"
  52.         "}"
  53.         "QComboBox:hover {"
  54.         "   border-color: #87CEFA;"         // Hover时浅蓝边框
  55.         "}"
  56.     );
  57.     // 设置标签样式
  58.     QString labelStyle =
  59.         "QLabel {"
  60.         "   font-size: 16px;"
  61.         "   color: #2F4F4F;"                // 深灰色文字
  62.         "   font-weight: bold;"
  63.         "}";
  64.     statusLabel->setStyleSheet(labelStyle);
  65.     modeLabel->setStyleSheet(labelStyle);
  66.     // 绑定信号与槽
  67.     connect(newGameButton, &QPushButton::clicked, this, &MainWindow::onNewGameClicked);
  68.     connect(switchModeButton, &QPushButton::clicked, this, &MainWindow::onSwitchModeClicked);
  69.     connect(difficultyComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
  70.             this, &MainWindow::onDifficultyChanged);
  71. }
  72. MainWindow::~MainWindow() {
  73.     delete ui;
  74. }
  75. void MainWindow::paintEvent(QPaintEvent *) {
  76.     QPainter painter(this);
  77.     painter.setRenderHint(QPainter::Antialiasing);
  78.     // 绘制棋盘背景
  79.     painter.setBrush(QColor(245, 222, 179)); // 棋盘为浅棕色
  80.     painter.drawRect(0, 0, gridSize * boardSize, gridSize * boardSize);
  81.     // 绘制棋盘线条
  82.     painter.setPen(QPen(Qt::black, 2)); // 黑色线条,宽度2px
  83.     for (int i = 0; i < boardSize; ++i) {
  84.         painter.drawLine(gridSize / 2, gridSize / 2 + i * gridSize,
  85.                          gridSize / 2 + (boardSize - 1) * gridSize, gridSize / 2 + i * gridSize);
  86.         painter.drawLine(gridSize / 2 + i * gridSize, gridSize / 2,
  87.                          gridSize / 2 + i * gridSize, gridSize / 2 + (boardSize - 1) * gridSize);
  88.     }
  89.     // 绘制棋子
  90.     for (int i = 0; i < boardSize; ++i) {
  91.         for (int j = 0; j < boardSize; ++j) {
  92.             if (board[i][j] == 1) {
  93.                 painter.setBrush(Qt::black);
  94.                 painter.drawEllipse(gridSize / 2 + i * gridSize - 15,
  95.                                     gridSize / 2 + j * gridSize - 15, 30, 30);
  96.             } else if (board[i][j] == 2) {
  97.                 painter.setBrush(Qt::white);
  98.                 painter.drawEllipse(gridSize / 2 + i * gridSize - 15,
  99.                                     gridSize / 2 + j * gridSize - 15, 30, 30);
  100.             }
  101.         }
  102.     }
  103. }
  104. void MainWindow::mousePressEvent(QMouseEvent *event) {
  105.     if (!isBlackTurn && isPlayerVsAI) return;  // 人机对战时禁止白棋玩家操作
  106.     // 计算点击的位置对应的棋盘格子
  107.     int x = (event->x() - gridSize / 2 + gridSize / 2) / gridSize;
  108.     int y = (event->y() - gridSize / 2 + gridSize / 2) / gridSize;
  109.     // 检查点击是否在有效范围内,且是否未落子
  110.     if (x < 0 || x >= boardSize || y < 0 || y >= boardSize || board[x][y] != 0)
  111.         return;
  112.     // 记录当前落子
  113.     board[x][y] = isBlackTurn ? 1 : 2;
  114.     isBlackTurn = !isBlackTurn;
  115.     // 更新提示信息
  116.     statusLabel->setText(isBlackTurn ? "当前轮到黑棋" : "当前轮到白棋");
  117.     update(); // 更新界面
  118.     checkWin(x, y);
  119.     // 如果是人机对战模式,AI 落子
  120.     if (!isBlackTurn && isPlayerVsAI) {
  121.         aiMove();
  122.     }
  123. }
  124. void MainWindow::checkWin(int x, int y) {
  125.     int directions[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}};
  126.     int currentPlayer = board[x][y];
  127.     for (auto &dir : directions) {
  128.         int count = 1;
  129.         for (int i = 1; i < 5; ++i) {
  130.             int nx = x + i * dir[0], ny = y + i * dir[1];
  131.             if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && board[nx][ny] == currentPlayer)
  132.                 ++count;
  133.             else
  134.                 break;
  135.         }
  136.         for (int i = 1; i < 5; ++i) {
  137.             int nx = x - i * dir[0], ny = y - i * dir[1];
  138.             if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && board[nx][ny] == currentPlayer)
  139.                 ++count;
  140.             else
  141.                 break;
  142.         }
  143.         if (count >= 5) {
  144.             QString winner = (currentPlayer == 1) ? "黑棋" : "白棋";
  145.             QMessageBox::information(this, "游戏结束", winner + " 胜利!");
  146.             resetGame();
  147.             return;
  148.         }
  149.     }
  150. }
  151. void MainWindow::resetGame() {
  152.     for (auto &row : board)
  153.         row.fill(0);
  154.     isBlackTurn = true;
  155.     statusLabel->setText("当前轮到黑棋");
  156.     update();
  157. }
  158. void MainWindow::onNewGameClicked() {
  159.     resetGame();
  160. }
  161. void MainWindow::onSwitchModeClicked() {
  162.     isPlayerVsAI = !isPlayerVsAI; // 切换模式
  163.     QString modeText = isPlayerVsAI ? "人机对战模式" : "人人对战模式";
  164.     modeLabel->setText("当前模式:" + modeText); // 更新模式显示
  165.     QMessageBox::information(this, "模式切换", modeText);
  166.     resetGame(); // 切换模式后重置棋盘
  167. }
  168. void MainWindow::onDifficultyChanged(int index) {
  169.     difficultyLevel = index + 1; // 更新难度等级
  170.     QString difficultyText = difficultyComboBox->currentText();
  171.     QMessageBox::information(this, "难度选择", "当前难度:" + difficultyText);
  172. }
  173. void MainWindow::aiMove() {
  174.     int bestX = -1, bestY = -1;
  175.     if (difficultyLevel == 1) {
  176.         // 简单模式:随机选择空位
  177.         while (true) {
  178.             int x = rand() % boardSize;
  179.             int y = rand() % boardSize;
  180.             if (board[x][y] == 0) {
  181.                 bestX = x;
  182.                 bestY = y;
  183.                 break;
  184.             }
  185.         }
  186.     } else {
  187.         // 中等/困难模式:计算最佳位置
  188.         int maxScore = -1;
  189.         QVector<QVector<int>> score(boardSize, QVector<int>(boardSize, 0));
  190.         for (int x = 0; x < boardSize; ++x) {
  191.             for (int y = 0; y < boardSize; ++y) {
  192.                 if (board[x][y] != 0) continue;
  193.                 int baseScore = calculateScore(x, y, 2) + calculateScore(x, y, 1);
  194.                 if (difficultyLevel == 3) {
  195.                     // 困难模式:增加中心权重
  196.                     int distanceToCenter = abs(x - boardSize / 2) + abs(y - boardSize / 2);
  197.                     baseScore += (boardSize - distanceToCenter) * 5;
  198.                 }
  199.                 score[x][y] = baseScore;
  200.                 if (baseScore > maxScore) {
  201.                     maxScore = baseScore;
  202.                     bestX = x;
  203.                     bestY = y;
  204.                 }
  205.             }
  206.         }
  207.     }
  208.     // 在最佳位置落子
  209.     if (bestX != -1 && bestY != -1) {
  210.         board[bestX][bestY] = 2;
  211.         isBlackTurn = true;
  212.         statusLabel->setText("当前轮到黑棋");
  213.         update();
  214.         checkWin(bestX, bestY);
  215.     }
  216. }
  217. int MainWindow::calculateScore(int x, int y, int player) {
  218.     int directions[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}};
  219.     int score = 0;
  220.     for (auto &dir : directions) {
  221.         int count = 1; // 当前玩家的连子数
  222.         int block = 0; // 是否被堵住:0为两端均开,1为一端堵,2为两端堵
  223.         int empty = 0; // 连子中的空位数
  224.         // 正向检查
  225.         for (int i = 1; i < 5; ++i) {
  226.             int nx = x + i * dir[0];
  227.             int ny = y + i * dir[1];
  228.             if (nx < 0 || nx >= boardSize || ny < 0 || ny >= boardSize) {
  229.                 block++; // 超出棋盘视为堵住
  230.                 break;
  231.             }
  232.             if (board[nx][ny] == player) {
  233.                 count++;
  234.             } else if (board[nx][ny] == 0) {
  235.                 empty++;
  236.                 break; // 停止正向检查
  237.             } else {
  238.                 block++;
  239.                 break; // 对方棋子,视为堵住
  240.             }
  241.         }
  242.         // 反向检查
  243.         for (int i = 1; i < 5; ++i) {
  244.             int nx = x - i * dir[0];
  245.             int ny = y - i * dir[1];
  246.             if (nx < 0 || nx >= boardSize || ny < 0 || ny >= boardSize) {
  247.                 block++;
  248.                 break;
  249.             }
  250.             if (board[nx][ny] == player) {
  251.                 count++;
  252.             } else if (board[nx][ny] == 0) {
  253.                 empty++;
  254.                 break;
  255.             } else {
  256.                 block++;
  257.                 break;
  258.             }
  259.         }
  260.         // 根据连子数、空位数和堵塞情况评估分值
  261.         if (count >= 5) {
  262.             score += 10000; // 连成五子,最高分
  263.         } else if (count == 4 && block == 0) {
  264.             score += 1000; // 活四
  265.         } else if (count == 4 && block == 1) {
  266.             score += 500; // 冲四
  267.         } else if (count == 3 && block == 0) {
  268.             score += 200; // 活三
  269.         } else if (count == 3 && block == 1) {
  270.             score += 50; // 冲三
  271.         } else if (count == 2 && block == 0) {
  272.             score += 10; // 活二
  273.         }
  274.     }
  275.     return score;
  276. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表