Qt 学习笔记(5)绘图 五子棋游戏
在上一篇博客C++ Qt学习笔记(4)绘图中介绍了Qt中的绘图方法,基于上一篇的博客的知识,使用QPainter设计一个五子棋的棋盘,后续会完成五子棋的游戏设计。
1. 棋盘的设计
首先需要绘制棋盘的界面,这里采用的方法是,首先需要设定棋盘的大小,定义BOARD_WIDTH,以及BOARD_HEIGHT表示棋盘的大小,CELL_SIZE表示棋盘中每个格子的大小,单位为像素。START_POS表示棋盘中第一个方格左上角的坐标。WIDGET_SIZE设定绘图设备的大小,也即Widget窗口的初始大小。
然后通过:
setFixedSize(WIDGET_SIZE);
设置绘图设备为固定大小。
ROW_NUM_START, COLUMN_NUM_START表示绘制放个上的数字的起始位置,具体的值可以根据绘制的效果进行调节。
BOARD_WIDTH = 15; // 表示有十五个格子
BOARD_HEIGHT = 15;
CELL_SIZE = (25,25);
START_POS = (40, 40)
ROW_NUM_START = (15, 45)
COLUMN_NUM_START = (39, 25)
绘制棋盘的代码如下所示:
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this); // this代表Widget,及绘图设备
painter.setRenderHint(QPainter::Antialiasing); // 绘画普通图形启用反走样, 即开启抗锯齿
painter.setRenderHint(QPainter::TextAntialiasing); // 绘画文字反走样, 即开启抗锯齿
int width = this->width(); // 获取绘图区域的大小
int height = this->height();
painter.fillRect(this->rect(), Qt::gray); //填充背景颜色
// 设置字体
QFont font;
font.setPointSize(10);
font.setBold(true);
painter.setFont(font);
// 设置画笔
QPen pen;
pen.setWidth(2); // 设置线宽
pen.setColor(Qt::black); // 设置颜色
pen.setStyle(Qt::SolidLine); // 设置线型
pen.setCapStyle(Qt::FlatCap);
pen.setJoinStyle(Qt::BevelJoin);
painter.setPen(pen);
for(int i=0; i<BOARD_WIDTH; i++)
{
painter.drawText(COLUMN_NUM_START+QPoint(i*CELL_SIZE.width(), 0), QString::number(i+1));
}
for(int j=0; j<BOARD_HEIGHT; j++)
{
painter.drawText(ROW_NUM_START + QPoint(0, j*CELL_SIZE.height()), QString::number(j+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));
}
}
}
在绘制完棋盘之后,需要在棋盘上绘制一个落子提示的图标,随着鼠标的移动而提示落子的位置,这里需要使用到鼠标事件,
MouseMoveEvent()来实时获取鼠标的移动位置:
获取坐标以及计算落子位置的方法如下:
1. 获取鼠标在绘图设备中的实际位置:
2. 计算当前位置在棋盘中的坐标,将棋盘的起点START_POS看为(0,0)点
3. 得到鼠标在棋盘中的坐标
4. 给坐标加上一个半个方格大小的偏置量,此时相当于提示符号的位置从方格左上角偏移到十字交叉的位置。
5.判断鼠标的坐标是否在合理的范围之内。
void Widget::mouseMoveEvent(QMouseEvent *event) // 鼠标事件
{
QPoint pos = event->pos() - START_POS; // 相对于棋盘起始位置的坐标
QPoint temp_point = pos + QPoint(CELL_SIZE.width()/2, CELL_SIZE.height()/2); // 坐标
int x = temp_point.x();
int y = temp_point.y();
// 检测此时坐标的范围是否合理
// if(x <= START_POS.x() || y <= START_POS.y() || x >= BOARD_WIDTH*CELL_SIZE.width()-START_POS.x() || y >= BOARD_HEIGHT*CELL_SIZE.height() - START_POS.y())
if(x <= 0 || y <= 0 || x >= BOARD_WIDTH*CELL_SIZE.width() || y >= BOARD_HEIGHT*CELL_SIZE.height())
{
return;
}
int offset_x = x % CELL_SIZE.width(); // 这个坐标表示不够一个cell_size的坐标的大小
int offset_y = y % CELL_SIZE.height();
QPoint tip_position = QPoint(x-offset_x, y-offset_y)+START_POS-QPoint(CELL_SIZE.width()/2, CELL_SIZE.height()/2)+QPoint(8, 8);
setTrackPos(tip_position);
}
定义一个点trackPos来保存鼠标实时变化的坐标,在构造函数中,需要设置开启MouseTracking:
// 构造函数
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget), trackPos(28, 28)
{
setFixedSize(WIDGET_SIZE); // 设置窗口为固定大小
setMouseTracking(true); // 开启MouseTracking
ui->setupUi(this);
}
setTrackPos()方法用于更新窗口部件:
void Widget::setTrackPos(QPoint point)
{
trackPos = point;
update(); // update的作用是更新窗口部件
}
当窗口部件更新后,就会重新绘制棋盘。
绘制落子提示位置的图标,在上述的MouseMoveEvent中已经获取到了绘制的坐标位置,接下来需要定义几个点,连接成图标:
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); // poses相当于指针
painter.drawPolyline(poses+3, 3); // 从poses+3的点开始,将三个点连成一条线
painter.drawPolyline(poses+6, 3);
painter.drawPolyline(poses+9, 3);
绘制后的棋盘效果如下所示:
其中红色部分表示落子的提示标记,会随着鼠标的移动而变动位置。
单机鼠标下棋:
单击鼠标下棋的操作需要使用鼠标事件完成,MouseReleasedEvent(),在鼠标送开始,将棋子放到棋盘相应的位置:
首先,用一个二维的数组表示棋盘相应位置的落子情况,board[ i][ j]
1表示白色玩家
2表示黑色玩家
首先在开始棋局之前,需要随棋盘进行初始化操作,初始化的流程如下所示:
需要定义一些棋局中常用的状态量:
// 定义变量
QPoint trackPos; // 记录鼠标的位置
bool endGame; // 标志游戏是否结束
bool WHITE_PALYER; // 白色玩家
bool BLACK_PLAYER;
bool next_player; // 下一位玩家
static const int NO_PIECE = 0; // 用于标记棋盘中某个位置没有棋子
// 定义玩家
static const int WHITE_PIECE = 1; // 白棋
static const int BLACK_PIECE = 2; // 黑棋
初始化的过程如下所示,这里添加了随即决定开局的是白棋还是黑棋的部分:
void Widget::initBoard()
{
// 对棋盘进行初始化
for(int i=0; i<BOARD_WIDTH; i++)
{
for(int j=0; j<BOARD_HEIGHT; j++)
{
board[i][j] = NO_PIECE;
}
}
endGame = false;
// 随机决定谁先落子 等概率
std::default_random_engine e;
std::uniform_real_distribution<double> u(0, 1);
e.seed(10); // 设置随机数种子
double rand_number = e(); // 生成随机数
if(rand_number > 0.5)
{
// 白棋先落子
WHITE_PALYER = true;
BLACK_PLAYER = false;
next_player = WHITE_PALYER;
}
else
{
// 黑棋先落子
WHITE_PALYER = false;
BLACK_PLAYER = true;
next_player = BLACK_PLAYER;
}
}
在游戏的过程中,每次都是通过鼠标点击棋盘的某一位置,实现棋子的落下,所以需要是实现鼠标事件MouseReleasedEvent();
在MouseReleasedEvent()事件函数中,首先获取到鼠标按下时的坐标,其次,就是在鼠标所按下的坐标处绘制相应的棋子,MouseReleasedEvent()的实现如下所示:
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
if(!endGame) // 游戏未结束
{
QPoint pos = event->pos() - START_POS;
int x = pos.x();
int y = pos.y();
int x_pos = x / CELL_SIZE.width(); // 整数,确定第几个格子
int y_pos = y / CELL_SIZE.height();
int x_offset = x % CELL_SIZE.width(); // 余数,计算是否需要偏移
int y_offset = y % CELL_SIZE.height();
if(x_offset > CELL_SIZE.width()/2)
{
x_pos++;
}
if(y_offset > CELL_SIZE.height()/2)
{
y_pos++;
}
dropPiece(x_pos, y_pos); //落子
}
}
这里的dropPiece就是表示在对应的位置(x,y)处绘制棋子,具体的实现过程为:
void Widget::dropPiece(int x, int y)
{
if(x>=0 && x<=BOARD_WIDTH && y>=0 && y<=BOARD_HEIGHT && board[x][y]==NO_PIECE)
{
if(next_player == WHITE_PALYER)
{
board[x][y] = WHITE_PIECE; // 当前位置是白棋
}
else
{
board[x][y] = BLACK_PIECE; // 当前位置是黑棋
}
// 切换落子的玩家
next_player = !next_player;
// 判断输赢
checkWinner();
update(); // 更新窗口组件
}
}
在落子之后,还需要判断此时的输赢情况,这里使用checkWinner()实现:
bool Widget::isHFivePiece(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;
}
}
return true;
}
bool Widget::isVFivePiece(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;
}
}
return true;
}
bool Widget::isLeftSlash(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;
}
}
return true;
}
bool Widget::isRightSlash(int x, int y)
{
// 沿着右对角线
int piece = board[x][y];
for(int i=1; i<5; i++)
{
if(x-i<0 || y+i>BOARD_HEIGHT || board[x-i][y+i]!=piece)
{
return false;
}
}
return true;
}
bool Widget::isFivePiece(int x, int y)
{
// 是否赢棋
return isHFivePiece(x, y) || isVFivePiece(x, y) || isLeftSlash(x, y) || isRightSlash(x, y);
}
void Widget::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 && isFivePiece(i, j))
{
int winner;
if(board[i][j]==WHITE_PIECE)
{
winner = WHITE_PIECE;
qDebug() << "White win!" << endl;
}
else
{
winner = BLACK_PIECE;
qDebug() << "Black win" << endl;
}
endGame = true;
}
}
}
if(fullPieces)
{
qDebug() << "middle result" << endl;
}
}
最后,在执行update()之后,需要在QPainterEvent()中再次绘制新添加的棋子,实际上是将棋盘重新刷新依次,添加新的棋子:
// 绘制棋子
painter.setPen(Qt::NoPen);
// 查看棋盘的状态
for(int i=0; i<BOARD_WIDTH; i++)
{
for(int j=0; j<BOARD_HEIGHT; j++)
{
QColor piece_color;
if(board[i][j] == WHITE_PIECE)
{
piece_color = Qt::white;
painter.setBrush(QBrush(piece_color));
painter.drawEllipse(START_POS.x() + i*CELL_SIZE.width() - CELL_SIZE.width()/2, START_POS.y() + j*CELL_SIZE.height() - CELL_SIZE.height()/2,
CELL_SIZE.width()-4, CELL_SIZE.height()-4);
}
else if(board[i][j] == BLACK_PIECE)
{
piece_color = Qt::black;
painter.setBrush(QBrush(piece_color));
painter.drawEllipse(START_POS.x() + i*CELL_SIZE.width() - CELL_SIZE.width()/2, START_POS.y() + j*CELL_SIZE.height() - CELL_SIZE.height()/2,
CELL_SIZE.width()-4, CELL_SIZE.height()-4);
}
else
{
painter.setPen(Qt::NoPen);
}
}
}
最终的效果如下图所示:
1. 现在需要对代码进行一下整合,第一步是实现五子棋界面的部分,创建一个基于QWidget类的类BoardWidget来表示棋盘,以及在下棋过程中的具体操作,这里不需要创建.ui文件:
boardwidget.h文件的实现:包含棋盘基本功能的实现,如棋盘绘制,落子位置跟踪,鼠标事件实现落子,判断棋局输赢等功能,都在BoardWidget类中实现:
#ifndef BOARDWIDGET_H
#define BOARDWIDGET_H
#include <QWidget>
class BoardWidget : public QWidget
{
Q_OBJECT
public:
explicit BoardWidget(QWidget *parent = 0);
~BoardWidget();
signals:
void game_over(int winner); // 游戏结束的信号
public slots:
protected:
void paintEvent(QPaintEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
public:
void initBoard();
private:
void setTrackPos(QPoint point); // 追踪鼠标位置
void dropPiece(int x, int y); // 落子
// 判断输赢
bool isFivePiece(int x, int y);
bool isVFivePiece(int x, int y); // 从(x,y)开始,沿着垂直方向
bool isHFivePiece(int x, int y); // 从(x,y)开始,沿着水平方向
bool isLeftSlash(int x, int y); // 从(x,y)开始,沿着左对角线
bool isRightSlash(int x, int y); // 从(x,y)开始,沿着右对角线
void checkWinner();
// 定义变量
QPoint trackPos; // 记录鼠标的位置
bool endGame; // 标志游戏是否结束
bool WHITE_PALYER; // 白色玩家
bool BLACK_PLAYER;
bool next_player; // 下一位玩家
public:
// 棋盘的大小15x15
static const int BOARD_WIDTH = 15;
static const int BOARD_HEIGHT = 15;
int board[BOARD_WIDTH][BOARD_HEIGHT]; // 定义棋盘
//棋盘起始的的位置 行和列
static const QPoint ROW_NUM_START;
static const QPoint COLUMN_NUM_START;
// size
static const QSize WIDGET_SIZE;
static const QSize CELL_SIZE;
static const QPoint START_POS;
static const int NO_PIECE = 0; // 用于标记棋盘中某个位置没有棋子
// 定义玩家
static const int WHITE_PIECE = 1; // 白棋
static const int BLACK_PIECE = 2; // 黑棋
};
#endif // BOARDWIDGET_H
boardwidget.cpp
#include "boardwidget.h"
#include <QPainter>
#include <QRect>
#include <QPen>
#include <QBrush>
#include <QPoint>
#include <qmath.h>
#include <QMouseEvent>
#include <QString>
#include <QSize>
#include <random>
#include <QDebug>
// 初始化参数
// 1. 棋盘的起始位置:
const QPoint BoardWidget::ROW_NUM_START(15, 45);
const QPoint BoardWidget::COLUMN_NUM_START(35, 25);
// 2.size初始化
const QSize BoardWidget::WIDGET_SIZE(830, 830);
const QSize BoardWidget::CELL_SIZE(45, 45);
const QPoint BoardWidget::START_POS(40, 40);
BoardWidget::BoardWidget(QWidget *parent) : QWidget(parent), trackPos(28, 28)
{
setFixedSize(WIDGET_SIZE); // 设置窗口为固定大小
setMouseTracking(true); // 开启MouseTracking
initBoard(); // 初始化棋盘
}
BoardWidget::~BoardWidget()
{
}
void BoardWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this); // this代表Widget,及绘图设备
painter.setRenderHint(QPainter::Antialiasing); // 绘画普通图形启用反走样, 即开启抗锯齿
painter.setRenderHint(QPainter::TextAntialiasing); // 绘画文字反走样, 即开启抗锯齿
int width = this->width(); // 获取绘图区域的大小
int height = this->height();
painter.fillRect(this->rect(), Qt::gray); //填充背景颜色
// 设置字体
QFont font;
font.setPointSize(10);
font.setBold(true);
painter.setFont(font);
// 设置画笔
QPen pen;
pen.setWidth(2); // 设置线宽
pen.setColor(Qt::black); // 设置颜色
pen.setStyle(Qt::SolidLine); // 设置线型
pen.setCapStyle(Qt::FlatCap);
pen.setJoinStyle(Qt::BevelJoin);
painter.setPen(pen);
for(int i=0; i<BOARD_WIDTH; i++)
{
painter.drawText(COLUMN_NUM_START+QPoint(i*CELL_SIZE.width(), 0), QString::number(i+1));
}
for(int j=0; j<BOARD_HEIGHT; j++)
{
painter.drawText(ROW_NUM_START + QPoint(0, j*CELL_SIZE.height()), QString::number(j+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); // poses相当于指针
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++)
{
QColor piece_color;
if(board[i][j] == WHITE_PIECE)
{
piece_color = Qt::white;
painter.setBrush(QBrush(piece_color));
painter.drawEllipse(START_POS.x() + i*CELL_SIZE.width() - CELL_SIZE.width()/2, START_POS.y() + j*CELL_SIZE.height() - CELL_SIZE.height()/2,
CELL_SIZE.width()-4, CELL_SIZE.height()-4);
}
else if(board[i][j] == BLACK_PIECE)
{
piece_color = Qt::black;
painter.setBrush(QBrush(piece_color));
painter.drawEllipse(START_POS.x() + i*CELL_SIZE.width() - CELL_SIZE.width()/2, START_POS.y() + j*CELL_SIZE.height() - CELL_SIZE.height()/2,
CELL_SIZE.width()-4, CELL_SIZE.height()-4);
}
else
{
painter.setPen(Qt::NoPen);
}
}
}
}
void BoardWidget::mouseMoveEvent(QMouseEvent *event) // 鼠标事件
{
QPoint pos = event->pos() - START_POS; // 相对于棋盘起始位置的坐标
QPoint temp_point = pos + QPoint(CELL_SIZE.width()/2, CELL_SIZE.height()/2); // 坐标
int x = temp_point.x();
int y = temp_point.y();
// 检测此时坐标的范围是否合理
// if(x <= START_POS.x() || y <= START_POS.y() || x >= BOARD_WIDTH*CELL_SIZE.width()-START_POS.x() || y >= BOARD_HEIGHT*CELL_SIZE.height() - START_POS.y())
if(x <= 0 || y <= 0 || x >= BOARD_WIDTH*CELL_SIZE.width() || y >= BOARD_HEIGHT*CELL_SIZE.height())
{
return;
}
int offset_x = x % CELL_SIZE.width(); // 这个坐标表示不够一个cell_size的坐标的大小
int offset_y = y % CELL_SIZE.height();
// 绘制的图标的位置,中心的为十字交叉的位置
QPoint tip_position = QPoint(x-offset_x, y-offset_y)+START_POS-QPoint(CELL_SIZE.width()/2, CELL_SIZE.height()/2)+QPoint(8, 8);
setTrackPos(tip_position);
}
void BoardWidget::mouseReleaseEvent(QMouseEvent *event)
{
if(!endGame) // 游戏未结束
{
QPoint pos = event->pos() - START_POS;
int x = pos.x();
int y = pos.y();
int x_pos = x / CELL_SIZE.width(); // 整数,确定第几个格子
int y_pos = y / CELL_SIZE.height();
int x_offset = x % CELL_SIZE.width(); // 余数,计算是否需要偏移
int y_offset = y % CELL_SIZE.height();
if(x_offset > CELL_SIZE.width()/2)
{
x_pos++;
}
if(y_offset > CELL_SIZE.height()/2)
{
y_pos++;
}
dropPiece(x_pos, y_pos); //落子
}
}
void BoardWidget::initBoard()
{
// 对棋盘进行初始化
for(int i=0; i<BOARD_WIDTH; i++)
{
for(int j=0; j<BOARD_HEIGHT; j++)
{
board[i][j] = NO_PIECE;
}
}
endGame = false;
// 随机决定谁先落子 等概率
std::default_random_engine e;
std::uniform_real_distribution<double> u(0, 1);
e.seed(10); // 设置随机数种子
double rand_number = e(); // 生成随机数
if(rand_number > 0.5)
{
// 白棋先落子
WHITE_PALYER = true;
BLACK_PLAYER = false;
next_player = WHITE_PALYER;
}
else
{
// 黑棋先落子
WHITE_PALYER = false;
BLACK_PLAYER = true;
next_player = BLACK_PLAYER;
}
update();
}
void BoardWidget::setTrackPos(QPoint point)
{
trackPos = point;
update(); // update的作用是更新窗口部件
}
void BoardWidget::dropPiece(int x, int y)
{
if(x>=0 && x<=BOARD_WIDTH && y>=0 && y<=BOARD_HEIGHT && board[x][y]==NO_PIECE)
{
if(next_player == WHITE_PALYER)
{
board[x][y] = WHITE_PIECE; // 当前位置是白棋
}
else
{
board[x][y] = BLACK_PIECE; // 当前位置是黑棋
}
// 切换落子的玩家
next_player = !next_player;
// 判断输赢
checkWinner();
update(); // 更新窗口组件
}
}
bool BoardWidget::isHFivePiece(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;
}
}
return true;
}
bool BoardWidget::isVFivePiece(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;
}
}
return true;
}
bool BoardWidget::isLeftSlash(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;
}
}
return true;
}
bool BoardWidget::isRightSlash(int x, int y)
{
// 沿着右对角线
int piece = board[x][y];
for(int i=1; i<5; i++)
{
if(x-i<0 || y+i>BOARD_HEIGHT || board[x-i][y+i]!=piece)
{
return false;
}
}
return true;
}
bool BoardWidget::isFivePiece(int x, int y)
{
// 是否赢棋
return isHFivePiece(x, y) || isVFivePiece(x, y) || isLeftSlash(x, y) || isRightSlash(x, y);
}
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 && isFivePiece(i, j))
{
int winner;
if(board[i][j]==WHITE_PIECE)
{
winner = WHITE_PIECE;
qDebug() << "White win!" << endl;
// 发出信号
emit game_over(WHITE_PIECE);
}
else
{
winner = BLACK_PIECE;
qDebug() << "Black win" << endl;
// 发出信号
emit game_over(BLACK_PIECE);
}
endGame = true;
}
}
}
if(fullPieces)
{
emit game_over(3);
qDebug() << "middle result" << endl;
}
}
在设计完成BoardWidget类的设计后,需要设计一个游戏的界面,这个界面的作用是可以将棋盘,以及其他的一些功能性的按钮添加到上面,形成一个面向用户的游戏界面。将这个类命名为GameWidget类:
gamewidget.h文件:
#ifndef GAMEWIDGET_H
#define GAMEWIDGET_H
#include <QWidget>
#include "boardwidget.h"
class GameWidget : public QWidget
{
Q_OBJECT
public:
GameWidget(QWidget *parent = 0);
~GameWidget();
private:
BoardWidget* board;
private slots:
void restart_game();
public slots:
void showWinner(int winner);
};
#endif // GAMEWIDGET_H
在gamewidget中,需要对棋盘以及按钮的位置进行布局,所以需要用到布局管理器,在界面中添加一个重新开始游戏的按钮,在这个按钮按下的时候,需要调用棋盘的初始化函数,将整个棋盘清空,同时,在判断为玩家赢棋之后,需要弹出一个模态对话框,进行提示:
#include "gamewidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QDebug>
#include <QString>
#include <QMessageBox>
GameWidget::GameWidget(QWidget *parent)
: QWidget(parent)
{
setWindowTitle("Gomoku game"); // 设置窗口标题:
QVBoxLayout* mainLayout = new QVBoxLayout(this); // 布局管理器 垂直
board = new BoardWidget(this); // 新建棋盘对象
QHBoxLayout* horizon_layout = new QHBoxLayout(this); // 水平布局管理器
QPushButton* btn_new_game = new QPushButton("Resart"); // 按钮,开始新游戏
horizon_layout->addWidget(btn_new_game);
horizon_layout->addStretch();
mainLayout->addLayout(horizon_layout);
mainLayout->addWidget(board);
connect(btn_new_game, SIGNAL(clicked()), this, SLOT(restart_game()));
// 将当前的按钮与当前页面的resart_game()槽函数连接,在槽函数中调用initBoard()
connect(board, SIGNAL(game_over(int)), this, SLOT(showWinner(int)));
// 这种连接方式才能保证信号与槽连接正确, 不同对象之间信号与槽连接传递消息
}
GameWidget::~GameWidget()
{
}
void GameWidget::showWinner(int winner)
{
QString playerName;
if(winner==BoardWidget::WHITE_PIECE)
{
playerName = "White Winner";
// qDebug() << "Winner is 1" << endl;
}
else if(winner==2)
{
playerName = "Black Winner";
// qDebug() << "Winner is 2" << endl;
}
else
{
playerName = "Both Winner";
// qDebug() << "Winner is 3" << endl;
}
QMessageBox::information(this, "Game Over", playerName, QMessageBox::Ok);
}
void GameWidget::restart_game()
{
qDebug() << "Restart the game" << endl;
board->initBoard();
}
main.cpp文件:
#include "gamewidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GameWidget w;
w.show();
return a.exec();
}
整个项目的目录如下所示:
需要在项目中的.pro文件中添加:
QMAKE_CXXFLAGS += -std=c++11
使得Qt项目能够支持C++11.
程序运行后的效果:
-----------------------------------------------------------------------------------------------------------------------------------------
由于刚开始学习Qt,对教材上好多的地方理解不到位,后续会不断完善五子棋程序。