(add): finish version 1.0.0
This commit is contained in:
commit
3f8aab27ba
|
@ -0,0 +1,2 @@
|
||||||
|
.vscode
|
||||||
|
cmake-build
|
|
@ -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)
|
|
@ -0,0 +1,20 @@
|
||||||
|
使用SDL2实现的扫雷游戏
|
||||||
|
|
||||||
|
## 操控
|
||||||
|
|
||||||
|
鼠标左键翻开格子,右键插旗子。
|
||||||
|
|
||||||
|
显示出失败/胜利图标后再次点击鼠标可以重开一局。
|
||||||
|
|
||||||
|
## 截图
|
||||||
|
|
||||||
|
![snapshot](./snapshot/snapshot.png)
|
||||||
|
|
||||||
|
## 编译
|
||||||
|
|
||||||
|
Windows下使用CMake可以进行编译:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B cmake-build -DSDL2_ROOT=<your SDL path>
|
||||||
|
cmake --build cmake-build
|
||||||
|
```
|
|
@ -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()
|
|
@ -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 $<TARGET_FILE_DIR:${target_name}>)
|
||||||
|
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 $<TARGET_FILE_DIR:${target_name}>
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/luabind/defs.lua ${CMAKE_SOURCE_DIR}/resources/script)
|
||||||
|
endmacro()
|
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,84 @@
|
||||||
|
#pragma once
|
||||||
|
#include "window.hpp"
|
||||||
|
#include "renderer.hpp"
|
||||||
|
#include "matrix.hpp"
|
||||||
|
|
||||||
|
struct Tile {
|
||||||
|
enum Type {
|
||||||
|
Bomb = -1,
|
||||||
|
} type = static_cast<Type>(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<Tile>;
|
||||||
|
|
||||||
|
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<SDL_Texture, decltype(TextureDestroy)> numberImage;
|
||||||
|
std::unique_ptr<SDL_Texture, decltype(TextureDestroy)> mineImage;
|
||||||
|
std::unique_ptr<SDL_Texture, decltype(TextureDestroy)> flagImage;
|
||||||
|
std::unique_ptr<SDL_Texture, decltype(TextureDestroy)> gameoverImage;
|
||||||
|
std::unique_ptr<SDL_Texture, decltype(TextureDestroy)> 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<Context>(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<Context> 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&);
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Matrix final {
|
||||||
|
public:
|
||||||
|
Matrix(int w, int h): data_(std::unique_ptr<T[]>(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<T[]> data_;
|
||||||
|
int w_;
|
||||||
|
int h_;
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <random>
|
|
@ -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_;
|
||||||
|
};
|
|
@ -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_;
|
||||||
|
};
|
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -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> 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<Tile::Type>(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<int>(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<int>(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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
#include <iostream>
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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_);
|
||||||
|
}
|
Reference in New Issue