Search code examples
c++user-interfacewxwidgets

wxWidgets Function calling


I am currently writing a mine-sweeper program. There is a function which is connected to a menu. When users click on the menu of "Level", the program will show a submenu of several different levels (which means a different number of blocks). I have only defined "SetNovice" function now but found an issue that the mines won't be randomly distributed again as what constructor does(which is binding the buttons with "ButtonOnClicked" function to set the mines).

In SetNovice, I first call "RemoveChild()" function to remove btn on the GUIPanel, then delete the array "MineField", and then create btn and MineField again as what constructor does, nut the mines are just not there. The mines should be set when calling "ButtonOnClicked" function. As I wrote in SetNovice when creating btn, I did "Bind" btn to ButtonOnClicked, but no mines are set.

The program is supposed to randomly reset the mines when SetNovice is invoked, but it turns out not doing so. Why?

"MineField" is an array of integers in which "-1" is where mines are. But I'm not sure why in "SetNovice" function when I try to remove the original MineField to resize to a new one, the function to which buttons are bound to won't set the mines.

Below is my GUIWindow.h

#pragma once
#include "wx/wx.h"
#include "wx/mediactrl.h"
#include "wx/hyperlink.h"
#include <wx/thread.h>
#include "cuppMediaCtrl.h"


class GUIWindow : public wxFrame
{
public:
    GUIWindow();
    ~GUIWindow();


public:
    int Width = 20; // default
    int Height = 20; // default
    int score = 0;
    int count = 0;
    wxButton **btn; // an array of pointers
    int* MineField = nullptr; // a pointer, determining if mines exist
    bool ISFirstClick = true;




    wxPanel* GUIPanel = new wxPanel(this, wxID_ANY);
    wxGridSizer* grid;

    wxGridSizer* gridNovice;
    

    wxStatusBar* statusBar = CreateStatusBar(3);

    void ButtonOnClicked(wxCommandEvent &evt);
    void OnQuit(wxCommandEvent& event);
    void ChangeBtnColorRed(wxCommandEvent& evt);
    void ChangeBtnColorWhite(wxCommandEvent& evt);
    void ChangeBtnColorGreen(wxCommandEvent& evt);
    void ChangeBtnColorBlue(wxCommandEvent& evt);
    void ChangeBtnColorTokyoHot(wxCommandEvent& evt);

    void SetNovice(wxCommandEvent& evt);
    void SetIntermediate(wxCommandEvent& evt);
    void SetProfessional(wxCommandEvent& evt);
    void SetMaster(wxCommandEvent& evt);
    void SetGodLevel(wxCommandEvent& evt);



    void Website1(wxCommandEvent& evt);
    void Website2(wxCommandEvent& evt);
    void ShowCreator(wxCommandEvent& evt);

    DECLARE_EVENT_TABLE();

};

