自学内容网 自学内容网

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

介绍

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

标题项目核心功能

  1. 棋盘绘制:通过 QPainter 实现网格棋盘和棋子的绘制,使用坐标映射鼠标点击位置到棋盘。
  2. 落子规则:处理玩家或 AI 的落子,并检查是否获胜。
  3. 人机对战:根据难度,AI 实现从简单的随机落子到基于评分系统的智能落子。
  4. 模式切换:支持人人对战和人机对战模式,切换后自动重置棋盘。
  5. 难度选择:通过下拉框提供“简单”、“中等”、“困难”三种难度。

本项目是一个典型的五子棋游戏,涵盖了 Qt 界面开发、绘图、事件处理以及简单的 AI 逻辑实现。通过界面美化和模式选择功能,为玩家提供了良好的用户体验,非常适合作为 Qt 项目的学习和展示案例。

在这里插入图片描述

上代码

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPainter>
#include <QMouseEvent>
#include <QVector>
#include <QPushButton>
#include <QLabel>
#include <QComboBox>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    void paintEvent(QPaintEvent *event) override;  // 绘制棋盘和棋子
    void mousePressEvent(QMouseEvent *event) override;  // 处理鼠标点击落子

private slots:
    void onNewGameClicked();   // 新游戏按钮事件
    void onSwitchModeClicked(); // 切换模式按钮事件
    void onDifficultyChanged(int index); // 选择难度下拉框事件

private:
    Ui::MainWindow *ui;

    // 界面控件
    QLabel *modeLabel;           // 显示当前模式
    QLabel *statusLabel;         // 显示当前轮次
    QComboBox *difficultyComboBox; // 难度选择下拉框
    QPushButton *newGameButton;   // 新游戏按钮
    QPushButton *switchModeButton; // 切换模式按钮

    // 游戏状态
    const int gridSize = 40;      // 棋盘格子大小
    const int boardSize = 15;     // 棋盘行列数
    QVector<QVector<int>> board; // 棋盘状态,0为空,1为黑棋,2为白棋
    bool isBlackTurn = true;      // 当前是否轮到黑棋
    bool isPlayerVsAI = false;    // 是否是人机对战模式
    int difficultyLevel = 1;      // 默认难度为简单模式

    // 内部方法
    void checkWin(int x, int y);  // 检查胜负
    void resetGame();             // 重置棋盘
    void aiMove();                // AI 落子逻辑
    int calculateScore(int x, int y, int player); // 计算分数
};

#endif // MAINWINDOW_H

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <cstdlib>  // 用于随机数生成(AI 落子)

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow), board(boardSize, QVector<int>(boardSize, 0)) {
    ui->setupUi(this);

    // 设置窗口大小
    setFixedSize(gridSize * boardSize, gridSize * boardSize + 80);

    // 初始化按钮
    newGameButton = new QPushButton("新游戏", this);
    switchModeButton = new QPushButton("切换模式", this);

    // 初始化标签
    statusLabel = new QLabel("当前轮到黑棋", this);
    modeLabel = new QLabel("当前模式:人人对战", this);

    // 初始化难度选择下拉框
    difficultyComboBox = new QComboBox(this);
    difficultyComboBox->addItem("简单");
    difficultyComboBox->addItem("中等");
    difficultyComboBox->addItem("困难");
    difficultyComboBox->setCurrentIndex(difficultyLevel - 1); // 默认选中“简单”

    // 设置控件位置
    newGameButton->setGeometry(10, gridSize * boardSize + 10, 100, 30);
    switchModeButton->setGeometry(120, gridSize * boardSize + 10, 100, 30);
    difficultyComboBox->setGeometry(240, gridSize * boardSize + 10, 80, 30);
    statusLabel->setGeometry(330, gridSize * boardSize + 10, 120, 30);
    modeLabel->setGeometry(10, gridSize * boardSize + 50, 200, 30);

    // 设置按钮样式
    QString buttonStyle =
        "QPushButton {"
        "   background-color: #87CEFA;"      // 浅蓝色背景
        "   color: white;"                  // 白色文字
        "   border-radius: 10px;"           // 圆角
        "   font-size: 14px;"               // 字体大小
        "   padding: 5px 10px;"
        "}"
        "QPushButton:hover {"
        "   background-color: #4682B4;"     // Hover时深蓝色
        "}";

    newGameButton->setStyleSheet(buttonStyle);
    switchModeButton->setStyleSheet(buttonStyle);

    // 设置下拉框样式
    difficultyComboBox->setStyleSheet(
        "QComboBox {"
        "   border: 2px solid #4682B4;"     // 深蓝色边框
        "   border-radius: 5px;"
        "   padding: 3px 8px;"
        "   font-size: 14px;"
        "}"
        "QComboBox::drop-down {"
        "   border: none;"
        "}"
        "QComboBox:hover {"
        "   border-color: #87CEFA;"         // Hover时浅蓝边框
        "}"
    );

    // 设置标签样式
    QString labelStyle =
        "QLabel {"
        "   font-size: 16px;"
        "   color: #2F4F4F;"                // 深灰色文字
        "   font-weight: bold;"
        "}";

    statusLabel->setStyleSheet(labelStyle);
    modeLabel->setStyleSheet(labelStyle);

    // 绑定信号与槽
    connect(newGameButton, &QPushButton::clicked, this, &MainWindow::onNewGameClicked);
    connect(switchModeButton, &QPushButton::clicked, this, &MainWindow::onSwitchModeClicked);
    connect(difficultyComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &MainWindow::onDifficultyChanged);
}



MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    // 绘制棋盘背景
    painter.setBrush(QColor(245, 222, 179)); // 棋盘为浅棕色
    painter.drawRect(0, 0, gridSize * boardSize, gridSize * boardSize);

    // 绘制棋盘线条
    painter.setPen(QPen(Qt::black, 2)); // 黑色线条,宽度2px
    for (int i = 0; i < boardSize; ++i) {
        painter.drawLine(gridSize / 2, gridSize / 2 + i * gridSize,
                         gridSize / 2 + (boardSize - 1) * gridSize, gridSize / 2 + i * gridSize);
        painter.drawLine(gridSize / 2 + i * gridSize, gridSize / 2,
                         gridSize / 2 + i * gridSize, gridSize / 2 + (boardSize - 1) * gridSize);
    }

    // 绘制棋子
    for (int i = 0; i < boardSize; ++i) {
        for (int j = 0; j < boardSize; ++j) {
            if (board[i][j] == 1) {
                painter.setBrush(Qt::black);
                painter.drawEllipse(gridSize / 2 + i * gridSize - 15,
                                    gridSize / 2 + j * gridSize - 15, 30, 30);
            } else if (board[i][j] == 2) {
                painter.setBrush(Qt::white);
                painter.drawEllipse(gridSize / 2 + i * gridSize - 15,
                                    gridSize / 2 + j * gridSize - 15, 30, 30);
            }
        }
    }
}


void MainWindow::mousePressEvent(QMouseEvent *event) {
    if (!isBlackTurn && isPlayerVsAI) return;  // 人机对战时禁止白棋玩家操作

    // 计算点击的位置对应的棋盘格子
    int x = (event->x() - gridSize / 2 + gridSize / 2) / gridSize;
    int y = (event->y() - gridSize / 2 + gridSize / 2) / gridSize;

    // 检查点击是否在有效范围内,且是否未落子
    if (x < 0 || x >= boardSize || y < 0 || y >= boardSize || board[x][y] != 0)
        return;

    // 记录当前落子
    board[x][y] = isBlackTurn ? 1 : 2;
    isBlackTurn = !isBlackTurn;

    // 更新提示信息
    statusLabel->setText(isBlackTurn ? "当前轮到黑棋" : "当前轮到白棋");

    update(); // 更新界面
    checkWin(x, y);

    // 如果是人机对战模式,AI 落子
    if (!isBlackTurn && isPlayerVsAI) {
        aiMove();
    }
}

void MainWindow::checkWin(int x, int y) {
    int directions[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}};
    int currentPlayer = board[x][y];

    for (auto &dir : directions) {
        int count = 1;
        for (int i = 1; i < 5; ++i) {
            int nx = x + i * dir[0], ny = y + i * dir[1];
            if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && board[nx][ny] == currentPlayer)
                ++count;
            else
                break;
        }
        for (int i = 1; i < 5; ++i) {
            int nx = x - i * dir[0], ny = y - i * dir[1];
            if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && board[nx][ny] == currentPlayer)
                ++count;
            else
                break;
        }
        if (count >= 5) {
            QString winner = (currentPlayer == 1) ? "黑棋" : "白棋";
            QMessageBox::information(this, "游戏结束", winner + " 胜利!");
            resetGame();
            return;
        }
    }
}

