diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..760af55 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,25 @@ +Checks: | + bugprone-* + clang-analyzer-* + cppcoreguidelines-* + misc-* + modernize-* + performance-* + readability-* + portability-* + -cppcoreguidelines-avoid-magic-numbers + -readability-magic-numbers + -readability-identifier-length + -modernize-use-nodiscard + -cppcoreguidelines-init-variables + -bugprone-easily-swappable-parameters + -cppcoreguidelines-avoid-non-const-global-variables + -modernize-use-trailing-return-type + -performance-enum-size + -cppcoreguidelines-pro-bounds-constant-array-index + -cppcoreguidelines-non-private-member-variables-in-classes + -cppcoreguidelines-avoid-const-or-ref-data-members + -misc-include-cleaner + -misc-non-private-member-variables-in-classes +WarningsAsErrors: true +HeaderFilterRegex: ".*" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 78bf9d8..a18b51e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,10 +7,6 @@ on: tags: - v* -env: - EM_VERSION: "latest" - EM_CACHE_FOLDER: "emsdk-cache" - permissions: contents: read pages: write @@ -25,21 +21,23 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 - with: - submodules: recursive + uses: actions/checkout@v4 - name: Setup cache - id: cache-system-libraries - uses: actions/cache@v2 + uses: actions/cache@v4 + with: + path: "_deps" + key: deps-${{ runner.os }} + + - uses: awalsh128/cache-apt-pkgs-action@latest with: - path: ${{env.EM_CACHE_FOLDER}} - key: ${{env.EM_VERSION}}-${{ runner.os }} + packages: libharfbuzz-dev libfreetype-dev libasound2-dev + version: 1.0 - - uses: mymindstorm/setup-emsdk@v11 + - uses: mymindstorm/setup-emsdk@v14 with: - version: ${{env.EM_VERSION}} - actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + version: 1.38.40 + actions-cache-folder: "emsdk-cache" - name: Run CMake run: emcmake cmake -G "Unix Makefiles" . @@ -47,20 +45,20 @@ jobs: - name: Make run: emmake make -j4 - - uses: actions/upload-pages-artifact@v1 + - uses: actions/upload-pages-artifact@v3 with: path: ./build - name: Deploy to GitHub Pages if: github.ref == 'refs/heads/main' id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 - name: Deploy to A.D.S. Games if: startsWith(github.ref, 'refs/tags/v') uses: adsgames/deploy-to-adsgames@v1.1.2 with: - project-id: breaker + project-id: minesweeper build-dir: ./build/ platform: WEB bucket-access-key: ${{ secrets.LINODE_BUCKET_ACCESS_KEY }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 441f5b8..d91638f 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -15,48 +15,35 @@ jobs: SONAR_SERVER_URL: "https://sonarcloud.io" BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - submodules: "recursive" - - uses: awalsh128/cache-apt-pkgs-action@latest + - name: Setup cache + uses: actions/cache@v4 with: - packages: libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-gfx-dev - version: 1.0 + path: "_deps" + key: deps-${{ runner.os }} - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: awalsh128/cache-apt-pkgs-action@latest with: - java-version: 11 - - - name: Download and set up sonar-scanner - env: - SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip - run: | - mkdir -p $HOME/.sonar - curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} - unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH - - - name: Download and set up build-wrapper - env: - BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip - run: | - curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} - unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH + packages: libharfbuzz-dev libfreetype-dev libasound2-dev + version: 1.0 - name: Setup project - run: cmake . + run: cmake . -DSDL_UNIX_CONSOLE_BUILD=ON - - name: Run build-wrapper + - name: Install Build Wrapper + uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v5 + + - name: Run Build Wrapper run: | - build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make + build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make -j4 - - name: Run sonar-scanner + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@v5 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" + with: + args: > + --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" diff --git a/.gitignore b/.gitignore index 1d134fd..134e451 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ $RECYCLE.BIN/ # Icon must ends with two \r. Icon - # Thumbnails ._* @@ -50,4 +49,5 @@ bin docs build/ *.ninja* -compile_commands.json \ No newline at end of file +compile_commands.json +_deps/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ae582e6..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "lib/asw"] - path = lib/asw - url = https://github.com/AdsGames/asw diff --git a/.vscode/settings.json b/.vscode/settings.json index bfd5164..2194d32 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -50,7 +50,33 @@ "stdexcept": "cpp", "streambuf": "cpp", "cinttypes": "cpp", - "typeinfo": "cpp" + "typeinfo": "cpp", + "__bit_reference": "cpp", + "__config": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__verbose_abort": "cpp", + "bitset": "cpp", + "charconv": "cpp", + "complex": "cpp", + "execution": "cpp", + "ios": "cpp", + "locale": "cpp", + "optional": "cpp", + "queue": "cpp", + "set": "cpp", + "variant": "cpp", + "format": "cpp", + "list": "cpp", + "map": "cpp", + "span": "cpp", + "stack": "cpp", + "unordered_set": "cpp", + "forward_list": "cpp" }, "sonarlint.pathToCompileCommands": "${workspaceFolder}\\compile_commands.json" } diff --git a/CMakeLists.txt b/CMakeLists.txt index 5341da5..c93c094 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,84 +1,58 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.24) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +include(FetchContent) + project (Breaker) # Source code -file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lib/*.cpp) -file(GLOB_RECURSE HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h ${CMAKE_CURRENT_SOURCE_DIR}/lib/*.h) +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +file(GLOB_RECURSE HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -pedantic) -add_subdirectory(lib/asw) +FetchContent_Declare( + asw + GIT_REPOSITORY https://github.com/adsgames/asw.git + GIT_TAG 36fac408305d134f2def93580bbafc871c359536 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(asw) -# Find libs -if(NOT EMSCRIPTEN) - find_library(SDL_LIBRARY NAMES SDL2 REQUIRED) - find_library(SDL_MIXER_LIBRARY NAMES SDL2_mixer REQUIRED) - find_library(SDL_IMAGE_LIBRARY NAMES SDL2_image REQUIRED) - find_library(SDL_TTF_LIBRARY NAMES SDL2_ttf REQUIRED) - find_library(SDL_GFX_LIBRARY NAMES SDL2_gfx REQUIRED) - find_library(SDL_MAIN_LIBRARY NAMES SDL2main REQUIRED) -endif(NOT EMSCRIPTEN) +# Link Libs +if(MINGW) + target_link_libraries(${PROJECT_NAME} -lmingw32) +endif(MINGW) +target_link_libraries( + ${PROJECT_NAME} + asw +) -# Link Libs # Emscripten support if(EMSCRIPTEN) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "index") set(CMAKE_EXECUTABLE_SUFFIX ".html") - target_compile_options( - ${PROJECT_NAME} - PRIVATE - -sUSE_SDL=2 - -sUSE_SDL_IMAGE=2 - -sUSE_SDL_TTF=2 - -sUSE_SDL_MIXER=2 - -sUSE_SDL_GFX=2 - -sSDL2_IMAGE_FORMATS=["png"] - ) + target_link_libraries( ${PROJECT_NAME} -sWASM=1 - -sUSE_SDL=2 - -sUSE_SDL_IMAGE=2 - -sUSE_SDL_TTF=2 - -sUSE_SDL_MIXER=2 - -sUSE_SDL_GFX=2 - -sSDL2_IMAGE_FORMATS=["png"] - -sDEMANGLE_SUPPORT=1 - -sTOTAL_MEMORY=512MB - asw + -sALLOW_MEMORY_GROWTH=1 + -sMAXIMUM_MEMORY=1gb ) + set_target_properties( ${PROJECT_NAME} PROPERTIES LINK_FLAGS "--preload-file ${CMAKE_CURRENT_LIST_DIR}/assets@/assets --use-preload-plugins --shell-file ${CMAKE_CURRENT_LIST_DIR}/index.html" ) - -# Run of the mill executable -else(EMSCRIPTEN) - if(MINGW) - target_link_libraries(${PROJECT_NAME} -lmingw32) - endif(MINGW) - target_link_libraries( - ${PROJECT_NAME} - -lm - ${SDL_MAIN_LIBRARY} - ${SDL_LIBRARY} - ${SDL_MIXER_LIBRARY} - ${SDL_IMAGE_LIBRARY} - ${SDL_TTF_LIBRARY} - ${SDL_GFX_LIBRARY} - asw - ) endif(EMSCRIPTEN) file(COPY ${CMAKE_CURRENT_LIST_DIR}/assets/ DESTINATION ${CMAKE_BINARY_DIR}/build/assets/) diff --git a/README.md b/README.md index ca6f8e8..775fd20 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ A simple clear the board style breaking game. -[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=AdsGames_Breaker&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=AdsGames_Breaker) -[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=AdsGames_Breaker&metric=bugs)](https://sonarcloud.io/summary/new_code?id=AdsGames_Breaker) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=AdsGames_breaker&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=AdsGames_breaker) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=AdsGames_breaker&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=AdsGames_breaker) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=AdsGames_breaker&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=AdsGames_breaker) ## Demo @@ -11,42 +12,16 @@ A simple clear the board style breaking game. ## Setup -### Dependencies - -To pull the submodules, run the following command: - -```bash -git submodule update --init --recursive -``` - -### Windows (MSYS2) - -```bash -pacman -S mingw-w64-i686-gcc-libs mingw-w64-i686-SDL2 mingw-w64-i686-SDL2_mixer mingw-w64-i686-SDL2_image mingw-w64-i686-SDL2_ttf mingw-w64-i686-SDL2_gfx -``` - -### Mac OS - -```bash -brew install sdl2 sdl2_image sdl2_gfx sdl2_ttf sdl2_mixer -``` - -### Linux - -```bash -sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-gfx-dev -``` - ### Build ```bash cmake . -make +make -j ``` ### Build Emscripten ```bash emcmake cmake . -make +make -j ``` diff --git a/lib/asw b/lib/asw deleted file mode 160000 index d7adc6a..0000000 --- a/lib/asw +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d7adc6a5b82929378e44c394b8be62b6d70398f7 diff --git a/sonar-project.properties b/sonar-project.properties index eb92aa7..8e85c72 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,2 +1,2 @@ -sonar.projectKey=AdsGames_Breaker -sonar.organization=adsgames \ No newline at end of file +sonar.projectKey=AdsGames_breaker +sonar.organization=adsgames diff --git a/src/Block.cpp b/src/Block.cpp index 1fbf736..915bce6 100644 --- a/src/Block.cpp +++ b/src/Block.cpp @@ -1,18 +1,11 @@ #include "Block.h" -#include - -#include "utility/tools.h" - // Images -asw::Texture Block::images[8] = {nullptr}; - -// Default Constructor -Block::Block() : Block(0, 0, 0) {} +std::array Block::images = {nullptr}; // Constructor -Block::Block(int x, int y, int type) - : x(x), y(y), type(type), frame(0), selected(false) { +Block::Block(const asw::Vec2& position, int type) + : transform(position, {80, 80}), type(type) { if (!images[0]) { loadImages(); } @@ -31,46 +24,43 @@ void Block::loadImages() { } // Explode that block -void Block::explode(ParticleEmitter& emitter) { +void Block::explode(ParticleEmitter& emitter) const { // Number of particles to expode into - int num_particles = 10; - emitter.setPosition(vec2(x, y)); + const int num_particles = 10; + emitter.setPosition(transform.position); + emitter.setSize(transform.size); for (int i = 0; i < num_particles; i++) { emitter.createParticle(); } } -// Draw block to screen -void Block::draw(int offset) { - // Draw overlay if selected - if (selected && int(floor(frame / 8)) == 1) { - asw::draw::sprite(Block::images[7], x, y - offset); - } else { - asw::draw::sprite(Block::images[type], x, y - offset); - } - - // Increase frame counter - frame = (frame + 1) % 16; +// Get position on screen +const asw::Quad& Block::getTransform() const { + return transform; } -// Get position -int Block::getX() const { - return x; -} -int Block::getY() const { - return y; -} +// Update +void Block::update(float deltaTime) { + acc += deltaTime; -// Get width -int Block::getWidth() { - auto size = asw::util::getTextureSize(images[0]); - return images[0] ? size.x : 0; + // Increase frame counter + if (acc > 32) { + frame = (frame + 1) % 16; + acc -= 32; + } } -int Block::getHeight() { - auto size = asw::util::getTextureSize(images[0]); - return images[0] ? size.y : 0; +// Draw block to screen +void Block::draw(float offset) const { + auto position = transform.position - asw::Vec2(0, offset); + + // Draw overlay if selected + if (selected && int(floor(frame / 8)) == 1) { + asw::draw::sprite(Block::images[7], position); + } else { + asw::draw::sprite(Block::images[type], position); + } } // Get type diff --git a/src/Block.h b/src/Block.h index 37ef4ce..d8cf820 100644 --- a/src/Block.h +++ b/src/Block.h @@ -8,31 +8,27 @@ #define BLOCK_H #include +#include #include "ParticleEmitter.h" class Block { public: - // Ctor / Dtor - Block(); - Block(int x, int y, int type); + Block() = default; + + Block(const asw::Vec2& position, int type); + + // Update + void update(float deltaTime); // Draw - void draw(int offset); + void draw(float offset) const; // Explode that block - void explode(ParticleEmitter& emitter); - - // Change images depending on block - void change(); + void explode(ParticleEmitter& emitter) const; // Get position on screen - int getX() const; - int getY() const; - - // Get width - static int getWidth(); - static int getHeight(); + const asw::Quad& getTransform() const; // Get type int getType() const; @@ -49,19 +45,20 @@ class Block { private: // Load images static void loadImages(); - static asw::Texture images[8]; + static std::array images; // Coordinates for screen - int x, y; + asw::Quad transform{}; // Type of block - int type; + int type{0}; // Frame on for flashing - int frame; + int frame{0}; + float acc{0.0F}; // Selected by flash - bool selected; + bool selected{false}; }; #endif diff --git a/src/Game.cpp b/src/Game.cpp deleted file mode 100644 index e7bf184..0000000 --- a/src/Game.cpp +++ /dev/null @@ -1,359 +0,0 @@ -#include "Game.h" - -#include "globals.h" -#include "utility/tools.h" - -// INIT -void Game::init() { - // Sets Cursors - cursor[0] = asw::assets::loadTexture("assets/images/cursor1.png"); - cursor[1] = asw::assets::loadTexture("assets/images/cursor2.png"); - - // Sets background - background = asw::assets::loadTexture("assets/images/background.png"); - - // Sets Foreground - foreground = asw::assets::loadTexture("assets/images/foreground.png"); - - // Sets menu - dialog_box = asw::assets::loadTexture("assets/images/menu.png"); - - // Trans overlay - trans_overlay = asw::assets::loadTexture("assets/images/overlay.png"); - - // Sets Sounds - block_break = asw::assets::loadSample("assets/sounds/break.wav"); - click = asw::assets::loadSample("assets/sounds/click.wav"); - - // Sets Font - font = asw::assets::loadFont("assets/fonts/ariblk.ttf", 24); - - // Sets Variables - score = 0; - startAnimate = 1200; - blocks_selected = 0; - game_over = false; - gameOverMessage = "Game Over"; - - // Sets block info - for (int i = 0; i < 14; i++) { - for (int t = 0; t < 9; t++) { - MyBlocks[i][t] = Block(i * 80 + 80, t * 80 + 80, random(0, difficulty)); - } - } - - // Give score files - highscores = ScoreManager("scores.dat"); - - // Buttons - done = Button(500, 10); - dialog_yes = Button(41 + 300, 215 + 300); - dialog_no = Button(461 + 300, 215 + 300); - - // Images - done.setImages("assets/images/buttons/done.png", - "assets/images/buttons/done_hover.png"); - dialog_yes.setImages("assets/images/buttons/yes.png", - "assets/images/buttons/yes_hover.png"); - dialog_no.setImages("assets/images/buttons/no.png", - "assets/images/buttons/no_hover.png"); - - // On clicks - done.setOnClick([this]() { game_over = true; }); - - dialog_yes.setOnClick([this]() { - highscores.add(ib_name.getValue(), score); - setNextState(ProgramState::STATE_GAME); - }); - - dialog_no.setOnClick([this]() { - highscores.add(ib_name.getValue(), score); - setNextState(ProgramState::STATE_MENU); - }); - - ib_name = InputBox(488, 405, 404, 44, font, "Player"); - - particles = ParticleEmitter(vec2(0, 0), vec2(64, 64)); -} - -// Deselect all blocks -void Game::deselectBlocks() { - for (int i = 0; i < BLOCKS_WIDE; i++) - for (int t = 0; t < BLOCKS_HIGH; t++) { - MyBlocks[i][t].setSelected(false); - } -} - -// Select group of blocks -int Game::selectBlock(int x, int y, int type = -1) { - if (x < BLOCKS_WIDE && y < BLOCKS_HIGH && x >= 0 && y >= 0) { - if ((type == -1 || MyBlocks[x][y].getType() == type) && - !MyBlocks[x][y].getSelected() && MyBlocks[x][y].getType() != 6) { - // Select it - MyBlocks[x][y].setSelected(true); - - // Select 4 around it - int btype = MyBlocks[x][y].getType(); - return 1 + selectBlock(x - 1, y, btype) + selectBlock(x + 1, y, btype) + - selectBlock(x, y - 1, btype) + selectBlock(x, y + 1, btype); - } - } - - return 0; -} - -// Destroy selected -void Game::destroySelectedBlocks() { - // Remove blocks - int num_destroyed = 0; - - for (int i = 0; i < BLOCKS_WIDE; i++) { - for (int t = 0; t < BLOCKS_HIGH; t++) { - if (MyBlocks[i][t].getSelected()) { - MyBlocks[i][t].setSelected(false); - MyBlocks[i][t].explode(particles); - MyBlocks[i][t].setType(6); - num_destroyed++; - } - } - } - - asw::sound::play(block_break, 64 + num_destroyed * 5, - 100); // 900 + num_destroyed * 80 - - // Settle blocks downwards - for (int i = 0; i < BLOCKS_WIDE; i++) { - int num_blank = BLOCKS_HIGH - 1; - - for (int t = BLOCKS_HIGH - 1; t >= 0; t--) - if (MyBlocks[i][t].getType() != 6) { - MyBlocks[i][num_blank--].setType(MyBlocks[i][t].getType()); - } - - while (num_blank >= 0) { - MyBlocks[i][num_blank--].setType(6); - } - } - - // Settle blocks across - int num_back = 0; - - for (int i = 0; i < BLOCKS_WIDE; i++) { - for (int t = 0; t < BLOCKS_HIGH; t++) { - MyBlocks[i - num_back][t].setType(MyBlocks[i][t].getType()); - } - - if (MyBlocks[i][BLOCKS_HIGH - 1].getType() == 6) { - num_back++; - } - } - - while (num_back > 0) { - for (int t = 0; t < BLOCKS_HIGH; t++) { - MyBlocks[BLOCKS_WIDE - num_back][t].setType(6); - } - - num_back--; - } -} - -// Block at x y -Block* Game::blockAt(int x, int y) { - if (x < BLOCKS_WIDE && y < BLOCKS_HIGH && x >= 0 && y >= 0) { - return &MyBlocks[x][y]; - } - - return nullptr; -} - -// Remaining blocks -int Game::countBlocks() { - int blocks_left = 0; - - for (int i = 0; i < BLOCKS_WIDE; i++) - for (int t = 0; t < BLOCKS_HIGH; t++) - if (MyBlocks[i][t].getType() != 6) { - blocks_left++; - } - - return blocks_left; -} - -// Moves left -int Game::countRemainingMoves() { - int matchesLeft = 0; - - for (int i = 0; i < BLOCKS_WIDE; i++) - for (int t = 0; t < BLOCKS_HIGH; t++) - if (MyBlocks[i][t].getType() != 6) - if ((i < 13 && - MyBlocks[i][t].getType() == MyBlocks[i + 1][t].getType()) || - (i > 0 && - MyBlocks[i][t].getType() == MyBlocks[i - 1][t].getType()) || - (t < 8 && - MyBlocks[i][t].getType() == MyBlocks[i][t + 1].getType()) || - (t > 0 && - MyBlocks[i][t].getType() == MyBlocks[i][t - 1].getType())) { - matchesLeft++; - } - - return matchesLeft; -} - -// Block index -Game::coordinate Game::getBlockIndex(int screen_x, int screen_y) { - for (int i = 0; i < BLOCKS_WIDE; i++) { - for (int t = 0; t < BLOCKS_HIGH; t++) { - if (MyBlocks[i][t].getX() < screen_x && - MyBlocks[i][t].getY() < screen_y && - MyBlocks[i][t].getX() + MyBlocks[i][t].getWidth() > screen_x && - MyBlocks[i][t].getY() + MyBlocks[i][t].getHeight() > screen_y) { - return coordinate(i, t); - } - } - } - - return coordinate(-1, -1); -} - -// Update -void Game::update() { - // Animation for start of game - if (startAnimate > 10) { - startAnimate -= 10; - } else { - startAnimate = 0; - - if (!game_time.isRunning()) - game_time.start(); - } - - // In Game - if (!game_over) { - // Update particles - particles.update(16); - done.update(); - - // Select blocks - if (asw::input::mouse.pressed[1]) { - coordinate selected_block = - getBlockIndex(asw::input::mouse.x, asw::input::mouse.y); - - if (selected_block.x != -1) { // Ensure existing block - if (blocks_selected > 1 && - blockAt(selected_block.x, selected_block.y)->getSelected()) { - destroySelectedBlocks(); - blocks_selected = 0; - } else { - deselectBlocks(); - blocks_selected = selectBlock( - selected_block.x, selected_block.y); // Select and count blocks - - if (!config_double_click && blocks_selected > 1 && - blockAt(selected_block.x, selected_block.y)->getSelected()) { - destroySelectedBlocks(); - blocks_selected = 0; - } - } - } - - // Quit - if (done.isHovering()) { - game_over = true; - gameOverMessage = "Game Over"; - } - } - - // Lose - if (countRemainingMoves() == 0) { - if (countBlocks() == 0) { - gameOverMessage = "You Win!"; - } else { - gameOverMessage = "Out of moves"; - } - - game_over = true; - } - } - - // Game over state - else { - if (game_time.isRunning()) - game_time.stop(); - - // Set score - if (score == 0) { - score = difficulty * (((BLOCKS_WIDE * BLOCKS_HIGH) - countBlocks()) - - game_time.getElapsedTime()); - } else if (score <= 0) { - score = 1; - } - - // Name input - ib_name.focus(); - ib_name.update(); - dialog_yes.update(); - dialog_no.update(); - } -} - -// Draw -void Game::draw() { - // Draws background - asw::draw::sprite(background, 0, 0); - - // Draws Blocks - for (int i = 0; i < 14; i++) { - for (int t = 0; t < 9; t++) { - MyBlocks[i][t].draw(startAnimate); - } - } - - // Draw particles - particles.draw(); - - // Draws foreground - asw::draw::sprite(foreground, 0, 0); - - // Draw done button - done.draw(); - - // Draws text - asw::draw::textRight(font, "Blocks Left: " + std::to_string(countBlocks()), - 1240, 16, asw::util::makeColor(0, 0, 0)); - asw::draw::text( - font, - "Time: " + - std::to_string(game_time.getElapsedTime()), - 40, 16, asw::util::makeColor(0, 0, 0)); - - // End game dialog - if (game_over) { - // Blur background - asw::draw::sprite(trans_overlay, 0, 0); - - // Create gui - asw::draw::sprite(dialog_box, 300, 300); - - asw::draw::sprite(foreground, 0, 0); - - asw::draw::textCenter(font, gameOverMessage, 640, 310, - asw::util::makeColor(0, 0, 0)); - asw::draw::textCenter(font, "Score: " + std::to_string(score), 640, 360, - asw::util::makeColor(0, 0, 0)); - - // Input rectangle - ib_name.draw(); - - // Buttons - dialog_yes.draw(); - dialog_no.draw(); - } - - // Draws Cursor - if (asw::input::mouse.down[1]) { - asw::draw::sprite(cursor[1], asw::input::mouse.x, asw::input::mouse.y); - } else { - asw::draw::sprite(cursor[0], asw::input::mouse.x, asw::input::mouse.y); - } -} diff --git a/src/Init.h b/src/Init.h deleted file mode 100644 index 6795c78..0000000 --- a/src/Init.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * INIT - * Allan Legemaate - * 26/10/2017 - **/ -#ifndef INIT_H -#define INIT_H - -#include - -#include "State.h" - -class Init : public State { - public: - using State::State; - - void init() override; - void update() override; - void draw() override{ - // Nothing to do - }; - void cleanup() override { - // Nothing to do - } -}; - -#endif // INIT_H diff --git a/src/Intro.cpp b/src/Intro.cpp deleted file mode 100644 index 5340171..0000000 --- a/src/Intro.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "Intro.h" - -#include "utility/tools.h" - -void Intro::init() { - // Sets Starting Images - title = asw::assets::loadTexture("assets/images/title.png"); - intro = asw::assets::loadTexture("assets/images/intro.png"); - - // Start timer - timer.start(); -} - -void Intro::update() { - auto time = timer.getElapsedTime(); - - if (time >= 3000 || asw::input::keyboard.anyPressed) { - setNextState(ProgramState::STATE_MENU); - } -} - -void Intro::draw() { - auto time = timer.getElapsedTime(); - - if (time < 1500) { - asw::draw::sprite(intro, 0, 0); - } else { - asw::draw::sprite(title, 0, 0); - } -} diff --git a/src/Intro.h b/src/Intro.h deleted file mode 100644 index 5ff83b1..0000000 --- a/src/Intro.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef INTRO_H -#define INTRO_H - -#include -#include - -#include "State.h" - -// Intro screen of game -class Intro : public State { - public: - using State::State; - - void init() override; - void update() override; - void draw() override; - void cleanup() override{ - // Nothing to do - }; - - private: - asw::Texture intro; - asw::Texture title; - - Timer timer; -}; - -#endif // INTRO_H diff --git a/src/Menu.cpp b/src/Menu.cpp deleted file mode 100644 index 0c2df57..0000000 --- a/src/Menu.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "Menu.h" - -#include "globals.h" -#include "utility/tools.h" - -// INIT -void Menu::init() { - // Sets Cursors - cursor[0] = asw::assets::loadTexture("assets/images/cursor1.png"); - cursor[1] = asw::assets::loadTexture("assets/images/cursor2.png"); - - // Sets help - menuHelp = asw::assets::loadTexture("assets/images/help.png"); - - // Sets main menu - mainmenu = asw::assets::loadTexture("assets/images/mainmenu.png"); - - // Sets the high score table image - highScoresTable = - asw::assets::loadTexture("assets/images/highScoresTable.png"); - - // Trans overlay - trans_overlay = asw::assets::loadTexture("assets/images/overlay.png"); - - // Give score files - highscores = ScoreManager("scores.dat"); - - // Samples - button_hover = asw::assets::loadSample("assets/sounds/button_click.wav"); - button_select = asw::assets::loadSample("assets/sounds/button_select.wav"); - - // Sets Font - font = asw::assets::loadFont("assets/fonts/ariblk.ttf", 24); - - // Sets button positions - btn_start = Button(380, 240); - btn_scores = Button(380, 380); - btn_help = Button(380, 520); - btn_quit = Button(380, 660); - - btn_easy = Button(380, 240); - btn_medium = Button(380, 380); - btn_hard = Button(380, 520); - btn_back = Button(380, 800); - - // Sets button images - btn_start.setImages("assets/images/buttons/start.png", - "assets/images/buttons/start_hover.png"); - btn_scores.setImages("assets/images/buttons/highscores.png", - "assets/images/buttons/highscores_hover.png"); - btn_help.setImages("assets/images/buttons/help.png", - "assets/images/buttons/help_hover.png"); - btn_quit.setImages("assets/images/buttons/quit.png", - "assets/images/buttons/quit_hover.png"); - - btn_easy.setImages("assets/images/buttons/start_easy.png", - "assets/images/buttons/start_easy_hover.png"); - btn_medium.setImages("assets/images/buttons/start_medium.png", - "assets/images/buttons/start_medium_hover.png"); - btn_hard.setImages("assets/images/buttons/start_hard.png", - "assets/images/buttons/start_hard_hover.png"); - btn_back.setImages("assets/images/buttons/back.png", - "assets/images/buttons/back_hover.png"); - - // On clicks - // Start - btn_start.setOnClick([this]() { - menu_state = MENU_DIFFICULTY; - asw::sound::play(button_select); - }); - - // View high scores - btn_scores.setOnClick([this]() { - menu_state = MENU_SCORES; - asw::sound::play(button_select); - }); - - // Start game - btn_help.setOnClick([this]() { - menu_state = MENU_HELP; - asw::sound::play(button_select); - }); - - // Exit - btn_quit.setOnClick([this]() { - setNextState(ProgramState::STATE_EXIT); - asw::sound::play(button_select); - }); - - // Easy - btn_easy.setOnClick([this]() { - difficulty = 3; - setNextState(ProgramState::STATE_GAME); - asw::sound::play(button_select); - }); - - // Medium - btn_medium.setOnClick([this]() { - difficulty = 4; - setNextState(ProgramState::STATE_GAME); - asw::sound::play(button_select); - }); - - // Hard - btn_hard.setOnClick([this]() { - difficulty = 5; - setNextState(ProgramState::STATE_GAME); - asw::sound::play(button_select); - }); - - // Back - btn_back.setOnClick([this]() { - menu_state = MENU_MAIN; - asw::sound::play(button_select); - }); - - // Menu state - menu_state = MENU_MAIN; -} - -// Update -void Menu::update() { - // Menu - if (menu_state == MENU_MAIN) { - btn_start.update(); - btn_scores.update(); - btn_help.update(); - btn_quit.update(); - } else if (menu_state == MENU_DIFFICULTY) { - btn_back.update(); - btn_easy.update(); - btn_medium.update(); - btn_hard.update(); - } else if (menu_state == MENU_HELP || menu_state == MENU_SCORES) { - if (asw::input::mouse.pressed[1] || asw::input::mouse.anyPressed || - asw::input::keyboard.anyPressed) { - menu_state = MENU_MAIN; - } - } -} - -// Draw -void Menu::draw() { - // Draws menu - asw::draw::sprite(mainmenu, 0, 0); - - // Main menu - if (menu_state == MENU_MAIN) { - btn_start.draw(); - btn_help.draw(); - btn_quit.draw(); - btn_scores.draw(); - } else if (menu_state == MENU_DIFFICULTY) { - btn_easy.draw(); - btn_medium.draw(); - btn_hard.draw(); - btn_back.draw(); - } else if (menu_state == MENU_HELP) { - asw::draw::sprite(trans_overlay, 0, 0); - asw::draw::sprite(menuHelp, 36, 98); - } else if (menu_state == MENU_SCORES) { - asw::draw::sprite(trans_overlay, 0, 0); - - asw::draw::sprite(highScoresTable, 318, 100); - - for (int i = 0; i < 10; i++) { - asw::draw::text(font, highscores.getName(i), 400, (i * 50) + 260, - asw::util::makeColor(0, 0, 0)); - asw::draw::text(font, std::to_string(highscores.getScore(i)), 860, - (i * 50) + 260, asw::util::makeColor(0, 0, 0)); - } - } - - // Draws Cursor - if (asw::input::mouse.down[1]) { - asw::draw::sprite(cursor[1], asw::input::mouse.x, asw::input::mouse.y); - } else { - asw::draw::sprite(cursor[0], asw::input::mouse.x, asw::input::mouse.y); - } -} diff --git a/src/Menu.h b/src/Menu.h deleted file mode 100644 index 1a05df2..0000000 --- a/src/Menu.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * MENU - * Allan Legemaate - * 26/10/2017 - **/ -#ifndef MENU_H -#define MENU_H - -#include - -#include "ScoreManager.h" -#include "State.h" -#include "ui/Button.h" - -class Menu : public State { - public: - using State::State; - - void init() override; - void update() override; - void draw() override; - void cleanup() override { - // Nothing to do - } - - private: - // Score table - ScoreManager highscores; - - // Manu state - char menu_state; - - // States - enum menu_states { MENU_MAIN, MENU_DIFFICULTY, MENU_SCORES, MENU_HELP }; - - // Init buttons - Button btn_start, btn_easy, btn_medium, btn_hard, btn_back, btn_help, - btn_quit, btn_scores; - - // Images - asw::Texture mainmenu; - asw::Texture menuHelp; - asw::Texture highScoresTable; - asw::Texture cursor[2]; - asw::Texture trans_overlay; - - // Button sounds - asw::Sample button_hover; - asw::Sample button_select; - - // Fonts - asw::Font font; -}; - -#endif // INIT_H diff --git a/src/Particle.cpp b/src/Particle.cpp index a77aabd..6042f6e 100644 --- a/src/Particle.cpp +++ b/src/Particle.cpp @@ -1,93 +1,59 @@ #include "Particle.h" -// Constructor -Particle::Particle(int x, - int y, - vec2 velocity, - vec2 acceleration, - vec2 size, - asw::Color colorStart, - asw::Color colorEnd, - int life, - char type, - bool transLife) { - this->x = x; - this->y = y; +Particle& Particle::setPosition(const asw::Vec2& position) { + transform.position = position; + return *this; +} - this->velocity = velocity; - this->acceleration = acceleration; +Particle& Particle::setSize(const asw::Vec2& size) { + transform.size = size; + return *this; +} - this->colorStart = colorStart; - this->colorEnd = colorEnd; - this->color = colorStart; +Particle& Particle::setVelocity(const asw::Vec2& velocity) { + this->velocity = velocity; + return *this; +} - this->size = size; +Particle& Particle::setAcceleration(const asw::Vec2& acceleration) { + this->acceleration = acceleration; + return *this; +} +Particle& Particle::setLife(float life) { this->life = life; - this->lifeStart = life; - - this->image = nullptr; - - this->type = type; - this->transLife = transLife; - - mixColors(); + return *this; } -// Make new color -void Particle::mixColors() { - // For readability - int c_red = - (colorStart.r * life + colorEnd.r * (lifeStart - life)) / lifeStart; - int c_green = - (colorStart.g * life + colorEnd.g * (lifeStart - life)) / lifeStart; - int c_blue = - (colorStart.b * life + colorEnd.b * (lifeStart - life)) / lifeStart; - int c_alpha = - (colorStart.a * life + colorEnd.a * (lifeStart - life)) / lifeStart; - - this->color = asw::util::makeColor(c_red, c_green, c_blue, c_alpha); +Particle& Particle::setImage(const asw::Texture& image) { + this->image = image; + return *this; } // Is dead bool Particle::isDead() const { - return (life <= 0); -} - -// Set image -void Particle::setImage(asw::Texture image) { - this->image = image; - this->type = IMAGE; + return life <= 0; } // Logic -void Particle::update(int dt) { - if (!isDead()) { - life -= dt; - x += velocity.x * dt; - y += velocity.y * dt; - velocity.x += acceleration.x * dt; - velocity.y += acceleration.y * dt; - mixColors(); +void Particle::update(float deltaTime) { + if (isDead()) { + return; } + + life -= deltaTime; + transform.position.x += velocity.x * deltaTime; + transform.position.y += velocity.y * deltaTime; + velocity.x += acceleration.x * deltaTime; + velocity.y += acceleration.y * deltaTime; } // Draw -void Particle::draw() { - if (!isDead()) { - // if (transLife) { - // set_trans_blender(255, 255, 255, int(float(life) / lifeStart * 255)); - // } - - if (type == IMAGE && image != nullptr) { - auto imageSize = asw::util::getTextureSize(image); - asw::draw::sprite(image, x - imageSize.x / 2, y - imageSize.y / 2); - } else if (type == PIXEL) { - asw::draw::point(x, y, color); - } else if (type == CIRCLE) { - asw::draw::circleFill(x, y, size.x / 2, color); - } else if (type == RECTANGLE) { - asw::draw::rectFill(x, y, size.x, size.y, color); - } +void Particle::draw() const { + if (isDead()) { + return; } + + auto image_size = asw::util::getTextureSize(image) / 2; + asw::draw::sprite(image, transform.position + image_size); } diff --git a/src/Particle.h b/src/Particle.h index 6ef0413..5a47a7f 100644 --- a/src/Particle.h +++ b/src/Particle.h @@ -7,64 +7,40 @@ #ifndef PARTICLE_H #define PARTICLE_H -#define CIRCLE 0 -#define RECTANGLE 1 -#define PIXEL 2 -#define RANDOM 3 -#define IMAGE 4 - #include -#include "vec2.h" class Particle { public: - Particle(int x = 0, - int y = 0, - vec2 velocity = vec2(0), - vec2 acceleration = vec2(0), - vec2 size = vec2(1), - asw::Color colorStart = {255, 255, 255, 0}, - asw::Color colorEnd = {255, 255, 255, 0}, - int life = 100, - char type = PIXEL, - bool transLife = false); + Particle() = default; + + // Chaining setters + Particle& setPosition(const asw::Vec2& position); + Particle& setSize(const asw::Vec2& size); + Particle& setVelocity(const asw::Vec2& velocity); + Particle& setAcceleration(const asw::Vec2& acceleration); + Particle& setLife(float life); + Particle& setImage(const asw::Texture& image); // Is dead bool isDead() const; - // Set image - void setImage(asw::Texture image); - // Update - void update(int dt); + void update(float deltaTime); // Draw - void draw(); - - // Make new color - void mixColors(); + void draw() const; - // Position / size - float x; - float y; - vec2 size; - - // Looks - asw::Color colorStart; - asw::Color colorEnd; - asw::Color color; - char type; - bool transLife; + private: + /// @brief Transform coordinates + asw::Quad transform; + /// @brief Image asw::Texture image; // Behavior - vec2 velocity; - vec2 acceleration; - int life; - int lifeStart; - - private: + asw::Vec2 velocity{0, 0}; + asw::Vec2 acceleration{0, 0}; + float life{100.0F}; }; #endif diff --git a/src/ParticleEmitter.cpp b/src/ParticleEmitter.cpp index d8e45b0..14d8bb1 100644 --- a/src/ParticleEmitter.cpp +++ b/src/ParticleEmitter.cpp @@ -1,54 +1,58 @@ #include "ParticleEmitter.h" #include +#include -#include "utility/tools.h" - -// Constructor basic -ParticleEmitter::ParticleEmitter() : ParticleEmitter(vec2(0, 0), vec2(0, 0)) {} +ParticleEmitter& ParticleEmitter::setPosition( + const asw::Vec2& position) { + transform.position = position; + return *this; +} -// Constructor with parameters -ParticleEmitter::ParticleEmitter(vec2 position, vec2 size) - : position(position), size(size), launchVelocity(vec2(-10, 10)), type(0) { - // Load image - images[0] = asw::assets::loadTexture("assets/images/fuzzball.png"); - images[1] = asw::assets::loadTexture("assets/images/fuzzball2.png"); - images[2] = asw::assets::loadTexture("assets/images/fuzzball_blue.png"); +ParticleEmitter& ParticleEmitter::setSize(const asw::Vec2& size) { + transform.size = size; + return *this; } // Create particles void ParticleEmitter::createParticle() { - Particle newPart( - random(position.x, position.x + size.x), - random(position.y, position.y + size.y), - vec2(randomf(-0.1, 0.2), randomf(-0.4, -0.9)), vec2(0.008, -0.008), - vec2(random(5, 10)), asw::util::makeColor(255, 255, 255), - asw::util::makeColor(0, 0, 0), random(500, 1000), IMAGE, true); - newPart.setImage(images[0]); - particles.push_back(newPart); -} - -// Set emitter position -void ParticleEmitter::setPosition(vec2 position) { - this->position = position; + auto part_size = asw::random::between(5.0F, 10.0F); + auto random_x = asw::random::between(transform.position.x, + transform.position.x + transform.size.x); + auto random_y = asw::random::between(transform.position.y, + transform.position.y + transform.size.y); + + const Particle particle = + Particle() + .setPosition({random_x, random_y}) + .setSize({part_size, part_size}) + .setVelocity(asw::Vec2(asw::random::between(-0.1F, 0.2F), + asw::random::between(-0.4F, -0.9F))) + .setAcceleration(asw::Vec2(0.008, -0.008)) + .setLife(asw::random::between(500.0F, 1000.0F)) + .setImage(image); + + particles.push_back(particle); } // Update -void ParticleEmitter::update(int dt) { - // Update each particle - for (unsigned int i = 0; i < particles.size(); i++) { - if (particles.at(i).isDead() || - asw::input::keyboard.down[SDL_SCANCODE_BACKSPACE]) { - particles.erase(particles.begin() + i); - } else { - particles.at(i).update(dt / 4); - } +void ParticleEmitter::update(float deltaTime) { + // Erase dead particles + particles.erase(std::remove_if(particles.begin(), particles.end(), + [](const Particle& particle) { + return particle.isDead(); + }), + particles.end()); + + // Update particles + for (auto& particle : particles) { + particle.update(deltaTime / 4.0F); } } // Draw -void ParticleEmitter::draw() { - for (unsigned int i = 0; i < particles.size(); i++) { - particles.at(i).draw(); +void ParticleEmitter::draw() const { + for (const auto& particle : particles) { + particle.draw(); } } diff --git a/src/ParticleEmitter.h b/src/ParticleEmitter.h index a431598..2f05b3d 100644 --- a/src/ParticleEmitter.h +++ b/src/ParticleEmitter.h @@ -10,45 +10,39 @@ #include #include "Particle.h" -#include "vec2.h" class ParticleEmitter { public: // Constructor - ParticleEmitter(); - ParticleEmitter(vec2 position, vec2 size); + ParticleEmitter() { + image = asw::assets::loadTexture("assets/images/fuzzball.png"); + }; + + // Set transform + ParticleEmitter& setPosition(const asw::Vec2& position); + ParticleEmitter& setSize(const asw::Vec2& size); // Create particle void createParticle(); - // Set position - void setPosition(vec2 position); - // Update - void update(int dt); + void update(float deltaTime); // Draw - void draw(); + void draw() const; private: // Emitter area - vec2 position; - vec2 size; - - // Velocity - vec2 launchVelocity; - - // Type - int type; - - // Fuzzy image - asw::Texture images[3]; + asw::Quad transform; // Create particle - void createParticle(int x, int y); + void createParticle(asw::Vec2 position); // Particles std::vector particles; + + // Texture + asw::Texture image; }; #endif // PARTICLE_EMITTER_H diff --git a/src/ScoreManager.h b/src/ScoreManager.h index c5e7626..9a320ac 100644 --- a/src/ScoreManager.h +++ b/src/ScoreManager.h @@ -15,8 +15,6 @@ class ScoreManager { ScoreManager(); explicit ScoreManager(const std::string& path); - virtual ~ScoreManager(){}; - void add(const std::string& name, int score); int getScore(int index) const; diff --git a/src/State.cpp b/src/State.cpp deleted file mode 100644 index d50a558..0000000 --- a/src/State.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "State.h" - -#include - -#include "Game.h" -#include "Init.h" -#include "Intro.h" -#include "Menu.h" - -/***************** - * STATE ENGINE - *****************/ - -// Draw -void StateEngine::draw() const { - if (state) { - // Clear screen - SDL_RenderClear(asw::display::renderer); - - state->draw(); - - // Update screen - SDL_RenderPresent(asw::display::renderer); - } -} - -// Update -void StateEngine::update() { - if (state) { - state->update(); - } - - changeState(); -} - -// Set next state -void StateEngine::setNextState(const ProgramState newState) { - nextState = newState; -} - -// Get state id -ProgramState StateEngine::getStateId() const { - return currentState; -} - -// Change game screen -void StateEngine::changeState() { - // If the state needs to be changed - if (nextState == ProgramState::STATE_NULL) { - return; - } - - // Delete the current state - if (state) { - state->cleanup(); - state = nullptr; - } - - // Change the state - switch (nextState) { - case ProgramState::STATE_GAME: - state = std::make_unique(*this); - std::cout << "Switched state to game." << std::endl; - break; - - case ProgramState::STATE_MENU: - state = std::make_unique(*this); - std::cout << "Switched state to main menu." << std::endl; - break; - - case ProgramState::STATE_INIT: - state = std::make_unique(*this); - std::cout << "Switched state to init." << std::endl; - break; - - case ProgramState::STATE_INTRO: - state = std::make_unique(*this); - std::cout << "Switched state to intro." << std::endl; - break; - - default: - std::cout << "Exiting program." << std::endl; - break; - } - - state->init(); - - // Change the current state ID - currentState = nextState; - - // NULL the next state ID - nextState = ProgramState::STATE_NULL; -} - -/********* - * STATE - *********/ - -// Change state -void State::setNextState(const ProgramState state) { - this->engine.setNextState(state); -} \ No newline at end of file diff --git a/src/State.h b/src/State.h deleted file mode 100644 index 25ef564..0000000 --- a/src/State.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - * State for machine and State Engine - * Allan Legemaate - * 30/12/2016 - * Compartmentalize program into states - * which can handle only their own logic, - * drawing and transitions - */ - -#ifndef STATE_H -#define STATE_H - -#include -#include - -// Class -class State; - -// Game states -enum class ProgramState { - STATE_INIT, - STATE_GAME, - STATE_MENU, - STATE_INTRO, - STATE_EXIT, - STATE_NULL -}; - -/***************** - * STATE ENGINE - *****************/ -class StateEngine { - public: - // Update - void update(); - - // Draw - void draw() const; - - // Set next state - void setNextState(const ProgramState state); - - // Get state id - ProgramState getStateId() const; - - private: - // Change state - void changeState(); - - // Next state - ProgramState nextState{ProgramState::STATE_NULL}; - - // State id - ProgramState currentState{ProgramState::STATE_NULL}; - - // Stores states - std::unique_ptr state{nullptr}; -}; - -/********* - * STATE - *********/ -class State { - public: - // Constructor - explicit State(StateEngine& engine) : engine(engine){}; - - virtual ~State() = default; - - // Init the state - virtual void init() = 0; - - // Draw to screen - virtual void draw() = 0; - - // Cleanup - virtual void cleanup() = 0; - - // Update logic - virtual void update() = 0; - - // Change state - void setNextState(const ProgramState state); - - private: - StateEngine& engine; -}; - -#endif // STATE_H diff --git a/src/globals.cpp b/src/globals.cpp index 899b0d1..d02d638 100644 --- a/src/globals.cpp +++ b/src/globals.cpp @@ -2,9 +2,6 @@ #include -int NATIVE_SCREEN_W = 1280; -int NATIVE_SCREEN_H = 960; - // Config bool config_sound = true; int config_max_FPS = 100; diff --git a/src/globals.h b/src/globals.h index 53a73ab..10ba7bc 100644 --- a/src/globals.h +++ b/src/globals.h @@ -9,9 +9,6 @@ #include #include -extern int NATIVE_SCREEN_W; -extern int NATIVE_SCREEN_H; - // Config extern bool config_sound; extern int config_max_FPS; diff --git a/src/main.cpp b/src/main.cpp index cc330ef..07c3016 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,102 +1,33 @@ // Includes #include -#include -#include +#include "./state/Game.h" +#include "./state/Init.h" +#include "./state/Intro.h" +#include "./state/Menu.h" +#include "./state/States.h" -#ifdef __EMSCRIPTEN__ -#include -#include -#endif - -// For state engine -#include "State.h" - -using namespace std::chrono_literals; -using namespace std::chrono; -constexpr nanoseconds timestep(16ms); - -// State engine -std::unique_ptr state; - -// Functions -void setup(); -void draw(); -void update(); - -// FPS system -int fps = 0; -int frames_done = 0; +// Main function*/ +int main() { + // Game state object + asw::scene::SceneManager app; -// Setup game -void setup() { - // Load allegro library + // Load asw library asw::core::init(1280, 960); - state = std::make_unique(); -} - -// Update -void update() { - // Update core - asw::core::update(); - - // Do state logic - state->update(); - - // Handle exit - if (state->getStateId() == ProgramState::STATE_EXIT) { - asw::core::exit = true; - } -} - -// Do state rendering -void draw() { - state->draw(); -} - -// Loop (emscripten compatibility) -#ifdef __EMSCRIPTEN__ -void loop() { - update(); - draw(); -} -#endif - -// Main function*/ -int main(int argc, char* argv[]) { - // Setup basic functionality - setup(); - - // Set the current state ID - state->setNextState(ProgramState::STATE_INIT); + // Init states + app.registerScene(States::Init, app); + app.registerScene(States::Intro, app); + app.registerScene(States::Menu, app); + app.registerScene(States::Game, app); + app.setNextScene(States::Init); // Background Music - asw::Sample music = asw::assets::loadSample("assets/sounds/music.ogg"); + const auto music = asw::assets::loadSample("assets/sounds/music.ogg"); asw::sound::play(music, 255, 128, -1); -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(loop, 0, 1); -#else - - using clock = high_resolution_clock; - nanoseconds lag(0ns); - auto time_start = clock::now(); - - while (!asw::input::keyboard.down[SDL_SCANCODE_ESCAPE] && !asw::core::exit) { - auto delta_time = clock::now() - time_start; - time_start = clock::now(); - lag += duration_cast(delta_time); - - while (lag >= timestep) { - lag -= timestep; - update(); - } - - draw(); - frames_done++; - } -#endif + // Start app + app.start(); return 0; } diff --git a/src/state/Game.cpp b/src/state/Game.cpp new file mode 100644 index 0000000..57249fd --- /dev/null +++ b/src/state/Game.cpp @@ -0,0 +1,376 @@ +#include "./Game.h" + +#include "../globals.h" + +// INIT +void Game::init() { + // Sets background + createObject()->setTexture( + asw::assets::loadTexture("assets/images/background.png")); + + // Sets Foreground + foreground = asw::assets::loadTexture("assets/images/foreground.png"); + + // Sets menu + dialog_box = asw::assets::loadTexture("assets/images/menu.png"); + + // Trans overlay + trans_overlay = asw::assets::loadTexture("assets/images/overlay.png"); + + // Sets Cursors + cursor[0] = asw::assets::loadTexture("assets/images/cursor1.png"); + cursor[1] = asw::assets::loadTexture("assets/images/cursor2.png"); + + // Sets Sounds + block_break = asw::assets::loadSample("assets/sounds/break.wav"); + click = asw::assets::loadSample("assets/sounds/click.wav"); + + // Sets Font + font = asw::assets::loadFont("assets/fonts/ariblk.ttf", 24); + + // Sets Variables + score = 0; + startAnimate = 1200.0F; + blocks_selected = 0; + game_over = false; + gameOverMessage = "Game Over"; + + // Sets block info + for (int i = 0; i < 14; i++) { + for (int t = 0; t < 9; t++) { + auto position = asw::Vec2((i * 80) + 80, (t * 80) + 80); + tiles[i][t] = Block(position, asw::random::between(0, difficulty)); + } + } + + // Give score files + highscores = ScoreManager("scores.dat"); + + // Buttons + done = Button() + .setPosition({500, 10}) + .setImages("assets/images/buttons/done.png", + "assets/images/buttons/done_hover.png") + .setOnClick([this]() { game_over = true; }); + + dialog_yes = Button() + .setPosition({41 + 300, 215 + 300}) + .setImages("assets/images/buttons/yes.png", + "assets/images/buttons/yes_hover.png") + .setOnClick([this]() { + highscores.add(ib_name.getValue(), score); + sceneManager.setNextScene(States::Game); + }); + + dialog_no = Button() + .setPosition({461 + 300, 215 + 300}) + .setImages("assets/images/buttons/no.png", + "assets/images/buttons/no_hover.png") + .setOnClick([this]() { + highscores.add(ib_name.getValue(), score); + sceneManager.setNextScene(States::Menu); + }); + + ib_name = InputBox({488, 405, 404, 44}, font, "Player"); +} + +// Deselect all blocks +void Game::deselectBlocks() { + for (auto& row : tiles) { + for (auto& block : row) { + block.setSelected(false); + } + } +} + +// Select group of blocks +int Game::selectBlock(int x, int y, int type = -1) { + if (x >= BLOCKS_WIDE || y >= BLOCKS_HIGH || x < 0 || y < 0) { + return 0; + } + + auto& tile = tiles[x][y]; + + if ((type == -1 || tile.getType() == type) && !tile.getSelected() && + tile.getType() != 6) { + // Select it + tile.setSelected(true); + + // Select 4 around it + const int btype = tile.getType(); + + return 1 + selectBlock(x - 1, y, btype) + selectBlock(x + 1, y, btype) + + selectBlock(x, y - 1, btype) + selectBlock(x, y + 1, btype); + } + + return 0; +} + +// Destroy selected +void Game::destroySelectedBlocks() { + // Remove blocks + int num_destroyed = 0; + + for (auto& row : tiles) { + for (auto& block : row) { + if (block.getSelected()) { + block.setSelected(false); + block.explode(particles); + block.setType(6); + num_destroyed++; + } + } + } + + asw::sound::play(block_break, 64 + num_destroyed * 5, + 100); // 900 + num_destroyed * 80 + + // Settle blocks downwards + for (int i = 0; i < BLOCKS_WIDE; i++) { + int num_blank = BLOCKS_HIGH - 1; + + for (int t = BLOCKS_HIGH - 1; t >= 0; t--) + if (tiles[i][t].getType() != 6) { + tiles[i][num_blank--].setType(tiles[i][t].getType()); + } + + while (num_blank >= 0) { + tiles[i][num_blank--].setType(6); + } + } + + // Settle blocks across + int num_back = 0; + + for (int i = 0; i < BLOCKS_WIDE; i++) { + for (int t = 0; t < BLOCKS_HIGH; t++) { + tiles[i - num_back][t].setType(tiles[i][t].getType()); + } + + if (tiles[i][BLOCKS_HIGH - 1].getType() == 6) { + num_back++; + } + } + + while (num_back > 0) { + for (int t = 0; t < BLOCKS_HIGH; t++) { + tiles[BLOCKS_WIDE - num_back][t].setType(6); + } + + num_back--; + } +} + +// Block at x y +Block* Game::blockAt(int x, int y) { + if (x < BLOCKS_WIDE && y < BLOCKS_HIGH && x >= 0 && y >= 0) { + return &tiles[x][y]; + } + + return nullptr; +} + +// Remaining blocks +int Game::countBlocks() { + int blocks_left = 0; + + for (auto& row : tiles) { + for (auto& block : row) { + if (block.getType() != 6) { + blocks_left++; + } + } + } + + return blocks_left; +} + +// Moves left +bool Game::hasRemainingMoves() { + for (int i = 0; i < BLOCKS_WIDE; i++) { + for (int t = 0; t < BLOCKS_HIGH; t++) { + if (tiles[i][t].getType() == 6) { + continue; + } + + if (i < BLOCKS_WIDE - 1 && + tiles[i][t].getType() == tiles[i + 1][t].getType()) { + return true; + } + + if (t < BLOCKS_HIGH - 1 && + tiles[i][t].getType() == tiles[i][t + 1].getType()) { + return true; + } + } + } + + return false; +} + +// Block index +asw::Vec2 Game::getBlockIndex(float screen_x, float screen_y) { + for (int i = 0; i < BLOCKS_WIDE; i++) { + for (int t = 0; t < BLOCKS_HIGH; t++) { + if (tiles[i][t].getTransform().contains({screen_x, screen_y})) { + return {i, t}; + } + } + } + + return {-1, -1}; +} + +// Update +void Game::update(float deltaTime) { + Scene::update(deltaTime); + + // Animation for start of game + if (startAnimate > 10) { + startAnimate -= deltaTime; + } else { + startAnimate = 0; + if (!game_time.isRunning()) { + game_time.start(); + } + } + + // In Game + if (!game_over) { + // Update particles + particles.update(deltaTime); + // done.update(); + + // Update Tiles + for (auto& row : tiles) { + for (auto& tile : row) { + tile.update(deltaTime); + } + } + + // Select blocks + if (asw::input::wasButtonPressed(asw::input::MouseButton::LEFT)) { + auto selected_block = + getBlockIndex(asw::input::mouse.x, asw::input::mouse.y); + + if (blocks_selected > 1 && + blockAt(selected_block.x, selected_block.y)->getSelected()) { + destroySelectedBlocks(); + blocks_selected = 0; + } else { + deselectBlocks(); + blocks_selected = selectBlock( + selected_block.x, selected_block.y); // Select and count blocks + + if (!config_double_click && blocks_selected > 1 && + blockAt(selected_block.x, selected_block.y)->getSelected()) { + destroySelectedBlocks(); + blocks_selected = 0; + } + } + + // Quit + if (done.isHovering()) { + game_over = true; + gameOverMessage = "Game Over"; + } + } + + // Lose + if (!hasRemainingMoves()) { + if (countBlocks() == 0) { + gameOverMessage = "You Win!"; + } else { + gameOverMessage = "Out of moves"; + } + + // Set score + if (score == 0) { + score = difficulty * (((BLOCKS_WIDE * BLOCKS_HIGH) - countBlocks()) - + game_time.getElapsedTime()); + } else if (score <= 0) { + score = 1; + } + + game_over = true; + + ib_name.focus(); + } + } + + // Game over state + else { + if (game_time.isRunning()) { + game_time.stop(); + } + + // Name input + ib_name.update(); + dialog_yes.update(deltaTime); + dialog_no.update(deltaTime); + } +} + +// Draw +void Game::draw() { + Scene::draw(); + + // Draws Tiles + for (auto& row : tiles) { + for (auto& tile : row) { + tile.draw(startAnimate); + } + } + + // Draw particles + particles.draw(); + + // Draws foreground + asw::draw::sprite(foreground, asw::Vec2(0, 0)); + + // Draw done button + done.draw(); + + // Draws text + asw::draw::textRight(font, "Blocks Left: " + std::to_string(countBlocks()), + asw::Vec2(1240, 16), + asw::util::makeColor(0, 0, 0)); + asw::draw::text( + font, + "Time: " + std::to_string(static_cast( + game_time.getElapsedTime())), + asw::Vec2(40, 16), asw::util::makeColor(0, 0, 0)); + + // End game dialog + if (game_over) { + // Blur background + asw::draw::sprite(trans_overlay, asw::Vec2(0, 0)); + + // Create gui + asw::draw::sprite(dialog_box, asw::Vec2(300, 300)); + + asw::draw::sprite(foreground, asw::Vec2(0, 0)); + + asw::draw::textCenter(font, gameOverMessage, asw::Vec2(640, 310), + asw::util::makeColor(0, 0, 0)); + asw::draw::textCenter(font, "Score: " + std::to_string(score), + asw::Vec2(640, 360), + asw::util::makeColor(0, 0, 0)); + + // Input rectangle + ib_name.draw(); + + // Buttons + dialog_yes.draw(); + dialog_no.draw(); + } + + // Draws Cursor + if (asw::input::isButtonDown(asw::input::MouseButton::LEFT)) { + asw::draw::sprite( + cursor[1], asw::Vec2(asw::input::mouse.x, asw::input::mouse.y)); + } else { + asw::draw::sprite( + cursor[0], asw::Vec2(asw::input::mouse.x, asw::input::mouse.y)); + } +} diff --git a/src/Game.h b/src/state/Game.h similarity index 56% rename from src/Game.h rename to src/state/Game.h index e5bac18..250fe4f 100644 --- a/src/Game.h +++ b/src/state/Game.h @@ -6,49 +6,38 @@ #ifndef GAME_H #define GAME_H -#define BLOCKS_WIDE 14 -#define BLOCKS_HIGH 9 -#define MAX_BLOCK_DIMENSION 1000 - #include #include +#include -#include "State.h" +#include "../Block.h" +#include "../Particle.h" +#include "../ParticleEmitter.h" +#include "../ScoreManager.h" +#include "../ui/Button.h" +#include "../ui/InputBox.h" +#include "./States.h" -#include "Block.h" -#include "Particle.h" -#include "ParticleEmitter.h" -#include "ScoreManager.h" -#include "ui/Button.h" -#include "ui/InputBox.h" +constexpr int BLOCKS_WIDE = 14; +constexpr int BLOCKS_HIGH = 9; +constexpr int MAX_BLOCK_DIMENSION = 1000; -class Game : public State { +class Game : public asw::scene::Scene { public: - using State::State; + using asw::scene::Scene::Scene; void init() override; - void update() override; + + void update(float deltaTime) override; + void draw() override; - void cleanup() override { - // Nothing to do - } private: - struct coordinate { - coordinate(int x, int y) { - this->x = x; - this->y = y; - } - int x; - int y; - }; - // Init the blocks on screen - Block MyBlocks[BLOCKS_WIDE][BLOCKS_HIGH]; + std::array, BLOCKS_WIDE> tiles; // Images - asw::Texture background; - asw::Texture cursor[2]; + std::array cursor; asw::Texture foreground; asw::Texture dialog_box; asw::Texture trans_overlay; @@ -65,7 +54,7 @@ class Game : public State { // Variables int score; - int startAnimate; + float startAnimate; int blocks_selected; bool game_over; std::string gameOverMessage; @@ -80,10 +69,10 @@ class Game : public State { void deselectBlocks(); int selectBlock(int x, int y, int type); Block* blockAt(int x, int y); - coordinate getBlockIndex(int screen_x, int screen_y); + asw::Vec2 getBlockIndex(float screen_x, float screen_y); void destroySelectedBlocks(); int countBlocks(); - int countRemainingMoves(); + bool hasRemainingMoves(); // Particles ParticleEmitter particles; diff --git a/src/Init.cpp b/src/state/Init.cpp similarity index 63% rename from src/Init.cpp rename to src/state/Init.cpp index 50bb234..0de8b83 100644 --- a/src/Init.cpp +++ b/src/state/Init.cpp @@ -1,9 +1,8 @@ -#include "Init.h" +#include "./Init.h" #include -#include "globals.h" -#include "utility/tools.h" +#include "../globals.h" // Init state (and game) void Init::init() { @@ -15,7 +14,7 @@ void Init::init() { } // Update -void Init::update() { +void Init::update(float _deltaTime) { // Goto splash - setNextState(ProgramState::STATE_INTRO); + sceneManager.setNextScene(States::Intro); } diff --git a/src/state/Init.h b/src/state/Init.h new file mode 100644 index 0000000..240191e --- /dev/null +++ b/src/state/Init.h @@ -0,0 +1,22 @@ +/** + * INIT + * Allan Legemaate + * 26/10/2017 + **/ +#ifndef INIT_H +#define INIT_H + +#include + +#include "./States.h" + +class Init : public asw::scene::Scene { + public: + using asw::scene::Scene::Scene; + + void init() override; + + void update(float deltaTime) override; +}; + +#endif // INIT_H diff --git a/src/state/Intro.cpp b/src/state/Intro.cpp new file mode 100644 index 0000000..ac9cd7f --- /dev/null +++ b/src/state/Intro.cpp @@ -0,0 +1,41 @@ +#include "./Intro.h" + +void Intro::init() { + // Intro + intro = createObject(); + intro->setTexture(asw::assets::loadTexture("assets/images/intro.png")); + + // Title + title = createObject(); + title->setTexture(asw::assets::loadTexture("assets/images/title.png")); + + // Start timer + timer.start(); +} + +void Intro::update(float deltaTime) { + Scene::update(deltaTime); + + auto time = timer.getElapsedTime(); + + intro->active = time < 1000; + title->active = time > 1000; + + if (time < 200) { + intro->alpha = + asw::util::lerp(0.0F, 1.0F, static_cast(time) / 200.0F); + } else if (time > 800 && time < 1000) { + intro->alpha = + asw::util::lerp(1.0F, 0.0F, static_cast(time - 800) / 200.0F); + } else if (time > 1000 && time < 1200) { + title->alpha = + asw::util::lerp(0.0F, 1.0F, static_cast(time - 1000) / 200.0F); + } else if (time > 2800 && time < 3000) { + title->alpha = + asw::util::lerp(1.0F, 0.0F, static_cast(time - 2800) / 200.0F); + } + + if (time >= 3000 || asw::input::keyboard.anyPressed) { + sceneManager.setNextScene(States::Menu); + } +} diff --git a/src/state/Intro.h b/src/state/Intro.h new file mode 100644 index 0000000..618ff66 --- /dev/null +++ b/src/state/Intro.h @@ -0,0 +1,26 @@ +#ifndef INTRO_H +#define INTRO_H + +#include +#include +#include + +#include "./States.h" + +// Intro screen of game +class Intro : public asw::scene::Scene { + public: + using asw::scene::Scene::Scene; + + void init() override; + + void update(float deltaTime) override; + + private: + std::shared_ptr intro; + std::shared_ptr title; + + Timer timer; +}; + +#endif // INTRO_H diff --git a/src/state/Menu.cpp b/src/state/Menu.cpp new file mode 100644 index 0000000..c38863c --- /dev/null +++ b/src/state/Menu.cpp @@ -0,0 +1,170 @@ +#include "./Menu.h" + +#include "../globals.h" + +// INIT +void Menu::init() { + // Sets main menu + createObject()->setTexture( + asw::assets::loadTexture("assets/images/mainmenu.png")); + + // Sets Cursors + cursor[0] = asw::assets::loadTexture("assets/images/cursor1.png"); + cursor[1] = asw::assets::loadTexture("assets/images/cursor2.png"); + + // Sets help + menu_help = createObject(); + menu_help->setTexture(asw::assets::loadTexture("assets/images/help.png")); + menu_help->transform.position = asw::Vec2(36, 98); + + // Sets the high score table image + high_scores_table = createObject(); + high_scores_table->setTexture( + asw::assets::loadTexture("assets/images/highScoresTable.png")); + high_scores_table->transform.position = asw::Vec2(318, 100); + + // Trans overlay + trans_overlay = createObject(); + trans_overlay->setTexture( + asw::assets::loadTexture("assets/images/overlay.png")); + + // Give score files + highscores = ScoreManager("scores.dat"); + + // Samples + button_hover = asw::assets::loadSample("assets/sounds/button_click.wav"); + button_select = asw::assets::loadSample("assets/sounds/button_select.wav"); + + // Sets Font + font = asw::assets::loadFont("assets/fonts/ariblk.ttf", 24); + + // Sets button positions + btn_start = createObject