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,对教材上好多的地方理解不到位,后续会不断完善五子棋程序。

posted @ 2020-01-16 21:32  Alpha205  阅读(674)  评论(0编辑  收藏  举报