原创,转载请注明出处。
这里用Qt实现了五子棋,可进行双人游戏,人机对战,悔棋等操作,是C++,Qt的必备练手项目,界面设置如下图:
添加三个类,分别为游戏总控制类gamewidget 、界面类boardwidget和AI控制类gomokuai。代码分别如下:
boardwidget.h:
#ifndef BOARDWIDGET_H
#define BOARDWIDGET_H
#include <QWidget>
#include <QStack>
#include <QPoint>
#include <QSet>
typedef int (*Board)[15];
class BoardWidget : public QWidget
{
Q_OBJECT
public:
explicit BoardWidget(QWidget *parent = nullptr);
//设置棋盘控件接受的下棋方,在ai模式中只接受白色方,双人模式中都接受
void setReceivePlayers(const QSet<int> &value);
//获取棋盘信息
Board getBoard();
protected:
void paintEvent(QPaintEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
//初始化棋盘控件
void initBoard();
//切换下一位下棋者
void switchNextPlayer();
//检查是否有获胜方,或者平局
void checkWinner();
//判断从(x, y)处开始,是否有五个同色棋子在一条线上
bool isFivePieceFrom(int x, int y);
//判断从(x, y)处开始,向下是否有五个同色棋子
bool isVFivePieceFrom(int x, int y);
//判断从(x, y)处开始,向下是否有五个同色棋子
bool isHFivePieceFrom(int x, int y);
//判断从(x, y)处开始,右上方向是否有五个同色棋子
bool isFSFivePieceFrom(int x, int y);
//判断(x, y)处开始, 右下方向是否有五个同色棋子
bool isBSFivePieceFrom(int x, int y);
signals:
void gameOver(int winner);
void turnNextPlayer(int player);
public slots:
//清除棋盘信息,开始新游戏
void newGame();
//落子
void downPiece(int x, int y);
void undo(int steps); //悔棋
public:
static const QSize WIDGET_SIZE; //棋盘控件大小
static const QSize CELL_SIZE; //棋盘单元格大小
static const QPoint START_POS; //棋盘单元格开始位置
static const QPoint ROW_NUM_START; //行标号开始位置
static const QPoint CLU_NUM_START; //列标号开始位置
static const int BOARD_WIDTH = 15; //棋盘列数
static const int BOARD_HEIGHT = 15; //棋盘行数
static const int NO_PIECE = 0; //棋子标志,表示无子
static const int WHITE_PIECE = 1; //棋子标志, 表示白子
static const int BLACK_PIECE = 2; //棋子标志,表示黑子
static const bool WHITE_PLAYER = true; //棋手标志, 表示白方
static const bool BLACK_PLAYER = false; //棋手标志, 表示黑方
void setTrackPos(const QPoint &value); //设置当前鼠标所在棋盘中的位置
private:
bool endGame; //游戏是否结束标志
int board[BOARD_WIDTH][BOARD_HEIGHT]; //棋盘信息
int nextPlayer; //表示下一位棋手
QPoint trackPos; //记录鼠标在棋盘中的位置
QVector<QPoint> winPoses; //获胜的五子位置
QSet<int> receivePlayers; //棋盘接受点击下棋的棋手
QStack<QPoint> dropedPieces; //每一步落子位置
};
#endif // BOARDWIDGET_H
boardwidget.cpp:
#include "boardwidget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QFile>
#include <QDataStream>
/*类静态数据成员定义*/
const QSize BoardWidget::WIDGET_SIZE(430, 430);
const QSize BoardWidget::CELL_SIZE(25, 25);
const QPoint BoardWidget::START_POS(40, 40);
const QPoint BoardWidget::ROW_NUM_START(15, 45);
const QPoint BoardWidget::CLU_NUM_START(39, 25);
const int BoardWidget::BOARD_WIDTH;
const int BoardWidget::BOARD_HEIGHT;
const int BoardWidget::NO_PIECE;
const int BoardWidget::WHITE_PIECE;
const int BoardWidget::BLACK_PIECE;
const bool BoardWidget::WHITE_PLAYER;
const bool BoardWidget::BLACK_PLAYER;
BoardWidget::BoardWidget(QWidget *parent) :
QWidget(parent),
trackPos(28, 28)
{
setFixedSize(WIDGET_SIZE);
setMouseTracking(true);
initBoard();
}
void BoardWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(0, 0, width(), height(), Qt::lightGray); //背景颜色
for (int i = 0; i < BOARD_WIDTH; i++)
{
painter.drawText(CLU_NUM_START + QPoint(i * CELL_SIZE.width(), 0),
QString::number(i + 1));
}
for (int i = 0; i < BOARD_HEIGHT; i++)
{
painter.drawText(ROW_NUM_START + QPoint(0, i * CELL_SIZE.height()),
QString::number(i + 1));
}
for (int i = 0; i < BOARD_WIDTH - 1; i++) //绘制棋盘格子
{
for (int j = 0; j < BOARD_HEIGHT - 1; j++)
{
painter.drawRect(QRect(START_POS + QPoint(i * CELL_SIZE.width(), j * CELL_SIZE.height()),
CELL_SIZE));
}
}
painter.setPen(Qt::red);
QPoint poses[12] = {
trackPos + QPoint(0, 8),
trackPos,
trackPos + QPoint(8, 0),
trackPos + QPoint(17, 0),
trackPos + QPoint(25, 0),
trackPos + QPoint(25, 8),
trackPos + QPoint(25, 17),
trackPos + QPoint(25, 25),
trackPos + QPoint(17, 25),
trackPos + QPoint(8, 25),
trackPos + QPoint(0, 25),
trackPos + QPoint(0, 17)
};
painter.drawPolyline(poses, 3);
painter.drawPolyline(poses + 3, 3);
painter.drawPolyline(poses + 6, 3);
painter.drawPolyline(poses + 9, 3);
painter.setPen(Qt::NoPen);
for (int i = 0; i < BOARD_WIDTH; i++) //绘制棋子
{
for (int j = 0; j < BOARD_HEIGHT; j++)
{
if (board[i][j] != NO_PIECE)
{
QColor color = (board[i][j] == WHITE_PIECE) ? Qt::white : Qt::black;
painter.setBrush(QBrush(color));
painter.drawEllipse(START_POS.x() - CELL_SIZE.width()/2 + i*CELL_SIZE.width(),
START_POS.y() - CELL_SIZE.height()/2 + j*CELL_SIZE.height(),
CELL_SIZE.width(), CELL_SIZE.height());
}
}
}
painter.setPen(Qt::red);
if (!dropedPieces.isEmpty())
{
QPoint lastPos = dropedPieces.top();
QPoint drawPos = START_POS + QPoint(lastPos.x() * CELL_SIZE.width(), lastPos.y() * CELL_SIZE.height());
painter.drawLine(drawPos + QPoint(0, 5), drawPos + QPoint(0, -5));
painter.drawLine(drawPos + QPoint(5, 0), drawPos + QPoint(-5, 0));
}
for (QPoint pos : winPoses)
{
QPoint drawPos = START_POS + QPoint(pos.x() * CELL_SIZE.width(), pos.y() * CELL_SIZE.height());
painter.drawLine(drawPos + QPoint(0, 5), drawPos + QPoint(0, -5));
painter.drawLine(drawPos + QPoint(5, 0), drawPos + QPoint(-5, 0));
}
}
void BoardWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (receivePlayers.contains(nextPlayer) && !endGame)
{
QPoint pos = event->pos() - START_POS;
int x = pos.x();
int y = pos.y();
int pieceX = x / CELL_SIZE.width();
int pieceY = y / CELL_SIZE.height();
int offsetX = x % CELL_SIZE.width();
int offsetY = y % CELL_SIZE.height();
if (offsetX > CELL_SIZE.width() / 2)
{
pieceX++;
}
if (offsetY > CELL_SIZE.height() / 2)
{
pieceY++;
}
downPiece(pieceX, pieceY);
}
}
void BoardWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint pos = event->pos() - START_POS + QPoint(CELL_SIZE.width()/2, CELL_SIZE.height()/2);
int x = pos.x();
int y = pos.y();
//超过范围
if (x < 0 || x >= CELL_SIZE.width() * BOARD_WIDTH ||
y < 0 || y >= CELL_SIZE.height() * BOARD_HEIGHT)
{
return;
}
int offsetX = x % CELL_SIZE.width();
int offsetY = y % CELL_SIZE.height();
setTrackPos(QPoint(x - offsetX, y - offsetY) + START_POS - QPoint(CELL_SIZE.width()/2, CELL_SIZE.height()/2));
}
void BoardWidget::initBoard()
{
receivePlayers << WHITE_PLAYER << BLACK_PLAYER;
newGame();
}
void BoardWidget::downPiece(int x, int y)
{
if (x >= 0 && x < BOARD_WIDTH && y >= 0 && y < BOARD_HEIGHT && board[x][y] == NO_PIECE)
{
dropedPieces.push(QPoint(x, y));
board[x][y] = (nextPlayer == WHITE_PLAYER) ? WHITE_PIECE : BLACK_PIECE;
update();
checkWinner();
if (!endGame)
{
switchNextPlayer();
}
}
}
void BoardWidget::undo(int steps)
{
if (!endGame)
{
for (int i = 0; i < steps && !dropedPieces.isEmpty(); i++)
{
QPoint pos = dropedPieces.pop();
board[pos.x()][pos.y()] = NO_PIECE;
}
update();
if(steps==2)
{
nextPlayer=BLACK_PLAYER;
}
switchNextPlayer();
}
}
void BoardWidget::setTrackPos(const QPoint &value)
{
trackPos = value;
update();
}
void BoardWidget::setReceivePlayers(const QSet<int> &value)
{
receivePlayers = value;
}
Board BoardWidget::getBoard()
{
return board;
}
void BoardWidget::switchNextPlayer()
{
nextPlayer = !nextPlayer;
emit turnNextPlayer(nextPlayer);
}
void BoardWidget::checkWinner()
{
bool fullPieces = true;
for (int i = 0; i < BOARD_WIDTH; i++)
{
for (int j = 0; j < BOARD_HEIGHT; j++)
{
if (board[i][j] == NO_PIECE)
{
fullPieces = false;
}
if (board[i][j] != NO_PIECE && isFivePieceFrom(i, j))
{
bool winner = (board[i][j] == WHITE_PIECE) ? WHITE_PLAYER : BLACK_PLAYER;
endGame = true;
emit gameOver(winner);
}
}
}
if (fullPieces)
{
endGame = true;
emit gameOver(2); //代表和棋
}
}
bool BoardWidget::isFivePieceFrom(int x, int y)
{
return isVFivePieceFrom(x, y) || isHFivePieceFrom(x, y) || isFSFivePieceFrom(x, y) || isBSFivePieceFrom(x, y);
}
bool BoardWidget::isVFivePieceFrom(int x, int y)
{
int piece = board[x][y];
for (int i = 1; i < 5; i++)
{
if (y + i >= BOARD_HEIGHT || board[x][y + i] != piece)
{
return false;
}
}
winPoses.clear();
for (int i = 0; i < 5; i++)
{
winPoses.append(QPoint(x, y + i));
}
return true;
}
bool BoardWidget::isHFivePieceFrom(int x, int y)
{
int piece = board[x][y];
for (int i = 1; i < 5; i++)
{
if (x + i >= BOARD_WIDTH || board[x + i][y] != piece)
{
return false;
}
}
winPoses.clear();
for (int i = 0; i < 5; i++)
{
winPoses.append(QPoint(x + i, y));
}
return true;
}
bool BoardWidget::isFSFivePieceFrom(int x, int y)
{
int piece = board[x][y];
for (int i = 1; i < 5; i++)
{
if (x + i >= BOARD_WIDTH || y - i < 0 || board[x + i][y - i] != piece)
{
return false;
}
}
winPoses.clear();
for (int i = 0; i < 5; i++)
{
winPoses.append(QPoint(x + i, y - i));
}
return true;
}
bool BoardWidget::isBSFivePieceFrom(int x, int y)
{
int piece = board[x][y];
for (int i = 1; i < 5; i++)
{
if (x + i >= BOARD_WIDTH || y + i >= BOARD_HEIGHT || board[x + i][y + i] != piece)
{
return false;
}
}
winPoses.clear();
for (int i = 0; i < 5; i++)
{
winPoses.append(QPoint(x + i, y + i));
}
return true;
}
void BoardWidget::newGame()
{
for (int i = 0; i < BOARD_WIDTH; i++)
{
for (int j = 0; j < BOARD_HEIGHT; j++)
{
board[i][j] = NO_PIECE;
}
}
winPoses.clear();
dropedPieces.clear();
nextPlayer = BLACK_PLAYER;
endGame = false;
update();
emit turnNextPlayer(nextPlayer);
}
gamewidget.h :
#ifndef GAMEWIDGET_H
#define GAMEWIDGET_H
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include "boardwidget.h"
#include "gomokuai.h"
class GameWidget : public QWidget
{
Q_OBJECT
public:
GameWidget(QWidget *parent = 0);
~GameWidget();
private:
//初始化游戏窗口界面
void initWidget();
//显示获胜者
void showWinner(int winner);
//切换playerLabel标签显示内容
void switchPlayerLabel(bool player);
//切换至双人对战模式
void switchHumanGame();
//切换到Ai对战模式
void switchAiGame();
//接受下棋信号,如果为ai模式且轮到ai时,搜索最佳位置下棋
void nextDropPiece(bool player);
//悔棋
void undo();
//新游戏;
void newGame();
private:
GomokuAi *ai; //ai 对象指针
bool isGameWithAi; //是否为ai对战模式
BoardWidget *boardWidget; //棋盘控件
QLabel *playerLabel; //用于提示下一步轮哪一方
QLabel *winLabel; //提示获胜的标签
QPushButton *newGameBtn; //新游戏按钮
QPushButton *humanGameBtn; //双人游戏按钮
QPushButton *aiGameBtn; //ai对战按钮
QPushButton *undoBtn; //悔棋按钮
};
#endif // GAMEWIDGET_H
gamewidget.cpp:
#include "gamewidget.h"
#include <QPushButton>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QFileDialog>
#include <QLabel>
#include <QDebug>
GameWidget::GameWidget(QWidget *parent)
: QWidget(parent)
{
ai = new GomokuAi(this);
isGameWithAi = false;
initWidget();
switchHumanGame();
}
GameWidget::~GameWidget()
{
}
void GameWidget::initWidget()
{
setWindowTitle("五子棋");
//创建水平布局作为总布局
QHBoxLayout *mainLayout = new QHBoxLayout(this);
//棋盘控件
boardWidget = new BoardWidget(this);
mainLayout->addWidget(boardWidget);
//创建垂直布局作为界面右边标签和菜单的子布局
QVBoxLayout *vLayout = new QVBoxLayout();
playerLabel = new QLabel("轮到白方落子", this);
playerLabel->setFont(QFont("微软雅黑", 25));
vLayout->addWidget(playerLabel);
vLayout->addStretch();
winLabel = new QLabel(this);
winLabel->setFont(QFont("微软雅黑", 20));
vLayout->addWidget(winLabel);
vLayout->addStretch(); //添加一个伸缩因子
//创建栅格布局作为菜单按钮的布局
QGridLayout *gLayout = new QGridLayout();
newGameBtn = new QPushButton("新游戏");
humanGameBtn = new QPushButton("双人游戏");
aiGameBtn = new QPushButton("人机对战");
undoBtn = new QPushButton("悔棋");
gLayout->addWidget(newGameBtn, 0, 0);
gLayout->addWidget(humanGameBtn, 0, 1);
gLayout->addWidget(aiGameBtn, 0, 2);
gLayout->addWidget(undoBtn, 0, 3);
vLayout->addLayout(gLayout);
mainLayout->addLayout(vLayout);
connect(boardWidget, &BoardWidget::gameOver, this, &GameWidget::showWinner);
connect(boardWidget, &BoardWidget::turnNextPlayer, this, &GameWidget::switchPlayerLabel, Qt::QueuedConnection);
connect(boardWidget, &BoardWidget::turnNextPlayer, this, &GameWidget::nextDropPiece, Qt::QueuedConnection);
connect(newGameBtn, &QPushButton::clicked, this, &GameWidget::newGame);
connect(humanGameBtn, &QPushButton::clicked, this, &GameWidget::switchHumanGame);
connect(aiGameBtn, &QPushButton::clicked, this, &GameWidget::switchAiGame);
connect(undoBtn, &QPushButton::clicked, this, &GameWidget::undo);
}
void GameWidget::showWinner(int winner)
{
if (winner != 2)
{
QString playerName = (winner == BoardWidget::WHITE_PLAYER) ? "白方" : "黑方";
QMessageBox::information(this, "游戏结束", tr("恭喜%1获胜!!").arg(playerName), QMessageBox::Ok);
}
else
{
QMessageBox::information(this, "游戏结束", "和棋!", QMessageBox::Ok);
}
}
void GameWidget::switchPlayerLabel(bool player)
{
QString playerName = (player == BoardWidget::WHITE_PLAYER) ? "白方" : "黑方";
QString labelText = tr("轮到%1落子").arg(playerName);
playerLabel->setText(labelText);
}
void GameWidget::switchHumanGame()
{
humanGameBtn->setEnabled(false);
aiGameBtn->setEnabled(true);
QSet<int> receivePlayers;
receivePlayers << BoardWidget::WHITE_PLAYER << BoardWidget::BLACK_PLAYER;
boardWidget->setReceivePlayers(receivePlayers);
isGameWithAi = false;
boardWidget->newGame();
}
void GameWidget::switchAiGame()
{
aiGameBtn->setEnabled(false);
humanGameBtn->setEnabled(true);
QSet<int> receivePlayers;
receivePlayers << BoardWidget::WHITE_PLAYER;
boardWidget->setReceivePlayers(receivePlayers);
isGameWithAi = true;
boardWidget->newGame();
}
void GameWidget::nextDropPiece(bool player)
{
if (isGameWithAi && player == GomokuAi::AI_PLAYER)
{
QPoint pos = ai->searchAGoodPos(boardWidget->getBoard());
boardWidget->downPiece(pos.x(), pos.y());
}
}
void GameWidget::undo()
{
if (isGameWithAi)
{
boardWidget->undo(2);
}
else
{
boardWidget->undo(1);
}
}
void GameWidget::newGame()
{
winLabel->setText("");
boardWidget->newGame();
}
gomokuai.h:
#ifndef GOMOKUAI_H
#define GOMOKUAI_H
#include <QObject>
#include "boardwidget.h"
class GomokuAi : public QObject
{
Q_OBJECT
public:
explicit GomokuAi(QObject *parent = nullptr);
private:
//返回棋盘上所有空的位置
QVector<QPoint> getAllDropPos();
//返回该位置的相对于player的分数
int getPieceScore(int x, int y, int player);
//返回该位置的分数,越靠近中间分数越大
int getPosValue(int x, int y);
//返回该位置dire方向上的棋型
int getChessType(int x, int y, int dire, int pieceColor); //返回此处棋型
//获取该位置该方向上pieceColor颜色的连续棋子数, pieceEnd为结束位置棋子
int getLinePieceNum(int x, int y, int dire, int pieceColor, int &pieceEnd);
bool isBeyond(int x, int y);
signals:
public slots:
QPoint searchAGoodPos(Board nBoard);
public:
static const int CHESS_VALUES[9];
static const int MIN_VALUE = -2000000;
static const int AI_PLAYER = BoardWidget::BLACK_PLAYER;
private:
int board[BoardWidget::BOARD_WIDTH][BoardWidget::BOARD_HEIGHT];
QPoint bestPos;
};
#endif // GOMOKUAI_H
gomokuai.cpp:
#include "gomokuai.h"
#include <QDebug>
#include <cmath>
const int GomokuAi::CHESS_VALUES[9] = {0, 50, 100, 200, 500, 650, 2500, 10000, 30000};
const int GomokuAi::MIN_VALUE;
const int GomokuAi::AI_PLAYER;
GomokuAi::GomokuAi(QObject *parent) : QObject(parent)
{
}
QVector<QPoint> GomokuAi::getAllDropPos()
{
QVector<QPoint> allPos;
for (int i = 0; i < BoardWidget::BOARD_WIDTH; i++)
{
for (int j = 0; j < BoardWidget::BOARD_HEIGHT; j++)
{
if (board[i][j] == BoardWidget::NO_PIECE)
{
allPos.push_back(QPoint(i, j));
}
}
}
return allPos;
}
int GomokuAi::getPieceScore(int x, int y, int player)
{
int value = 0;
int piece = (player == BoardWidget::WHITE_PLAYER) ? BoardWidget::WHITE_PIECE : BoardWidget::BLACK_PIECE;
for (int i = 0; i < 8; i++)
{
int t = getChessType(x, y, i, piece);
value += CHESS_VALUES[t];
}
value += getPosValue(x, y);
return value;
}
int GomokuAi::getPosValue(int x, int y)
{
int valuex =BoardWidget::BOARD_WIDTH - std::abs(x - BoardWidget::BOARD_WIDTH / 2);
int valuey =BoardWidget::BOARD_HEIGHT - std::abs(y - BoardWidget::BOARD_HEIGHT / 2);
return valuex + valuey;
}
int GomokuAi::getChessType(int x, int y, int dire, int piece)
{
int chessType = 0;
int end = BoardWidget::NO_PIECE;
int num = getLinePieceNum(x, y, dire, piece, end);
if (num == 5)
{
chessType = 8;
}
else if (num == 4 && end == BoardWidget::NO_PIECE)
{
chessType = 7;
}
else if (num == 4 && end != BoardWidget::NO_PIECE)
{
chessType = 6;
}
else if (num == 3 && end == BoardWidget::NO_PIECE)
{
chessType = 5;
}
else if (num == 3 && end != BoardWidget::NO_PIECE)
{
chessType = 4;
}
else if (num == 2 && end == BoardWidget::NO_PIECE)
{
chessType = 3;
}
else if (num == 2 && end != BoardWidget::NO_PIECE)
{
chessType = 2;
}
else if (num == 1 && end == BoardWidget::NO_PIECE)
{
chessType = 1;
}
else
{
chessType = 0;
}
return chessType;
}
int GomokuAi::getLinePieceNum(int x, int y, int dire, int pieceColo, int &pieceEnd)
{
int offset[8][2] = {{1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}};
int num = 0;
x += offset[dire][0];
y += offset[dire][1];
if (isBeyond(x, y) || board[x][y] != pieceColo)
{
return 0;
}
int pieceStart = board[x][y];
while (!isBeyond(x, y) && board[x][y] == pieceStart)
{
x += offset[dire][0];
y += offset[dire][1];
num++;
}
pieceEnd = board[x][y]; //终止处的棋子
return num;
}
bool GomokuAi::isBeyond(int x, int y)
{
return x < 0 || x >= BoardWidget::BOARD_WIDTH || y < 0 || y >= BoardWidget::BOARD_HEIGHT;
}
QPoint GomokuAi::searchAGoodPos(Board nBoard)
{
memcpy(board, nBoard, sizeof(board));
int bestScore = MIN_VALUE;
for (QPoint pos : getAllDropPos())
{
int value = getPieceScore(pos.x(), pos.y(), AI_PLAYER);
int oppoValue = getPieceScore(pos.x(), pos.y(), !AI_PLAYER);
int totalValue = std::max(value, oppoValue * 4 / 5);
if (totalValue > bestScore)
{
bestScore = totalValue;
bestPos = pos;
}
}
return bestPos;
}
欢迎学习交流~
源码下载:https://download.csdn.net/download/weixin_45737857/12060550
来源:CSDN
作者:时间之外的往事&
链接:https://blog.csdn.net/weixin_45737857/article/details/103746150