第零章:介紹
看到這個游戲了,感覺蠻好玩的,實現了一下。
界面如下:

游戲玩法:在3×*3的矩陣中,每個按鈕都可以點擊,如果按鈕四周有一個是空白,則點擊此按鈕則會移動到這個空白。按鈕字母順序變成“ABCD……”這樣有序就贏了,最後空格可以出現在任何地方。
第一章:構思
設計模式基本上沒接觸過,所以就沒有按書上的方式,自己想了大概要怎麼實現,可能自己像的沒有它給出的方式好吧,但是畢竟是菜鳥嘛,一步一步來!
1、用什麼裝這些按鈕
學習了QGridLayOut,“The QGridLayout class lays out widgets in a grid”,這就好辦了,它會把窗體控件放到一個網格裡面,也就是說類似與矩陣啦,ABC……這些肯定就是就是一個個QPushButton啦,創建了一個個按鈕,再把它裝進去即可。最後這個QGridLayOut設置為QDialog的LayOut就可以了。
這個是從顯示層面考慮的。
2、如何用代碼表示一個3×3的矩陣
雖然可以把一個窗體放到一個QGridLayOut來進行布局,它有如下添加函數:
void addWidget ( QWidget * widget, int row, int column, Qt::Alignment alignment = 0 )
但是由於我對QGridLayOut不熟,不知道是否可以用類似與矩陣的存取方式,即給定行列,獲取裡面的東西。所以我用了一個矩陣,也就是一個二維數組啦,對應每一個按鈕,數組裡面放的是指向按鈕的指針,空白就放一個NULL指針,因為幾個按鈕創建好了就在內存那裡了,不動了,所以以後我交換一個按鈕與一個空白的時候就這要交換這兩個指針就加上重新QGridLayOut的方法addWidget就可以了。
3、需要哪些類
需要兩個類,一個MyButton,公有繼承QPushButton,還有Dialog類,公有繼承QDialog,即顯示主界面啦。
4、如何實現點擊按鈕就移動呢
點擊按鈕時按鈕發射clicked()信號,但是Dialog類不知道是哪一個按鈕,所以要重新發射一個信號,把指向自己的指針this當做參數,按後Dialog類就可以知道是誰了,在判斷時候需要移動。
第三章:MyButton類的實現
直接上代碼:

![]()
1 #ifndef MYBUTTON_H
2 #define MYBUTTON_H
3
4 #include <QPushButton>
5
6
7 struct Coord
8 {
9 int x;
10 int y;
11 };
12
13
14 class MyButton : public QPushButton
15 {
16 Q_OBJECT
17
18 private:
19 Coord m_coord;
20
21 public:
22 explicit MyButton(char c,Coord coord,QWidget* parent=0);
23 void setCoord(Coord newCd);
24 Coord getCoord() const;
25
26 signals:
27 void myClick(MyButton* p); //signal,argument is poiter to myself,so someone else can identify me
28
29 private slots:
30 void btClicked();
31 };
32
33 #endif // MYBUTTON_H
mybutton.h

![]()
1 #include "mybutton.h"
2
3 #include <QMessageBox>
4
5 MyButton::MyButton(char c,Coord coord, QWidget *parent) : QPushButton(parent)
6 {
7
8 setText(QString("%1").arg(c));
9 this->m_coord=coord;
10 connect(this,SIGNAL(clicked()),this,SLOT(btClicked()));
11 }
12
13 void MyButton::setCoord(Coord newCd)
14 {
15 m_coord=newCd;
16 }
17
18 Coord MyButton::getCoord() const
19 {
20 return m_coord;
21 }
22
23
24 void MyButton::btClicked()
25 {
26 emit myClick(this);
27 }
mybutton.cpp
我給每一個按鈕一個坐標的數據了,因為按鈕創建好了就在內存的某個位置不動了,如何表示他們對應顯示的哪一個呢?這個坐標就相當於標示一個按鈕,ABCD……只是他們顯示的文字,這個是不變的。
構造函數的參數char是它要顯示的東西,Coord是自定義結構體表示坐標。
connect(this,SIGNAL(clicked()),this,SLOT(btClicked()));
void MyButton::btClicked()
{
emit myClick(this);
}
注意這個,這樣就可以讓Dialog類自己寫一個槽,這個槽由上面的this參數就可以知道是哪個按鈕被點了,在這裡居然想了好久……
第四章:Dialog類的實現
1、類的聲明:

