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

news2024/11/20 21:19:18

介绍

本项目基于 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;
}


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vulnhub靶场案例渗透[12]-Grotesque: 1.0.1

文章目录 一、靶场搭建1. 靶场描述2. 下载靶机环境3. 靶场搭建 二、渗透靶场1. 确定靶机IP2. 探测靶场开放端口及对应服务3. 目录扫描4. 敏感信息获取5. 反弹shell6. 权限提升 一、靶场搭建 1. 靶场描述 get flags difficulty: medium about vm: tested and exported from vi…

git日志查询和导出

背景 查看git的提交记录并下载 操作 1、找到你idea代码的路径&#xff0c;然后 git bash here打开窗口 2、下载所有的日志记录 git log > commit.log3、下载特定日期范围内记录 git log --since"2024-09-01" --until"2024-11-18" 你的分支 > c…

LeetCode Hot100 15.三数之和

题干&#xff1a; 思路&#xff1a; 首先想到的是哈希表&#xff0c;类似于两数之和的想法&#xff0c;共两层循环&#xff0c;将遍历到的第一个元素和第二个元素存入哈希表中&#xff0c;然后按条件找第三个元素&#xff0c;但是这道题有去重的要求&#xff0c;哈希表实现较为…

Vue3、Vite5、Primevue、Oxlint、Husky9 简单快速搭建最新的Web项目模板

Vue3、Vite5、Oxlint、Husky9 简单搭建最新的Web项目模板 特色进入正题创建基础模板配置API自动化导入配置组件自动化导入配置UnoCss接入Primevue接入VueRouter4配置项目全局环境变量 封装Axios接入Pinia状态管理接入Prerttier OXLint ESLint接入 husky lint-staged&#xf…

基于深度学习的文本信息提取方法研究(pytorch python textcnn框架)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

Linux(命令格式详细+字符集 图片+大白话)

后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都会及时修改的&#xff01; 在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频 王晓春老师的个人空间…

机器学习中的概率超能力:如何用朴素贝叶斯算法结合标注数据做出精准预测

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

01 —— Webpack打包流程及一个例子

静态模块打包工具 静态模块&#xff1a;html、css、js、图片等固定内容的文件 打包&#xff1a;把静态模块内容&#xff0c;压缩、转译等 Webpack打包流程 src中新建一个index.js模块文件&#xff1b;然后将check.js模块内的两个函数导入过来&#xff0c;进行使用下载webpack…

时间类的实现

在现实生活中&#xff0c;我们常常需要计算某一天的前/后xx天是哪一天&#xff0c;算起来十分麻烦&#xff0c;为此我们不妨写一个程序&#xff0c;来减少我们的思考时间。 1.基本实现过程 为了实现时间类&#xff0c;我们需要将代码写在3个文件中&#xff0c;以增强可读性&a…

学习笔记024——Ubuntu 安装 Redis遇到相关问题

目录 1、更新APT存储库缓存&#xff1a; 2、apt安装Redis&#xff1a; 3、如何查看检查 Redis版本&#xff1a; 4、配置文件相关设置&#xff1a; 5、重启服务&#xff0c;配置生效&#xff1a; 6、查看服务状态&#xff1a; 1、更新APT存储库缓存&#xff1a; sudo apt…

【时间之外】IT人求职和创业应知【35】-RTE三进宫

目录 新闻一&#xff1a;京东工业发布11.11战报&#xff0c;多项倍增数据体现工业经济信心提升 新闻二&#xff1a;阿里云100万核算力支撑天猫双11&#xff0c;弹性计算规模刷新纪录 新闻三&#xff1a;声网CEO赵斌&#xff1a;RTE将成为生成式AI时代AI Infra的关键部分 认知…

css3中的多列布局,用于实现文字像报纸一样的布局

作用&#xff1a;专门用于实现类似于报纸类的布局 常用的属性如下&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevic…

网络基础(4)IP协议

经过之前的学习对传输协议的学习&#xff0c;对于传输协议从系统底层到应用层对于socket套接字的学习已经有了一套完整的理论。 对于网络的层状结构&#xff0c;现在已经学习到了应用层和传输层: 在之前的学习中&#xff0c;通信的双方都只考虑了双方的传输层的东西&#xff0…

【图像处理识别】数据集合集!

本文将为您介绍经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 CNN-ImageProc-Robotics 机器人 更新时间&#xff1a;2024-07-29 访问地址: GitHub 描述&#xff1a; 通过 CNN 和图像处理进行机器人对象识别项目侧重于集成最先进的深度学习技术和…

Leetcode 快乐数

算法思想&#xff1a; 这段代码的目的是判断一个正整数是否是 快乐数&#xff08;Happy Number&#xff09;。根据题目要求&#xff0c;快乐数定义如下&#xff1a; 对于一个正整数&#xff0c;不断将它每个位上的数字替换为这些数字平方和。重复这个过程&#xff0c;如果最终…

探索Python PDF处理的奥秘:pdfrw库揭秘

文章目录 探索Python PDF处理的奥秘&#xff1a;pdfrw库揭秘1. 背景&#xff1a;为何选择pdfrw&#xff1f;2. pdfrw是什么&#xff1f;3. 如何安装pdfrw&#xff1f;4. 五个简单的库函数使用方法4.1 读取PDF信息4.2 修改PDF元数据4.3 旋转PDF页面4.4 提取PDF中的图片4.5 合并P…

若点集A=B则A必能恒等变换地变为B=A这一几何常识推翻直线(平面)公理

黄小宁 关键词&#xff1a;“更无理”复数 复平面z各点z的对应点z1的全体是z1面。z面平移变为z1面就使x轴⊂z面沿本身平移变为ux1轴。R可几何化为R轴&#xff0c;R轴可沿本身平移变为R′轴&#xff0c;R′轴可沿本身平移变为R″轴&#xff0c;...。直线公理和平面公理使几百年…

详细分析ipvsadm负载均衡的命令

目录 前言1. 基本知识2. 命令参数3. 拓展 前言 LVS四层负载均衡架构详解Lvs推荐阅读&#xff1a;添加链接描述 1. 基本知识 ipvsadm 是用于管理和配置 Linux 服务器上 IP Virtual Server (IPVS) 的工具&#xff0c;是 Linux 提供的一个负载均衡模块&#xff0c;支持多种负载…

PH热榜 | 2024-11-19

DevNow 是一个精简的开源技术博客项目模版&#xff0c;支持 Vercel 一键部署&#xff0c;支持评论、搜索等功能&#xff0c;欢迎大家体验。 在线预览 1. Layer 标语&#xff1a;受大脑启发的规划器 介绍&#xff1a;体验一下这款新一代的任务和项目管理系统吧&#xff01;它…

哥德巴赫猜想渐行渐远

我现在的工作&#xff0c;表明经典分析可能出了问题&#xff0c;如此则连Vinogradov的三素数定理都不成立了&#xff0c;更别说基于L-函数方程的陈氏定理“12”了。事实上即使L-函数方程成立&#xff0c;由于我指出Siegel定理不成立&#xff0c;陈景润和张益唐的工作就不成立。…