【C++/Qt】童年益智小游戏-点灯

        今天我们尝试用Qt + C++实现以前玩过的一个小游戏,规则很简单,在一个圈上均匀分布着八盏灯,点击其中一盏就会同时切换它与它对面两盏灯(即它正对面灯的左右相邻两盏)的状态,将所有灯点亮即为游戏胜利。使用VS2022 + Qt5.15.2开发。

     

        本文不光是为了过一遍开发流程,让读者(也让笔者)熟悉一下Qt VS开发的一些流程操作,更重要的是笔者想表达整个开发过程中一定要模拟用户(玩家)视角来设计软件的流程,因为操作界面讲究的就是用户的交互性,如果只站在开发者便利的角度设计软件,最终的成品会出现很多反直觉的操作方式和逻辑。

        下面开始我们的开发过程。

        首先创建Qt工程,使用designer设计界面,注意善用布局,这样既能保证界面窗口缩放不影响控件显示,还能适应不同分辨率的屏幕。我们设计如下的界面,尽量使用栅格布局,以及水平垂直布局加弹簧来调整间隔。

 

        使用layoutStretch参数来调整某布局里面的子布局之间所占的空间比例,默认0,0就是一比一。

        注意对需要的控件或窗口进行尺寸(或最小尺寸)的固定,以保证其维持完整的形态,比如我们最关键的放灯和操作按钮的widget,我们固定尺寸并使用打破布局,来自由移动调整其中控件的位置,使其看起来位于一个圆上。

        接着是载入资源文件的部分,我们先收集好需要的素材,这里共一张gif(作为logo),以及表示灯开关两种状态的两个图标,logo我们直接在网上找合适的gif使用,灯的图标这里选择用电脑画图自己画了两个,大家可以自行决定。我们把所有的资源放在同一个目录下,在VS解决方案资源管理器中找到资源文件.qrc打开,向其中add我们全部的资源。

         我们可以选择在designer中直接为控件加载资源,但这只能影响软件开启时的外观,因为游戏进行中我们的代码会实时更新每个控件显示的图片(logo除外),我们可以在designer中为每个控件加载资源如下

 

         要注意gif图像在designer中以pixmap形式加载的话并不能动起来,所以我们需要在代码的初始化部分使用QMovie加载让它动起来,详见代码部分。

         至此界面基本完成。

         下面开始代码部分,首先捋一遍逻辑:我们的思路是初始将所有八个操作按钮禁用,按下start按钮后游戏开始,此时随机点亮八个灯中的若干盏,并解禁所有的按钮,初始化倒计时的定时器,初始化步数,接下来开始玩家的操作时间。

        玩家可在八个按钮中一次选择一个点击,点击后会同时切换本身所在与对面两盏共三盏灯的亮灭,并使步数+1,要注意需要一个state数组记录所有灯的状态,每次点击后需要判断是否满足胜利条件。

    倒计时用一个定时器实现,归零时判定为游戏失败,游戏失败和胜利都需要弹出提示,并重新禁用所有操作按钮,知道玩家下一次点击start开始一局游戏。

        下面展示全部头文件(LightGame.h)内容,我们所需的变量及函数的声明都在其中。

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_LightGame.h"
#include <vector>
#include <qmovie.h>
#include <qtimer.h>
#include <time.h>
#include <qmessagebox.h>
using namespace std;
class LightGame : public QMainWindow
{
    Q_OBJECT

public:
    LightGame(QWidget *parent = Q_NULLPTR);

private:
    Ui::LightGameClass ui;
    QTimer* timer_countDown;
    int g_count = 0;//记录倒计时
    int g_steps = 0;//记录步数

    vector<QPushButton*> buttons;//按钮组,总共八个按钮,放到数组里方便循环操作,减少冗余代码量
    vector<QLabel*> labels;//图标组,同上
    vector<int> state;//记录状态判定游戏胜利
    QImage* on;//加载亮的灯资源
    QImage* off;//加载灭的灯资源