![]()
1 #ifndef DIALOG_H
2 #define DIALOG_H
3
4 #include <QDialog>
5 #include "mybutton.h"
6
7 class QGridLayout;
8 class QPushButton;
9
10 namespace Ui {
11 class Dialog;
12 }
13
14 class Dialog : public QDialog
15 {
16 Q_OBJECT
17
18 public:
19 explicit Dialog(QWidget *parent = 0);
20 ~Dialog();
21
22 private:
23 Ui::Dialog *ui;
24
25 static const int N=3;
26 QGridLayout* m_lay;
27 MyButton* m_pbArr[N][N];
28 QString m_btnTextOrder;
29 private slots:
30 void btClicked(MyButton* p); //one of N*N buttons has been clicked
31
32 private:
33 void disorder(); //disorder the N*N buttons
34 void exchangeButton(Coord a,Coord b); //exchange two buttons based on coord
35 bool isOver() const; //is it in order???
36 };
37
38 #endif // DIALOG_H
dialog.h
一一說明數據成員的含義,源代碼沒有注釋是因為輸入不了中文,我才不要雞雞比注釋短呢……
static const int N=3; 表明是幾乘幾的矩陣
QGridLayout* m_lay; 布局
MyButton* m_pbArr[N][N]; 對應布局的矩陣,數組裡面放的都是指向按鈕的指針,移動一個按鈕只要交換對應的指針並把按鈕的私有坐標改一下並在布局裡面弄一下即可
QString m_btnTextOrder; 用到就知道了
2、構造函數:

![]()
1 Dialog::Dialog(QWidget *parent) :
2 QDialog(parent),
3 ui(new Ui::Dialog)
4 {
5 ui->setupUi(this);
6 setWindowTitle("Let us have a game!");
7 setMinimumSize(300,250);
8 setMaximumSize(300,250);
9
10 m_lay=new QGridLayout(this);
11
12 for(int i=0;i<N;i++)
13 {
14 int colums;
15 if(i<N-1) colums=N;
16 else colums=N-1;
17
18 for(int j=0;j<colums;j++)
19 {
20 char c=i*N+j+'A';
21 m_btnTextOrder.append(c);
22
23 Coord cd={i,j};
24 m_pbArr[i][j]=new MyButton(c,cd);
25
26 m_lay->addWidget(m_pbArr[i][j],i,j);
27 connect(m_pbArr[i][j],SIGNAL(myClick(MyButton*)),this,SLOT(btClicked(MyButton*)));
28 }
29 }
30 m_pbArr[N-1][N-1]=NULL;
31 this->setLayout(m_lay);
32
33 disorder(); //make buttons disorder
34 }
構造函數
先把窗口大小固定了。
new一個QGridLayout。
然後的雙層循環用來創建一個個按鈕對象,創建一個對象就把它添加到QGridLayOut,這樣就有了秩序了,然後就連接每個按鈕被點擊的槽了。
最後把最後一個位置的沒有按鈕的賦值為空指針。
最最後使這些按鈕無序,這個稍後介紹。
3、按鈕被點擊啦

![]()
1 void Dialog::btClicked(MyButton* p)
2 {
3 Coord cd=p->getCoord(); //get the button that been clicked
4 Coord cdTarget=cd; //target position that maybe switch
5
6 if((cd.x-1>=0)&&(m_pbArr[cd.x-1][cd.y]==NULL)) //test top
7 {
8 cdTarget.x--;
9 }
10 else if((cd.x+1<N)&&(m_pbArr[cd.x+1][cd.y]==NULL)) //test down
11 {
12 cdTarget.x++;
13 }
14 else if((cd.y-1>=0)&&(m_pbArr[cd.x][cd.y-1]==NULL)) //test left
15 {
16 cdTarget.y--;
17 }
18 else if((cd.y+1<N)&&(m_pbArr[cd.x][cd.y+1]==NULL)) //test right
19 {
20 cdTarget.y++;
21 }
22 else
23 {
24 return; //can not move this button
25 }
26
27 /*let us switch!*/
28 exchangeButton(cd,cdTarget);
29
30 /*check whether game is over*/
31 if(isOver())
32 {
33 QMessageBox::warning(this,"Sucess","You made it! Congratulations!");
34 }
35 }
按鈕被點擊事件
被點擊後可以通過按鈕發射的信號獲得按鈕的指針,再通過按鈕的獲得按鈕的坐標。然後看這個坐標上下左右是否有一個是空格,沒有就什麼都不做,有的話就把這個按鈕移動到那個位置,實質上是交換。

