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,對教材上好多的地方理解不到位,後續會不斷完善五子棋程序。

發佈了103 篇原創文章 · 獲贊 57 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章