Below is GUIWindow.cpp (I've removed some unrelated code and fuctions)

#include "GUIWindow.h"

wxBEGIN_EVENT_TABLE(GUIWindow, wxFrame)
    EVT_BUTTON(wxID_ANY, ButtonOnClicked)
wxEND_EVENT_TABLE()


GUIWindow::GUIWindow() : wxFrame(nullptr, wxID_ANY, "地雷遊戲", wxPoint(30, 30), wxSize(700, 700))
{


    btn = new wxButton *[Width * Height]; // btn == an array of pointers (should allocate new memory)
    grid = new wxGridSizer(Width, Height, 0, 0);  // set a grid pointer to control the grid


    MineField = new int[Width * Height]; // This is where mines are distributed, using nField to indicate mines
    wxFont font(18, wxFONTFAMILY_ROMAN, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_BOLD, false);





    wxString str1 = "Current points: ";
    statusBar->SetStatusText(str1, 0);


    wxMenuBar* menuBar = new wxMenuBar();


    wxMenu* menu1 = new wxMenu();
    menuBar->Append(menu1, wxT("About"));


    wxMenu* menu2 = new wxMenu();
    menuBar->Append(menu2, wxT("Game"));


    SetMenuBar(menuBar);
    menu1->Append(10001, wxT("Creator"));


    wxMenu* imp2 = new wxMenu();
    imp2->Append(10011, wxT("Tokyo Hot"));
    imp2->Append(10012, wxT("White"));
    imp2->Append(10013, wxT("Green"));
    imp2->Append(10014, wxT("Red"));
    imp2->Append(10015, wxT("Blue"));
    menu2->AppendSubMenu(imp2, wxT("Theme"));

    

    wxMenu* imp3 = new wxMenu();
    imp3->Append(10020, wxT("Novice"));
    imp3->Append(wxID_ANY, wxT("Intermediate"));
    imp3->Append(wxID_ANY, wxT("Professional"));
    imp3->Append(wxID_ANY, wxT("Master"));
    imp3->Append(wxID_ANY, wxT("God-Level"));
    menu2->AppendSubMenu(imp3, wxT("Level"));

    wxMenu* imp4 = new wxMenu();
    imp4->Append(10002, "Introduction");
    imp4->Append(10003, "Explanation");
    menu1->AppendSubMenu(imp4, "Hints");


    wxMenuItem* quit = new wxMenuItem();
    quit = new wxMenuItem(menu1, wxID_EXIT, wxT("Quit\tCtrl+Q"));
    menu1->Append(quit);



    Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::OnQuit));
    Centre();

    Connect(10001, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::ShowCreator));
    Connect(10011, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::ChangeBtnColorTokyoHot));
    Connect(10012, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::ChangeBtnColorWhite));
    Connect(10013, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::ChangeBtnColorGreen));
    Connect(10014, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::ChangeBtnColorRed));
    Connect(10015, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::ChangeBtnColorBlue));
    Connect(10020, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::SetNovice));
    Connect(10002, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::Website1));
    Connect(10003, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GUIWindow::Website2));


    

    for (int x = 0; x < Width; x++)
    {
        for (int y = 0; y < Height; y++)
        {
            btn[y * Width + x] = new wxButton(GUIPanel, (y * Width + x)); // every pointer points to a button object with class wxButton
            btn[y * Width + x]->SetFont(font);
            
            grid->Add(btn[y * Width + x], 1, wxEXPAND, wxALL);
            btn[y * Width + x]->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &GUIWindow::ButtonOnClicked, this);
            MineField[y * Width + x] = 0; //set each block to 0 (default value)
        }
    }



    GUIPanel->SetSizer(grid); // "this" is pointing at the parent wxFrame: GUIWindow
    GUIPanel->Show();
}






GUIWindow::~GUIWindow()
{
    delete[] btn;
}

void GUIWindow::OnQuit(wxCommandEvent& WXUNUSED(event))
{
    Close(true);
}


void GUIWindow::SetNovice(wxCommandEvent& evt)
{

    Width = 10;
    Height = 10;
    score = 0;
    
    GUIPanel->DestroyChildren(); // delete btn, but NOT removing grid.
    delete[] MineField;
    gridNovice = new wxGridSizer(Width, Height, 0, 0);


    wxFont font(18, wxFONTFAMILY_ROMAN, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_BOLD, false);
    

    btn = new wxButton* [Width * Height];
    MineField = new int[Width * Height];



    for (int x = 0; x < Width; x++)
    {
        for (int y = 0; y < Height; y++)
        {
            btn[y * Width + x] = new wxButton(GUIPanel, (y * Width + x)); // every pointer points to a button object with class wxButton
            btn[y * Width + x]->SetFont(font);
            
            gridNovice->Add(btn[y * Width + x], 1, wxEXPAND, wxALL);
            btn[y * Width + x]->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &GUIWindow::ButtonOnClicked, this);
            MineField[y * Width + x] = 0; //set each block to 0 (default value)
        }
    }

    GUIPanel->SetSizer(gridNovice);
    GUIPanel->Show();
}