    void rev(int n0, int n1, int n2);//每次点击按钮后,调用该函数对点击处的灯及对面两个灯进行反转
    bool checkWin();//每次点击按钮后,调用该函数检查是否满足胜利条件
    void showWin();//胜利后调用该函数,提示用户胜利并禁用操作按钮,直到点击start后开始一局新的游戏
    void showLost();//超时失败后调用该函数,同上
    void lightOP(int index, bool turnOn);//具体对一盏灯的操作,第一个参数为灯的序号,第二个参数为亮或者灭
private slots:
    void countDown();//计时器槽函数
    //按钮槽函数,新版qt支持将函数名定义为如下的格式,省去connect操作
    void on_button_start_clicked();
    void on_pushButton_1_clicked();
    void on_pushButton_2_clicked();
    void on_pushButton_3_clicked();
    void on_pushButton_4_clicked();
    void on_pushButton_5_clicked();
    void on_pushButton_6_clicked();
    void on_pushButton_7_clicked();
    void on_pushButton_8_clicked();
};

        下面是源文件部分,初始化加载资源部分的代码如下:

    //加载logo    
    QMovie* logo = new QMovie(QString::fromUtf8(":/LightGame/x64/Debug/qrc/fire.gif"));
    ui.label_logo->setMovie(logo);
    logo->start();
    ui.label_logo->show();
    //加载图标
    on = new QImage(QString::fromUtf8(":/LightGame/x64/Debug/qrc/on.jpg"));
    off = new QImage(QString::fromUtf8(":/LightGame/x64/Debug/qrc/off.jpg"));

         当按下start按钮开始游戏后,我们需要做的事包括:重置倒计时和步数,解禁按钮,随机点亮几个灯。其中随机点亮灯我们要使用随机数实现。

    //随机点灯的思路是生成一个二进制八位随机数,其每一位对应一盏灯
    srand(time(nullptr));
    int randoxNumber = rand() % 256;
    for (int i = 0; i < 8; ++i) {
        if ((randoxNumber >> i) & 1) {
            lightOP(i, true);
            state[i] = 1;
        }
        else {
            lightOP(i, false);//注意不点亮的灯也要初始化
            state[i] = 0;
        }
    }

下面直接放出源文件(LightGame.cpp)全部内容,相关函数的作用及调用逻辑在头文件的注释中已说清楚。

#include "LightGame.h"

