QT Mini game development: Minesweeper

Source: Internet
Author: User

Go ahead and write a little QT game ~


Preview

Steps
The whole code structure, the game logic class and the game interface class separate, adopt the idea of MVC.
1 defining game data StructuresThe game map is actually a two-dimensional matrix consisting of a block of squares, where no block stores numbers, thunder, or tags, and also defines some of the game's running state enumerations.
----------------Global definition Variable----------------the state of the////block, not excavated, opened, tagged, mined, error tagged enum blockstate{    un_dig,    digged,    MARKED,    BOMB,    wrong_bomb};//block class struct mineblock{    blockstate curstate;//Current state    int valueflag;//value, 0 to 8,-1 for Ray};// Game state, divided into unfinished, there is error Mark, lose, win four kinds of enum gamestate{    PLAYING,    FAULT, over    ,    win};//game difficulty, have low level, intermediate, Advanced enum gamelevel{    BASIC,    MEDIUM,    hard};//game default parameter const int krow = 15;const int kcol = 20;const int kminecount = 50;const int ktime = 0;//----- ------------------------------------------ //

2 Creating a game logic class
A game logic class includes maps, game states, and some control functions
Class Gamemodel{public:    GameModel ();    Virtual ~gamemodel ();p ublic:    void digmine (int m, int n);//mine, M is row, n is column    void markmine (int m, int n);//Mark Lei    vo ID creategame (int row = krow, int col = kcol, int minecount = Kminecount, gamelevel level = MEDIUM); Initialize game    void Restartgame ();  Restart game by current parameter    void Checkgame ();//Check game win/lose public:    std::vector<std::vector<mineblock>> Gamemap; /game map    int mrow;//Map rows    int mCol;//Map column number    int totalminenumber;//Thunder number    int curminenumber;   Current number of mines (for display only)    int timerseconds;//Chronograph (seconds)    gamestate gamestate;//Current game status    gamelevel gamelevel;//current game difficulty};

3 game Logic
(1) Game initialization
void Gamemodel::creategame (int row, int col, int minecount, gamelevel level) {//First empty the existing game map gamemap.clear ();    Set member Variable Mrow = row;    MCol = col;    Totalminenumber = Minecount;    Curminenumber = Minecount;    GameState = PLAYING;    Gamelevel = level;    timerseconds = 0;        Initialize the Thunder box for (int i = 0; i < Mrow; i++) {//Add block std::vector<mineblock> lineblocks per line;            for (int j = 0; J < MCol; J + +) {Mineblock mineblock; Mineblock.curstate = Un_dig; The default is no mining Mineblock.valueflag = 0;        The default is 0 lineblocks.push_back (mineblock);    } gamemap.push_back (Lineblocks);    }//Random Bray Srand ((unsigned int) time (0));    int k = Totalminenumber;        while (k > 0) {//Bury the Thunder and prevent overlapping int pRow = rand ()% Mrow;        int pcol = rand ()% MCol;             if (gamemap[prow][pcol].valueflag! =-1) {gamemap[prow][pcol].valueflag =-1; k--;  If it turns out that Ray is recycled again,      }//Calculate the number of squares around the Thunder for (int i = 0; i < Mrow; i++) {for (int j = 0; J < MCol; J + +) { The number of surrounding eight blocks (excluding themselves, within the map range) is superimposed on the number of mines//Y for the line offset, x is the column offset//precondition is this block is not a ray if (gamemap[i][ J].valueflag! =-1) {for (int y =-1, y <= 1; y++) {for (in T x =-1; x <= 1;                        X + +) {if (i + Y >= 0 && i + y < Mrow && j + x >= 0 && j + x < MCol &AMP;&A mp Gamemap[i + y][j + X].valueflag = =-1 &&!                        (x = = 0 && y = = 0))                        {//block digit plus 1 gamemap[i][j].valueflag++; }                    }                }            }        }    }}
    • Random Bray
    • Calculate block numbers
(2) Digging Thunder
void GameModel::d igmine (int m, int n) {//normal block and not opened, marked as Dug if (Gamemap[m][n].valueflag > 0 && gamemap [M]    [n].curstate = = un_dig) {gamemap[m][n].curstate = digged;    }//encountered blank block (number 0) on the recursive mine, if the Thunder will explode, the game is over if (Gamemap[m][n].valueflag = = 0 && gamemap[m][n].curstate = = un_dig)        {gamemap[m][n].curstate = digged; for (int y =-1, y <= 1; y++) {for (int x =-1; x <= 1; + +) {if (M + Y & gt;= 0 && m + y < mrow && n + x >= 0 && n + x & Lt MCol &&!                (x = = 0 && y = = 0))                {digmine (M + y, n + x);        }}}}//Step on the Thunder if (Gamemap[m][n].valueflag = =-1) {gamestate = over;    Gamemap[m][n].curstate = BOMB; }//Check the game win and lose, and make adjustments checkgame ();}
    • Recursive mine-digging
    • Dig to the end of the Thunder game
(3) Mark block
void Gamemodel::markmine (int m, int n) {    //If the tag is wrong, mark it as an error mark, draw a distinction at the end of the UI layer game    //Note there is a logic that if a block is marked two times it will return to the untapped state    if (gamemap[m][n].curstate = = Un_dig)    {        if (Gamemap[m][n].valueflag = =-1)        {            gamemap[m][n].curstate = MARKED;        }        else        {            gamestate = FAULT;            Gamemap[m][n].curstate = Wrong_bomb;        }        curminenumber--; Digging right, Ray minus 1    }    else if (gamemap[m][n].curstate = = MARKED | | gamemap[m][n].curstate = = wrong_bomb)    {        Gamemap[m][n].curstate = Un_dig;        GameState = PLAYING;        curminenumber++; Thunder number plus back    }    //Check the game win and lose, and make adjustments    checkgame ();}
    • Mark Thunder, and the current display of the Thunder number follows the minus
    • Flag error will set the game state to fault
    • If you dig it out and dig the right game, you win.
(4) Check the game status
void Gamemodel::checkgame () {    //end of game, show All Thunder    if (gamestate = = over)    {        //lose to show all thunder as well as the wrong Ray for        (int i = 0; i < Mrow; i++)        {for            (int j = 0; J < MCol; J + +)            {                if (Gamemap[i][j].valueflag = =-1)                {                    gamemap[i][j]. Curstate = BOMB;}}        }        return;    }    If Ray is finished and all blocks are dug up or marked    if (gamestate! = FAULT)    {for        (int i = 0; i < Mrow; i++)        {for            (int j = 0; J < MCol; J + +)            {                if (gamemap[i][j].curstate = = Un_dig)                {                    gamestate = PLAYING;                    return        ;        }}} Otherwise there is no error marking the game state is not lost or in progress, the game is to win        gamestate = win;    }}
    • This function is called every time the mine is mined and the lightning is marked.
    • Update game status for each check
4 Game Interface class
The game interface is actually re-painted in the window, and set the mouse click to listen
Class Maingamewindow:public qmainwindow{    q_objectpublic:    explicit Maingamewindow (Qwidget *parent = 0);    ~maingamewindow ();p rotected:    virtual void paintevent (Qpaintevent *event);       Interface Redraw    virtual void mousepressevent (Qmouseevent *event);  Mouse control private:    Ui::maingamewindow *ui;    GameModel *game; Game    Qtimer *timer;     Timer    Qlabel *timelabel;//Chronograph number    void Handlegamestate (GameModel *game);//handling Game State private slots:    void Onstartgameclicked ();    Start Game    void onlevelchooseclicked ();  Choose the game difficulty    void onquitclicked ();         Exit game    void Updatetimer ();           Timing};

5 Game Interface Control(1) Set some elements at startup and initialize the game model
Maingamewindow::maingamewindow (Qwidget *parent): Qmainwindow (parent), UI (new Ui::maingamewindow) {UI-&GT;SETUPUI    (this);    Create a timing number label TimeLabel = new Qlabel (this);    Associated signal slot connect (Ui->actionstart, SIGNAL (triggered (bool)), this, slot (onstartgameclicked ()));    Connect (Ui->actionbasic, SIGNAL (triggered (bool)), this, SLOT (onlevelchooseclicked ()));    Connect (Ui->actionmedium, SIGNAL (triggered (bool)), this, SLOT (onlevelchooseclicked ()));    Connect (Ui->actionhard, SIGNAL (triggered (bool)), this, SLOT (onlevelchooseclicked ()));    Connect (Ui->actionquit, SIGNAL (triggered (bool)), this, SLOT (onquitclicked ()));    Timer = new Qtimer (this);    Connect (timer, SIGNAL (), this, SLOT (Updatetimer ()));    Create game initialization game, set parameters, default is intermediate, start Timer//define window size (must be placed after the game is created, the function is set after the size is not variable, the window forced redraw) game = new GameModel;    Game->creategame ();    Setfixedsize (Game->mcol * blockSize + OffsetX * 2, Game->mrow * blockSize + OffsetY * 2 + SpaceY); Timelabel->setgEometry (Game->mcol * blockSize + OffsetX * 2-80, SPACEY/2, 80, 20);    Timelabel->settext ("Time:" + qstring::number (game->timerseconds) + "s"); Timer->start (1000);}
This is linked to some of the button's signal slots, initialized the interface and game model, set a timer for game timing.
(2) window redraw
void Maingamewindow::p aintevent (qpaintevent *event) {qpainter painter (this);    Qpixmap bmpblocks (":/res/blocks.bmp");    Qpixmap bmpfaces (":/res/faces.bmp");    Qpixmap bmpframe (":/res/frame.bmp");    Qpixmap bmpnumber (":/res/timenumber.bmp"); Draw smiley Switch (game->gamestate) {case OVER:painter.drawPixmap (Game->mcol * blockSize + OffsetX * 2)/2-12, SPACEY/2, bmpfaces, 0 * 24, 0, 24, 24);    24 is the side length of the smiling face, the anchor point is on the left, because the toolbar occupies some, so it looks no longer the middle break; Case PLAYING:painter.drawPixmap ((Game->mcol * blockSize + OffsetX * 2)/2-12, SPACEY/2, bmpfaces, 1 * 24,        0, 24, 24);    Break Case WIN:painter.drawPixmap ((Game->mcol * blockSize + OffsetX * 2)/2-12, SPACEY/2, Bmpfaces, 2 * 24, 0, 2        4, 24);    Break Default:painter.drawPixmap ((Game->mcol * blockSize + OffsetX * 2)/2-12, SPACEY/2, bmpfaces, 1 * 24, 0, 24        , 24);    Break    }//Draw the remaining number of mines int n = game->curminenumber; int PosX = (game->MCol * blockSize + OffsetX * 2)/2-50; The last digit of the horizontal axis if (n <= 0)//If the number of mines is 0 or less than 0, draw separately {Painter.drawpixmap (PosX, SPACEY/2, Bmpnumber, n * 20, 0 , 20, 28); 20 is the width of the number, 28 is high} while (n > 0)//If it is a multiple-digit {Painter.drawpixmap (posX-20, SPACEY/2, Bmpnumber, n 10 * 20, 0, 20, 28);        Draw an n/= 10 from behind each time;    PosX-= 20;            }//Plot minefield for (int i = 0; i < game->mrow; i++) {for (int j = 0; J < game->mcol; J + +) { Switch (game->gamemap[i][j].curstate) {//based on different block states, calculate the offset in BMP case Un_di G:painter.drawpixmap (J * blockSize + OffsetX, I * blockSize + OffsetY + SpaceY, bmpblocks, BlockSize * 10                , 0, BlockSize, blockSize);            Break Case DIGGED:painter.drawPixmap (J * blockSize + OffsetX, I * blockSize + OffsetY + SpaceY, bmpblocks, block                Size * game->gamemap[i][j].valueflag, 0, BlockSize, blockSize); BreAk Case MARKED:painter.drawPixmap (J * blockSize + OffsetX, I * blockSize + OffsetY + SpaceY, bmpblocks, block                Size * One, 0, blockSize, blockSize);            Break Case BOMB:painter.drawPixmap (J * blockSize + OffsetX, I * blockSize + OffsetY + SpaceY, bmpblocks, Blocksi                Ze * 9, 0, BlockSize, blockSize);            Break                    Case Wrong_bomb:if (game->gamestate = = PLAYING | | game->gamestate = = FAULT) { If the flag is still displayed in the game Painter.drawpixmap (J * blockSize + OffsetX, I * blockSize + OffsetY + SpaceY                , Bmpblocks, BlockSize * One, 0, blockSize, blockSize);                    } else if (game->gamestate = = over) {//If the game is finished, the label is wrong Painter.drawpixmap (J * blockSize + OffsetX, I * blockSize + OffsetY + SpaceY, bmpblocks, blockSize * 0, BlockSize                , blockSize);                }Break            Default:break; }}}//Process game Status handlegamestate (game);}
According to the state of the game and the situation of the blocks in the game model, the situation of the timer, the specific redraw, to achieve the game interface update. The bitmap offset is used to draw the polygon.
(3) Mouse control
void Maingamewindow::mousepressevent (Qmouseevent *event) {if (Event->y () < SpaceY + OffsetY) {int x = e        Vent->x ();        int y = event->y (); At this point determine whether to click the smiley if (x >= (Game->mcol * blockSize + OffsetX * 2)/2-12 && x <= (Game->mco        L * blockSize + OffsetX * 2)/2 + && y >= spacey/2 && y <= spacey/2 + 24)            {game->restartgame ();//re-play Timer->start (1000); Timelabel->settext ("Time:" + qstring::number (game->timerseconds) + "s");        The timings are displayed as 0s update () for each replay; }} else if (game->gamestate! = over && game->gamestate! = WIN) {//The game did not lose or won before accepting click//This        When to determine which block is clicked//get click coordinates int px = event->x ()-OffsetX;        int py = event->y ()-Offsety-spacey;        Convert to Grid index int row = Py/blocksize;        int col = px/blocksize; Respond to switch (event-&) depending on the situationGt;button ()) {Case qt::leftbutton:game->digmine (row, col); Update ();        Redraw the break for each click;            Case Qt::rightbutton:game->markmine (Row, col);            Update ();        Break        Default:break; }    }}
Do a simple collision detection, left-click to mine, right-click Mark.
(4) Select difficulty
void maingamewindow::onlevelchooseclicked () {qaction *actionsender = (qaction *) dynamic_cast<qaction *> (Sender (    ));        if (Actionsender = = Ui->actionbasic) {qdebug () << "Basic";    First set up the game model Game->creategame (8, ten, BASIC);        } else if (Actionsender = = Ui->actionmedium) {qdebug () << "Medium";    Game->creategame (MEDIUM);        } else if (Actionsender = = Ui->actionhard) {qdebug () << "hard";    Game->creategame (a);    }//re-chronograph Timer->start (1000);    Refresh the UI again, the window resizing will force redraw timelabel->settext ("Time:" + qstring::number (game->timerseconds) + "s");    Timelabel->setgeometry (Game->mcol * blockSize + OffsetX * 2-80, SPACEY/2, 80, 20); Setfixedsize (Game->mcol * blockSize + OffsetX * 2, Game->mrow * blockSize + OffsetY * 2 + SpaceY);}
Choose the difficulty inside can according to signal slot in the signal type, set different difficulty, directly to the game set parameters, convenient and quick.
(5) Timing
void Maingamewindow::updatetimer () {    //timer timer    game->timerseconds++;    Timelabel->settext ("Time:" + qstring::number (game->timerseconds) + "s");    Qdebug () << game->timerseconds;}
When the game is over you can see the timekeeping score


SOURCE csdn: Minesweeper Github:minesweep


QT Mini game development: Minesweeper

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.