逆向软件工程

1.扫雷开源项目
来源:https://github.com/xiaoxi666/mines_sweeper

2.运行环境:Visual Studio 2022
下面是几个主要的代码
每个格子元素:

点击查看代码
#include "cellitem.h"
//#include <qDebug>
#include <fielddata.h>

int cellItem::_refCount = 0;

QPixmap * cellItem::_initial    =   nullptr;
QPixmap * cellItem::_flag       =   nullptr;
QPixmap * cellItem::_question   =   nullptr;
QPixmap * cellItem::_blank      =   nullptr;
QPixmap * cellItem::_explode    =   nullptr;
QPixmap * cellItem::_digits[9]  =   {nullptr};

cellItem::cellItem(int x, int y,
                   enum cellStatus status, QGraphicsItem *parent)
    :
      QGraphicsPixmapItem(parent) {
    cx = x;
    cy = y;
    _status = status;

    ++_refCount;//创建第一个cellItem时加载所有图片

    if(_refCount == 1) {//引用计数,保证只new一次
        _initial    =   new QPixmap("://images/initial.png");
        _flag       =   new QPixmap("://images/flag.png");
        _question   =   new QPixmap("://images/question.png");
        _blank      =   new QPixmap("://images/blank.png");
        _explode    =   new QPixmap("://images/explode.png");

        QString prefix = "://images/";
        QString num;
        QString suffix = ".png";

        for(int i=1;i<9;++i){
            QString fileName;
            fileName+=prefix;
            fileName+=num.setNum(i);
            fileName+=suffix;
            _digits[i] = new QPixmap(fileName);
        }
    }
    setPixmap(*_initial);
    //qDebug()<<this<<endl;
}

cellItem::~cellItem(){
    //释放所有cellItem后,释放图片
    if(--_refCount == 0){
        delete _initial   ;
        delete _flag      ;
        delete _question  ;
        delete _blank     ;
        delete _explode   ;
        for(int i=0;i<9;++i){
            delete _digits[i];
        }
    }
}

enum cellStatus cellItem::getStatus(){
    return _status;
}

void cellItem::setStatus(enum cellStatus status,int digit){
    _status=status;
    switch(_status){
    case INITIAL:
        setPixmap(*_initial);
        break;
    case FLAG:
        setPixmap(*_flag);
        break;
    case QUESTION:
        setPixmap(*_question);
        break;
    case EXPLODE:
        setPixmap(*_explode);
        break;
    case DIGIT:
        setPixmap(*_digits[digit]);
        break;
    case BLANK:
        setPixmap(*_blank);
        break;
    default:
        break;
    }
}
游戏配置操作:
点击查看代码
#include "configdialog.h"
#include "ui_configdialog.h"
#include <QIntValidator>
//#include <qDebug>

configDialog::configDialog(int cw, int ch, int cm, QWidget *parent) :

    QDialog(parent),

    _width(cw),
    _height(ch),
    _mines(cm),

    ui(new Ui::configDialog)
{
    ui->setupUi(this);

    QIntValidator * vaildW = new QIntValidator(1, 30, this);//宽度限制
    QIntValidator * vaildH = new QIntValidator(1, 19, this);//高度限制
    QIntValidator * vaildM = new QIntValidator(0, 570, this);//雷数初步限制(后面还有个数检查)

    ui->lineEditWidth->setValidator(vaildW);
    ui->lineEditHeight->setValidator(vaildH);
    ui->lineEditMines->setValidator(vaildM);

    ui->lineEditWidth->setText(QString::number(_width));
    ui->lineEditHeight->setText(QString::number(_height));
    ui->lineEditMines->setText(QString::number(_mines));

    connect(ui->buttonBox,SIGNAL(accepted()),this,SLOT(accept()));
}

//自定义accept槽(本来要调用父类的槽,现在调用自己的,因此稍后需要显示调用父类槽)
void configDialog::accept(){

    _width=ui->lineEditWidth->text().toInt();
    _height=ui->lineEditHeight->text().toInt();
    _mines=ui->lineEditMines->text().toInt();

    QDialog::accept();//自己的实现以后,再显示调用父类的accept槽
}

configDialog::~configDialog() {
    delete ui;
}

底层数据:
点击查看代码
#include "fielddata.h"
#include <stdlib.h>
#include <ctime>
//#include <iostream>
//#include <qDebug>
//using namespace std;

FieldData *FieldData::_ins=nullptr;

FieldData::FieldData()
    : _width(20), _height(15), _mines(50)//初始扫雷矩阵大小
{
    reset();
    srand(time(nullptr));
}

FieldData::~FieldData() {

}

void FieldData::reset(){
    _cells.clear();
    initCells();
    deployMines();
    _visited.clear();
    initVisited();
}

//初始化,申请空间
void FieldData::initCells() {
    for(int x = 0; x < _width; ++x) {
        _cells.push_back(CellColumn(_height));
    }
}

