QT之贪吃蛇
1、开始界面
对话框设置:设置对话框控件以及标题
- GameStart::GameStart(QDialog *parent)
- : QDialog(parent)
- {
- createWidgets();
- setWindowTitle(tr("Snake"));
- }
设置3个按钮,连接三种操作:开始游戏,获得历史排名,结束游戏。
- beginGame=new QPushButton(tr("start"));
- beginGame->setFixedSize(120, 30);
- beginGame->setFont(font);
- connect(beginGame, SIGNAL(clicked()), this, SLOT(startGame()));
- endGame=new QPushButton(tr("end"));
- endGame->setFixedSize(120, 30);
- endGame->setFont(font);
- connect(endGame, SIGNAL(clicked()), this, SLOT(close()));
- GameRank=new QPushButton(tr("rank"));
- GameRank->setFixedSize(120, 30);
- GameRank->setFont(font);
- connect(GameRank, SIGNAL(clicked()), this, SLOT(loadRank()));
设置游戏等级选项,含有3个等级。
- GameLevelCombo=new QComboBox(this);
- GameLevelCombo->setFixedSize(120, 30);
- GameLevelCombo->setFont(font);
- GameLevelCombo->addItem(tr("elementary"));
- GameLevelCombo->addItem(tr("intermediate"));
- GameLevelCombo->addItem(tr("advanced"));
- connect(GameLevelCombo, SIGNAL(activated(const QString &)), this, SLOT(getGameLevel()));
设置label和lineedit,用于输入玩家姓名。
- NameLabel=new QLabel(tr("player"));
- NameLabel->setFixedSize(120, 30);
- NameLabel->setFont(font);
- NameEdit=new QLineEdit;
- NameEdit->setFixedSize(200, 30);
- connect(NameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(setName()));
利用QTextEdit来显示排名信息。
- RankText=new QTextEdit(this);
- RankText->setStyleSheet("background:argb(248, 248, 255, 0%)");
- RankLabel=new QLabel(tr("show rank"));
- RankLabel->setFixedSize(100, 30);
- RankLabel->setFont(font);
布局:
- QVBoxLayout *LeftLayout=new QVBoxLayout;
- LeftLayout->addWidget(beginGame);
- LeftLayout->addWidget(GameRank);
- LeftLayout->addWidget(GameLevelCombo);
- LeftLayout->addWidget(endGame);
- QVBoxLayout *RightLayout=new QVBoxLayout;
- RightLayout->addWidget(RankLabel);
- RightLayout->addWidget(RankText);
- QHBoxLayout *TopLayout=new QHBoxLayout;
- TopLayout->addWidget(NameLabel);
- TopLayout->addWidget(NameEdit);
- QHBoxLayout *BelowLayout=new QHBoxLayout;
- BelowLayout->addLayout(LeftLayout);
- BelowLayout->addLayout(RightLayout);
- QVBoxLayout *MainLayout=new QVBoxLayout;
- MainLayout->addLayout(TopLayout);
- MainLayout->addLayout(BelowLayout);
- setLayout(MainLayout);
玩家名字获得、获得排名信息、产生游戏等级的函数实现:
- void GameStart::setName(){
- player=NameEdit->text();
- }
- void GameStart::getGameLevel(){
- QString LevelString=GameLevelCombo->currentText();
- if(LevelString==tr("elementary"))
- GameLevel=1;
- else if(LevelString==tr("intermediate"))
- GameLevel=2;
- else if(LevelString==tr("advanced"))
- GameLevel=3;
- else
- GameLevel=1;
- }
- void GameStart::loadRank(){
- QFile file("H:/soft programme/QTMySnaker/snake/Rank.txt");
- if(!file.open(QFile::ReadOnly)){
- QMessageBox::warning(this, tr("load rank"), tr("cannot load rank file %1").arg(file.fileName()));
- return;
- }
- QTextStream in(&file);
- RankText->setPlainText(in.readAll());
- }
对于游戏界面调出,则要求能够隐藏当前对话框,并且终止当前线程,而可以去执行游戏界面的线程,那么需要在游戏界面重新实现一个执行函数exec()。
- void GameStart::startGame(){
- this->close();
- ImageShow ImageShow_M1(player,GameLevel);
- ImageShow_M1.exec();
- this->show();
- this->exec();
- }
如果仅仅是游戏界面用show()函数,则游戏界面不会产生控件,仅仅会出现一个主窗口。这就是模式和非模式显示,模式不仅显示界面而且会锁定在这个界面上,从而执行操作。一般QDialog有自己的exec()函数,而QWidget没有,这就需要在QWidget中实现一个。
2、游戏界面
初始化、定义计时器:
- ImageShow::ImageShow(const QString player, const int game_level):
- player(player), game_level(game_level){
- setWindowTitle(tr("In Game..."));
- setFixedSize(ROWS+20, COLUMNS+50);
- InitSnake();
- Timer=new QTimer(this);
- connect(Timer, SIGNAL(timeout()),this, SLOT(timeout()));
- Timer->start(1000/game_level);
- }
- void ImageShow::InitSnake(){
- game_score = 0;
- IsOver=false;
- direction=3;//right
- body_x = new vector<char>;
- body_y = new vector<char>;
- for (int i = 4; i >= 1; i--) {
- body_x->push_back(1);
- body_y->push_back(i);//初始化具有1个蛇头和3个蛇身的蛇
- }
- food_x = 3;
- food_y = 5;
- }
- void ImageShow::timeout(){
- IsOver=walk();
- if(IsOver == true){
- Timer->stop();
- int r=QMessageBox::warning(this,tr("save rank"), tr("geme is over!\n"
- "Do you want to save the game results?"),
- QMessageBox::Yes|QMessageBox::No);
- if(r==QMessageBox::Yes)
- saveRank();
- return;
- }
- update();
- }
根据游戏等级,产生了不同时间限定,时间到就会去执行蛇行走,然后再绘制图像,通过调用了update函数实现绘制事件。
- bool ImageShow::walk() {
- switch (direction) {
- case 1: {
- body_x->insert(body_x->begin(), (*body_x)[0]);
- body_x->pop_back();
- body_y->insert(body_y->begin(), (*body_y)[0] - 1);
- body_y->pop_back();
- break;
- }//左移动
- case 2: {
- body_x->insert(body_x->begin(), (*body_x)[0] - 1);
- body_x->pop_back();
- body_y->insert(body_y->begin(), (*body_y)[0]);
- body_y->pop_back();
- break;
- }//向上移动
- case 3: {
- body_x->insert(body_x->begin(), (*body_x)[0]);
- body_x->pop_back();
- body_y->insert(body_y->begin(), (*body_y)[0] + 1);
- body_y->pop_back();
- break;
- }//向右移动
- case 4: {
- body_x->insert(body_x->begin(), (*body_x)[0] + 1);
- body_x->pop_back();
- body_y->insert(body_y->begin(), (*body_y)[0]);
- body_y->pop_back();
- break;
- }//向下移动
- default: ;
- }
- if (((*body_x)[0] == food_x) && ((*body_y)[0] == food_y)) {
- body_x->push_back(body_x->back());
- body_y->push_back(body_y->back());
- game_score++;
- produce();
- }//吃下食物
- if (((*body_x)[0] == 0) || ((*body_x)[0] == (ROWS/8)) || ((*body_y)[0] == 0) || ((*body_y)[0] == (COLUMNS/8))) {
- return true;//蛇撞墙,游戏失败
- }
- return false;
- }
- void ImageShow::paintEvent(QPaintEvent *){
- QPainter *painter = new QPainter(this);
- painter->setRenderHint(QPainter::Antialiasing, true);
- QPen ThinPen(palette().foreground(), 1);
- QColor LightSlateBlue(132, 112, 255);
- QPen ThickPen(LightSlateBlue, 4);
- painter->save();
- painter->translate(10, 40);
- painter->setPen(ThickPen);
- painter->setBrush(LightSlateBlue);
- painter->drawLine(0, 0, ROWS, 0);
- painter->drawLine(0, 0, 0, COLUMNS);
- painter->drawLine(ROWS, 0, ROWS, COLUMNS);
- painter->drawLine(0, COLUMNS, ROWS, COLUMNS);
- painter->restore();
- QColor brown(255,64,64);
- QPen textPen(brown, 4);
- painter->setPen(textPen);
- QFont font=painter->font();
- font.setPixelSize(20);
- painter->setFont(font);
- const QRect rectangle1=QRect(50, 2, 150, 25);
- QRect boundingrect;
- painter->drawText(rectangle1, 0, player, &boundingrect);
- const QRect rectangle2=QRect(130, 2, 150, 25);
- painter->drawText(rectangle2, 0, tr("Game Level: %1").arg(game_level), &boundingrect);
- const QRect rectangle3=QRect(300, 2, 150, 25);
- painter->drawText(rectangle3, 0, tr("game score: %1").arg(game_score), &boundingrect);
- painter->save();
- painter->rotate(270);
- painter->translate(-40, 10);
- QPoint FoodPoint(-food_x*8,food_y*8);
- QRadialGradient RadialGradient(-food_x*8, food_y*8, 6, -food_x*8, food_y*8);
- RadialGradient.setColorAt(0, Qt::green);
- RadialGradient.setColorAt(1.0, Qt::darkGreen);
- painter->setPen(Qt::NoPen);
- painter->setBrush(RadialGradient);
- painter->drawEllipse(FoodPoint, 6, 6);
- QPoint SnakeHeadPoint(-8*((*body_x)[0]), 8*((*body_y)[0]));
- QConicalGradient ConicalGradient(-8*((*body_x)[0]), 8*((*body_y)[0]), 270);
- ConicalGradient.setColorAt(0,Qt::red);
- ConicalGradient.setColorAt(1,Qt::darkRed);
- painter->setPen(ThinPen);
- painter->setBrush(ConicalGradient);
- painter->drawEllipse(SnakeHeadPoint, 5, 5);
- QColor LightSalmon(255,160,122);
- painter->setPen(ThinPen);
- painter->setBrush(LightSalmon);
- for(int i=1;i<body_x->size();i++){
- painter->drawRect(-8*((*body_x)[i])-4, 8*((*body_y)[i])-4,8,8);
- }
- painter->restore();
- }
对于蛇的移动和控制,通过重载keyPressEvent函数来实现。
- void ImageShow::keyPressEvent(QKeyEvent *KeyEvent){
- switch(KeyEvent->key()){
- case Qt::Key_Left: {direction = 1;break;}
- case Qt::Key_Up:{direction=2;break;}
- case Qt::Key_Right:{direction=3;break;}
- case Qt::Key_Down:{direction=4;break;}
- default:break;
- }
- QWidget::keyPressEvent(KeyEvent);
- }
保存游戏结果,并且将原来游戏历史记录根据得分高低排序。
- void ImageShow::saveRank(){
- archive archive_now;
- archive archive_history[10];
- int archive_num=0;
- string PlayerString=player.toStdString();
- strcpy(archive_now.player_temp, PlayerString.c_str());
- archive_now.game_leve_temp=game_level;
- archive_now.game_score_temp=game_score;
- QFile File("H:/soft programme/QTMySnaker/snake/Rank.txt");
- if(!File.open(QIODevice::ReadWrite))
- return;
- QTextStream InOut(&File);
- while(!File.atEnd()){
- QString player_string;
- InOut>>player_string>>archive_history[archive_num].game_leve_temp
- >>archive_history[archive_num].game_score_temp;
- string PlayerString=player_string.toStdString();
- strcpy(archive_history[archive_num].player_temp, PlayerString.c_str());
- archive_num++;
- }
- for(int i=0;i<=archive_num;i++){
- if(archive_now.game_score_temp>archive_history[i].game_score_temp){
- for(int j=archive_num;j>=i+1;j--){
- archive_history[j]=archive_history[j-1];
- }
- archive_history[i]=archive_now;
- break;
- }
- else if(i==archive_num){
- archive_history[archive_num]=archive_now;
- }
- }
- for(int i=0;i<=archive_num;i++){
- InOut<<archive_history[i].player_temp<<'\t'
- <<archive_history[i].game_leve_temp<<'\t'
- <<archive_history[i].game_score_temp<<'\n';
- }
- File.close();
- }
接下来就是如何让这个界面能够在开始界面点击开始后可以执行,通过利用一个QEventLoop,可以将执行锁定住,然后为了退出循环,则可以通过关闭事件来调用其退出。
- void ImageShow::exec(){
- this->show();
- QEventLoop loop;
- m_loop=&loop;
- loop.exec();
- }
- void ImageShow::closeEvent(QCloseEvent *event){
- this->hide();
- m_loop->exit();
- event->accept();
- }