From 3f8aab27ba1453674e9543515ffe3fa07b7b0745 Mon Sep 17 00:00:00 2001 From: VisualGMQ <2142587070@qq.com> Date: Sat, 24 Jun 2023 18:38:24 +0800 Subject: [PATCH] (add): finish version 1.0.0 --- .gitignore | 2 + CMakeLists.txt | 18 +++ ReadMe.md | 20 ++++ cmake/FindSDL2.cmake | 72 +++++++++++ cmake/copydll.cmake | 20 ++++ cmake/fetch_sdl.cmake | 81 +++++++++++++ cmake/utility.cmake | 37 ++++++ include/context.hpp | 84 +++++++++++++ include/matrix.hpp | 46 ++++++++ include/pch.hpp | 6 + include/renderer.hpp | 25 ++++ include/window.hpp | 18 +++ resources/flag.bmp | Bin 0 -> 3126 bytes resources/font.bmp | Bin 0 -> 30678 bytes resources/gameover.bmp | Bin 0 -> 196662 bytes resources/mine.bmp | Bin 0 -> 3126 bytes resources/win.bmp | Bin 0 -> 196662 bytes snapshot/snapshot.png | Bin 0 -> 20089 bytes src/context.cpp | 262 +++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 36 ++++++ src/renderer.cpp | 43 +++++++ src/window.cpp | 15 +++ 22 files changed, 785 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 ReadMe.md create mode 100644 cmake/FindSDL2.cmake create mode 100644 cmake/copydll.cmake create mode 100644 cmake/fetch_sdl.cmake create mode 100644 cmake/utility.cmake create mode 100644 include/context.hpp create mode 100644 include/matrix.hpp create mode 100644 include/pch.hpp create mode 100644 include/renderer.hpp create mode 100644 include/window.hpp create mode 100644 resources/flag.bmp create mode 100644 resources/font.bmp create mode 100644 resources/gameover.bmp create mode 100644 resources/mine.bmp create mode 100644 resources/win.bmp create mode 100644 snapshot/snapshot.png create mode 100644 src/context.cpp create mode 100644 src/main.cpp create mode 100644 src/renderer.cpp create mode 100644 src/window.cpp 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 0000000000000000000000000000000000000000..b188a952f805297858e7c644b0cb9071b8943a05 GIT binary patch literal 3126 zcmeIz!45z$3QY^aOi=-Mx;7@X|D^C4_ZNbzHXSh-Kd|4dG|kK^P9_32Qv6 zfdQ{@;CkOu(G7srFW($7(DJN!a*S8(l5eR#->cf6+8@3H{AzuFe(VpxR;d3$M%>;% z$;SRoHuDp*fzQgXe1WX@#W+Uj|L?LCy-#0mF6FJCOR@3ODVF|IioJhLDdoqMvOcBA F=N+ubrrrPm literal 0 HcmV?d00001 diff --git a/resources/font.bmp b/resources/font.bmp new file mode 100644 index 0000000000000000000000000000000000000000..61c9623835473a3ab76ecb36cb4d5bc1b78abea9 GIT binary patch literal 30678 zcmeHP>2ur06<+{6!5aif@B~Fdlt}RsC0?Q=QWt4i)Kz_=%Z{#_n!>FW0fCoQ) zOApb*grF!uxUHM_iOZrWGHOGgUx*7HcXs=0U?1Z=q3<0vr#oej;2!G@GTuzpuMkH+=D2B;3yhGz}+#QsP3x9L-U{DCW>=vL6DqL z^X$x^1WvVz=>dRB;Hch((;2IdA_hUu4%+IAI?40k=fwIm_Au#L7FewuIWY_{3}9J$ zZj^m~quhxA*Yv4Ay<1YQ_bA-V!_VaC%-^snVhQ8cnQ2Xn> zv8EzHF4g@Dal7`{gG9Zxz}g!%!F-f4)(geRD}sAIjs4L^S`6d$Hr0u{M%-C1spf5& z9onRLv)RDgR<)Ys6!4Rabs%<5W%kd8ytTNc&x~C8R*8XwV)uw06Eisb0vKqu8sNAB z|2-)b3IYI$`N;+8O`pp$;KyCz*Mu|Izq2@|%Dy&_vS!cVYE%?#>tSbh|E98q*GBF2 zLNW3xV#9}#t(J#3o=|%5)qJNd?NqPTlVtbLs%xh77_)|Bq~gh<@Z+Ynb*&Qb@?(yl z_ouPeqTVG{eli|3nXo4hQzDrpNE?A*((}`kS`dk!vp2poJE!M-Z3eE%Jy?k$y(4*| z1;@bh7;SxZdHh7pUKH@rdyLN2Y9W5%A#FfjE-G7#+Dc9hKe^}=yRX&3!Vsl;a;C3s zV?-4y2R777?CmX49*wH}yxBPQWY!)LR7!e&S|c+e@ssNybZ8}L$NznBw6^DjhP5{+ z!CV?|b6*v_PXndTwboE&Oxz( zKwU`6i;KQU+CsP&`b6|=a+dSnhQ+Pe?}yq#5iUGdnZl)bn_FC4eZE3(X-}uda{WhR zG`)HqaU}*+_#ZD%@U_;RJ{6hEv-rDv&94}`vvy3ap+X^%_^nSz$7hB^kxDjvjDBm; zDdQ*CfY_yHD$Ku~YY{MvxdOPk-ML~cmQCax{*>D0_glf8!f-7Hu``jm zPXcD!{&bt?PhnlVCRWrYlNH0P1ff(a{RVJ-j^8Lcef;DQL<#PVuHnx&ri4Ya$=ck# zzrNoN1X#vp*)KIbk2PrubvZb<LUL=o^}-Cr%uCKoD6(RUy@UsHE~MlkiN4`~~fG(t~l&rRSzUlnoF&f<$MMbLjf zY&f@L_LK__9`l0S`MFfwY3;BoKlkUQIL$+6D?^^XPnV<^xLiwTc3&73BL%Crwbg=4 zn)oTW{bLLw{aXgT_10PT7#*>T%vgHeq!GyrvH2H_3|ucaH_%bI<|8!k_B*|`@Zjg{ zo$pg&;`aJzwMi1}uS{-#O?rw`@#g0};Mf8R zh$y>dTvYzuzpL&2>uFO{gxYY$L*oB=)ErEcTJ4etKe;-ASSjRm7H4w<@jL~*5}Y4$ zrq*c_xi#UgMVwvQ#E^lISyhvWof#?b=GW-{)MLoB03SWJ^8Fu-$3AB1PJ!*qOV>kVBX@(h3%T{ z;?aV0Fu!cE1cr4EEXj;XxAT< z`vruu$y8))nQ2S}!4QC6Rg1X4QXfO;`j2YY@M608!J%HK_dLf>?n@3102r&azPTxe zd9Wt>ZH$3LRQ+9hj(Ady%ynBRQ-(yI@g{Qb9u|5^*(atf|XYRf|YF=F~faAB%{&Hdpl*!tvb^-Y>?x;in)S1C2lgAOa^M z0QvdgM2w&UMBqaq0Qm{|2@#Ml0+65b?VArZXG6d8K>+d-@)IH;Uj!gO<=Z#%nZ;ow z9|{4;PsmS*fP4{v{FHCse5g4a`jrm?ke`sB5CQoj0Qo83zLC!?4kP(c2!Q_sk=aG& literal 0 HcmV?d00001 diff --git a/resources/gameover.bmp b/resources/gameover.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1aa19a74a4d6e2496d1cc8bff1bb10e6d333ddfa GIT binary patch literal 196662 zcmeI!OKufG3c|_{+f`tqP)*e8RoL^$ z0uzO5qE<)VxZbV;6NPG`Rv2^2YUc6__Yg6SaC3_B^scCRDFy+YI+aa=*gRHrhd}EKY!GwLsjM#_-BNkyl{W& zCzn+H(ftm+xu-xTe)q*PRWfJqcj(PM1=^%5QzdhD&z+k3zCfFFWvXP(-gofko&s&s zm8p_ByXQ_#eP5tWx-wPrXYXrxb2ouZy6#(Ls^rh!@6(&R31re0u*#GRpAGcs$R+~e zP#p&YM458ovw=Px*+d{5D*4|eA#0B*|7-8pbk7Ck|GAH!t}P({Ywy=|&jsZFxsRXj zAkgf$AX6xOc83j`F(VL;)^YH^>ShXs&;D13Ufxh394-0ZFfHqi+${g=(V1ug0r_8G ziAD$u$bXp5!36~5e}N?$AuJ&OVLAsF5Rm@`mS}{qfc%H)99%#^{ufxH5yArUAEt9~ z0Rj16V2MTu3&?+%&cOu)FxsZ-+cud=qNBt{=Wi_bLC4H#JweaIPAU8SYd58*ggHa^t6K{0?W_yPi*h@zXVahq5%U zz{u&!^md+|cP~0`Baq2h?G7`&$$#zbta1Oy85_B`HMVbxkux@OZ<4cyz{nXJxwkd8 zZ;DreeAvz#UM~zhlxzVdt|26WI5votJ3#ut49?7OWl? zaBydVf(h(<)y_*adsv`vX!F&>gF8sz5XdL6?+unnpol=<7>BDDncXD>!U^nq(BcRb z5$GG^aP=ayyM(|g5YA!ygH}eMxIp_DhbtDJ-Gv0+2!z8~@8FvbeMz96K)o1;Z>~42 zn+d!T2&c5(!8aZHl0Y+oda1s1bF(oOBJfv%J1MRA&R<*dO#;0H>P7v$xz}9k61XEE z|96zUv#Nmnuew=t6&LVdn&PA`oJc_aCt8+P2@oJafB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 X2oNAZfB*pk1PBlyK!5;&8UnunEtz&4 literal 0 HcmV?d00001 diff --git a/resources/mine.bmp b/resources/mine.bmp new file mode 100644 index 0000000000000000000000000000000000000000..139f21b9ef597a3049b3bd0a8ad253e30f2521f8 GIT binary patch literal 3126 zcmdUrK@P$&3#@W7TrTDNEb@KG zd;h(sa+8xh;$P4Ds!+;%eeLI`OKrE${`s;Yx&HtAa{_l|v0;E`csqCdzXg-3^xrz)|QbQ*;j3#ui47 xt6(0dm_}?dfmV_vv80{5%I6vGW=061T?7w9zK+1gq>|w$QEu$$eD_jZ`~XJTVF3UD literal 0 HcmV?d00001 diff --git a/resources/win.bmp b/resources/win.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a83212e255d6fdbe5ca51e7027cbd106128d6fad GIT binary patch literal 196662 zcmeIwL5?K34FJGi?9n%$`~<&XefQm8@oW5GYgYgvkdjK7Iq0qeqA>$d6iKN<`S1Vy z@%8)HpMQU}|Ns0m_^to{?duUx`&O;=3Yw?Em9BgSAgaID$H58F4BZM}oQ>V12Db;EmIecHbF z$?o*FtAW$vnta$^2gittqUDlSXnL4v)uFh%o4@W(0zj0wL z-zJbAw|*_to80S~j#?$t5wTp=Z}QQbf=$b~mAYa%)`&k;AYL4f|{vaS2Jnm>M}3KPUp1b z%f?>tu>J59Wj`WNAGcj2CF{j%LCUmc?Yf$&Gw+R^&N(0Tu>I<^>N$jDOvAR z3sR;nYuD9Go!K*2=d}8VqaL>3xUiP*5vVWQ&mHsiCgX04YfB_+?|CMj63g=mQ5n4{ zh=P5Px?;KSvitr5@stttJIAsI>4!{rQ?e~n*VSbm*fUq>wEBmm9=6}Ou$J!;s4v^k zP5E@0>26B4W$L=RtOI-I>YP^paMZ*08yD8{Jp%P*`?)EfE;HRt$+k>gHm zVJ+VyP+vCA9k(Lcf-&FX*h=|vD{3wUbzsk28q$j&j(XUBDQcGJ#*Nvb~OE)>6`Eb<3 z_VatH{APjrvT=d5^)lZIQcGJ#*S(+)yfqbzg^~^6E6-P{G#_lwjwn%=EKz-Kn@@Y4=R{2(t zw%RhfYEX+0Gj-I<%;DrqWiJm^@a6*bxzmMHLEoJwr(3Nu4IGzg&iQg2au}ZvKd(GI zTG`tQ)VEGoO$B{-nw)O6$}})7)2PE`8gjB+O>Vubt$qIFP&IEZP@gzmI2H8WX)+qq zZd=Ay4Qla8rp|epS&i+DtKCih<%tU3U!cBkz4KVpcgIYNQ%l;d1!H|JKFo|m4&&+M z-aFmeBo9wi_T~chiR*<^O)IFGu`BJi<+z*TT6~yEb554i$z$WRpeDx_@ka~P$8DMP zi&9P7t(mbU?bd<&;@UpVj6)9N>6~=ERZY(y9;ED31g6iOE}v@JZq4jlT74?`l;!8% z{8NN~{GRD!p1$~4(=I$NU)J~HAOD#yKwv76KIX&kdF-lV$K4*+8W{rbAaL@n)|xR> zf5o^PaV?V}@Z$pUeMT_7QtY;%6{KZt84G$`Yh(!gxWM6uI+nFNr)B*WV?mE=jSPVw z7dZS-YguHbKOuG_n3kO+W5O7mG^!!+Qegb3dOU5ZWsy0rpH}XZaY_yLjdlr~2%LXc zwX$bcr_}mYFI#@)LiNI}^AhkiPkGd1;Pf0-q}|e)c{7+~4Bn1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 n2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkL{w!r@ZDlfB@ literal 0 HcmV?d00001 diff --git a/snapshot/snapshot.png b/snapshot/snapshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9efbb3e4689e9b199f9c2da17775d77e36ecff82 GIT binary patch literal 20089 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 oCRf 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