QT五子棋项目

2024-07-08 1689阅读

目录

运行效果

代码分析

1.项目分析

2.创建项目

         3.创建五子棋数据模型

4.绘制棋盘

在mainwindow.h包含数据模型头文件

设置窗口大小

绘制棋盘

执行效果:

5.初始化游戏

在窗口类中,添加棋盘数据指针。

初始化游戏数据

在游戏模型类中,添加startGame()

6.监听鼠标移动事件

代码实现:

检查效果:红色箭头表示光标。

7.落子并绘制棋子

鼠标单击并抬起后,实现落子。        

效果:

8.添加落子音效

准备资源目录,把目录放在.pro项目文件的同目录下。

​编辑

添加音效素材。

设置/为前缀添加资源文件

修改项目文件

9.判断输赢

在游戏模型中添加输赢判断功能。

在mainWindow窗口的重绘函数中,添加输赢判断。

检查效果:

10.使用AI功能

在游戏数据模型中,添加AI下棋功能

计算落子位置的权值:

 在游戏窗体中使用AI功能

完整函数窗口,添加slots

11.主函数

12.主体功能完成!

完整代码

GameModel.h

MainWindow.h

GameModel.cpp

MainWindow.cpp

主函数

main.cpp

总结

附录


运行效果

QT五子棋项目emm,这个是没有设置AI前测试的,可不要小看AI了哦!!!

代码分析

1.项目分析

Qt五子棋人机对战

2.创建项目

使用默认配置:

QT五子棋项目

QT五子棋项目

3.创建五子棋数据模型

Gamemodel.h

#ifndef GAMEMODEL_H
#define GAMEMODEL_H
// ---- 五子棋游戏模型类 ---- //
#include 
// 游戏类型,双人还是AI(目前固定让AI下黑子)
enum GameType
{
    MAN,
    AI   //人机对弈模式
};
// 游戏状态
enum GameStatus
{
    PLAYING,
    WIN,
    DEAD
};
// 棋盘尺寸
const int BOARD_GRAD_SIZE = 15;
const int MARGIN = 30; // 棋盘边缘空隙
const int CHESS_RADIUS = 15; // 棋子半径
const int MARK_SIZE = 6; // 落子标记边长
const int BLOCK_SIZE = 40; // 格子的大小
const int POS_OFFSET = BLOCK_SIZE * 0.4; // 20 鼠标点击的模糊距离上限
const int AI_THINK_TIME = 700; // AI下棋的思考时间
class GameModel
{
public:
    GameModel();
public:
    // 存储当前游戏棋盘和棋子的情况,空白为0,黑子1,白子-1
    std::vector gameMapVec;
    // 存储各个点位的评分情况,作为AI下棋依据
    std::vector scoreMapVec;
    // 标示下棋方, true:黑棋方  false: AI 白棋方(AI方)
    bool playerFlag;
    // 游戏模式:人机对弈,还是双人
    GameType gameType;
    // 游戏状态
    GameStatus gameStatus;
    void startGame(GameType type); // 开始游戏
    void actionByPerson(int row, int col);
    void updateGameMap(int row, int col);
    bool isWin(int row, int col); //判断输赢
    void calculateScore();
    void actionByAI(int &clickRow, int &clickCol); // 机器执行下棋
};
#endif // GAMEMODEL_H

4.绘制棋盘

在mainwindow.h包含数据模型头文件

mainwindow.h

#include"GameModel.h"
设置窗口大小

mainwindow.cpp

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
     , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    // 设置棋盘大小
    setFixedSize(MARGIN * 2 + BLOCK_SIZE * BOARD_GRAD_SIZE,
                 MARGIN * 2 + BLOCK_SIZE * BOARD_GRAD_SIZE);
}
绘制棋盘

Mainwindow.h

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    // 绘制
    void paintEvent(QPaintEvent *event);
};

Mainwindow.cpp

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    // 绘制棋盘
    painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿
    for (int i = 0; i  
执行效果:

QT五子棋项目

5.初始化游戏

在附近的落子位置画一个小正方形,黑方准备落子时,显示黑色正方形,白方准备落子时,显示白色正方形。

在窗口类中,添加棋盘数据指针。

Mainwindow.h

private:
    GameModel *game; // 游戏指针
    GameType game_type; // 存储游戏类型
初始化游戏数据

Mainwindow.cpp

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //......
    // 开始游戏
    initGame();
}
voidMainWindow::initGame(){
    //初始化游戏模型
    game=newGameModel;
    initAIGame();
}
voidMainWindow::initAIGame(){
    game_type=AI;
    game->gameStatus=PLAYING;
    game->startGame(game_type);
    update();
}
在游戏模型类中,添加startGame()

