diff --git a/Array-Minesweeper/Array-Minesweeper.vcxproj b/Array-Minesweeper/Array-Minesweeper.vcxproj index 310df8e5..bec4d02e 100644 --- a/Array-Minesweeper/Array-Minesweeper.vcxproj +++ b/Array-Minesweeper/Array-Minesweeper.vcxproj @@ -133,6 +133,16 @@ + + + + + + + + + + @@ -153,10 +163,20 @@ + + + + + + + + + + diff --git a/Array-Minesweeper/SFML/include/SFML/Config.hpp b/Array-Minesweeper/SFML/include/SFML/Config.hpp index f4ea4ef3..3093a7a5 100644 --- a/Array-Minesweeper/SFML/include/SFML/Config.hpp +++ b/Array-Minesweeper/SFML/include/SFML/Config.hpp @@ -31,7 +31,7 @@ //////////////////////////////////////////////////////////// #define SFML_VERSION_MAJOR 2 #define SFML_VERSION_MINOR 6 -#define SFML_VERSION_PATCH 0 +#define SFML_VERSION_PATCH 1 //////////////////////////////////////////////////////////// diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s-d.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s-d.pdb index 9371818c..bbd97a4b 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s-d.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s-d.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s.pdb index 9371818c..bbd97a4b 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-audio-s.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-audio.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-audio.pdb index eec00db2..e0cd9357 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-audio.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-audio.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s-d.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s-d.pdb index c0bed6ba..d187d972 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s-d.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s-d.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s.pdb index c0bed6ba..d187d972 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-graphics-s.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-graphics.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-graphics.pdb index c516163a..310a084a 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-graphics.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-graphics.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-main-d.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-main-d.pdb index 3e17d4a2..432660dd 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-main-d.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-main-d.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-main-s.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-main-s.pdb index 3e17d4a2..432660dd 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-main-s.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-main-s.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-network-s-d.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-network-s-d.pdb index 7afce526..446f1fb8 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-network-s-d.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-network-s-d.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-network-s.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-network-s.pdb index 7afce526..446f1fb8 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-network-s.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-network-s.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-network.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-network.pdb index 93ec6553..fa073311 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-network.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-network.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-system-s-d.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-system-s-d.pdb index 2ba28522..aa448263 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-system-s-d.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-system-s-d.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-system-s.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-system-s.pdb index 2ba28522..aa448263 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-system-s.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-system-s.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-system.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-system.pdb index 59b5195a..ea487f4f 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-system.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-system.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-window-s-d.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-window-s-d.pdb index 4a11994b..98a4c966 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-window-s-d.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-window-s-d.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-window-s.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-window-s.pdb index 4a11994b..98a4c966 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-window-s.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-window-s.pdb differ diff --git a/Array-Minesweeper/SFML/lib/Debug/sfml-window.pdb b/Array-Minesweeper/SFML/lib/Debug/sfml-window.pdb index 332a83ca..1e2442bd 100644 Binary files a/Array-Minesweeper/SFML/lib/Debug/sfml-window.pdb and b/Array-Minesweeper/SFML/lib/Debug/sfml-window.pdb differ diff --git a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfig.cmake b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfig.cmake index f8b915b6..f28497b6 100644 --- a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfig.cmake +++ b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfig.cmake @@ -144,5 +144,5 @@ if (NOT SFML_FOUND) endif() if (SFML_FOUND AND NOT SFML_FIND_QUIETLY) - message(STATUS "Found SFML 2.6.0 in ${CMAKE_CURRENT_LIST_DIR}") + message(STATUS "Found SFML 2.6.1 in ${CMAKE_CURRENT_LIST_DIR}") endif() diff --git a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfigVersion.cmake b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfigVersion.cmake index b2208ca9..25db8760 100644 --- a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfigVersion.cmake +++ b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLConfigVersion.cmake @@ -9,19 +9,19 @@ # The variable CVF_VERSION must be set before calling configure_file(). -set(PACKAGE_VERSION "2.6.0") +set(PACKAGE_VERSION "2.6.1") if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() - if("2.6.0" MATCHES "^([0-9]+)\\.") + if("2.6.1" MATCHES "^([0-9]+)\\.") set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") endif() else() - set(CVF_VERSION_MAJOR "2.6.0") + set(CVF_VERSION_MAJOR "2.6.1") endif() if(PACKAGE_FIND_VERSION_RANGE) diff --git a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLSharedTargets.cmake b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLSharedTargets.cmake index 1c1c3c06..852e1a8d 100644 --- a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLSharedTargets.cmake +++ b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLSharedTargets.cmake @@ -7,7 +7,7 @@ if(CMAKE_VERSION VERSION_LESS "2.8.3") message(FATAL_ERROR "CMake >= 2.8.3 required") endif() cmake_policy(PUSH) -cmake_policy(VERSION 2.8.3...3.24) +cmake_policy(VERSION 2.8.3...3.25) #---------------------------------------------------------------- # Generated CMake target import file. #---------------------------------------------------------------- diff --git a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLStaticTargets.cmake b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLStaticTargets.cmake index 7257de65..419bb1b8 100644 --- a/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLStaticTargets.cmake +++ b/Array-Minesweeper/SFML/lib/cmake/SFML/SFMLStaticTargets.cmake @@ -7,7 +7,7 @@ if(CMAKE_VERSION VERSION_LESS "2.8.3") message(FATAL_ERROR "CMake >= 2.8.3 required") endif() cmake_policy(PUSH) -cmake_policy(VERSION 2.8.3...3.24) +cmake_policy(VERSION 2.8.3...3.25) #---------------------------------------------------------------- # Generated CMake target import file. #---------------------------------------------------------------- diff --git a/Array-Minesweeper/header/Event/EventService.h b/Array-Minesweeper/header/Event/EventService.h index 093932f5..9477ed8a 100644 --- a/Array-Minesweeper/header/Event/EventService.h +++ b/Array-Minesweeper/header/Event/EventService.h @@ -24,7 +24,7 @@ namespace Event bool gameWindowWasClosed(); bool hasQuitGame(); bool isKeyboardEvent(); - void updateButtonsState(ButtonState& button_state); + void updateButtonsState(ButtonState& button_state, sf::Mouse::Button button_type); public: EventService(); diff --git a/Array-Minesweeper/header/Gameplay/Board/BoardController.h b/Array-Minesweeper/header/Gameplay/Board/BoardController.h new file mode 100644 index 00000000..9d24ca25 --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/Board/BoardController.h @@ -0,0 +1,84 @@ +#pragma once +#include +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/UI/UIElement/ButtonView.h" +#include + +namespace Gameplay +{ + namespace Board + { + class BoardView; + + enum class BoardState + { + FIRST_CELL, // The state when the player opens first cell. + PLAYING, // The game is in progress. + COMPLETED, // The game is over. + }; + + class BoardController + { + public: + static const int number_of_rows = 9; + static const int number_of_colums = 9; + static const int mines_count = 8; + + BoardController(); + ~BoardController(); + + void initialize(); + void update(); + void render(); + + void processCellInput(Cell::CellController* cell_controller, UI::UIElement::ButtonType button_type); + void reset(); + + BoardState getBoardState(); + void setBoardState(BoardState state); + + int getMinesCount(); + + void flagAllMines(); + void openAllCells(); + void showBoard(); + + private: + BoardView* board_view; + Cell::CellController* board[number_of_rows][number_of_colums]; + + // To generate random values. + std::default_random_engine random_engine; + + // To give random seed to generator. + std::random_device random_device; + + BoardState board_state; + int flagged_cells; + + void createBoard(); + void initializeCells(); + + void populateBoard(sf::Vector2i cell_position); + void populateMines(sf::Vector2i cell_position); + void populateCells(); + int countMinesAround(sf::Vector2i cell_position); + + void flagCell(sf::Vector2i cell_position); + void openCell(sf::Vector2i cell_position); + bool areAllCellOpen(); + + void processCellType(sf::Vector2i cell_position); + void processEmptyCell(sf::Vector2i cell_position); + void processMineCell(sf::Vector2i cell_position); + + void openEmptyCells(sf::Vector2i cell_position); + bool isValidCellPosition(sf::Vector2i cell_position); + + + void resetBoard(); + void deleteBoard(); + void destroy(); + }; + } +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Gameplay/Board/BoardService.h b/Array-Minesweeper/header/Gameplay/Board/BoardService.h new file mode 100644 index 00000000..65ae995b --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/Board/BoardService.h @@ -0,0 +1,39 @@ +#pragma once +#include "../../header/Gameplay/Board/BoardController.h" +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/UI/UIElement/ButtonView.h" + + +namespace Gameplay +{ + enum class GameResult; + + namespace Board + { + class BoardService + { + private: + Board::BoardController* board_controller; + + void destroy(); + + public: + BoardService(); + ~BoardService(); + void initialize(); + void update(); + void render(); + + void processCellInput(Cell::CellController* cell_controller, UI::UIElement::ButtonType button_type); + + BoardState getBoardState(); + void setBoardState(BoardState state); + void resetBoard(); + + int getMinesCount(); + + void flagAllMines(); + void showBoard(); + }; + } +} diff --git a/Array-Minesweeper/header/Gameplay/Board/BoardView.h b/Array-Minesweeper/header/Gameplay/Board/BoardView.h new file mode 100644 index 00000000..2e453bc0 --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/Board/BoardView.h @@ -0,0 +1,41 @@ +#pragma once +#include "../../header/UI/UIElement/ImageView.h" + +namespace Gameplay +{ + namespace Board + { + class BoardController; + + class BoardView + { + private: + + const float board_width_offset = 115.f; + const float board_height_offset = 329.f; + + const float board_width = 866.f; + const float board_height = 1080.f; + + const float background_alpha = 85.f; + + BoardController* board_controller; + UI::UIElement::ImageView* board_image; + UI::UIElement::ImageView* background_image; + + void initializeBackgroudImage(); + void initializeBoardImage(); + + public: + BoardView(BoardController* controller); + ~BoardView(); + + void initialize(); + void update(); + void render(); + + float getCellWidth(); + float getCellHeight(); + }; + } +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Gameplay/Cell/CellController.h b/Array-Minesweeper/header/Gameplay/Cell/CellController.h new file mode 100644 index 00000000..5e965fc1 --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/Cell/CellController.h @@ -0,0 +1,45 @@ +#pragma once +#include + +namespace Gameplay +{ + namespace Cell + { + class CellView; + class CellModel; + enum class CellState; + enum class CellType; + + class CellController + { + private: + CellView* cell_view; + CellModel* cell_model; + + void destroy(); + + public: + CellController(sf::Vector2i grid_position); + ~CellController(); + + void initialize(float cell_width, float cell_height); + void update(); + void render(); + + void flagCell(); + void openCell(); + + bool canOpenCell(); + CellState getCellState(); + void setCellState(CellState state); + + CellType getCellType(); + void setCellType(CellType type); + + sf::Vector2i getCellPosition(); + int getMinesAround(); + + void reset(); + }; + } +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Gameplay/Cell/CellModel.h b/Array-Minesweeper/header/Gameplay/Cell/CellModel.h new file mode 100644 index 00000000..0b29cdda --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/Cell/CellModel.h @@ -0,0 +1,57 @@ +#pragma once +#include + +namespace Gameplay +{ + namespace Cell + { + enum class CellState + { + HIDDEN, + OPEN, + FLAGGED, + }; + + enum class CellType + { + EMPTY, + ONE, + TWO, + THREE, + FOUR, + FIVE, + SIX, + SEVEN, + EIGHT, + MINE, + }; + + class CellModel + { + private: + CellState cell_state; + CellType cell_type; + + sf::Vector2i position; + int mines_around; + + public: + CellModel(sf::Vector2i grid_position); + ~CellModel(); + + CellState getCellState(); + void setCellState(CellState state); + + CellType getCellType(); + void setCellType(CellType type); + + sf::Vector2i getCellPosition(); + void setCellPosition(sf::Vector2i grid_position); + + int getMinesAround(); + void setMinesAround(int mine_count); + + void reset(); + }; + } +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Gameplay/Cell/CellView.h b/Array-Minesweeper/header/Gameplay/Cell/CellView.h new file mode 100644 index 00000000..2ba19c49 --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/Cell/CellView.h @@ -0,0 +1,40 @@ +#pragma once +#include "../../header/UI/UIElement/ButtonView.h" + +namespace Gameplay +{ + namespace Cell + { + class CellController; + + class CellView + { + private: + const float cell_top_offset = 274.f; + const float cell_left_offset = 583.f; + + const float cell_texture_width = 384; + const float cell_texture_height = 32; + + const int tile_size = 32; + const int slice_count= 12; + + UI::UIElement::ButtonView* cell_button; + CellController* cell_controller; + + void initializeButtonImage(float width, float height); + sf::Vector2f getCellScreenPosition(float width, float height); + void setCellTexture(); + void registerButtonCallback(); + void cellButtonCallback(UI::UIElement::ButtonType button_type); + + public: + CellView(CellController* controller); + ~CellView(); + + void initialize(float width, float height); + void update(); + void render(); + }; + } +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Gameplay/GameplayController.h b/Array-Minesweeper/header/Gameplay/GameplayController.h new file mode 100644 index 00000000..5580d6b4 --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/GameplayController.h @@ -0,0 +1,49 @@ +#pragma once +#include "../../header/Gameplay/Board/BoardService.h" +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/UI/UIElement/ButtonView.h" +#include + +namespace Gameplay +{ + using namespace Gameplay::Board; + + enum class GameResult + { + NONE, + WON, + LOST + }; + + class GameplayController + { + private: + const float max_level_duration = 301.0f; + const float game_over_time = 11.f; + BoardService* board_service; + + float remaining_time; + GameResult game_result; + + void updateRemainingTime(); + bool isTimeOver(); + void showCredits();; + void beginGameOverTimer(); + + void gameWon(); + void gameLost(); + + public: + ~GameplayController(); + + void initialize(); + void update(); + void render(); + + void restart(); + void endGame(GameResult result); + + int getMinesCount(); + float getRemainingTime(); + }; +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Gameplay/GameplayService.h b/Array-Minesweeper/header/Gameplay/GameplayService.h new file mode 100644 index 00000000..0b5222af --- /dev/null +++ b/Array-Minesweeper/header/Gameplay/GameplayService.h @@ -0,0 +1,30 @@ +#pragma once +#include "../../header/Gameplay/GameplayController.h" +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/UI/UIElement/ButtonView.h" + +namespace Gameplay +{ + class GameplayService + { + private: + GameplayController* gameplay_controller; + + void createController(); + void destroy(); + + public: + GameplayService(); + ~GameplayService(); + + void initialize(); + void update(); + void render(); + + void startGame(); + void endGame(GameResult result); + + int getMinesCount(); + float getRemainingTime(); + }; +} \ No newline at end of file diff --git a/Array-Minesweeper/header/Global/ServiceLocator.h b/Array-Minesweeper/header/Global/ServiceLocator.h index a397af14..2eae95df 100644 --- a/Array-Minesweeper/header/Global/ServiceLocator.h +++ b/Array-Minesweeper/header/Global/ServiceLocator.h @@ -3,6 +3,9 @@ #include "../../header/Event/EventService.h" #include "../../header/UI/UIService.h" #include "../../header/Sound/SoundService.h" +#include "../../header/Gameplay/GameplayService.h" +#include "../../header/Gameplay/Board/BoardService.h" +#include "../../header/Time/TimeService.h" namespace Global { @@ -13,6 +16,9 @@ namespace Global Graphics::GraphicService* graphic_service; Sound::SoundService* sound_service; UI::UIService* ui_service; + Gameplay::GameplayService* gameplay_service; + Time::TimeService* time_service; + Gameplay::Board::BoardService* board_service; ServiceLocator(); ~ServiceLocator(); @@ -31,6 +37,9 @@ namespace Global Graphics::GraphicService* getGraphicService(); Sound::SoundService* getSoundService(); UI::UIService* getUIService(); + Gameplay::GameplayService* getGameplayService(); + Time::TimeService* getTimeService(); + Gameplay::Board::BoardService* getBoardService(); void deleteServiceLocator(); }; } \ No newline at end of file diff --git a/Array-Minesweeper/header/Main/GameService.h b/Array-Minesweeper/header/Main/GameService.h index 19d91570..74ef6216 100644 --- a/Array-Minesweeper/header/Main/GameService.h +++ b/Array-Minesweeper/header/Main/GameService.h @@ -9,8 +9,8 @@ namespace Main BOOT, SPLASH_SCREEN, MAIN_MENU, - GAMEPLAY, INSTRUCTIONS, + GAMEPLAY, CREDITS, }; diff --git a/Array-Minesweeper/header/Sound/SoundService.h b/Array-Minesweeper/header/Sound/SoundService.h index c8ec5d90..33261e07 100644 --- a/Array-Minesweeper/header/Sound/SoundService.h +++ b/Array-Minesweeper/header/Sound/SoundService.h @@ -6,6 +6,9 @@ namespace Sound enum class SoundType { BUTTON_CLICK, + FLAG, + EXPLOSION, + GAME_WON }; class SoundService @@ -16,6 +19,9 @@ namespace Sound sf::Music background_music; sf::Sound sound_effect; sf::SoundBuffer buffer_button_click; + sf::SoundBuffer buffer_flag_sound; + sf::SoundBuffer buffer_explosion; + sf::SoundBuffer buffer_game_won; void loadBackgroundMusicFromFile(); void loadSoundFromFile(); diff --git a/Array-Minesweeper/header/Time/TimeService.h b/Array-Minesweeper/header/Time/TimeService.h new file mode 100644 index 00000000..f201cb2f --- /dev/null +++ b/Array-Minesweeper/header/Time/TimeService.h @@ -0,0 +1,34 @@ +#pragma once +#include + +namespace Time +{ + /* + // The TimeService class helps keep track of time in game and calculate delta time. + // Utilizes the library to calculate delta time. + */ + class TimeService + { + private: + /* + // A time point that represents the previous moment in time using the steady clock. + // The steady clock is a clock that provides a monotonic and constant time source + // that is not subject to system clock adjustments, making it suitable for measuring + // time intervals precisely. + */ + std::chrono::time_point previous_time; + + float delta_time; + + void updateDeltaTime(); + float calculateDeltaTime(); + void updatePreviousTime(); + + public: + + void initialize(); + void update(); + + float getDeltaTime(); + }; +} diff --git a/Array-Minesweeper/header/UI/Gameplay/GameplayUIController.h b/Array-Minesweeper/header/UI/Gameplay/GameplayUIController.h new file mode 100644 index 00000000..13307e5a --- /dev/null +++ b/Array-Minesweeper/header/UI/Gameplay/GameplayUIController.h @@ -0,0 +1,59 @@ +#pragma once +#include "../../header/UI/Interface/IUIController.h" +#include "../../header/UI/UIElement/TextView.h" +#include "../../header/UI/UIElement/ButtonView.h" + +namespace UI +{ + namespace GameplayUI + { + class GameplayUIController : public Interface::IUIController + { + private: + const int font_size = 110; + + const float mine_text_top_offset = 65.f; + const float mine_text_left_offset = 660.f; + + const float time_text_top_offset = 65.f; + const float time_text_left_offset = 1090.f; + + const float restart_button_top_offset = 100.f; + const float restart_button_left_offset = 920.f; + + const float button_height = 80.f; + const float button_width = 80.f; + + const int tile_height = 32; + + const sf::Color text_color = sf::Color::Red; + + UIElement::TextView* mine_text; + UIElement::TextView* time_text; + UIElement::ButtonView* restart_button; + + void createButton(); + void createTexts(); + void initializeButton(); + void initializeTexts(); + void initializeMineText(); + void initializeTimeText(); + + void updateMineText(); + void updateTimeText(); + void restartButtonCallback(); + void registerButtonCallback(); + + void destroy(); + + public: + GameplayUIController(); + ~GameplayUIController(); + + void initialize() override; + void update() override; + void render() override; + void show() override; + }; + } +} \ No newline at end of file diff --git a/Array-Minesweeper/header/UI/MainMenu/MainMenuUIController.h b/Array-Minesweeper/header/UI/MainMenu/MainMenuUIController.h index dcff1cf5..e048dad0 100644 --- a/Array-Minesweeper/header/UI/MainMenu/MainMenuUIController.h +++ b/Array-Minesweeper/header/UI/MainMenu/MainMenuUIController.h @@ -15,9 +15,9 @@ namespace UI const float button_width = 400.f; const float button_height = 140.f; - const float play_button_y_position = 500.f; - const float instructions_button_y_position = 700.f; - const float quit_button_y_position = 900.f; + const float play_button_y_position = 400.f; + const float instructions_button_y_position = 600.f; + const float quit_button_y_position = 800.f; const float background_alpha = 85.f; diff --git a/Array-Minesweeper/header/UI/UIElement/ButtonView.h b/Array-Minesweeper/header/UI/UIElement/ButtonView.h index b89fc6c0..35683b75 100644 --- a/Array-Minesweeper/header/UI/UIElement/ButtonView.h +++ b/Array-Minesweeper/header/UI/UIElement/ButtonView.h @@ -6,11 +6,17 @@ namespace UI { namespace UIElement { + enum class ButtonType + { + LEFT_MOUSE_BUTTON, + RIGHT_MOUSE_BUTTON, + }; + class ButtonView : public ImageView { private: // Define a function pointer type for the callback function - using CallbackFunction = std::function; + using CallbackFunction = std::function; // Store the callback function CallbackFunction callback_function = nullptr; @@ -21,7 +27,8 @@ namespace UI sf::String button_title; virtual void handleButtonInteraction(); - virtual bool clickedButton(sf::Sprite* button_sprite, sf::Vector2f mouse_position); + virtual bool clickedLeftMouseButton(sf::Sprite* button_sprite, sf::Vector2f mouse_position); + virtual bool clickedRightMouseButton(sf::Sprite* button_sprite, sf::Vector2f mouse_position); public: ButtonView(); diff --git a/Array-Minesweeper/header/UI/UIElement/ImageView.h b/Array-Minesweeper/header/UI/UIElement/ImageView.h index aab42783..b9751a93 100644 --- a/Array-Minesweeper/header/UI/UIElement/ImageView.h +++ b/Array-Minesweeper/header/UI/UIElement/ImageView.h @@ -20,6 +20,7 @@ namespace UI virtual void render() override; virtual void setTexture(sf::String texture_path); + virtual void setTextureRect(sf::IntRect texture_rect); virtual void setScale(float width, float height); virtual void setPosition(sf::Vector2f position); virtual void setRotation(float rotation_angle); diff --git a/Array-Minesweeper/header/UI/UIElement/TextView.h b/Array-Minesweeper/header/UI/UIElement/TextView.h index 8a83a8e9..79261b9e 100644 --- a/Array-Minesweeper/header/UI/UIElement/TextView.h +++ b/Array-Minesweeper/header/UI/UIElement/TextView.h @@ -9,6 +9,7 @@ namespace UI { BUBBLE_BOBBLE, DS_DIGIB, + ROBOTO, }; class TextView : public UIView @@ -18,6 +19,7 @@ namespace UI static sf::Font font_bubble_bobble; static sf::Font font_DS_DIGIB; + static sf::Font font_roboto; sf::Text text; diff --git a/Array-Minesweeper/header/UI/UIService.h b/Array-Minesweeper/header/UI/UIService.h index 5bbd6347..372097ab 100644 --- a/Array-Minesweeper/header/UI/UIService.h +++ b/Array-Minesweeper/header/UI/UIService.h @@ -1,8 +1,9 @@ #pragma once #include "../../header/UI/MainMenu/MainMenuUIController.h" #include "../../header/UI/SplashScreen/SplashScreenUIController.h" -#include "../../header/UI/Credits/CreditsScreenUIController.h" #include "../../header/UI/Instructions/InstructionsScreenUIController.h" +#include "../../header/UI/Credits/CreditsScreenUIController.h" +#include "../../header/UI/Gameplay/GameplayUIController.h" #include "../../header/UI/Interface/IUIController.h" namespace UI @@ -10,10 +11,11 @@ namespace UI class UIService { private: - SplashScreen::SplashScreenUIController* splash_screen_controller; - MainMenu::MainMenuUIController* main_menu_controller; - Credits::CreditsScreenUIController* credit_screen_controller; - Instructions::InstructionsScreenUIController* instructions_screen_controller; + SplashScreen::SplashScreenUIController* splash_screen_ui_controller; + MainMenu::MainMenuUIController* main_menu_ui_controller; + Instructions::InstructionsScreenUIController* instructions_screen_ui_controller; + GameplayUI::GameplayUIController* gameplay_ui_controller; + Credits::CreditsScreenUIController* credit_screen_ui_controller; void createControllers(); void initializeControllers(); diff --git a/Array-Minesweeper/source/Event/EventService.cpp b/Array-Minesweeper/source/Event/EventService.cpp index 57e2d0c7..caedcfc4 100644 --- a/Array-Minesweeper/source/Event/EventService.cpp +++ b/Array-Minesweeper/source/Event/EventService.cpp @@ -19,8 +19,8 @@ namespace Event void EventService::update() { - updateButtonsState(left_mouse_button_state); - updateButtonsState(right_mouse_button_state); + updateButtonsState(left_mouse_button_state, sf::Mouse::Left); + updateButtonsState(right_mouse_button_state, sf::Mouse::Right); } void EventService::processEvents() @@ -36,9 +36,9 @@ namespace Event } } - void EventService::updateButtonsState(ButtonState& button_state) + void EventService::updateButtonsState(ButtonState& button_state, sf::Mouse::Button button_type) { - if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) + if (sf::Mouse::isButtonPressed(button_type)) { switch (button_state) { diff --git a/Array-Minesweeper/source/Gameplay/Board/BoardController.cpp b/Array-Minesweeper/source/Gameplay/Board/BoardController.cpp new file mode 100644 index 00000000..ce6ceed0 --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/Board/BoardController.cpp @@ -0,0 +1,361 @@ +#include "../../header/Gameplay/Board/BoardController.h" +#include "../../header/Gameplay/Board/BoardView.h" +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/Gameplay/Cell/CellModel.h" +#include "../../header/Global/ServiceLocator.h" +#include "../../header/Sound/SoundService.h" + +namespace Gameplay +{ + namespace Board + { + using namespace Cell; + using namespace Global; + using namespace Sound; + + BoardController::BoardController() : random_engine(random_device()) + { + board_view = new BoardView(this); + createBoard(); + } + + BoardController::~BoardController() + { + destroy(); + } + + void BoardController::createBoard() + { + for (int a = 0; a < number_of_rows; a++) + { + for (int b = 0; b < number_of_colums; b++) + { + board[a][b] = new CellController(sf::Vector2i(a, b)); + } + } + } + + void BoardController::initialize() + { + board_view->initialize(); + initializeCells(); + reset(); + } + + void BoardController::initializeCells() + { + float cell_width = board_view->getCellWidth(); + float cell_height = board_view->getCellHeight(); + + for (int a = 0; a < number_of_rows; a++) + { + for (int b = 0; b < number_of_colums; b++) + { + board[a][b]->initialize(cell_width, cell_height); + } + } + } + + void BoardController::update() + { + board_view->update(); + + for (int row = 0; row < number_of_rows; ++row) + { + for (int col = 0; col < number_of_colums; ++col) + { + board[row][col]->update(); + } + } + } + + void BoardController::render() + { + board_view->render(); + + for (int row = 0; row < number_of_rows; ++row) + { + for (int col = 0; col < number_of_colums; ++col) + { + board[row][col]->render(); + } + } + } + + void BoardController::processCellInput(Cell::CellController* cell_controller, UI::UIElement::ButtonType button_type) + { + if (board_state == BoardState::COMPLETED) + return; + + switch (button_type) + { + case UI::UIElement::ButtonType::LEFT_MOUSE_BUTTON: + openCell(cell_controller->getCellPosition()); + break; + case UI::UIElement::ButtonType::RIGHT_MOUSE_BUTTON: + flagCell(cell_controller->getCellPosition()); + break; + } + } + + void BoardController::populateBoard(sf::Vector2i cell_position) + { + populateMines(cell_position); + populateCells(); + } + + void BoardController::populateMines(sf::Vector2i cell_position) + { + // Co-ordinate distribution i.e. selecting random position for mines. + std::uniform_int_distribution x_distribution(0, number_of_colums - 1); + std::uniform_int_distribution y_distribution(0, number_of_rows - 1); + + // Generate mines. + for (int a = 0; a < mines_count; a++) + { + int i = static_cast(x_distribution(random_engine)); + int j = static_cast(y_distribution(random_engine)); + + // If the cell is already a mine or it's the same cell that the player wants to open, the loop will run an extra time + if (board[i][j]->getCellType() == CellType::MINE || (cell_position.x == i && cell_position.y == j)) a--; + else board[i][j]->setCellType(CellType::MINE); + } + } + + void BoardController::populateCells() + { + for (int a = 0; a < number_of_rows; a++) + { + for (int b = 0; b < number_of_colums; b++) + { + if (board[a][b]->getCellType() != CellType::MINE) + { + CellType type = static_cast(countMinesAround(sf::Vector2i(a, b))); + board[a][b]->setCellType(type); + } + } + } + } + + int BoardController::countMinesAround(sf::Vector2i cell_position) + { + int mines_around = 0; + + for (int a = -1; a < 2; a++) + { + for (int b = -1; b < 2; b++) + { + if ((a == 0 && b == 0) || !isValidCellPosition(sf::Vector2i(cell_position.x + a, cell_position.y + b))) continue; + if (board[a + cell_position.x][b + cell_position.y]->getCellType() == CellType::MINE) mines_around++; + } + } + + return mines_around; + } + + void BoardController::flagCell(sf::Vector2i cell_position) + { + switch (board[cell_position.x][cell_position.y]->getCellState()) + { + case::Gameplay::Cell::CellState::FLAGGED: + ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::FLAG); + flagged_cells--; + break; + case::Gameplay::Cell::CellState::HIDDEN: + ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::FLAG); + flagged_cells++; + break; + } + + board[cell_position.x][cell_position.y]->flagCell(); + } + + void BoardController::openCell(sf::Vector2i cell_position) + { + if (board[cell_position.x][cell_position.y]->canOpenCell()) + { + if (board_state == BoardState::FIRST_CELL) + { + populateBoard(cell_position); + board_state = BoardState::PLAYING; + } + + processCellType(cell_position); + board[cell_position.x][cell_position.y]->openCell(); + + if (areAllCellOpen()) + ServiceLocator::getInstance()->getGameplayService()->endGame(GameResult::WON); + } + } + + void BoardController::flagAllMines() + { + for (int row = 0; row < number_of_rows; ++row) + { + for (int col = 0; col < number_of_colums; ++col) + { + if (board[row][col]->getCellType() == CellType::MINE && board[row][col]->getCellState() != CellState::FLAGGED) + flagCell(sf::Vector2i(row, col)); + } + } + } + + void BoardController::processCellType(sf::Vector2i cell_position) + { + switch (board[cell_position.x][cell_position.y]->getCellType()) + { + case::Gameplay::Cell::CellType::EMPTY: + processEmptyCell(cell_position); + break; + case::Gameplay::Cell::CellType::MINE: + processMineCell(cell_position); + break; + default: + ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::BUTTON_CLICK); + break; + } + } + + void BoardController::processEmptyCell(sf::Vector2i cell_position) + { + ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::BUTTON_CLICK); + openEmptyCells(cell_position); + } + + void BoardController::processMineCell(sf::Vector2i cell_position) + { + ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::EXPLOSION); + ServiceLocator::getInstance()->getGameplayService()->endGame(GameResult::LOST); + } + + void BoardController::openEmptyCells(sf::Vector2i cell_position) + { + switch (board[cell_position.x][cell_position.y]->getCellState()) + { + case::Gameplay::Cell::CellState::OPEN: + return; + case::Gameplay::Cell::CellState::FLAGGED: + flagged_cells--; + default: + board[cell_position.x][cell_position.y]->openCell(); + } + + for (int a = -1; a < 2; a++) + { + for (int b = -1; b < 2; b++) + { + if ((a == 0 && b == 0) || !isValidCellPosition(sf::Vector2i(a + cell_position.x, b + cell_position.y))) continue; + + sf::Vector2i next_cell_position = sf::Vector2i(a + cell_position.x, b + cell_position.y); + openCell(next_cell_position); + } + } + } + + void BoardController::openAllCells() + { + for (int a = 0; a < number_of_rows; ++a) + { + for (int b = 0; b < number_of_colums; ++b) + { + board[a][b]->openCell(); + } + } + } + + bool BoardController::isValidCellPosition(sf::Vector2i cell_position) + { + return (cell_position.x >= 0 && cell_position.y >= 0 && cell_position.x < number_of_colums && cell_position.y < number_of_rows); + } + + bool BoardController::areAllCellOpen() + { + int total_cell_count = number_of_rows * number_of_colums; + int open_cell_count = 0; + + for (int a = 0; a < number_of_rows; a++) + { + for (int b = 0; b < number_of_colums; b++) + { + if (board[a][b]->getCellState() == CellState::OPEN) + { + open_cell_count++; + } + } + } + + return (total_cell_count - open_cell_count == mines_count); + } + + void BoardController::showBoard() + { + + switch (ServiceLocator::getInstance()->getBoardService()->getBoardState()) + { + case Gameplay::Board::BoardState::FIRST_CELL: + populateBoard(sf::Vector2i(0, 0)); + openAllCells(); + break; + case Gameplay::Board::BoardState::PLAYING: + openAllCells(); + break; + case Gameplay::Board::BoardState::COMPLETED: + break; + default: + break; + } + } + + + void BoardController::reset() + { + resetBoard(); + + board_state = BoardState::FIRST_CELL; + flagged_cells = 0; + } + + void BoardController::resetBoard() + { + for (int row = 0; row < number_of_rows; ++row) + { + for (int col = 0; col < number_of_colums; ++col) + { + board[row][col]->reset(); + } + } + } + + void BoardController::deleteBoard() + { + for (int a = 0; a < number_of_rows; a++) + { + for (int b = 0; b < number_of_colums; b++) + { + delete board[a][b]; + } + } + } + + void BoardController::destroy() + { + deleteBoard(); + delete (board_view); + } + + BoardState BoardController::getBoardState() + { + return board_state; + } + + void BoardController::setBoardState(BoardState state) + { + board_state = state; + } + + int BoardController::getMinesCount() + { + return mines_count - flagged_cells; + } + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/Gameplay/Board/BoardService.cpp b/Array-Minesweeper/source/Gameplay/Board/BoardService.cpp new file mode 100644 index 00000000..58ef1f62 --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/Board/BoardService.cpp @@ -0,0 +1,74 @@ +#include "../../header/Gameplay/Board/BoardService.h" + +#include "../../../header/Gameplay/GameplayController.h" + +namespace Gameplay +{ + namespace Board + { + using namespace Cell; + using namespace UI; + using namespace UI::UIElement; + + BoardService::BoardService() + { + board_controller = nullptr; + } + + BoardService::~BoardService() + { + destroy(); + } + + void BoardService::initialize() + { + board_controller = new BoardController(); + board_controller->initialize(); + } + + void BoardService::update() + { + board_controller->update(); + } + + void BoardService::render() + { + board_controller->render(); + } + + void BoardService::processCellInput(CellController* cell_controller, ButtonType button_type) + { + board_controller->processCellInput(cell_controller, button_type); + } + + BoardState BoardService::getBoardState() + { + return board_controller->getBoardState(); + } + + void BoardService::setBoardState(BoardState state) + { + board_controller->setBoardState(state); + } + + void BoardService::resetBoard() + { + board_controller->reset(); + } + + int BoardService::getMinesCount() + { + return board_controller->getMinesCount(); + } + + void BoardService::flagAllMines() { board_controller->flagAllMines(); } + + void BoardService::showBoard() { board_controller->showBoard(); } + + void BoardService::destroy() + { + delete(board_controller); + } + + } +} diff --git a/Array-Minesweeper/source/Gameplay/Board/BoardView.cpp b/Array-Minesweeper/source/Gameplay/Board/BoardView.cpp new file mode 100644 index 00000000..92099a75 --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/Board/BoardView.cpp @@ -0,0 +1,69 @@ +#include "../../header/Gameplay/Board/BoardView.h" +#include "../../header/Gameplay/Board/BoardController.h" +#include "../../header/Global/Config.h" +#include "../../header/Global/ServiceLocator.h" + +namespace Gameplay +{ + namespace Board + { + using namespace UI::UIElement; + using namespace Cell; + using namespace Global; + + BoardView::BoardView(BoardController* controller) + { + board_controller = controller; + board_image = new ImageView(); + background_image = new ImageView(); + } + + BoardView::~BoardView() + { + delete (board_image); + delete (background_image); + } + + void BoardView::initialize() + { + initializeBackgroudImage(); + initializeBoardImage(); + } + + void BoardView::initializeBackgroudImage() + { + sf::RenderWindow* game_window = ServiceLocator::getInstance()->getGraphicService()->getGameWindow(); + + background_image->initialize(Config::background_texture_path, game_window->getSize().x, game_window->getSize().y, sf::Vector2f(0, 0)); + background_image->setImageAlpha(background_alpha); + } + + void BoardView::initializeBoardImage() + { + board_image->initialize(Config::board_texture_path, board_width, board_height, sf::Vector2f(0, 0)); + board_image->setCentreAlinged(); + } + + void BoardView::update() + { + background_image->update(); + board_image->update(); + } + + void BoardView::render() + { + background_image->render(); + board_image->render(); + } + + float BoardView::getCellWidth() + { + return (board_width - board_width_offset) / static_cast(BoardController::number_of_colums); + } + + float BoardView::getCellHeight() + { + return (board_height - board_height_offset) / static_cast(BoardController::number_of_rows); + } + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/Gameplay/Cell/CellController.cpp b/Array-Minesweeper/source/Gameplay/Cell/CellController.cpp new file mode 100644 index 00000000..8e0180c3 --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/Cell/CellController.cpp @@ -0,0 +1,110 @@ +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/Gameplay/Cell/CellModel.h" +#include "../../header/Gameplay/Cell/CellView.h" +#include "../../header/Sound/SoundService.h" +#include "../../header/Global/ServiceLocator.h" + +namespace Gameplay +{ + namespace Cell + { + using namespace Global; + using namespace Sound; + + CellController::CellController(sf::Vector2i grid_position) + { + cell_model = new CellModel(grid_position); + cell_view = new CellView(this); + } + + CellController::~CellController() + { + destroy(); + } + + void CellController::initialize(float cell_width, float cell_height) + { + cell_view->initialize(cell_width, cell_height); + } + + void CellController::update() + { + cell_view->update(); + } + + void CellController::render() + { + cell_view->render(); + } + + void CellController::flagCell() + { + if (ServiceLocator::getInstance()->getBoardService()->getBoardState() == Gameplay::Board::BoardState::COMPLETED) + { + cell_model->setCellState(CellState::FLAGGED); + return; + } + + switch (cell_model->getCellState()) + { + case::Gameplay::Cell::CellState::FLAGGED: + cell_model->setCellState(CellState::HIDDEN); + break; + case::Gameplay::Cell::CellState::HIDDEN: + cell_model->setCellState(CellState::FLAGGED); + break; + } + } + + void CellController::openCell() + { + setCellState(CellState::OPEN); + } + + bool CellController::canOpenCell() + { + return cell_model->getCellState() != CellState::FLAGGED && cell_model->getCellState() != CellState::OPEN; + } + + CellState CellController::getCellState() + { + return cell_model->getCellState(); + } + + void CellController::setCellState(CellState state) + { + cell_model->setCellState(state); + } + + CellType CellController::getCellType() + { + return cell_model->getCellType(); + } + + void CellController::setCellType(CellType type) + { + cell_model->setCellType(type); + } + + sf::Vector2i CellController::getCellPosition() + { + return cell_model->getCellPosition(); + } + + int CellController::getMinesAround() + { + return cell_model->getMinesAround(); + } + + void CellController::reset() + { + cell_model->reset(); + } + + void CellController::destroy() + { + delete (cell_view); + delete (cell_model); + } + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/Gameplay/Cell/CellModel.cpp b/Array-Minesweeper/source/Gameplay/Cell/CellModel.cpp new file mode 100644 index 00000000..8e76e19c --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/Cell/CellModel.cpp @@ -0,0 +1,62 @@ +#include "../../header/Gameplay/Cell/CellModel.h" + +namespace Gameplay +{ + namespace Cell + { + CellModel::CellModel(sf::Vector2i grid_position) + { + reset(); + position = grid_position; + } + + CellModel::~CellModel() = default; + + CellState CellModel::getCellState() + { + return cell_state; + } + + void CellModel::setCellState(CellState state) + { + cell_state = state; + } + + CellType CellModel::getCellType() + { + return cell_type; + } + + void CellModel::setCellType(CellType type) + { + cell_type = type; + } + + sf::Vector2i CellModel::getCellPosition() + { + return position; + } + + void CellModel::setCellPosition(sf::Vector2i grid_position) + { + position = grid_position; + } + + int CellModel::getMinesAround() + { + return mines_around; + } + + void CellModel::setMinesAround(int mine_count) + { + mines_around = mine_count; + } + + void CellModel::reset() + { + cell_state = CellState::HIDDEN; + cell_type = CellType::EMPTY; + mines_around = 0; + } + } +} diff --git a/Array-Minesweeper/source/Gameplay/Cell/CellView.cpp b/Array-Minesweeper/source/Gameplay/Cell/CellView.cpp new file mode 100644 index 00000000..b247b786 --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/Cell/CellView.cpp @@ -0,0 +1,93 @@ +#include "../../header/Gameplay/Cell/CellView.h" + +#include + +#include "../../header/Gameplay/Cell/CellModel.h" +#include "../../header/Gameplay/Cell/CellController.h" +#include "../../header/Global/Config.h" +#include "../../header/Global/ServiceLocator.h" +#include "../../header/Gameplay/GameplayService.h" +#include "../../header/Sound/SoundService.h" + +namespace Gameplay +{ + namespace Cell + { + using namespace UI::UIElement; + using namespace Global; + using namespace Sound; + + CellView::CellView(CellController* controller) + { + cell_controller = controller; + cell_button = new ButtonView(); + } + + CellView::~CellView() { delete (cell_button); } + + void CellView::initialize(float width, float height) + { + initializeButtonImage(width, height); + } + + void CellView::initializeButtonImage(float width, float height) + { + sf::Vector2f cell_screen_position = getCellScreenPosition(width, height); + + cell_button->initialize("Cell", Config::cells_texture_path, width*slice_count, height, cell_screen_position); + + registerButtonCallback(); + } + + sf::Vector2f CellView::getCellScreenPosition(float width, float height) + { + sf::Vector2i grid_position = cell_controller->getCellPosition(); + + float x_screen_position = cell_left_offset + grid_position.x * width; + float y_screen_position = cell_top_offset + grid_position.y * height; + + return sf::Vector2f(x_screen_position, y_screen_position); + } + + void CellView::update() + { + cell_button->update(); + } + + void CellView::render() + { + setCellTexture(); + cell_button->render(); + } + + void CellView::setCellTexture() + { + int index = static_cast(cell_controller->getCellType()); + + switch (cell_controller->getCellState()) + { + case::Gameplay::Cell::CellState::HIDDEN: + cell_button->setTextureRect(sf::IntRect(10 * tile_size, 0, tile_size, tile_size)); + break; + + case::Gameplay::Cell::CellState::OPEN: + cell_button->setTextureRect(sf::IntRect(index * tile_size, 0, tile_size, tile_size)); + break; + + case::Gameplay::Cell::CellState::FLAGGED: + cell_button->setTextureRect(sf::IntRect(11 * tile_size, 0, tile_size, tile_size)); + break; + } + } + + void CellView::registerButtonCallback() + { + cell_button->registerCallbackFuntion(std::bind(&CellView::cellButtonCallback, this, std::placeholders::_1)); + } + + void CellView::cellButtonCallback(ButtonType button_type) + { + ServiceLocator::getInstance()->getBoardService()->processCellInput(cell_controller, button_type); + } + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/Gameplay/GameplayController.cpp b/Array-Minesweeper/source/Gameplay/GameplayController.cpp new file mode 100644 index 00000000..5129721c --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/GameplayController.cpp @@ -0,0 +1,95 @@ +#include "../../header/Gameplay/GameplayController.h" +#include "../../header/Gameplay/Board/BoardService.h" +#include "../../header/Time/TimeService.h" +#include "../../header/Global/ServiceLocator.h" +#include "../../header/Main/GameService.h" + +namespace Gameplay +{ + using namespace Main; + using namespace Board; + using namespace Global; + using namespace Cell; + using namespace UI::UIElement; + using namespace Time; + + GameplayController::~GameplayController() { board_service = nullptr; } + + void GameplayController::initialize() + { + board_service = ServiceLocator::getInstance()->getBoardService(); + } + + void GameplayController::update() + { + updateRemainingTime(); + + if (isTimeOver()) + endGame(GameResult::LOST); + } + + void GameplayController::render() { } + + void GameplayController::updateRemainingTime() + { + if (game_result == GameResult::WON) + return; + remaining_time -= ServiceLocator::getInstance()->getTimeService()->getDeltaTime(); + } + + bool GameplayController::isTimeOver() { return (remaining_time <= 1); } + + void GameplayController::restart() + { + game_result = GameResult::NONE; + board_service->resetBoard(); + remaining_time = max_level_duration; + } + + int GameplayController::getMinesCount() { return board_service->getMinesCount(); } + + float GameplayController::getRemainingTime() { return remaining_time; } + + void GameplayController::endGame(GameResult result) + { + switch(result) + { + case GameResult::WON: + gameWon(); + break; + case GameResult::LOST: + gameLost(); + break; + default: + break; + } + } + + void GameplayController::gameWon() + { + game_result = GameResult::WON; + board_service->flagAllMines(); + board_service->setBoardState(BoardState::COMPLETED); + ServiceLocator::getInstance()->getSoundService()->playSound(Sound::SoundType::GAME_WON); + } + + void GameplayController::gameLost() + { + if(game_result == GameResult::NONE) + { + game_result = GameResult::LOST; + beginGameOverTimer(); + board_service->showBoard(); + board_service->setBoardState(BoardState::COMPLETED); + } + else + { + showCredits(); + } + + } + + void GameplayController::beginGameOverTimer() { remaining_time = game_over_time; } + + void GameplayController::showCredits() { GameService::setGameState(GameState::CREDITS); } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/Gameplay/GameplayService.cpp b/Array-Minesweeper/source/Gameplay/GameplayService.cpp new file mode 100644 index 00000000..71eedafa --- /dev/null +++ b/Array-Minesweeper/source/Gameplay/GameplayService.cpp @@ -0,0 +1,60 @@ +#include "../../header/Gameplay/GameplayService.h" + +namespace Gameplay +{ + GameplayService::GameplayService() + { + gameplay_controller = nullptr; + createController(); + } + + GameplayService::~GameplayService() + { + destroy(); + } + + void GameplayService::createController() + { + gameplay_controller = new GameplayController(); + } + + void GameplayService::initialize() + { + gameplay_controller->initialize(); + } + + void GameplayService::update() + { + gameplay_controller->update(); + } + + void GameplayService::render() + { + gameplay_controller->render(); + } + + void GameplayService::startGame() + { + gameplay_controller->restart(); + } + + void GameplayService::endGame(GameResult result) + { + gameplay_controller->endGame(result); + } + + int GameplayService::getMinesCount() + { + return gameplay_controller->getMinesCount(); + } + + float GameplayService::getRemainingTime() + { + return gameplay_controller->getRemainingTime(); + } + + void GameplayService::destroy() + { + delete(gameplay_controller); + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/Gobal/ServiceLocator.cpp b/Array-Minesweeper/source/Gobal/ServiceLocator.cpp index bbe7a26e..f7d640c8 100644 --- a/Array-Minesweeper/source/Gobal/ServiceLocator.cpp +++ b/Array-Minesweeper/source/Gobal/ServiceLocator.cpp @@ -1,5 +1,5 @@ #include "../../header/Global/ServiceLocator.h" - +#include "../../header/Main/GameService.h" namespace Global { @@ -7,6 +7,10 @@ namespace Global using namespace Event; using namespace Sound; using namespace UI; + using namespace Gameplay; + using namespace Board; + using namespace Main; + using namespace Time; ServiceLocator::ServiceLocator() { @@ -14,6 +18,9 @@ namespace Global event_service = nullptr; sound_service = nullptr; ui_service = nullptr; + gameplay_service = nullptr; + time_service = nullptr; + board_service = nullptr; createServices(); } @@ -26,27 +33,48 @@ namespace Global graphic_service = new GraphicService(); sound_service = new SoundService(); ui_service = new UIService(); + gameplay_service = new GameplayService(); + time_service = new TimeService(); + board_service = new BoardService(); } void ServiceLocator::initialize() { graphic_service->initialize(); + time_service->initialize(); sound_service->initialize(); event_service->initialize(); ui_service->initialize(); + gameplay_service->initialize(); + board_service->initialize(); } void ServiceLocator::update() { + time_service->update(); event_service->update(); - ui_service->update(); graphic_service->update(); + + if (GameService::getGameState() == GameState::GAMEPLAY) + { + gameplay_service->update(); + board_service->update(); + } + + ui_service->update(); } void ServiceLocator::render() { - ui_service->render(); graphic_service->render(); + + if (GameService::getGameState() == GameState::GAMEPLAY) + { + gameplay_service->render(); + board_service->render(); + } + + ui_service->render(); } void ServiceLocator::clearAllServices() @@ -55,6 +83,9 @@ namespace Global delete(graphic_service); delete(sound_service); delete(event_service); + delete(gameplay_service); + delete(time_service); + delete(board_service); } ServiceLocator* ServiceLocator::getInstance() @@ -71,5 +102,12 @@ namespace Global UIService* ServiceLocator::getUIService() { return ui_service; } + GameplayService* ServiceLocator::getGameplayService() { return gameplay_service; } + + Time::TimeService* ServiceLocator::getTimeService() { return time_service; } + + Board::BoardService* ServiceLocator::getBoardService() { return board_service; } + + void ServiceLocator::deleteServiceLocator() { delete(this); } } \ No newline at end of file diff --git a/Array-Minesweeper/source/Sound/SoundService.cpp b/Array-Minesweeper/source/Sound/SoundService.cpp index ab41f71a..dc2247eb 100644 --- a/Array-Minesweeper/source/Sound/SoundService.cpp +++ b/Array-Minesweeper/source/Sound/SoundService.cpp @@ -20,7 +20,13 @@ namespace Sound void SoundService::loadSoundFromFile() { if (!buffer_button_click.loadFromFile(Config::button_click_sound_path)) - printf("Error loading background music file"); + printf("Error loading sound file"); + if (!buffer_flag_sound.loadFromFile(Config::flag_sound_path)) + printf("Error loading sound file"); + if (!buffer_explosion.loadFromFile(Config::explosion_sound_path)) + printf("Error loading sound file"); + if (!buffer_game_won.loadFromFile(Config::game_won_sound_path)) + printf("Error loading sound file"); } void SoundService::playSound(SoundType soundType) @@ -30,6 +36,15 @@ namespace Sound case SoundType::BUTTON_CLICK: sound_effect.setBuffer(buffer_button_click); break; + case SoundType::FLAG: + sound_effect.setBuffer(buffer_flag_sound); + break; + case SoundType::EXPLOSION: + sound_effect.setBuffer(buffer_explosion); + break; + case SoundType::GAME_WON: + sound_effect.setBuffer(buffer_game_won); + break; default: printf("Invalid sound type"); return; diff --git a/Array-Minesweeper/source/Time/TimeService.cpp b/Array-Minesweeper/source/Time/TimeService.cpp new file mode 100644 index 00000000..6d4231b5 --- /dev/null +++ b/Array-Minesweeper/source/Time/TimeService.cpp @@ -0,0 +1,42 @@ +#include "../../header/Time/TimeService.h" + +namespace Time +{ + void TimeService::initialize() + { + previous_time = std::chrono::steady_clock::now(); + delta_time = 0; + } + + void TimeService::update() + { + updateDeltaTime(); + } + + float TimeService::getDeltaTime() + { + return delta_time; + } + + void TimeService::updateDeltaTime() + { + delta_time = calculateDeltaTime(); + updatePreviousTime(); + } + + float TimeService::calculateDeltaTime() + { + // Calculate time difference in microseconds between the current and previous frame. + int delta = std::chrono::duration_cast( + std::chrono::steady_clock::now() - previous_time).count(); + + // To convert delta time from microseconds into seconds. + return static_cast(delta) / static_cast(1000000); + } + + // Update previous_time to the current time + void TimeService::updatePreviousTime() + { + previous_time = std::chrono::steady_clock::now(); + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/UI/Gameplay/GameplayUIController.cpp b/Array-Minesweeper/source/UI/Gameplay/GameplayUIController.cpp new file mode 100644 index 00000000..55e28427 --- /dev/null +++ b/Array-Minesweeper/source/UI/Gameplay/GameplayUIController.cpp @@ -0,0 +1,136 @@ +#include "../../header/UI/Gameplay/GameplayUIController.h" +#include "../../header/Global/Config.h" +#include "../../header/Global/ServiceLocator.h" +#include "../../header/Gameplay/GameplayService.h" +#include "../../header/Sound/SoundService.h" +#include "../../header/Main/GameService.h" +#include +#include + +namespace UI +{ + namespace GameplayUI + { + using namespace Main; + using namespace Sound; + using namespace UIElement; + using namespace Global; + + GameplayUIController::GameplayUIController() + { + createButton(); + createTexts(); + } + + GameplayUIController::~GameplayUIController() + { + destroy(); + } + + void GameplayUIController::initialize() + { + initializeButton(); + initializeTexts(); + } + + void GameplayUIController::createButton() + { + restart_button = new ButtonView(); + } + + void GameplayUIController::createTexts() + { + mine_text = new TextView(); + time_text = new TextView(); + } + + void GameplayUIController::initializeButton() + { + restart_button->initialize("Restart Button", + Config::restart_button_texture_path, + button_width, button_height, + sf::Vector2f(restart_button_left_offset, restart_button_top_offset)); + + registerButtonCallback(); + } + + void GameplayUIController::initializeTexts() + { + initializeMineText(); + initializeTimeText(); + } + + void GameplayUIController::initializeMineText() + { + mine_text->initialize("000", sf::Vector2f(mine_text_left_offset, mine_text_top_offset), FontType::ROBOTO, font_size, text_color); + } + + void GameplayUIController::initializeTimeText() + { + time_text->initialize("000", sf::Vector2f(time_text_left_offset, time_text_top_offset), FontType::ROBOTO, font_size, text_color); + } + + void GameplayUIController::update() + { + restart_button->update(); + updateMineText(); + updateTimeText(); + } + + void GameplayUIController::render() + { + restart_button->render(); + mine_text->render(); + time_text->render(); + } + + void GameplayUIController::show() + { + restart_button->show(); + mine_text->show(); + time_text->show(); + } + + void GameplayUIController::updateMineText() + { + int mines_count = ServiceLocator::getInstance()->getGameplayService()->getMinesCount(); + + std::stringstream stream; + stream << std::setw(3) << std::setfill('0') << mines_count; + std::string string_mine_count = stream.str(); + + mine_text->setText(string_mine_count); + mine_text->update(); + } + + void GameplayUIController::updateTimeText() + { + int remaining_time = ServiceLocator::getInstance()->getGameplayService()->getRemainingTime(); + + std::stringstream stream; + stream << std::setw(3) << std::setfill('0') << remaining_time; + std::string string_remaining_time = stream.str(); + + time_text->setText(string_remaining_time); + time_text->update(); + } + + void GameplayUIController::restartButtonCallback() + { + ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::BUTTON_CLICK); + ServiceLocator::getInstance()->getGameplayService()->startGame(); + } + + void GameplayUIController::registerButtonCallback() + { + restart_button->registerCallbackFuntion(std::bind(&GameplayUIController::restartButtonCallback, this)); + } + + void GameplayUIController::destroy() + { + delete (restart_button); + delete (mine_text); + delete (time_text); + } + } +} \ No newline at end of file diff --git a/Array-Minesweeper/source/UI/MainMenu/MainMenuUIController.cpp b/Array-Minesweeper/source/UI/MainMenu/MainMenuUIController.cpp index a4e5ac85..e43ccc57 100644 --- a/Array-Minesweeper/source/UI/MainMenu/MainMenuUIController.cpp +++ b/Array-Minesweeper/source/UI/MainMenu/MainMenuUIController.cpp @@ -1,10 +1,10 @@ #include "../../header/UI/MainMenu/MainMenuUIController.h" #include "../../header/Main/GameService.h" +#include "../../header/Global/ServiceLocator.h" #include "../../header/Graphics/GraphicService.h" #include "../../header/Sound/SoundService.h" #include "../../header/Event/EventService.h" #include "../../header/Global/Config.h" -#include "../../header/Global/ServiceLocator.h" namespace UI { @@ -12,9 +12,9 @@ namespace UI { using namespace Global; using namespace Graphics; + using namespace Main; using namespace UIElement; using namespace Sound; - using namespace Main; MainMenuUIController::MainMenuUIController() { @@ -74,8 +74,9 @@ namespace UI void MainMenuUIController::playButtonCallback() { - // GameState will change to gameplay state. ServiceLocator::getInstance()->getSoundService()->playSound(SoundType::BUTTON_CLICK); + GameService::setGameState(GameState::GAMEPLAY); + ServiceLocator::getInstance()->getGameplayService()->startGame(); } void MainMenuUIController::instructionsButtonCallback() diff --git a/Array-Minesweeper/source/UI/UIElement/ButtonView.cpp b/Array-Minesweeper/source/UI/UIElement/ButtonView.cpp index 8490dc4f..044ed297 100644 --- a/Array-Minesweeper/source/UI/UIElement/ButtonView.cpp +++ b/Array-Minesweeper/source/UI/UIElement/ButtonView.cpp @@ -44,18 +44,29 @@ namespace UI { sf::Vector2f mouse_position = sf::Vector2f(sf::Mouse::getPosition(*game_window)); - if (clickedButton(&image_sprite, mouse_position)) + if (clickedLeftMouseButton(&image_sprite, mouse_position)) { - if (callback_function) callback_function(); + if (callback_function) callback_function(ButtonType::LEFT_MOUSE_BUTTON); + } + + if (clickedRightMouseButton(&image_sprite, mouse_position)) + { + if (callback_function) callback_function(ButtonType::RIGHT_MOUSE_BUTTON); } } - bool ButtonView::clickedButton(sf::Sprite* button_sprite, sf::Vector2f mouse_position) + bool ButtonView::clickedLeftMouseButton(sf::Sprite* button_sprite, sf::Vector2f mouse_position) { return ServiceLocator::getInstance()->getEventService()->pressedLeftMouseButton() && button_sprite->getGlobalBounds().contains(mouse_position); } + bool ButtonView::clickedRightMouseButton(sf::Sprite* button_sprite, sf::Vector2f mouse_position) + { + return ServiceLocator::getInstance()->getEventService()->pressedRightMouseButton() && + button_sprite->getGlobalBounds().contains(mouse_position); + } + void ButtonView::printButtonClicked() { printf("Clicked %s\n", button_title.toAnsiString().c_str()); diff --git a/Array-Minesweeper/source/UI/UIElement/ImageView.cpp b/Array-Minesweeper/source/UI/UIElement/ImageView.cpp index e19b770d..e6f2e647 100644 --- a/Array-Minesweeper/source/UI/UIElement/ImageView.cpp +++ b/Array-Minesweeper/source/UI/UIElement/ImageView.cpp @@ -39,6 +39,11 @@ namespace UI } } + void ImageView::setTextureRect(sf::IntRect texture_rect) + { + image_sprite.setTextureRect(texture_rect); + } + void ImageView::setScale(float width, float height) { float scale_x = width / image_sprite.getTexture()->getSize().x; diff --git a/Array-Minesweeper/source/UI/UIElement/TextView.cpp b/Array-Minesweeper/source/UI/UIElement/TextView.cpp index ade1cbc4..d21f453f 100644 --- a/Array-Minesweeper/source/UI/UIElement/TextView.cpp +++ b/Array-Minesweeper/source/UI/UIElement/TextView.cpp @@ -66,6 +66,9 @@ namespace UI case FontType::DS_DIGIB: text.setFont(font_DS_DIGIB); break; + case FontType::ROBOTO: + text.setFont(font_DS_DIGIB); + break; } } diff --git a/Array-Minesweeper/source/UI/UIService.cpp b/Array-Minesweeper/source/UI/UIService.cpp index ea77b57e..63b5a883 100644 --- a/Array-Minesweeper/source/UI/UIService.cpp +++ b/Array-Minesweeper/source/UI/UIService.cpp @@ -7,27 +7,30 @@ namespace UI using namespace Main; using namespace MainMenu; using namespace SplashScreen; - using namespace Credits; - using namespace Instructions; + using namespace GameplayUI; using namespace UIElement; using namespace Interface; + using namespace Instructions; + using namespace Credits; UIService::UIService() { - splash_screen_controller = nullptr; - main_menu_controller = nullptr; - credit_screen_controller = nullptr; - instructions_screen_controller = nullptr; + splash_screen_ui_controller = nullptr; + main_menu_ui_controller = nullptr; + instructions_screen_ui_controller = nullptr; + gameplay_ui_controller = nullptr; + credit_screen_ui_controller = nullptr; createControllers(); } void UIService::createControllers() { - splash_screen_controller = new SplashScreenUIController(); - main_menu_controller = new MainMenuUIController(); - credit_screen_controller = new CreditsScreenUIController(); - instructions_screen_controller = new InstructionsScreenUIController(); + splash_screen_ui_controller = new SplashScreenUIController(); + main_menu_ui_controller = new MainMenuUIController(); + instructions_screen_ui_controller = new InstructionsScreenUIController(); + gameplay_ui_controller = new GameplayUIController(); + credit_screen_ui_controller = new CreditsScreenUIController(); } UIService::~UIService() @@ -61,10 +64,11 @@ namespace UI void UIService::initializeControllers() { - splash_screen_controller->initialize(); - main_menu_controller->initialize(); - credit_screen_controller->initialize(); - instructions_screen_controller->initialize(); + splash_screen_ui_controller->initialize(); + main_menu_ui_controller->initialize(); + instructions_screen_ui_controller->initialize(); + gameplay_ui_controller->initialize(); + credit_screen_ui_controller->initialize(); } IUIController* UIService::getCurrentUIController() @@ -72,16 +76,19 @@ namespace UI switch (GameService::getGameState()) { case GameState::SPLASH_SCREEN: - return splash_screen_controller; + return splash_screen_ui_controller; case GameState::MAIN_MENU: - return main_menu_controller; + return main_menu_ui_controller; case GameState::INSTRUCTIONS: - return instructions_screen_controller; + return instructions_screen_ui_controller; + + case GameState::GAMEPLAY: + return gameplay_ui_controller; case GameState::CREDITS: - return credit_screen_controller; + return credit_screen_ui_controller; default: return nullptr; @@ -90,9 +97,10 @@ namespace UI void UIService::destroy() { - delete(splash_screen_controller); - delete(main_menu_controller); - delete(instructions_screen_controller); - delete(credit_screen_controller); + delete(splash_screen_ui_controller); + delete(main_menu_ui_controller); + delete(instructions_screen_ui_controller); + delete(gameplay_ui_controller); + delete(credit_screen_ui_controller); } } \ No newline at end of file