![]()
1 void Dialog::exchangeButton(Coord a, Coord b)
2 {
3 if((m_pbArr[a.x][a.y]!=NULL)&&(m_pbArr[b.x][b.y]!=NULL)) //no NULL
4 {
5 /*remove from pre QGridLayOut*/
6 m_lay->removeWidget(m_pbArr[a.x][a.y]);
7 m_lay->removeWidget(m_pbArr[b.x][b.y]);
8
9 /*add to QGridLayOut*/
10 m_lay->addWidget(m_pbArr[a.x][a.y],b.x,b.y);
11 m_lay->addWidget(m_pbArr[b.x][b.y],a.x,a.y);
12
13 /*change two buttons's coord */
14 m_pbArr[a.x][a.y]->setCoord(b);
15 m_pbArr[b.x][b.y]->setCoord(a);
16
17 /*exchange poiter in m_pbArr[] */
18 MyButton* temp=m_pbArr[a.x][a.y];
19 m_pbArr[a.x][a.y]=m_pbArr[b.x][b.y];
20 m_pbArr[b.x][b.y]=temp;
21 }
22 else //one of two buttons' position is NULL (in array m_pbArr[])
23 {
24 if(m_pbArr[a.x][a.y]==NULL)
25 {
26 Coord temp=a;
27 a=b;
28 b=temp;
29 }
30
31 /*here,we can make sure that coord b is null*/
32 m_lay->removeWidget(m_pbArr[a.x][a.y]);
33 m_lay->addWidget(m_pbArr[a.x][a.y],b.x,b.y);
34 m_pbArr[a.x][a.y]->setCoord(b);
35 MyButton* temp=m_pbArr[a.x][a.y];
36 m_pbArr[a.x][a.y]=m_pbArr[b.x][b.y];
37 m_pbArr[b.x][b.y]=temp;
38 }
39 }
交換按鈕
交換分為兩周情況,兩個按鈕的交換,這個再使界面無序化時調用,另一個就是交換按鈕與空白,只要看前一個就足夠了。
先把待交換的兩個按鈕從QGridLayOut拿下來,具體表現就是不顯示了。
再交叉添加到QGridLayOut的特定坐標。
再把兩個按鈕的私有數據也就是坐標交換一下,因為坐標通過這個坐標從得到被點按鈕的位置的。
再把數據層(QGridLayOut是顯示層)的指針也互換一下,讓他們指向對的按鈕內存。
4、使無序化

![]()
1 void Dialog::disorder()
2 {
3 Coord cdA,cdB;
4 for(int i=0;i<56;i++)
5 {
6 QTime time=QTime::currentTime();
7 qsrand(time.msec()*7+time.second()*245);
8 cdA.x=qrand()%N; //from 0 to N-1
9 qsrand(time.second()+i*3+91);
10 cdA.y=qrand()%N;
11
12 qsrand(time.msec()*3+i);
13 cdB.x=qrand()%N;
14 qsrand(time.msec()+i*11);
15 cdB.y=qrand()%N;
16
17 // qDebug()<<cdA.x<<cdA.y<<"-----"<<cdB.x<<cdB.y<<endl;
18 if((cdA.x==cdB.x)&&(cdA.y==cdB.y))
19 {
20 i--;
21 }
22 else //not the same coord
23 {
24 exchangeButton(cdA,cdB);
25 }
26 }
27 }
無序化
游戲開始是無序的,這個函數就是干這事的,有了上面的交換,那麼只要隨即生成坐標,把指定坐標的按鈕交換即可。關鍵這裡的產生隨即坐標的函數,我寫的不太好,調試時可以看出好多對坐標一樣,因為計算機太快了。
5、檢測是否結束

![]()
1 bool Dialog::isOver() const
2 {
3 QString res;
4 for(int i=0;i<N;i++)
5 {
6 for(int j=0;j<N;j++)
7 {
8 if(m_pbArr[i][j]!=NULL)
9 {
10 res.append(m_pbArr[i][j]->text());
11 }
12 }
13 }
14
15 if(res==m_btnTextOrder)
16 {
17 return true;
18 }
19 else
20 {
21 return false;
22 }
23 }
是否結束
每一次點擊按鈕都執行一下,看時候結束了。我寫的就是按照矩陣順序一個一個按鈕讀,讀它的文本,追加到一個字符串,最後和“ABCD……”字符串比較,相等就結束了。
第五章:遺言
1、QGridLayOut
顯示與數據表示分離了,這個不太好。
2、啦啦啦啦啦啦
我會告訴你我只有一次成功了嗎?
我想隨機交換之後會不會本身就有成功不了的可能?數學理論啊!