GameModel.cpp

GameModel::GameModel()
{
}
void GameModel::startGame(GameType type)
{
    gameType = type;
    // 初始棋盘
    gameMapVec.clear();
    for (int i = 0; i  

6.监听鼠标移动事件

为了节省开销,默认不支持移动监听。设置支持鼠标移动监听:

QT五子棋项目

添加监听代码原理示意图,先计算出绿点,然后分别计算出3个黑点位置,计算当前位置离4个点的位置。如果小于阈值,就认为选择了哪个点。

QT五子棋项目

代码实现:
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    // 通过鼠标的hover确定落子的标记
    int x = event->x();
    int y = event->y();
    // 棋盘边缘不能落子
    if (x >= MARGIN + BLOCK_SIZE / 2 &&
            x = MARGIN + BLOCK_SIZE / 2 &&
            y gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
        len = sqrt((x - leftTopPosX - BLOCK_SIZE) * (x - leftTopPosX - BLOCK_SIZE) + (y - leftTopPosY) * (y - leftTopPosY));
        if (len gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
        len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY - BLOCK_SIZE) * (y - leftTopPosY - BLOCK_SIZE));
        if (len gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
        len = sqrt((x - leftTopPosX - BLOCK_SIZE) * (x - leftTopPosX - BLOCK_SIZE) + (y - leftTopPosY - BLOCK_SIZE) * (y - leftTopPosY - BLOCK_SIZE));
        if (len gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
    }
    // 存了坐标后也要重绘
    update();
}
检查效果:红色箭头表示光标。

QT五子棋项目

7.落子并绘制棋子

鼠标单击并抬起后,实现落子。        

点击并抬起后,落子:

Mainwindow.cpp

void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
    if (selectPos == false) {
        return;
    } else {
        selectPos = false;
    }
    chessOneByPerson();
    if (game_type == AI) { //人机模式
        // AI 下棋
    }
}
void MainWindow::chessOneByPerson()
{
    // 根据当前存储的坐标下子
    // 只有有效点击才下子,并且该处没有子
    if (clickPosRow != -1 && clickPosCol != -1 && game->gameMapVec[clickPosRow][clickPosCol] == 0)
    {
        game->actionByPerson(clickPosRow, clickPosCol);
        QSound::play(":sound/chessone.wav");
        // 重绘
        update();
    }
}
效果:

QT五子棋项目

8.添加落子音效

准备资源目录,把目录放在.pro项目文件的同目录下。
QT五子棋项目
添加音效素材。

QT五子棋项目

QT五子棋项目

设置/为前缀添加资源文件

QT五子棋项目

修改项目文件
QT+=coreguimultimedia

 Mainwindow.cpp

#include 
void MainWindow::chessOneByPerson()
{
    // 根据当前存储的坐标下子
    // 只有有效点击才下子,并且该处没有子
    if (clickPosRow != -1 && clickPosCol != -1 && game->gameMapVec[clickPosRow][clickPosCol] == 0)
    {
        game->actionByPerson(clickPosRow, clickPosCol);
        QSound::play(":sound/chessone.wav");
        // 重绘
        update();
    }
}

9.判断输赢

在游戏模型中添加输赢判断功能。

Gamemodel.cpp

bool GameModel::isWin(int row, int col)
{
    // 横竖斜四种大情况,每种情况都根据当前落子往后遍历5个棋子,有一种符合就算赢
    // 水平方向
    for (int i = 0; i  0 &&
            col - i + 4  0 &&
            row - i + 4  0 &&
            col - i > 0 &&
            col - i + 4  0 &&
            row - i + 4  0 &&
            col - i + 4  
在mainWindow窗口的重绘函数中,添加输赢判断。

Mainwindow.cpp

//......
      if (game->isWin(clickPosRow, clickPosCol) && game->gameStatus == PLAYING)
        {
            //qDebug() gameStatus = WIN;
            QSound::play(":sound/win.wav");
            QString str;
            if (game->gameMapVec[clickPosRow][clickPosCol] == 1)
                str = "黑棋";
            else if (game->gameMapVec[clickPosRow][clickPosCol] == -1)
                str = "白棋";
            QMessageBox::StandardButton btnValue = QMessageBox::information(this, "五子棋决战", str + " 胜利!");
            // 重置游戏状态,否则容易死循环
            if (btnValue == QMessageBox::Ok)
            {
                game->startGame(game_type);
                game->gameStatus = PLAYING;
            }
        }
//......
检查效果:

QT五子棋项目

10.使用AI功能

在游戏数据模型中,添加AI下棋功能
#include 
#include 
void GameModel::actionByAI(int &clickRow, int &clickCol)
{
    // 计算评分
    calculateScore();
    // 从评分中找出最大分数的位置
    int maxScore = 0;
    std::vector maxPoints;
    for (int row = 1; row  maxScore)          // 找最大的数和坐标
                {
                    maxPoints.clear();
                    maxScore = scoreMapVec[row][col];
                    maxPoints.push_back(std::make_pair(row, col));
                }
                else if (scoreMapVec[row][col] == maxScore)     // 如果有多个最大的数,都存起来
                    maxPoints.push_back(std::make_pair(row, col));
            }
        }
    // 随机落子,如果有多个点的话
    srand((unsigned)time(0));
    int index = rand() % maxPoints.size();
    std::pair pointPair = maxPoints.at(index);
    clickRow = pointPair.first; // 记录落子点
    clickCol = pointPair.second;
    updateGameMap(clickRow, clickCol);
}
计算落子位置的权值:
// 最关键的计算评分函数
void GameModel::calculateScore()
{
    // 统计玩家或者电脑连成的子
    int personNum = 0; // 玩家连成子的个数
    int botNum = 0; // AI连成子的个数
    int emptyNum = 0; // 各方向空白位的个数
    // 清空评分数组
    scoreMapVec.clear();
    for (int i = 0; i  0 && col > 0 &&
                gameMapVec[row][col] == 0)
            {
                // 遍历周围八个方向
                for (int y = -1; y  0 && row + i * y  0 && col + i * x  0 && col - i * x  0 && row - i * y  0 && col - i * x  0 && col + i * x  0 && row + i * y  0 && col + i * x  0 && col - i * x  0 && row - i * y  0 && col - i * x = 4)
                                scoreMapVec[row][col] += 20000;   // 活五,应该具有最高优先级
                        }
                    }
            }
        }
}
 在游戏窗体中使用AI功能

