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;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/920097.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue非单文件组件

目录 Vue非单文件组件 几个注意点 组件的嵌套 关于VueComponent 重要的内置关系 Vue非单文件组件 Vue中使用组件的三大步骤&#xff1a; 一、定义组件(创建组件) 二、注册组件 三、使用组件(写组件标签) 一、…

关于C++地址交换的实现

关于地址的交换实现&#xff0c;我们要使用指针引用的方式进行&#xff0c;例如&#xff1a; #include <iostream>// 定义函数交换两个整型指针的地址 void swapIntPtrAddresses(int* &ptr1, int* &ptr2) {int *temp ptr1;ptr1 ptr2;ptr2 temp; }int main() …

Windows 软件之 FFmpeg

文章目录 前言1 FFmpeg 视频处理1.1 编解码1.2 其它视频编辑命令1.3 视频抽帧 2 FFmpeg 音频处理3 FFmpeg 图片处理3.1 编解码3.2 拼接图片3.3 图片合成视频 附录1&#xff1a;mediainfo.ps1 前言 FFmpeg 是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的…

Android okhttp 网络链接各阶段监控

步骤 1: 添加依赖 在项目的 build.gradle 文件中&#xff0c;添加 OkHttp 依赖&#xff1a; implementation com.squareup.okhttp3:okhttp:4.11.0 步骤 2: 创建自定义的 EventListener 创建一个自定义的 EventListener 类&#xff1a; import android.util.Log import okht…

【Java】字节码文件

字节码文件组成部分 1、基本信息 1.1 Magic 魔数 文件是无法通过文件扩展名来确定文件类型的&#xff0c;文件扩展名可以随意修改&#xff0c;不影响文件的内容。软件使用文件的头几个字节&#xff08;文件头&#xff09;去校验文件的类型&#xff0c;如果软件不支持该种类型就…

Easyexcel(3-文件导出)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09; 响应头设置 通过设置文件导出的响应头&#xff0c;可以自定义文件导出的名字信息等 //编码格式为UTF-8 response.setC…

【机器学习】朴素贝叶斯算法

目录 什么是朴素贝叶斯算法&#xff1f; 算法引入 贝叶斯定理 朴素贝叶斯分类器 工作原理 优缺点 应用场景 实现示例 基本步骤&#xff1a; 在机器学习的世界里&#xff0c;朴素贝叶斯算法以其简单性和高效性而著称。尽管它的名字听起来有点复杂&#xff0c;但实际上…

机器翻译基础与模型 之二: 基于CNN的模型

一、CNN网络 相比于全连接网络&#xff0c;卷积神经网络最大的特点在于具有局部连接&#xff08;Locally Connected&#xff09;和权值共享&#xff08;Weight Sharing&#xff09;的特性。 1.1 卷积核与卷积操作 1.2 步长与填充 1.3 池化 以上关于CNN的基础概念和技术就不…

IntelliJ+SpringBoot项目实战(四)--快速上手数据库开发

对于新手学习SpringBoot开发&#xff0c;可能最急迫的事情就是尽快掌握数据库的开发。目前数据库开发主要流行使用Mybatis和Mybatis Plus,不过这2个框架对于新手而言需要一定的时间掌握&#xff0c;如果快速上手数据库开发&#xff0c;可以先按照本文介绍的方式使用JdbcTemplat…

C总结测评

测评代码&#xff1a;month_11/test_19/main.c Hera_Yc/bit_C_学习 - 码云 - 开源中国 第一题&#xff1a;该程序输出的是多少&#xff1f; #include <stdio.h> int main() {unsigned char i 7;//0~255int j 0;for (; i > 0; i - 3){j;}printf("%d\n",…

神经网络中常用的激活函数(公式 + 函数图像)

激活函数是人工神经网络中的一个关键组件&#xff0c;负责引入非线性&#xff0c;从而使神经网络能够学习和表示复杂的非线性关系。没有激活函数&#xff0c;神经网络中的所有计算都是线性变换&#xff0c;而线性模型的表达能力有限&#xff0c;无法处理复杂的任务。 激活函数…

在CentOS中,通过nginx访问php

其实是nginx反向代理到php-fpm&#xff0c;就像nginx反向代理到tomcat。 1、安装PHP-FPM 1.1 安装 yum install php yum install php-fpm php-common 这里只安装了php-fpm&#xff0c;根据需要安装php模块&#xff0c;比如需要访问mysql则添加安装 php-mysqlnd。 1.2 启动…

前端—Cursor编辑器

在当今快速发展的软件开发领域&#xff0c;效率和质量是衡量一个工具是否优秀的两个关键指标。今天&#xff0c;我要向大家推荐一款革命性的代码编辑器——Cursor&#xff0c;它集成了强大的AI功能&#xff0c;旨在提高开发者的编程效率。以下是Cursor编辑器的详细介绍和推荐理…

windows远程桌面打开rdp显卡调用

前情提要 服务器在公网环境&#xff0c;带宽只有30M。 远程桌面多开图形业务调试&#xff0c;设置RDP服务端使用GPU。 压缩传输带宽避免造成卡顿。 如果是内网&#xff0c;也可以用&#xff0c;还可以提供一个注册表键值&#xff0c;修改后提高fps帧率&#xff08;公网不推…

使用C++和QT开发应用程序入门以及开发实例分享

目录 1、搭建开发环境(VS2010和QT4.8.2) 2、创建一个QT窗口 3、在QT窗口中添加子窗口 4、QT界面布局 5、QT信号(SIGNAL)和槽(SLOT) 6、最后 C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/…

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…

php:使用socket函数创建WebSocket服务

一、前言 闲来无事&#xff0c;最近捣鼓了下websocket&#xff0c;但是不希望安装第三方类库&#xff0c;所以打算用socket基础函数创建个服务。 二、构建websocket服务端 <?phpclass SocketService {// 默认的监听地址和端口private $address 0.0.0.0;private $port 8…

【YOLOv8】安卓端部署-1-项目介绍

【YOLOv8】安卓端部署-1-项目介绍 1 什么是YOLOv81.1 YOLOv8 的主要特性1.2 YOLOv8分割模型1.2.1 YOLACT实例分割算法之计算掩码1.2.1.1 YOLACT 的掩码原型与最终的掩码的关系1.2.1.2 插值时的目标检测中提取的物体特征1.2.1.3 coefficients&#xff08;系数&#xff09;作用1.…

(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器

目录 1.业务逻辑实现 1.1 登录校验技术——会话 1.1.1Cookie 1.1.2session 1.1.3JWT令牌技术 2.参数配置化 3.yml格式配置文件 4.过滤器Filter 5.拦截器Interceptor 1.业务逻辑实现 Day10-02. 案例-部门管理-查询_哔哩哔哩_bilibili //Controller层/*** 新增部门*/Pos…

数字IC后端设计实现之Innovus place报错案例 (IMPSP-9099,9100三种解决方案)

最近吾爱IC社区星球会员问到跑place_opt_design时会报错退出的情况。小编今天把这个错误解决办法分享给大家。主要分享三个方法&#xff0c;大家可以根据自己的实际情况来选择。 数字IC后端低功耗设计实现案例分享(3个power domain&#xff0c;2个voltage domain) **ERROR: (I…