The game starts with beginGame function. beginGame Initializes a new Minesweeper game with specified dimensions and
bomb count. Clears any previous game state, sets up the grid layout, initializes cell views, connects click handlers for
opening cells and flagging, and finally packs UI elements to be displayed.
In a minesweeper game, right click toggles the flag. First, we check that game is not finished (mReveal), then we
check if cell is open. Finally, we toggle the flag:
In response to user's actions, the game updates its internal state. To visualize the state, updateCellViewStyle
function is called. This method updates the style of a cell at coordinates (x, y) based on its state or any other
conditions (e.g., game over, win/lose states). It triggers an event to let the framework know that custom styles need to
be re-evaluated and applied accordingly.
In game style sheets, a custom selector tied to CellView is used to display various cell states:
//// Created by alex2772 on 1/4/21.//#include<AUI/View/AButton.h>#include"CellView.h"#include"MinesweeperWindow.h"#include"NewGameWindow.h"#include<AUI/ASS/ASS.h>usingnamespaceass;/// [CellSelector]template<FieldCellfieldCell>structCellSelector:IAssSubSelector{public:boolisPossiblyApplicable(AView*view)override{returndynamic_cast<CellView*>(view)!=nullptr;}boolisStateApplicable(AView*view)override{if(autoc=dynamic_cast<CellView*>(view)){return(c->fieldCell()&fieldCell)==fieldCell;}returnfalse;}voidsetupConnections(AView*view,const_<AAssHelper>&helper)override{IAssSubSelector::setupConnections(view,helper);view->customCssPropertyChanged.clearAllOutgoingConnectionsWith(helper.get());AObject::connect(view->customCssPropertyChanged,AUI_SLOT(helper)::onInvalidateStateAss);}};/// [CellSelector]/// [RevealSelector]structRevealSelector:IAssSubSelector{public:boolisPossiblyApplicable(AView*view)override{returndynamic_cast<MinesweeperWindow*>(view)!=nullptr;}boolisStateApplicable(AView*view)override{if(autoc=dynamic_cast<MinesweeperWindow*>(view)){returnc->isReveal();}returnfalse;}voidsetupConnections(AView*view,const_<AAssHelper>&helper)override{IAssSubSelector::setupConnections(view,helper);view->customCssPropertyChanged.clearAllOutgoingConnectionsWith(helper.get());AObject::connect(view->customCssPropertyChanged,AUI_SLOT(helper)::onInvalidateStateAss);}};/// [RevealSelector]structGlobalStyle{GlobalStyle(){AStylesheet::global().addRules({{t<CellView>(),FixedSize{26_dp},BackgroundSolid{0xdedede_rgb},Border{1_px,0xeaeaea_rgb},},{!RevealSelector{}>>t<CellView>::hover(),BackgroundSolid{0xfdfdfd_rgb},},/// [open]{CellSelector<FieldCell::OPEN>(),Border{1_px,0xffffff_rgb},BackgroundSolid{0xeeeeee_rgb},},{CellSelector<FieldCell::HAS_FLAG>(),BackgroundImage{":minesweeper/flag.svg"},},/// [open]// display mines for dead/// [reveal]{RevealSelector{}>>CellSelector<FieldCell::HAS_BOMB>(),BackgroundImage{":minesweeper/bomb.svg"},},{RevealSelector{}>>CellSelector<FieldCell::HAS_FLAG>(),BackgroundImage{":minesweeper/no_bomb_flag.svg"},},{RevealSelector{}>>CellSelector<FieldCell::HAS_FLAG|FieldCell::HAS_BOMB>(),BackgroundImage{":minesweeper/bomb_flag.svg"},},/// [reveal]{CellSelector<FieldCell::RED_BG>(),BackgroundSolid{0xff0000_rgb},Border{nullptr},},// misc{class_of(".frame"),Border{1_dp,0x444444_rgb},},{class_of(".frame")>t<AButton>(),Margin{4_dp}},{t<NewGameWindow>(),Padding{4_dp}},});}}s;
#include"NewGameWindow.h"#include"AUI/Layout/AGridLayout.h"#include"AUI/Layout/AHorizontalLayout.h"#include"AUI/Layout/AVerticalLayout.h"#include"AUI/View/AButton.h"#include"AUI/View/ALabel.h"#include"AUI/View/ANumberPicker.h"#include"AUI/Util/UIBuildingHelpers.h"intgWidth=10;intgHeight=10;intgMines=10;voidNewGameWindow::updateMinesMax(){mMines->setMax(mWidth->getValue()*mHeight->getValue()-25);}voidNewGameWindow::updateDifficultyLabel(){mMines->setMax(mWidth->getValue()*mHeight->getValue()*3/4);intdifficulty=mWidth->getValue()*mHeight->getValue()/glm::max(mMines->getValue(),int64_t(1));AStringtext="Difficulty: ";switch(difficulty){default:case0:case1:text+="very low";break;case2:case3:text+="high";break;case4:case5:text+="medium";break;case6:case7:case8:text+="low";break;}mDifficultyLabel->setText(text);}NewGameWindow::NewGameWindow(MinesweeperWindow*minesweeper):AWindow("New game",100,100,minesweeper),mMinesweeper(minesweeper){setWindowStyle(WindowStyle::MODAL);setLayout(std::make_unique<AVerticalLayout>());setContents(Vertical{_form({{"Cells by width:"_as,mWidth=_new<ANumberPicker>()AUI_LET{it->setMin(8);it->setMax(25);},},{"Cells by height:"_as,mHeight=_new<ANumberPicker>()AUI_LET{it->setMin(8);it->setMax(25);},},{"Mines count:"_as,mMines=_new<ANumberPicker>()AUI_LET{it->setMin(8);},},}),mDifficultyLabel=_new<ALabel>(),Horizontal{_new<ASpacerExpanding>(),_new<AButton>("Start game")AUI_LET{it->setDefault();connect(it->clicked,me::begin);},_new<AButton>("Cancel").connect(&AButton::clicked,me::close),},});mWidth->setValue(gWidth);mHeight->setValue(gHeight);updateMinesMax();mMines->setValue(gMines);updateDifficultyLabel();connect(mWidth->valueChanging,me::updateMinesMax);connect(mWidth->valueChanging,me::updateDifficultyLabel);connect(mHeight->valueChanging,me::updateMinesMax);connect(mHeight->valueChanging,me::updateDifficultyLabel);connect(mMines->valueChanging,me::updateDifficultyLabel);pack();}voidNewGameWindow::begin(){close();mMinesweeper->beginGame(gWidth=mWidth->getValue(),gHeight=mHeight->getValue(),gMines=mMines->getValue());}