//初始化visited
void FieldData::initVisited() {
    for(int x = 0; x < _width; ++x) {
        _visited.push_back(CellColumn(_height));
    }
    for(int x = 0; x < _width; ++x) {
        for(int y = 0; y < _height; ++y) {
            _visited[x][y] = 0;
        }
    }
}


//布雷
void FieldData::deployMines() {
    //初始化
    for(int x = 0; x < _width; ++x) {
        for(int y = 0; y < _height; ++y) {
            _cells[x][y] = 0;
        }
    }
    int cpmines = _mines;
    while(cpmines) {
        int x = rand() % _width;
        int y = rand() % _height;

        //只有之前没有布过雷的位置才可以布雷
        if(_cells[x][y] != -1) {
            --cpmines;
            _cells[x][y] = -1;

            //更新周围的雷数
            updateSurrounding(x - 1, y - 1); //左上
            updateSurrounding(x, y - 1); //上
            updateSurrounding(x + 1, y - 1); //右上
            updateSurrounding(x - 1, y); //左
            updateSurrounding(x + 1, y); //右
            updateSurrounding(x - 1, y + 1); //左下
            updateSurrounding(x, y + 1); //下
            updateSurrounding(x + 1, y + 1); //右下

        }
    }
    //dump();
}

//布雷之后,更新周围的雷数
void FieldData::updateSurrounding(int x, int y) {
    //越界判断
    if(x >= 0 && x < _width && y >= 0 && y < _height) {
        if(_cells[x][y] != -1) {
            ++_cells[x][y];
        }
    }
}

//void FieldData::dump(){
//    for(int y = 0; y < _height; ++y) {
//        for(int x = 0; x < _width; ++x) {
//            cout<<_cells[x][y]<<'\t';
//        }
//        cout<<endl;
//    }
//    //qDebug()<<"---------------------------------"<<endl;
//}

//每次点击后更新(参数:宽,高,雷)
void FieldData::customizeWHM(int cw, int ch, int cm) {
    _width=cw;
    _height=ch;
    _mines=cm;
}

FieldData * FieldData::getInstance(){
    if(_ins==nullptr){
        _ins=new FieldData;
    }
    return _ins;
}

3.主要问题:
(1)踩到雷即结束游戏,没有加入地雷连锁爆炸动画
(2)左右键同时按下功能未实现

4.第一个问题,要想实现地雷连锁爆炸动画,可以使用递归或 BFS 算法 模拟爆炸传播,逐步显示动画效果。

点击查看代码
// 显示当前战场
void printField() {
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            printf("%c ", field[i][j]);
        }
        printf("\n");
    }
    Sleep(300);  // 让动画慢一点
}

// 递归爆炸扩散
void explode(int x, int y) {
    if (x < 0 || x >= SIZE || y < 0 || y >= SIZE || field[x][y] != '*') return;

    field[x][y] = 'X';  // 爆炸标记
    printField();

    // 递归向四周扩散爆炸
    explode(x - 1, y);  // 上
    explode(x + 1, y);  // 下
    explode(x, y - 1);  // 左
    explode(x, y + 1);  // 右
}

第二个问题,具体实现思路很简单:用两个标记位分别标记左右键是否处于按下状态,两者同时为真的时候,进一步检查标记旗,符合要求就打开

点击查看代码

// 处理鼠标点击
void handleMouseEvent(SDL_Event *event) {
    int x = event->button.x / 30; // 计算鼠标点到的格子
    int y = event->button.y / 30;

    if (event->type == SDL_MOUSEBUTTONDOWN) {
        if (event->button.button == SDL_BUTTON_LEFT) leftPressed = true;
        if (event->button.button == SDL_BUTTON_RIGHT) rightPressed = true;

        // **左右键同时按下时触发功能**
        if (leftPressed && rightPressed) {
            if (opened[x][y] && countFlags(x, y) == board[x][y]) {
                openSurrounding(x, y);
            }
        }
    }

    if (event->type == SDL_MOUSEBUTTONUP) {
        if (event->button.button == SDL_BUTTON_LEFT) leftPressed = false;
        if (event->button.button == SDL_BUTTON_RIGHT) rightPressed = false;
    }
}
5.鼠标左右键同时按下的效果如下(那个2就是鼠标点击的地方)

而地雷连锁爆炸由于我没有爆炸动画的文件,所以暂时还不能完全实现。

6.总结:
第一个难点在于去哪里找到合适的项目。
刚开始我在博客园上浏览,但那些软件不是太大太复杂,就是最初级的XX管理系统。
所以后来我就去github上找了,终于找到了相对合适可以入手的项目。

拿到代码首先要理清它总体的一个结构,然后再去仔细观察它的各个代码。代码里的许多东西都还看不懂,所以得去网上查或者问AI

逆向软件工程就是在改进代码中学习。必须要搞明白代码是怎么运行的、代码还有哪些问题,才能对代码进行改进。而以后的软件开发则是自己对自己的逆向软件工程,改进自己的代码,让软件能实现更好的效果。软件开发就是不断的改进的过程

posted @ 2025-02-27 21:42  励佳航  阅读(17)  评论(0)    收藏  举报