void GUIWindow::ButtonOnClicked(wxCommandEvent &evt)
{
    // get position of button
    int x = (evt.GetId()) % Width;
    int y = (evt.GetId()) / Width;
    int mine_count = 0;



    wxString s1 = "Believe in Yourself.";
    wxString s2 = "You are the PRIDE of NTUEE !";
    wxString s3 = "TAIWAN need you!!! so DO NOT give up!!!";
    wxString s4 = "We are all together, blessing you.";
    wxString s5 = "Who's the best minesweeper ? YOU!!!";
    wxString s6 = "Get bored? Try other functions in the menu!";
    wxString s7 = "想脫魯 ? 踩地雷吧 XDXDXD";
    wxString phraseArr[] = { s1, s2, s3, s4, s5, s6, s7};
    

    if (ISFirstClick == true)
    {
        int mine = Width * Height / 2;

        while (mine != 0)
        {
            int rx = rand() % Width;
            int ry = rand() % Height;

            if (MineField[ry * Width + rx] == 0 && rx != x && ry != y)
            {
                MineField[ry * Width + rx] = -1; // -1 indicates the mine
                mine--;
            }
        }

        ISFirstClick = false; // avoid mines been redistributed again
    }

    btn[y * Width + x]->Enable(false);

    if (MineField[y * Width + x] == -1)
    {
        wxMessageBox("BOOM!");
        statusBar->SetStatusText(wxT(""), 2); // reset the status bar
        this->count++;

        //reset mines
        this -> score = 0;
        ISFirstClick = true;
        for (int x = 0; x < Width; x++)
        {
            for (int y = 0; y < Height; y++)
            {
                MineField[y * Width + x] = 0;
                btn[y * Width + x]->SetLabel("");
                btn[y * Width + x]->Enable(true);

            }
        }
    }
    

    else
    {   
        if (this->count < 7)
        {
            wxString Newstr = phraseArr[this->count];
            statusBar->SetStatusText(Newstr, 2);
            this->count++;
        }
        else if (this->count >= 7) this->count = 0;
        

        this -> score += 1;
        wxString scoreStr = std::to_string(this->score);
        statusBar->SetStatusText(scoreStr, 1);

        //count the mines surrounding
        for (int i = -1; i < 2; i++)
        {
            for (int j = -1; j < 2; j++)
            {
                if (x + i >= 0 && x + i < Width && y + i >= 0 && y + j < Height)
                {
                    if (MineField[(y + j) * Width + (x + i)] == -1)
                    {
                        mine_count++;
                    }
                }
            }
        }

        if (mine_count >= 0)
        {
            btn[y * Width + x]->SetLabel(std::to_string(mine_count));
        }
    }

    evt.Skip();
}

Solution

  • Try adding

    GUIPanel->Layout();
    

    to the end of the GUIWindow::SetNovice method. That will cause all of the buttons that are added to the panel to be arranged by the grid sizer.


    Also the line

    btn[y * Width + x] = new wxButton(GUIPanel, (y * Width + x));
    

    could potentially be setting the buttons to have ids that are already assigned to other things. You should use something like:

    btn[y * Width + x] = new wxButton(GUIPanel, wxID_ANY);
    

    instead. If you need to keep track of the coordinates of the button for the ButtonOnClicked method, you could do something like

    1. add a map std::map<wxWindowID,std::pair<int,int>> m_buttonCoords; to the GUIWindow class.
    2. in GUIWindow::SetNovice clear the map at the start of the method and in the loop creating the buttons, call m_buttonCoords.insert( std::make_pair(btn[y * Width + x]->GetId(),std::make_pair(x,y)) );
    3. in GUIWindow::ButtonOnClicked extract the coordinates with a process like this:
    std::pair<int,int> coords = m_buttonCoords[evt.GetId()];
    int x = coords.first;
    int y = coords.second;
    

    There's probably better ways, but that's the first thing I thought of.