void MainWindow::resetGame() {
    for (auto &row : board)
        row.fill(0);
    isBlackTurn = true;
    statusLabel->setText("当前轮到黑棋");
    update();
}

void MainWindow::onNewGameClicked() {
    resetGame();
}

void MainWindow::onSwitchModeClicked() {
    isPlayerVsAI = !isPlayerVsAI; // 切换模式
    QString modeText = isPlayerVsAI ? "人机对战模式" : "人人对战模式";
    modeLabel->setText("当前模式:" + modeText); // 更新模式显示
    QMessageBox::information(this, "模式切换", modeText);
    resetGame(); // 切换模式后重置棋盘
}


void MainWindow::onDifficultyChanged(int index) {
    difficultyLevel = index + 1; // 更新难度等级
    QString difficultyText = difficultyComboBox->currentText();
    QMessageBox::information(this, "难度选择", "当前难度:" + difficultyText);
}


void MainWindow::aiMove() {
    int bestX = -1, bestY = -1;

    if (difficultyLevel == 1) {
        // 简单模式:随机选择空位
        while (true) {
            int x = rand() % boardSize;
            int y = rand() % boardSize;
            if (board[x][y] == 0) {
                bestX = x;
                bestY = y;
                break;
            }
        }
    } else {
        // 中等/困难模式:计算最佳位置
        int maxScore = -1;
        QVector<QVector<int>> score(boardSize, QVector<int>(boardSize, 0));

        for (int x = 0; x < boardSize; ++x) {
            for (int y = 0; y < boardSize; ++y) {
                if (board[x][y] != 0) continue;

                int baseScore = calculateScore(x, y, 2) + calculateScore(x, y, 1);
                if (difficultyLevel == 3) {
                    // 困难模式:增加中心权重
                    int distanceToCenter = abs(x - boardSize / 2) + abs(y - boardSize / 2);
                    baseScore += (boardSize - distanceToCenter) * 5;
                }

                score[x][y] = baseScore;
                if (baseScore > maxScore) {
                    maxScore = baseScore;
                    bestX = x;
                    bestY = y;
                }
            }
        }
    }

    // 在最佳位置落子
    if (bestX != -1 && bestY != -1) {
        board[bestX][bestY] = 2;
        isBlackTurn = true;
        statusLabel->setText("当前轮到黑棋");
        update();
        checkWin(bestX, bestY);
    }
}


int MainWindow::calculateScore(int x, int y, int player) {
    int directions[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}};
    int score = 0;

    for (auto &dir : directions) {
        int count = 1; // 当前玩家的连子数
        int block = 0; // 是否被堵住:0为两端均开,1为一端堵,2为两端堵
        int empty = 0; // 连子中的空位数

        // 正向检查
        for (int i = 1; i < 5; ++i) {
            int nx = x + i * dir[0];
            int ny = y + i * dir[1];
            if (nx < 0 || nx >= boardSize || ny < 0 || ny >= boardSize) {
                block++; // 超出棋盘视为堵住
                break;
            }
            if (board[nx][ny] == player) {
                count++;
            } else if (board[nx][ny] == 0) {
                empty++;
                break; // 停止正向检查
            } else {
                block++;
                break; // 对方棋子,视为堵住
            }
        }

        // 反向检查
        for (int i = 1; i < 5; ++i) {
            int nx = x - i * dir[0];
            int ny = y - i * dir[1];
            if (nx < 0 || nx >= boardSize || ny < 0 || ny >= boardSize) {
                block++;
                break;
            }
            if (board[nx][ny] == player) {
                count++;
            } else if (board[nx][ny] == 0) {
                empty++;
                break;
            } else {
                block++;
                break;
            }
        }

        // 根据连子数、空位数和堵塞情况评估分值
        if (count >= 5) {
            score += 10000; // 连成五子,最高分
        } else if (count == 4 && block == 0) {
            score += 1000; // 活四
        } else if (count == 4 && block == 1) {
            score += 500; // 冲四
        } else if (count == 3 && block == 0) {
            score += 200; // 活三
        } else if (count == 3 && block == 1) {
            score += 50; // 冲三
        } else if (count == 2 && block == 0) {
            score += 10; // 活二
        }
    }

    return score;
}



原文地址:https://blog.csdn.net/LuXiaoXin1999/article/details/143889440

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!