diff --git a/CMakeLists.txt b/CMakeLists.txt index a97f83b..c27cf2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,9 @@ set(SFML_STATIC_LIBRARIES TRUE) #set to FALSE if you have sfml installed from pa # You need to build SFML from sources with cmake if (SFML_STATIC_LIBRARIES) set(SFML_LIB_DIR - ${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-graphics.so - ${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-system.so - ${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-window.so) + ${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-graphics.so.2.5 + ${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-system.so.2.5 + ${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-window.so.2.5) set(SFML_INCL_DIR ${CMAKE_SOURCE_DIR}/SFML-2.5.1/include) include_directories(${SFML_INCL_DIR}) diff --git a/application.cpp b/application.cpp index 7264ef5..a70e43d 100644 --- a/application.cpp +++ b/application.cpp @@ -6,6 +6,7 @@ const sf::Time TIME_PER_SECOND = sf::seconds(1.f / 60.f); Application::Application(unsigned int window_width, unsigned int window_height) : + current_state(STATE::PLAY), render_window({window_width, window_height}, "Sliding Puzzle") {} @@ -37,30 +38,65 @@ void Application::processInput() { sf::Event event; while (render_window.pollEvent(event)) - { - switch (event.type) + { + switch (current_state) { - case sf::Event::Closed: - render_window.close(); - break; - - case sf::Event::KeyPressed: - // If already won, do nothing - if (board.isWinCondition()) - return; - // Go to selection mode - if (event.key.code == sf::Keyboard::Z) - board.onSelectionMode(); - else // or just move cursor to next tile - board.moveSelection(getDirection(event.key.code)); + case STATE::PLAY: + onGameState(event); break; - default: + case STATE::WIN: + onWinState(event); break; } } } +void Application::onGameState(sf::Event& event) +{ + switch (event.type) + { + case sf::Event::Closed: + render_window.close(); + break; + + case sf::Event::KeyPressed: + // Go to selection mode + if (event.key.code == sf::Keyboard::Z) + board.onSelectionMode(); + else // if it wasn't selection mode, then try to handle direction + board.moveSelection(getDirection(event.key.code)); + break; + + default: // otherwise - do nothing + break; + } + + if (board.isWinCondition()) + { + current_state = STATE::WIN; + board.setCursorVisibility(false); + } +} + +void Application::onWinState(sf::Event& event) +{ + switch (event.type) + { + case sf::Event::Closed: + render_window.close(); + break; + + case sf::Event::KeyPressed: + if (event.key.code == sf::Keyboard::Z) + render_window.close(); + break; + + default: + break; + } +} + DIRECTION Application::getDirection(sf::Keyboard::Key &key) const { switch (key) diff --git a/application.h b/application.h index 710d9ba..97d3e07 100644 --- a/application.h +++ b/application.h @@ -6,6 +6,13 @@ class Application { public: + + enum class STATE + { + PLAY, + WIN + } current_state; + explicit Application(unsigned int window_width, unsigned int window_height); // Init game @@ -20,6 +27,12 @@ public: // Handle keyboard commands void processInput(); + // Handle events for player to move cursor and swap tiles + void onGameState(sf::Event& event); + + // Handle events for winning state after the image gets assembled + void onWinState(sf::Event& event); + private: // Convert keyboard keys into moving direction DIRECTION getDirection(sf::Keyboard::Key &key) const; diff --git a/board.cpp b/board.cpp index a45989e..6d6e2b6 100644 --- a/board.cpp +++ b/board.cpp @@ -4,8 +4,18 @@ Board::Board() : selection_index(0), - on_selection(false) -{} + solved_tiles(0), + on_selection(false), + is_cursor_visible(true) +{ + std::size_t rect_size = 5; + rect_selection = sf::VertexArray(sf::LinesStrip, rect_size); + + for (std::size_t i = 0; i < rect_size; ++i) + rect_selection[i].color = sf::Color::Red; + + rect_filling.setFillColor(sf::Color(255, 0, 0, 128)); +} Board::~Board() { @@ -16,12 +26,109 @@ Board::~Board() } } +bool Board::init(const std::string& path, int splitting, const sf::RenderWindow &window) +{ + // PREPARING INITIAL BOARD STATE // + + if (!global_texture.loadFromFile(path) ) + return false; + + const int width = global_texture.getSize().x; + const int height = global_texture.getSize().y; + + Cell::side_length = (width < height) ? width / splitting : height / splitting; + cells_on_height = height / Cell::side_length; + cells_on_width = width / Cell::side_length; + + vec_field.reserve(cells_on_height * cells_on_width); + + /* Iterating board cells' screen positions. + * The initial image after this would look exactly like the loaded picture, not shuffled yet. */ + Cells::size_type index = 0; + for (int x = 0; x < height; x += Cell::side_length) + { + if ((height - x) >= Cell::side_length) + { + for (int y = 0; y < width; y += Cell::side_length) + { + if ((width - y) >= Cell::side_length) + { + sf::Sprite* sp = new sf::Sprite(global_texture, sf::IntRect(y, x, Cell::side_length, Cell::side_length)); + sp->setPosition(static_cast(y), static_cast(x)); + + vec_field.push_back(new Cell({index, index, sp})); + ++index; + } + } + } + } + + // SCALING // + + float scaling = 0.; + if (width >= height && width > static_cast(window.getSize().x)) + scaling = static_cast(window.getSize().x) / static_cast(width); + if (height >= width && height > static_cast(window.getSize().y)) + scaling = static_cast(window.getSize().y) / static_cast(height); + + if (scaling != 0.) + { + // Calculating new size of each tile + int old_side_length = Cell::side_length; + Cell::side_length = static_cast(static_cast(Cell::side_length) * scaling); + int shift = Cell::side_length - old_side_length; + float move_x, move_y; + // Moving all scaled tiles up and left, to remove spacing + for (Cells::size_type i = 0; i < vec_field.size(); ++i) + { + move_x = 0.f; + move_y = 0.f; + // The first column isn't allowed to move by x + if (!(((i % cells_on_width == 0) && (i >= cells_on_width)))) + move_x = static_cast(shift) * static_cast((i < cells_on_width) ? i : i % cells_on_width); + // The first row isn't allowed to move by y + if (i >= cells_on_width) + move_y = static_cast(shift) * static_cast(i / cells_on_width); + + vec_field[i]->sprite->scale(scaling, scaling); + vec_field[i]->sprite->move(move_x, move_y); + } + + } + + + // SHUFFLING // + + solved_tiles = vec_field.size(); // all tiles are solved for now + + srand(static_cast(time(nullptr))); + for (Cells::size_type curr_i = 0; curr_i < vec_field.size(); ++curr_i) + { + Cells::size_type swap_i; + do + { // find two different tiles + swap_i = rand() & (vec_field.size() - 1); + } while (curr_i == swap_i); + + swapCells(curr_i, swap_i); + } + + // Set initial position of cursor + setSelectionVertex(selection_index); + + return true; +} + void Board::draw(sf::RenderWindow& window) { for (const Cell *cell : vec_field) window.draw(*cell->sprite); - window.draw(rect_selection); + if (on_selection) + window.draw(rect_filling); + + if (is_cursor_visible) + window.draw(rect_selection); } bool Board::moveSelection(const DIRECTION &direction) @@ -111,6 +218,13 @@ bool Board::moveSelection(const DIRECTION &direction) void Board::onSelectionMode() { on_selection = !on_selection; + + if (on_selection) + { + rect_filling.setPosition(rect_selection[0].position); + rect_filling.setSize(sf::Vector2f(static_cast(rect_selection[1].position.x - rect_selection[0].position.x), + static_cast(rect_selection[2].position.y - rect_selection[1].position.y))); + } } void Board::setSelectionVertex(Cells::size_type index) @@ -121,21 +235,19 @@ void Board::setSelectionVertex(Cells::size_type index) const auto& y = pos.y; const float length = static_cast(Cell::side_length); - rect_selection = sf::VertexArray(sf::LinesStrip, 5); rect_selection[0].position = pos; - rect_selection[0].color = sf::Color::Red; rect_selection[1].position = sf::Vector2f(x + length, y); - rect_selection[1].color = sf::Color::Red; rect_selection[2].position = sf::Vector2f(x + length, y + length); - rect_selection[2].color = sf::Color::Red; rect_selection[3].position = sf::Vector2f(x, y + length); - rect_selection[3].color = sf::Color::Red; rect_selection[4].position = pos; - rect_selection[4].color = sf::Color::Red; } void Board::swapCells(Cells::size_type curr_index, Cells::size_type swap_index) { + // Check if the pair of cells for swapping was initially solved + bool curr_solved = (vec_field[curr_index]->inital_index == vec_field[curr_index]->current_index); + bool swap_solved = (vec_field[swap_index]->inital_index == vec_field[swap_index]->current_index); + Cell *curr_cell = vec_field[curr_index]; Cell *swap_cell = vec_field[swap_index]; const sf::Vector2f temp_pos = curr_cell->sprite->getPosition(); @@ -147,105 +259,36 @@ void Board::swapCells(Cells::size_type curr_index, Cells::size_type swap_index) swap_cell->sprite->setPosition(temp_pos); swap_cell->current_index = temp_cell_index; - Cell* temp = vec_field[curr_index]; + Cell *temp = vec_field[curr_index]; vec_field[curr_index] = vec_field[swap_index]; vec_field[swap_index] = temp; -} -bool Board::isWinCondition() const -{ - return std::all_of(vec_field.begin(), vec_field.end(), [](const Cell *cell){ return cell->current_index == cell->inital_index; }); -} + if ((vec_field[curr_index]->inital_index == vec_field[curr_index]->current_index) && !curr_solved) + // Wasn't solved and NOW is solved + ++solved_tiles; -bool Board::init(const std::string& path, int splitting, const sf::RenderWindow &window) -{ - // PREPARING INITIAL BOARD STATE // + if ((vec_field[curr_index]->inital_index != vec_field[curr_index]->current_index) && curr_solved) + // Was solved and NOW is unsolved + --solved_tiles; - if (!global_texture.loadFromFile(path) ) - return false; + if ((vec_field[swap_index]->inital_index == vec_field[swap_index]->current_index) && !swap_solved) + // Wasn't solved and NOW is solved + ++solved_tiles; - const int width = global_texture.getSize().x; - const int height = global_texture.getSize().y; - - Cell::side_length = (width < height) ? width / splitting : height / splitting; - cells_on_height = height / Cell::side_length; - cells_on_width = width / Cell::side_length; + if ((vec_field[swap_index]->inital_index != vec_field[swap_index]->current_index) && swap_solved) + // Was solved and NOW is unsolved + --solved_tiles; - vec_field.reserve(cells_on_height * cells_on_width); - - /* Iterating board cells' screen positions. - * The initial image after this would look exactly like the loaded picture, not shuffled yet. */ - Cells::size_type index = 0; - for (int x = 0; x < height; x += Cell::side_length) - { - if ((height - x) >= Cell::side_length) - { - for (int y = 0; y < width; y += Cell::side_length) - { - if ((width - y) >= Cell::side_length) - { - sf::Sprite* sp = new sf::Sprite(global_texture, sf::IntRect(y, x, Cell::side_length, Cell::side_length)); - sp->setPosition(static_cast(y), static_cast(x)); - - vec_field.push_back(new Cell({index, index, sp})); - ++index; - } - } - } - } - - // SCALING // - - float scaling = 0.; - if (width >= height && width > static_cast(window.getSize().x)) - scaling = static_cast(window.getSize().x) / static_cast(width); - if (height >= width && height > static_cast(window.getSize().y)) - scaling = static_cast(window.getSize().y) / static_cast(height); - - if (scaling != 0.) - { - // Calculating new size of each tile - int old_side_length = Cell::side_length; - Cell::side_length = static_cast(static_cast(Cell::side_length) * scaling); - int shift = Cell::side_length - old_side_length; - float move_x, move_y; - // Moving all scaled tiles up and left, to remove spacing - for (Cells::size_type i = 0; i < vec_field.size(); ++i) - { - move_x = 0.f; - move_y = 0.f; - // The first column isn't allowed to move by x - if (!(((i % cells_on_width == 0) && (i >= cells_on_width)))) - move_x = static_cast(shift) * static_cast((i < cells_on_width) ? i : i % cells_on_width); - // The first row isn't allowed to move by y - if (i >= cells_on_width) - move_y = static_cast(shift) * static_cast(i / cells_on_width); - - vec_field[i]->sprite->scale(scaling, scaling); - vec_field[i]->sprite->move(move_x, move_y); - } - - } - - - // SHUFFLING // - - srand(static_cast(time(nullptr))); - for (Cells::size_type curr_i = 0; curr_i < vec_field.size(); ++curr_i) - { - Cells::size_type swap_i; - do - { // find two different tiles - swap_i = rand() & (vec_field.size() - 1); - } while (curr_i == swap_i); - - swapCells(curr_i, swap_i); - } +} - // Set initial position of cursor - setSelectionVertex(selection_index); +bool Board::isWinCondition() const +{ + return (solved_tiles == vec_field.size()); +} - return true; +void Board::setCursorVisibility(bool visible) +{ + is_cursor_visible = visible; } int Board::Cell::side_length = 0; diff --git a/board.h b/board.h index 532a5da..285d5db 100644 --- a/board.h +++ b/board.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -26,6 +27,9 @@ public: explicit Board(); ~Board(); + // Set play image + bool init(const std::string& path, int splitting, const sf::RenderWindow& window); + // Output current graphical state on application window void draw(sf::RenderWindow& window); @@ -38,8 +42,8 @@ public: // Did player win the game bool isWinCondition() const; - // Set play image - bool init(const std::string& path, int splitting, const sf::RenderWindow& window); + // Show or hide selection cursos + void setCursorVisibility(bool visible = false); private: @@ -54,6 +58,7 @@ private: using Cells = std::vector; + sf::RectangleShape rect_filling; sf::VertexArray rect_selection; Cells::size_type selection_index; @@ -61,8 +66,11 @@ private: Cells::size_type cells_on_height; // amount of cells on vertical side of board Cells vec_field; + Cells::size_type solved_tiles; // amount of tiles placed on initial positions + sf::Texture global_texture; bool on_selection; + bool is_cursor_visible; void swapCells(Cells::size_type curr_index, Cells::size_type swap_index);