设置定时器的槽函数,注意slots:不能少!

Mainwindow.h

private slots:
    void chessOneByAI();

Mainwindow.cpp

void MainWindow::chessOneByAI()
{
    game->actionByAI(clickPosRow, clickPosCol);
    QSound::play(":sound/chessone.wav");
    update();
}
完整函数窗口,添加slots

Mainwindow.cpp

/*void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
    if (selectPos == false) {
        return;
    } else {
        selectPos = false;
    }
    chessOneByPerson();
    if (game_type == AI) { //人机模式
        // AI 下棋*/
        QTimer::singleShot(AI_THINK_TIME, this, SLOT(chessOneByAI()));/*
    }
}*/

11.主函数

main.cpp

#include "mainwindow.h"
#include 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

12.主体功能完成!

完整代码

GameModel.h
#ifndef GAMEMODEL_H
#define GAMEMODEL_H
// ---- 五子棋游戏模型类 ---- //
#include 
// 游戏类型,双人还是AI(目前固定让AI下黑子)
enum GameType
{
    MAN,
    AI   //人机对弈模式
};
// 游戏状态
enum GameStatus
{
    PLAYING,
    WIN,
    DEAD
};
// 棋盘尺寸
const int BOARD_GRAD_SIZE = 15;
const int MARGIN = 30; // 棋盘边缘空隙
const int CHESS_RADIUS = 15; // 棋子半径
const int MARK_SIZE = 6; // 落子标记边长
const int BLOCK_SIZE = 40; // 格子的大小
const int POS_OFFSET = BLOCK_SIZE * 0.4; // 20 鼠标点击的模糊距离上限
const int AI_THINK_TIME = 700; // AI下棋的思考时间
class GameModel
{
public:
    GameModel();
public:
    // 存储当前游戏棋盘和棋子的情况,空白为0,黑子1,白子-1
    std::vector gameMapVec;
    // 存储各个点位的评分情况,作为AI下棋依据
    std::vector scoreMapVec;
    // 标示下棋方, true:黑棋方  false: AI 白棋方(AI方)
    bool playerFlag;
    // 游戏模式:人机对弈,还是双人
    GameType gameType;
    // 游戏状态
    GameStatus gameStatus;
    void startGame(GameType type); // 开始游戏
    void actionByPerson(int row, int col);
    void updateGameMap(int row, int col);
    bool isWin(int row, int col); //判断输赢
    void calculateScore();
    void actionByAI(int &clickRow, int &clickCol); // 机器执行下棋
};
#endif // GAMEMODEL_H
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include 
#include "GameModel.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    // 绘制
    void paintEvent(QPaintEvent *event);
    void initGame();
    void initAIGame();