#define COUNTDOWN 60
//using namespace std;
LightGame::LightGame(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    //加载logo
    QMovie* logo = new QMovie(QString::fromUtf8(":/LightGame/x64/Debug/qrc/fire.gif"));
    ui.label_logo->setMovie(logo);
    logo->start();
    ui.label_logo->show();
    //加载图标
    on = new QImage(QString::fromUtf8(":/LightGame/x64/Debug/qrc/on.jpg"));
    off = new QImage(QString::fromUtf8(":/LightGame/x64/Debug/qrc/off.jpg"));
    timer_countDown = new QTimer();
    connect(timer_countDown, SIGNAL(timeout()), this, SLOT(countDown()));
    g_count = COUNTDOWN;
    g_steps = 0;
    buttons = { ui.pushButton_1, ui.pushButton_2, ui.pushButton_3, ui.pushButton_4, ui.pushButton_5, ui.pushButton_6, ui.pushButton_7, ui.pushButton_8 };
    labels = { ui.label_1, ui.label_2, ui.label_3, ui.label_4, ui.label_5, ui.label_6, ui.label_7, ui.label_8 };
    state = { 0, 0, 0, 0, 0, 0, 0, 0 };
}
//定时器timeout槽函数,实现倒计时
void LightGame::countDown() {
    g_count--;
    if (g_count == 0) {
        timer_countDown->stop();
        showLost();//超时结束
    }
    ui.label_time->setText(QStringLiteral("剩余") + QString::number(g_count) + QStringLiteral("秒"));
}
//按下开始按钮,要做的事包括:重置倒计时和步数,解禁按钮,随机点亮几个灯
void LightGame::on_button_start_clicked() {
    g_count = COUNTDOWN;
    timer_countDown->start(1000);
    g_steps = 0;
    ui.label_steps->setText(QStringLiteral("已用") + QString::number(g_steps) + QStringLiteral("步"));

    for (int i = 0; i < 8; ++i) {
        buttons[i]->setEnabled(true);
    }
    //随机点灯的思路是生成一个二进制八位随机数,其每一位对应一盏灯
    srand(time(nullptr));
    int randoxNumber = rand() % 256;
    for (int i = 0; i < 8; ++i) {
        if ((randoxNumber >> i) & 1) {
            lightOP(i, true);
            state[i] = 1;
        }
        else {
            lightOP(i, false);//注意不点亮的灯也要初始化
            state[i] = 0;
        }
    }
}
void LightGame::rev(int n0, int n1, int n2) {
    ui.label_steps->setText(QStringLiteral("已用") + QString::number(++g_steps) + QStringLiteral("步"));
    state[n0] = state[n0] ^ 1;
    lightOP(n0, state[n0]);
    state[n1] = state[n1] ^ 1;
    lightOP(n1, state[n1]);
    state[n2] = state[n2] ^ 1;
    lightOP(n2, state[n2]);
}
bool LightGame::checkWin() {
    bool res = true;
    for (int i = 0; i < 8; ++i) {
        if (!state[i]) res = false;
    }
    return res;
}
void LightGame::showWin() {
    for (int i = 0; i < 8; ++i) {

        buttons[i]->setEnabled(false);
    }
    timer_countDown->stop();
    //使用QStringLiteral代替QString以保证中文显示不是乱码
    QString dlgTitle = QStringLiteral("Win!");
    //QTextCodec::setCodecForCStrings(QTextCodec::codecForName(“GB2312”));
    QString strInfo = QStringLiteral("游戏获胜,用时") + QString::number(60 - g_count) + QStringLiteral("秒,花费") + QString::number(g_steps) + QStringLiteral("步");
    QMessageBox::about(this, dlgTitle, strInfo);
}
void LightGame::showLost() {
    for (int i = 1; i < 9; ++i) {
        buttons[i]->setEnabled(false);
    }
    QString dlgTitle = "Lost!";
    QString strInfo = QStringLiteral("游戏失败,时间耗尽!");
    QMessageBox::about(this, dlgTitle, strInfo);
}
void LightGame::lightOP(int index, bool turnOn) {
    if (turnOn) labels[index]->setPixmap(QPixmap::fromImage(*on));
    else labels[index]->setPixmap(QPixmap::fromImage(*off));
    labels[index]->setScaledContents(true);
}
//pushButtons
void LightGame::on_pushButton_1_clicked() {
    rev(0, 3, 5);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_2_clicked() {
    rev(1, 4, 6);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_3_clicked() {
    rev(2, 5, 7);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_4_clicked() {
    rev(3, 6, 0);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_5_clicked() {
    rev(4, 7, 1);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_6_clicked() {
    rev(5, 0, 2);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_7_clicked() {
    rev(6, 1, 3);
    if (checkWin()) showWin();
}
void LightGame::on_pushButton_8_clicked() {
    rev(7, 2, 4);
    if (checkWin()) showWin();
}

        本文主要内容基本结束,后续我们会尝试对软件进行打包以及生成安装包操作。

        用到的资源文件的ui文件链接如下:提取码:0ruj

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Command line: -prefix /home/liuyh/workspace/qt5.14.2-arm -opensource -confirm-license -release -strip -shared -xplatform linux-arm-gnueabi-g++ -optimized-qmake -c++std c++11 --rpath=no -pch -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d -skip qtconnectivity -skip qtdatavis3d -skip qtdoc -skip qtgamepad -skip qtlocation -skip qtmacextras -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors -skip qtspeech -skip qtsvg -skip qttools -skip qttranslations -skip qtwayland -skip qtwebengine -skip qtwebview -skip qtwinextras -skip qtx11extras -skip qtxmlpatterns -make libs -make examples -nomake tools -nomake tests -gui -widgets -dbus-runtime --glib=no --iconv=no --pcre=qt --zlib=qt -no-openssl --freetype=qt --harfbuzz=qt -no-opengl -linuxfb --xcb=no -tslib --libpng=qt --libjpeg=qt --sqlite=qt -plugin-sql-sqlite -I/opt/tslib/include -L/opt/tslib/lib -recheck-all executing config test machineTuple + arm-linux-gnueabi-g++ -dumpmachine > sh: 1: arm-linux-gnueabi-g++: not found test config.qtbase.tests.machineTuple FAILED executing config test verifyspec + cd /home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/config.tests/verifyspec && /home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/qtbase/bin/qmake "CONFIG -= qt debug_and_release app_bundle lib_bundle" "CONFIG += shared warn_off console single_arch" 'QMAKE_LIBDIR += /opt/tslib/lib' 'INCLUDEPATH += /opt/tslib/include' -early "CONFIG += cross_compile" /home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/qtbase/config.tests/verifyspec + cd /home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/config.tests/verifyspec && MAKEFLAGS= /usr/bin/make clean && MAKEFLAGS= /usr/bin/make > rm -f verifyspec.o > rm -f *~ core *.core > arm-linux-gnueabi-g++ -c -O2 -march=armv7-a -mtune=cortex-a7 -mfpu=neon -mfloat-abi=hard -O2 -march=armv7-a -mtune=cortex-a7 -mfpu=neon -mfloat-abi=hard -pipe -O2 -w -fPIC -I/home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/qtbase/config.tests/verifyspec -I. -I/opt/tslib/include -I/home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/qtbase/mkspecs/linux-arm-gnueabi-g++ -o verifyspec.o /home/liuyh/workspace/QT5.14.2/qt-everywhere-src-5.14.2/qtbase/config.tests/verifyspec/verifyspec.cpp > make:arm-linux-gnueabi-g++:命令未找到 > make: *** [Makefile:172:verifyspec.o] 错误 127
06-09

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值