介绍
本项目基于 Qt C++ 实现了一个完整的五子棋游戏,支持 人机对战 和 人人对战 模式,并提供了三种难度选择(简单、中等、困难)。界面美观,逻辑清晰,是一个综合性很强的 Qt 小项目
标题项目核心功能
- 棋盘绘制:通过 QPainter 实现网格棋盘和棋子的绘制,使用坐标映射鼠标点击位置到棋盘。
- 落子规则:处理玩家或 AI 的落子,并检查是否获胜。
- 人机对战:根据难度,AI 实现从简单的随机落子到基于评分系统的智能落子。
- 模式切换:支持人人对战和人机对战模式,切换后自动重置棋盘。
- 难度选择:通过下拉框提供“简单”、“中等”、“困难”三种难度。
本项目是一个典型的五子棋游戏,涵盖了 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;
}