commit 3f8aab27ba1453674e9543515ffe3fa07b7b0745 Author: VisualGMQ <2142587070@qq.com> Date: Sat Jun 24 18:38:24 2023 +0800 (add): finish version 1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18b5ffa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +cmake-build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dfa0864 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10) + +project( + MineSweep + VERSION 1.0.0 + LANGUAGES CXX +) + +include(cmake/utility.cmake) +include(cmake/FindSDL2.cmake) +include(cmake/copydll.cmake) + +aux_source_directory(src SRC) +add_executable(mine-sweep ${SRC}) +target_include_directories(mine-sweep PRIVATE include) +target_link_libraries(mine-sweep PRIVATE SDL2) +target_compile_features(mine-sweep PRIVATE cxx_std_17) +CopyDLL(mine-sweep) \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..e3fc1fb --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,20 @@ +使用SDL2实现的扫雷游戏 + +## 操控 + +鼠标左键翻开格子,右键插旗子。 + +显示出失败/胜利图标后再次点击鼠标可以重开一局。 + +## 截图 + +![snapshot](./snapshot/snapshot.png) + +## 编译 + +Windows下使用CMake可以进行编译: + +```bash +cmake -S . -B cmake-build -DSDL2_ROOT= +cmake --build cmake-build +``` \ No newline at end of file diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake new file mode 100644 index 0000000..ebe7d2f --- /dev/null +++ b/cmake/FindSDL2.cmake @@ -0,0 +1,72 @@ +if (NOT TARGET SDL2) + if (NOT SDL2_ROOT) + set(SDL2_ROOT "" CACHE PATH "SDL2 root directory") + endif() + if (WIN32) # Windows + IsMSVCBackend(is_msvc_backend) + IsMinGWBackend(is_mingw_backend) + IsX64Compiler(is_x64_compiler) + if (${is_msvc_backend}) # use MSVC + if(${is_x64_compiler}) + set(SDL_LIB_DIR "${SDL2_ROOT}/lib/x64") + set(SDL2_DYNAMIC_LIB_DIR "${SDL2_ROOT}/lib/x64" CACHE PATH "SDL2.dll directory" FORCE) + else() + set(SDL_LIB_DIR "${SDL2_ROOT}/lib/x86") + set(SDL2_DYNAMIC_LIB_DIR "${SDL2_ROOT}/lib/x86" CACHE PATH "SDL2.dll directory" FROCE) + endif() + set(LIB_PATH "${SDL_LIB_DIR}/SDL2.lib") + set(DYNAMIC_LIB_PATH "${SDL2_DYNAMIC_LIB_DIR}/SDL2.dll") + set(MAIN_LIB_PATH "${SDL_LIB_DIR}/SDL2main.lib") + set(SDL_INCLUDE_DIR "${SDL2_ROOT}/include") + + mark_as_advanced(SDL2_DYNAMIC_LIB_DIR) + add_library(SDL2::SDL2 SHARED IMPORTED GLOBAL) + set_target_properties( + SDL2::SDL2 + PROPERTIES + IMPORTED_LOCATION ${DYNAMIC_LIB_PATH} + IMPORTED_IMPLIB ${LIB_PATH} + INTERFACE_INCLUDE_DIRECTORIES ${SDL_INCLUDE_DIR} + ) + add_library(SDL2::SDL2main SHARED IMPORTED GLOBAL) + set_target_properties( + SDL2::SDL2main + PROPERTIES + IMPORTED_LOCATION ${DYNAMIC_LIB_PATH} + IMPORTED_IMPLIB ${MAIN_LIB_PATH} + INTERFACE_INCLUDE_DIRECTORIES ${SDL_INCLUDE_DIR} + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + ) + add_library(SDL2 INTERFACE IMPORTED GLOBAL) + target_link_libraries(SDL2 INTERFACE SDL2::SDL2main SDL2::SDL2) + elseif (${is_mingw_backend}) # use MINGW + if(${is_x64_compiler}) + set(SDL_INCLUDE_DIR "${SDL2_ROOT}/x86_64-w64-mingw32/include/SDL2") + set(SDL_LIB_DIR "${SDL2_ROOT}/x86_64-w64-mingw32/lib") + set(SDL2_DYNAMIC_LIB_DIR "${SDL2_ROOT}/x86_64-w64-mingw32/bin" CACHE PATH "SDL2.dll directory" FORCE) + else() + set(SDL_INCLUDE_DIR "${SDL2_ROOT}/i686-w64-mingw32/include/SDL2") + set(SDL_LIB_DIR "${SDL2_ROOT}/i686-w64-mingw32/lib") + set(SDL2_DYNAMIC_LIB_DIR "${SDL2_ROOT}/i686-w64-mingw32/bin" CACHE PATH "SDL2.dll directory" FORCE) + endif() + + mark_as_advanced(SDL2_DYNAMIC_LIB_DIR) + + add_library(SDL2 INTERFACE) + target_link_directories(SDL2 INTERFACE ${SDL_LIB_DIR}) + target_link_libraries(SDL2 INTERFACE "-lmingw32 -lSDL2main -lSDL2 -mwindows") + target_include_directories(SDL2 INTERFACE ${SDL_INCLUDE_DIR}) + else() + message(FATAL_ERROR "your compiler don't support, please use MSVC, Clang++ or MinGW") + endif() + else() # Linux, MacOSX + find_package(SDL2 QUIET) + if (SDL2_FOUND) + add_library(SDL2 ALIAS SDL2::SDL2) + else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(SDL2 sdl2 REQUIRED IMPORTED_TARGET) + add_library(SDL2 ALIAS PkgConfig::SDL2) + endif() + endif() +endif() \ No newline at end of file diff --git a/cmake/copydll.cmake b/cmake/copydll.cmake new file mode 100644 index 0000000..be4b214 --- /dev/null +++ b/cmake/copydll.cmake @@ -0,0 +1,20 @@ +macro(CopyDLL target_name) + if (WIN32) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $CACHE{SDL2_DYNAMIC_LIB_DIR}/SDL2.dll $) + endif() +endmacro(CopyDLL) + +macro(CopyResForWASM target_name) + add_custom_command( + TARGET ${target_name} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/resources ${CMAKE_BINARY_DIR}/resources) +endmacro() + +macro(CopyDefScript target_name) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/luabind/defs.lua $ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/luabind/defs.lua ${CMAKE_SOURCE_DIR}/resources/script) +endmacro() \ No newline at end of file diff --git a/cmake/fetch_sdl.cmake b/cmake/fetch_sdl.cmake new file mode 100644 index 0000000..2537db2 --- /dev/null +++ b/cmake/fetch_sdl.cmake @@ -0,0 +1,81 @@ +function(FetchSDL_MSVC) + include(FetchContent) + + message(STATUS "fetching SDL2 ...") + FetchContent_Declare( + SDL2 + URL https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-devel-2.24.2-VC.zip + ) + + message(STATUS "fetching SDL2_image ...") + FetchContent_Declare( + SDL2_image + URL https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.2/SDL2_image-devel-2.6.2-VC.zip + ) + + message(STATUS "fetching SDL2_ttf ...") + FetchContent_Declare( + SDL2_ttf + URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.1/SDL2_ttf-devel-2.20.1-VC.zip + ) + + message(STATUS "fetching SDL2_mixer ...") + FetchContent_Declare( + SDL2_mixer + URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.6.2/SDL2_mixer-devel-2.6.2-VC.zip + ) + + FetchContent_MakeAvailable(SDL2 SDL2_image SDL2_ttf SDL2_mixer) + + set(SDL2_ROOT ${sdl2_SOURCE_DIR} CACHE PATH "SDL2 root directory") + set(SDL2_MIXER_ROOT ${sdl2_mixer_SOURCE_DIR} CACHE PATH "SDL2_mixer root directory") + set(SDL2_TTF_ROOT ${sdl2_ttf_SOURCE_DIR} CACHE PATH "SDL2_ttf root directory") + set(SDL2_IMAGE_ROOT ${sdl2_image_SOURCE_DIR} CACHE PATH "SDL2_image root directory") +endfunction(FetchSDL_MSVC) + +function(FetchSDL_MINGW) + include(FetchContent) + + message(STATUS "fetching SDL2 ...") + FetchContent_Declare( + SDL2 + URL https://github.com/libsdl-org/SDL/releases/download/release-2.26.2/SDL2-devel-2.26.2-mingw.zip + ) + + message(STATUS "fetching SDL2_image ...") + FetchContent_Declare( + SDL2_image + URL https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.2/SDL2_image-devel-2.6.2-mingw.zip + ) + + message(STATUS "fetching SDL2_ttf ...") + FetchContent_Declare( + SDL2_ttf + URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.1/SDL2_ttf-devel-2.20.1-mingw.zip + ) + + message(STATUS "fetching SDL2_mixer ...") + FetchContent_Declare( + SDL2_mixer + URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.6.2/SDL2_mixer-devel-2.6.2-mingw.zip + ) + + FetchContent_MakeAvailable(SDL2 SDL2_image SDL2_ttf SDL2_mixer) + + set(SDL2_ROOT ${sdl2_SOURCE_DIR} CACHE PATH "SDL2 root directory") + set(SDL2_MIXER_ROOT ${sdl2_mixer_SOURCE_DIR} CACHE PATH "SDL2_mixer root directory") + set(SDL2_TTF_ROOT ${sdl2_ttf_SOURCE_DIR} CACHE PATH "SDL2_ttf root directory") + set(SDL2_IMAGE_ROOT ${sdl2_image_SOURCE_DIR} CACHE PATH "SDL2_image root directory") +endfunction(FetchSDL_MINGW) + +function(FetchSDL) + if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR + (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC")) # use MSVC + FetchSDL_MSVC() + elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR + (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU")) # use MINGW + FetchSDL_MINGW() + endif() +endfunction() \ No newline at end of file diff --git a/cmake/utility.cmake b/cmake/utility.cmake new file mode 100644 index 0000000..61de846 --- /dev/null +++ b/cmake/utility.cmake @@ -0,0 +1,37 @@ +function(IsMSVCBackend RETURN_VALUE) + if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR + (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC")) + set(${RETURN_VALUE} 1 PARENT_SCOPE) + else() + set(${RETURN_VALUE} 0 PARENT_SCOPE) + endif() +endfunction() + +function(IsMinGWBackend RETURN_VALUE) + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR + (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU" AND WIN32)) # use MINGW + set(${RETURN_VALUE} 1 PARENT_SCOPE) + else() + set(${RETURN_VALUE} 0 PARENT_SCOPE) + endif() +endfunction() + +function(IsGNUBackend RETURN_VALUE) + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR + (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU" AND (UNIX OR APPLE))) # use GNU G++ + set(${RETURN_VALUE} 1 PARENT_SCOPE) + else() + set(${RETURN_VALUE} 0 PARENT_SCOPE) + endif() +endfunction() + +function(IsX64Compiler RETURN_VALUE) + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(${RETURN_VALUE} 1 PARENT_SCOPE) + else() + set(${RETURN_VALUE} 0 PARENT_SCOPE) + endif() +endfunction() \ No newline at end of file diff --git a/include/context.hpp b/include/context.hpp new file mode 100644 index 0000000..910d989 --- /dev/null +++ b/include/context.hpp @@ -0,0 +1,84 @@ +#pragma once +#include "window.hpp" +#include "renderer.hpp" +#include "matrix.hpp" + +struct Tile { + enum Type { + Bomb = -1, + } type = static_cast(0); + bool isVisiable = false; + bool isFlaged = false; + +}; + +constexpr int TileLen = 32; +constexpr int WindowWidth = TileLen * 20; +constexpr int WindowHeight = TileLen * 20; +constexpr int MineCount = 50; + + +using Map = Matrix; + +Map CreateRandomMap(int bombCount, int w, int h); + +inline auto TextureDestroy = [](SDL_Texture* texture) { + SDL_DestroyTexture(texture); +}; + +struct Context final { + enum GameState { + Gaming, + Win, + Explode, + }; + GameState state = Gaming; + + std::unique_ptr numberImage; + std::unique_ptr mineImage; + std::unique_ptr flagImage; + std::unique_ptr gameoverImage; + std::unique_ptr winImage; + Window window; + Renderer renderer; + Map map; + + bool debugMode = false; + int nakkedCount = 0; + int mineCount = 0; + + static void Init() { + if (!instance_) { + Window window("mine-sweep", WindowWidth, WindowHeight); + Renderer renderer(window); + instance_ = std::unique_ptr(new Context( + std::move(window), + std::move(renderer), + CreateRandomMap(MineCount, WindowWidth / TileLen, WindowHeight / TileLen), + MineCount + )); + } + } + + static void Quit() { + instance_.reset(); + } + + static Context& Inst() { + SDL_assert(instance_); + return *instance_; + } + + void DrawMap(); + void HandleEvent(SDL_Event&); + +private: + static std::unique_ptr instance_; + + Context(Window&& window, Renderer&& renderer, Map&& map, int mineCount); + + void drawOneTile(int x, int y, const Tile& tile); + void handleMouseLeftBtnDown(const SDL_MouseButtonEvent&); + void handleMouseRightBtnDown(const SDL_MouseButtonEvent&); + void handleKeyDown(const SDL_KeyboardEvent&); +}; \ No newline at end of file diff --git a/include/matrix.hpp b/include/matrix.hpp new file mode 100644 index 0000000..2eb5a04 --- /dev/null +++ b/include/matrix.hpp @@ -0,0 +1,46 @@ +#pragma once + +template +class Matrix final { +public: + Matrix(int w, int h): data_(std::unique_ptr(new T[w * h])), w_(w), h_(h) {} + + void Fill(const T& value) { + for (int i = 0 ; i < w_ * h_; i++) { + GetByIndex(i) = value; + } + } + + T& Get(int x, int y) { + return data_.get()[x + y * w_]; + } + + const T& Get(int x, int y) const { + return data_.get()[x + y * w_]; + } + + T& GetByIndex(int idx) { + return data_.get()[idx]; + } + + int MaxSize() const { + return w_ * h_; + } + + int Width() const { + return w_; + } + + int Height() const { + return h_; + } + + bool IsIn(int x, int y) const { + return x >= 0 && x < w_ && y >= 0 && y < h_; + } + +private: + std::unique_ptr data_; + int w_; + int h_; +}; \ No newline at end of file diff --git a/include/pch.hpp b/include/pch.hpp new file mode 100644 index 0000000..1030e7e --- /dev/null +++ b/include/pch.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "SDL.h" +#include +#include +#include diff --git a/include/renderer.hpp b/include/renderer.hpp new file mode 100644 index 0000000..89aa565 --- /dev/null +++ b/include/renderer.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "pch.hpp" +#include "window.hpp" + +class Renderer final { +public: + friend struct Context; + + Renderer(const Window&); + ~Renderer(); + Renderer(const Renderer&) = delete; + Renderer(Renderer&&); + Renderer& operator=(const Renderer&) = delete; + + void SetColor(const SDL_Color&); + void Clear(); + void Present(); + void DrawRect(const SDL_Rect& rect); + void FillRect(const SDL_Rect& rect); + void DrawLine(const SDL_Point& p1, const SDL_Point& p2); + void DrawTexture(SDL_Texture*, const SDL_Rect&, int x, int y); + +private: + SDL_Renderer* renderer_; +}; \ No newline at end of file diff --git a/include/window.hpp b/include/window.hpp new file mode 100644 index 0000000..014637e --- /dev/null +++ b/include/window.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "pch.hpp" + +class Window final { +public: + friend class Renderer; + + Window(const std::string& title, int w, int h); + Window(const Window&) = delete; + Window(Window&&); + Window& operator=(const Window&) = delete; + + ~Window(); + +private: + SDL_Window* window_; +}; \ No newline at end of file diff --git a/resources/flag.bmp b/resources/flag.bmp new file mode 100644 index 0000000..b188a95 Binary files /dev/null and b/resources/flag.bmp differ diff --git a/resources/font.bmp b/resources/font.bmp new file mode 100644 index 0000000..61c9623 Binary files /dev/null and b/resources/font.bmp differ diff --git a/resources/gameover.bmp b/resources/gameover.bmp new file mode 100644 index 0000000..1aa19a7 Binary files /dev/null and b/resources/gameover.bmp differ diff --git a/resources/mine.bmp b/resources/mine.bmp new file mode 100644 index 0000000..139f21b Binary files /dev/null and b/resources/mine.bmp differ diff --git a/resources/win.bmp b/resources/win.bmp new file mode 100644 index 0000000..a83212e Binary files /dev/null and b/resources/win.bmp differ diff --git a/snapshot/snapshot.png b/snapshot/snapshot.png new file mode 100644 index 0000000..9efbb3e Binary files /dev/null and b/snapshot/snapshot.png differ diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 0000000..806d774 --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,262 @@ +#include "context.hpp" + + +constexpr SDL_Color NormalTileColor = {150, 150, 150, 255}; +constexpr SDL_Color HoverTileColor = {200, 200, 200, 255}; +constexpr SDL_Color BorderTileColor = {0, 0, 0, 255}; +constexpr SDL_Color NakedTileColor = {50, 50, 50, 255}; +constexpr SDL_Color KeyColor = {118, 66, 138, 255}; + +std::unique_ptr Context::instance_ = nullptr; + +Map CreateRandomMap(int bombCount, int w, int h) { + SDL_assert(bombCount < w * h); + + std::random_device d; + std::mt19937 gen(d()); + std::uniform_int_distribution dist1(0, w - 1); + std::uniform_int_distribution dist2(0, h - 1); + + Map map(w, h); + + constexpr int MaxBombSetupCount = 100; + + // generate all mines + while (bombCount > 0) { + bool setupBomb = false; + int setupCount = MaxBombSetupCount; + while (!setupBomb && setupCount > 0) { + int x = dist1(gen); + int y = dist2(gen); + auto& tile = map.Get(x, y); + if (tile.type != Tile::Bomb) { + tile.type = Tile::Bomb; + setupBomb = true; + } else { + setupCount --; + } + } + + if (setupCount == 0) { + for (int i = 0; i < map.MaxSize(); i++) { + if (map.GetByIndex(i).type != Tile::Bomb) { + map.GetByIndex(i).type = Tile::Bomb; + break; + } + } + } + + bombCount --; + } + + // generate nearly mine number + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + auto& tile = map.Get(x, y); + if (tile.type == Tile::Bomb) { + continue; + } + + int mineCount = 0; + for (int nx = -1; nx <= 1; nx ++) { + for (int ny = -1; ny <= 1; ny++) { + int detectX = x + nx; + int detectY = y + ny; + if (map.IsIn(detectX, detectY)) { + if (map.Get(detectX, detectY).type == Tile::Bomb) { + mineCount ++; + } + } + } + } + + tile.type = static_cast(mineCount); + } + } + + return map; +} + +SDL_Texture* loadTexture(SDL_Renderer* renderer, const std::string& bmpFilename, const SDL_Color& keycolor) { + SDL_Surface* surface = SDL_LoadBMP(bmpFilename.c_str()); + SDL_SetColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, keycolor.r, keycolor.g, keycolor.b)); + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + return texture; +} + +Context::Context(Window&& window, Renderer&& renderer, Map&& map, int mineCount) + : numberImage(loadTexture(renderer.renderer_, "resources/font.bmp", KeyColor), TextureDestroy), + mineImage(loadTexture(renderer.renderer_,"resources/mine.bmp", KeyColor), TextureDestroy), + flagImage(loadTexture(renderer.renderer_,"resources/flag.bmp", KeyColor), TextureDestroy), + gameoverImage(loadTexture(renderer.renderer_,"resources/gameover.bmp", KeyColor), TextureDestroy), + winImage(loadTexture(renderer.renderer_,"resources/win.bmp", KeyColor), TextureDestroy), + window(std::move(window)), + renderer(std::move(renderer)), + map(std::move(map)), + mineCount(mineCount) {} + +void Context::DrawMap() { + for (int y = 0; y < map.Height(); y++) { + for (int x = 0; x < map.Width(); x++) { + const auto& tile = map.Get(x, y); + drawOneTile(x, y, tile); + } + } + + if (state == GameState::Explode) { + renderer.DrawTexture(gameoverImage.get(), SDL_Rect{0, 0, 256, 256}, + (WindowWidth - 256) / 2, + (WindowHeight - 256) / 2); + } + if (state == GameState::Win) { + renderer.DrawTexture(winImage.get(), SDL_Rect{0, 0, 256, 256}, + (WindowWidth - 256) / 2, + (WindowHeight - 256) / 2); + } +} + +void Context::drawOneTile(int x, int y, const Tile& tile) { + int tileX = x * TileLen; + int tileY = y * TileLen; + SDL_Rect rect = {tileX, tileY, TileLen, TileLen}; + SDL_Point mousePos; + SDL_GetMouseState(&mousePos.x, &mousePos.y); + if (SDL_PointInRect(&mousePos, &rect)) { + renderer.SetColor(HoverTileColor); + } else { + renderer.SetColor(NormalTileColor); + } + if (tile.isVisiable && static_cast(tile.type) >= 0) { + renderer.SetColor(NakedTileColor); + } + renderer.FillRect(rect); + renderer.SetColor(BorderTileColor); + renderer.DrawRect(rect); + + if (tile.isVisiable) { + if (tile.type == Tile::Bomb) { + renderer.DrawTexture(mineImage.get(), SDL_Rect{0, 0, 32, 32}, tileX, tileY); + } else { + int mineCount = tile.type; + if (mineCount > 0) { + renderer.DrawTexture( + numberImage.get(), + SDL_Rect{309 / 8 * (mineCount - 1), 0, 32, 32}, + tileX, tileY); + } + } + } else { + if (tile.isFlaged) { + renderer.DrawTexture(flagImage.get(), SDL_Rect{0, 0, 32, 32}, tileX, tileY); + } + } + + if (debugMode) { + if (tile.type == Tile::Bomb) { + renderer.DrawTexture(mineImage.get(), SDL_Rect{0, 0, 32, 32}, tileX, tileY); + } else { + int mineCount = tile.type; + if (mineCount > 0) { + renderer.DrawTexture( + numberImage.get(), + SDL_Rect{309 / 8 * (mineCount - 1), 0, 32, 32}, + tileX, tileY); + } + } + } +} + +void Context::HandleEvent(SDL_Event& e) { + if (!state == GameState::Gaming) { + if (e.type == SDL_MOUSEBUTTONDOWN) { + map = CreateRandomMap(MineCount, WindowWidth / TileLen, WindowHeight / TileLen); + mineCount = MineCount; + nakkedCount = 0; + state = GameState::Gaming; + } + return; + } + + if (e.type == SDL_MOUSEBUTTONDOWN) { + if (e.button.button == SDL_BUTTON_LEFT) { + handleMouseLeftBtnDown(e.button); + } + if (e.button.button == SDL_BUTTON_RIGHT) { + handleMouseRightBtnDown(e.button); + } + } + if (e.type == SDL_KEYDOWN) { + handleKeyDown(e.key); + } +} + +inline SDL_Point calcTileCoord(int mouseX, int mouseY) { + return {mouseX / TileLen, mouseY / TileLen}; +} + +void floodFill(Context& ctx, Map& map, int x, int y) { + if (!map.IsIn(x, y)) { + return; + } + + auto& tile = map.Get(x, y); + if (!tile.isVisiable && !tile.isFlaged && tile.type != Tile::Bomb) { + tile.isVisiable = true; + ctx.nakkedCount ++; + if (static_cast(tile.type) == 0) { + floodFill(ctx, map, x - 1, y); + floodFill(ctx, map, x + 1, y); + floodFill(ctx, map, x, y - 1); + floodFill(ctx, map, x, y + 1); + } + } +} + +void Context::handleMouseLeftBtnDown(const SDL_MouseButtonEvent& e) { + auto tileCoord = calcTileCoord(e.x, e.y); + if (!map.IsIn(tileCoord.x, tileCoord.y)) { + return; + } + + auto& tile = map.Get(tileCoord.x, tileCoord.y); + if (tile.isVisiable || tile.isFlaged) { + return; + } + + if (!tile.isVisiable && tile.type == Tile::Bomb) { + state = GameState::Explode; + for (int i = 0; i < map.MaxSize(); i++) { + auto& tile = map.GetByIndex(i); + tile.isVisiable = true; + tile.isFlaged = false; + } + return; + } + + floodFill(*this, map, tileCoord.x, tileCoord.y); + + if (nakkedCount == map.MaxSize() - mineCount) { + state = GameState::Win; + } +} + +void Context::handleMouseRightBtnDown(const SDL_MouseButtonEvent& e) { + auto tileCoord = calcTileCoord(e.x, e.y); + if (!map.IsIn(tileCoord.x, tileCoord.y)) { + return; + } + + auto& tile = map.Get(tileCoord.x, tileCoord.y); + + if (!tile.isVisiable) { + tile.isFlaged = !tile.isFlaged; + } + +} + +void Context::handleKeyDown(const SDL_KeyboardEvent& e) { + if (e.keysym.scancode == SDL_SCANCODE_G) { + debugMode = !debugMode; + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f175da5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,36 @@ +#include +#include "context.hpp" + +int main(int argc, char** argv) { + SDL_Init(SDL_INIT_EVERYTHING); + + Context::Init(); + auto& ctx = Context::Inst(); + auto& renderer = ctx.renderer; + + SDL_Event event; + + bool shouldClose = false; + + while(!shouldClose) { + while(SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + shouldClose = true; + } + ctx.HandleEvent(event); + } + + renderer.SetColor(SDL_Color{200, 200, 200, 255}); + renderer.Clear(); + + ctx.DrawMap(); + + renderer.Present(); + SDL_Delay(30); + } + + + Context::Quit(); + SDL_Quit(); + return 0; +} \ No newline at end of file diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..d3462c4 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,43 @@ +#include "renderer.hpp" + +Renderer::Renderer(const Window& window) { + renderer_ = SDL_CreateRenderer(window.window_, -1, 0); +} + +void Renderer::SetColor(const SDL_Color& c) { + SDL_SetRenderDrawColor(renderer_, c.r, c.g, c.b, c.a); +} + +Renderer::Renderer(Renderer&& oth) { + renderer_ = oth.renderer_; + oth.renderer_ = nullptr; +} + +void Renderer::Clear() { + SDL_RenderClear(renderer_); +} + +void Renderer::Present() { + SDL_RenderPresent(renderer_); +} + +Renderer::~Renderer() { + SDL_DestroyRenderer(renderer_); +} + +void Renderer::DrawRect(const SDL_Rect& rect) { + SDL_RenderDrawRect(renderer_, &rect); +} + +void Renderer::FillRect(const SDL_Rect& rect) { + SDL_RenderFillRect(renderer_, &rect); +} + +void Renderer::DrawLine(const SDL_Point& p1, const SDL_Point& p2) { + SDL_RenderDrawLine(renderer_, p1.x, p1.y, p2.x, p2.y); +} + +void Renderer::DrawTexture(SDL_Texture* texture, const SDL_Rect& rect, int x, int y) { + SDL_Rect dst = {x, y, rect.w, rect.h}; + SDL_RenderCopy(renderer_, texture, &rect, &dst); +} \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..56866b8 --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,15 @@ +#include "window.hpp" + +Window::Window(const std::string& title, int w, int h) { + window_ = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, w, h, SDL_WINDOW_SHOWN); +} + +Window::Window(Window&& oth) { + window_ = oth.window_; + oth.window_ = nullptr; +} + +Window::~Window() { + SDL_DestroyWindow(window_); +} \ No newline at end of file