private:
    Ui::MainWindow *ui;
    GameModel *game; // 游戏指针
    GameType game_type; // 存储游戏类型
    int clickPosRow, clickPosCol; // 存储将点击的位置
    bool selectPos = false; // 是否移动到合适的位置,以选中摸个交叉点
protected:
    void mouseMoveEvent(QMouseEvent *event);
    // 实际落子
    void mouseReleaseEvent(QMouseEvent *event);
    void chessOneByPerson();
private slots:
    void chessOneByAI();
};
#endif // MAINWINDOW_H
GameModel.cpp
#include "GameModel.h"
#include 
#include 
GameModel::GameModel()
{
}
void GameModel::startGame(GameType type)
{
    gameType = type;
    // 初始棋盘
    gameMapVec.clear();
    for (int i = 0; i  0 &&
            col - i + 4  0 &&
            row - i + 4  0 &&
            col - i > 0 &&
            col - i + 4  0 &&
            row - i + 4  0 &&
            col - i + 4  maxScore)          // 找最大的数和坐标
                {
                    maxPoints.clear();
                    maxScore = scoreMapVec[row][col];
                    maxPoints.push_back(std::make_pair(row, col));
                }
                else if (scoreMapVec[row][col] == maxScore)     // 如果有多个最大的数,都存起来
                    maxPoints.push_back(std::make_pair(row, col));
            }
        }
    // 随机落子,如果有多个点的话
    srand((unsigned)time(0));
    int index = rand() % maxPoints.size();
    std::pair pointPair = maxPoints.at(index);
    clickRow = pointPair.first; // 记录落子点
    clickCol = pointPair.second;
    updateGameMap(clickRow, clickCol);
}
// 最关键的计算评分函数
void GameModel::calculateScore()
{
    // 统计玩家或者电脑连成的子
    int personNum = 0; // 玩家连成子的个数
    int botNum = 0; // AI连成子的个数
    int emptyNum = 0; // 各方向空白位的个数
    // 清空评分数组
    scoreMapVec.clear();
    for (int i = 0; i  0 && col > 0 &&
                gameMapVec[row][col] == 0)
            {
                // 遍历周围八个方向
                for (int y = -1; y  0 && row + i * y  0 && col + i * x  0 && col - i * x  0 && row - i * y  0 && col - i * x  0 && col + i * x  0 && row + i * y  0 && col + i * x  0 && col - i * x  0 && row - i * y  0 && col - i * x = 4)
                                scoreMapVec[row][col] += 20000;   // 活五,应该具有最高优先级
                        }
                    }
            }
        }
}
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include 
#include 
#include 
#include 
#include 
#include 
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
     , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //setMouseTracking(true);
    // 设置棋盘大小
    setFixedSize(MARGIN * 2 + BLOCK_SIZE * BOARD_GRAD_SIZE,
                 MARGIN * 2 + BLOCK_SIZE * BOARD_GRAD_SIZE);
    // 开始游戏
    initGame();
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    // 绘制棋盘
    painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿
    for (int i = 0; i  0 && clickPosRow  0 && clickPosCol gameMapVec[clickPosRow][clickPosCol] == 0)
    {
        if (game->playerFlag)
            brush.setColor(Qt::black);
        else
            brush.setColor(Qt::white);
        painter.setBrush(brush);
        painter.drawRect(MARGIN + BLOCK_SIZE * clickPosCol - MARK_SIZE / 2, MARGIN + BLOCK_SIZE * clickPosRow - MARK_SIZE / 2, MARK_SIZE, MARK_SIZE);
    }
    // 绘制棋子
    for (int i = 0; i gameMapVec[i][j] == 1)
            {
                //brush.setColor(Qt::white);
                brush.setColor(Qt::black);
                painter.setBrush(brush);
                painter.drawEllipse(MARGIN + BLOCK_SIZE * j - CHESS_RADIUS, MARGIN + BLOCK_SIZE * i - CHESS_RADIUS, CHESS_RADIUS * 2, CHESS_RADIUS * 2);
            }
            else if (game->gameMapVec[i][j] == -1)
            {
                //brush.setColor(Qt::black);
                brush.setColor(Qt::white);
                painter.setBrush(brush);
                painter.drawEllipse(MARGIN + BLOCK_SIZE * j - CHESS_RADIUS, MARGIN + BLOCK_SIZE * i - CHESS_RADIUS, CHESS_RADIUS * 2, CHESS_RADIUS * 2);
            }
        }
    // 判断输赢
    if (clickPosRow > 0 && clickPosRow  0 && clickPosCol gameMapVec[clickPosRow][clickPosCol] == 1 ||
            game->gameMapVec[clickPosRow][clickPosCol] == -1))
    {
        if (game->isWin(clickPosRow, clickPosCol) && game->gameStatus == PLAYING)
        {
            //qDebug() gameStatus = WIN;
            QSound::play(":sound/win.wav");
            QString str;
            if (game->gameMapVec[clickPosRow][clickPosCol] == 1)
                str = "黑棋";
            else if (game->gameMapVec[clickPosRow][clickPosCol] == -1)
                str = "白棋";
            QMessageBox::StandardButton btnValue = QMessageBox::information(this, "五子棋决战", str + " 胜利!");
            // 重置游戏状态,否则容易死循环
            if (btnValue == QMessageBox::Ok)
            {
                game->startGame(game_type);
                game->gameStatus = PLAYING;
            }
        }
    }
}
void MainWindow::initGame()
{
    // 初始化游戏模型
    game = new GameModel;
    initAIGame();
}
void MainWindow::initAIGame()
{
    game_type = AI;
    game->gameStatus = PLAYING;
    game->startGame(game_type);
    update();
}
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    // 通过鼠标的hover确定落子的标记
    int x = event->x();
    int y = event->y();
    // 棋盘边缘不能落子
    if (x >= MARGIN + BLOCK_SIZE / 2 &&
            x = MARGIN + BLOCK_SIZE / 2 &&
            y gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
        len = sqrt((x - leftTopPosX - BLOCK_SIZE) * (x - leftTopPosX - BLOCK_SIZE) + (y - leftTopPosY) * (y - leftTopPosY));
        if (len gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
        len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY - BLOCK_SIZE) * (y - leftTopPosY - BLOCK_SIZE));
        if (len gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
        len = sqrt((x - leftTopPosX - BLOCK_SIZE) * (x - leftTopPosX - BLOCK_SIZE) + (y - leftTopPosY - BLOCK_SIZE) * (y - leftTopPosY - BLOCK_SIZE));
        if (len gameMapVec[clickPosRow][clickPosCol]==0) {
                selectPos = true;
            }
        }
    }
    // 存了坐标后也要重绘
    update();
}
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
    if (selectPos == false) {
        return;
    } else {
        selectPos = false;
    }
    chessOneByPerson();
    if (game_type == AI) { //人机模式
        // AI 下棋
        QTimer::singleShot(AI_THINK_TIME, this, SLOT(chessOneByAI()));
    }
}
void MainWindow::chessOneByPerson()
{
    // 根据当前存储的坐标下子
    // 只有有效点击才下子,并且该处没有子
    if (clickPosRow != -1 && clickPosCol != -1 && game->gameMapVec[clickPosRow][clickPosCol] == 0)
    {
        game->actionByPerson(clickPosRow, clickPosCol);
        QSound::play(":sound/chessone.wav");
        // 重绘
        update();
    }
}
void MainWindow::chessOneByAI()
{
    game->actionByAI(clickPosRow, clickPosCol);
    QSound::play(":sound/chessone.wav");
    update();
}

主函数

main.cpp
#include "mainwindow.h"
#include 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

总结

这就是Qt五子棋人机对战,快拿走试试看吧!!!

记得点赞+关注吧!

附录

要声音素材si我哦

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]