第零章:介绍
看到这个游戏了,感觉蛮好玩的,实现了一下。
界面如下:
游戏玩法:在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
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 }
我给每一个按钮一个坐标的数据了,因为按钮创建好了就在内存的某个位置不动了,如何表示他们对应显示的哪一个呢?这个坐标就相当于标示一个按钮,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
一一说明数据成员的含义,源代码没有注释是因为输入不了中文,我才不要鸡鸡比注释短呢……
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、啦啦啦啦啦啦
我会告诉你我只有一次成功了吗?
我想随机交换之后会不会本身就有成功不了的可能?数学理论啊!