Compare commits
10 Commits
37485820a4
...
bb0c4d9fc4
Author | SHA1 | Date |
---|---|---|
NaiJi ✨ | bb0c4d9fc4 | |
NaiJi ✨ | 9c9fd95ea9 | |
NaiJi ✨ | 93449bd39a | |
NaiJi ✨ | 291c23124f | |
NaiJi ✨ | 5b8b0f8fb8 | |
NaiJi ✨ | 3257e534c3 | |
NaiJi ✨ | 0d2143925f | |
NaiJi ✨ | a766708147 | |
NaiJi ✨ | 81bf4c7e8a | |
NaiJi ✨ | 90ed819f17 |
|
@ -4,31 +4,21 @@ project(sliding-puzzle LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(SOURCES application.cpp board.cpp main.cpp)
|
set(SOURCES application.cpp board.cpp main.cpp argsprocessor.cpp)
|
||||||
set(HEADER_FILES application.h board.h filepath_util.h output_util.h)
|
set(HEADER_FILES application.h board.h filepath_util.h output_util.h argsprocessor.h)
|
||||||
|
|
||||||
set(SFML_STATIC_LIBRARIES TRUE) #set to FALSE if you have sfml installed from package manager and you don't want to link it statically
|
|
||||||
|
|
||||||
# STATIC #
|
# STATIC #
|
||||||
# You need to build SFML from sources with cmake
|
# You need to build SFML from sources with cmake
|
||||||
if (SFML_STATIC_LIBRARIES)
|
set(SFML_LIB_DIR
|
||||||
set(SFML_LIB_DIR
|
${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-graphics.so.2.5
|
||||||
${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-system.so.2.5
|
${CMAKE_SOURCE_DIR}/SFML-2.5.1/lib/libsfml-window.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)
|
||||||
set(SFML_INCL_DIR ${CMAKE_SOURCE_DIR}/SFML-2.5.1/include)
|
#include_directories(${SFML_INCL_DIR})
|
||||||
|
#target_link_libraries(sliding-puzzle ${SFML_LIB_DIR})
|
||||||
include_directories(${SFML_INCL_DIR})
|
|
||||||
|
|
||||||
add_executable(sliding-puzzle ${SOURCES} ${HEADER_FILES} )
|
|
||||||
target_link_libraries(sliding-puzzle ${SFML_LIB_DIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# DYNAMIC #
|
# DYNAMIC #
|
||||||
# You only need to install SFML from your package manager
|
# You only need to install SFML from your package manager
|
||||||
if (NOT SFML_STATIC_LIBRARIES)
|
find_package(SFML REQUIRED graphics window system)
|
||||||
find_package(SFML REQUIRED graphics window system)
|
add_executable(sliding-puzzle ${SOURCES} ${HEADER_FILES} )
|
||||||
include_directories(${SFML_INCLUDE_DIR})
|
target_link_libraries(sliding-puzzle sfml-system sfml-graphics sfml-network)
|
||||||
add_executable(sliding-puzzle ${SOURCES} ${HEADER_FILES} )
|
|
||||||
target_link_libraries(sliding-puzzle ${SFML_LIBRARIES} ${SFML_DEPENDENCIES})
|
|
||||||
endif()
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
|
@ -0,0 +1,39 @@
|
||||||
|
# sliding-puzzle
|
||||||
|
A simple game which takes an image, then slices it into tiles and shuffles. You need to restore the initial image by swapping neighbor tiles! It's a command line application and does support --help.
|
||||||
|
|
||||||
|
### How to build
|
||||||
|
You only need a compiler supporting c++17 and sfml 2.5.1 (or latter versions).
|
||||||
|
|
||||||
|
First of all, clone the project and go to the project folder
|
||||||
|
```
|
||||||
|
git clone https://dev.udongein.xyz/NaiJi/sliding-puzzle
|
||||||
|
cd sliding-puzzle/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then simply build it with CMake
|
||||||
|
```
|
||||||
|
cmake CMakeLists.txt -B ../sliding-puzzle-build
|
||||||
|
cd ../sliding-puzzle-build
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
Run it from terminal, providing optional flags and filepath. Move cursor by either WASD or arrows. Press Z to select a tile and then select a direction to swap the tile with a neighbor from that direction. Repeat until the inital image gets completely assembled!
|
||||||
|
```
|
||||||
|
usage: sliding-puzzle [-OPTIONS...] FILE-OR-DIRECTORY
|
||||||
|
|
||||||
|
Provide FILE-OR-DIRECTORY variable as a path to either a directory
|
||||||
|
which contains images (one will be picked randomly) or a specific
|
||||||
|
image file of .bmp, .jpg, of .png format.
|
||||||
|
|
||||||
|
If no path was provided, it will be assumed as '.'!
|
||||||
|
|
||||||
|
Options:
|
||||||
|
[-r NUMxNUM] Provide it if you want to explicitly define
|
||||||
|
window resolution.
|
||||||
|
[-s NUM] Provide it if you want to explicitly define
|
||||||
|
qualifier for image slicing, it's counted
|
||||||
|
by the smallest side of given source texture.
|
||||||
|
Hence, if your image is square, the amount of tiles
|
||||||
|
will be num * num.
|
||||||
|
```
|
|
@ -61,14 +61,13 @@ void Application::onGameState(sf::Event& event)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case sf::Event::KeyPressed:
|
case sf::Event::KeyPressed:
|
||||||
// Go to selection mode
|
|
||||||
if (event.key.code == sf::Keyboard::Z)
|
if (event.key.code == sf::Keyboard::Z)
|
||||||
board.onSelectionMode();
|
board.onSelectionMode();
|
||||||
else // if it wasn't selection mode, then try to handle direction
|
else
|
||||||
board.moveSelection(getDirection(event.key.code));
|
board.tryProcessDirection(getDirection(event.key.code));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // otherwise - do nothing
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
#include "argsprocessor.h"
|
||||||
|
#include "output_util.h"
|
||||||
|
#include "filepath_util.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static constexpr int DEFAULT_SCREEN_WIDTH = 1280;
|
||||||
|
static constexpr int DEFAULT_SCREEN_HEIGHT = 720;
|
||||||
|
static constexpr int DEFAULT_SPLITTING = 4;
|
||||||
|
static const char * const DEFAULT_PATH = ".";
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ArgsProcessor::ArgsProcessor(int argc, char **argv) :
|
||||||
|
image_splitting(DEFAULT_SPLITTING),
|
||||||
|
game_resolution({DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT}),
|
||||||
|
image_path(DEFAULT_PATH)
|
||||||
|
{
|
||||||
|
parse_result = tryConvertInput(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArgsProcessor::broken() const
|
||||||
|
{
|
||||||
|
return parse_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<int, sf::Vector2i, std::string> ArgsProcessor::unpack() const
|
||||||
|
{
|
||||||
|
return {image_splitting, game_resolution, image_path};
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArgsProcessor::tryConvertInput(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int error = iterateArgc(argc, argv);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
if (image_path == DEFAULT_PATH && loaded_image_pathes.empty())
|
||||||
|
{
|
||||||
|
// no path was given, loading random image from '.'
|
||||||
|
const auto &[error, ret_path] = filepath::parsePath(image_path);
|
||||||
|
if (error)
|
||||||
|
return makeError(output::IMG_FAIL_MSG);
|
||||||
|
|
||||||
|
loaded_image_pathes.insert(loaded_image_pathes.end(), ret_path.begin(), ret_path.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
image_path = filepath::randomChoice(loaded_image_pathes);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArgsProcessor::isFlag(const char *arg, const char *flag) const
|
||||||
|
{
|
||||||
|
return (strcmp(arg, flag) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArgsProcessor::iterateArgc(int argc, char **argv)
|
||||||
|
{
|
||||||
|
for (int current_arg = 1; current_arg < argc; ++current_arg) // current_arg = 0 is executable name
|
||||||
|
{
|
||||||
|
if (isFlag(argv[current_arg], output::HELP_FLAG))
|
||||||
|
return makeError(output::HELP_MSG);
|
||||||
|
|
||||||
|
if (isFlag(argv[current_arg], output::SPLITTING_FLAG))
|
||||||
|
{
|
||||||
|
int error = parseSplitting(current_arg, argc, argv);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
++current_arg;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFlag(argv[current_arg], output::RESOLUTION_FLAG))
|
||||||
|
{
|
||||||
|
int error = parseResolution(current_arg, argc, argv);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
++current_arg;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &[error, ret_path] = filepath::parsePath(argv[current_arg]);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
loaded_image_pathes.insert(loaded_image_pathes.end(), ret_path.begin(), ret_path.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArgsProcessor::parseSplitting(int curr_arg, int argc, char **argv)
|
||||||
|
{
|
||||||
|
const int value_rg = curr_arg + 1;
|
||||||
|
if (value_rg == argc)
|
||||||
|
return makeError(output::SPLITTING_MSG);
|
||||||
|
|
||||||
|
image_splitting = -1;
|
||||||
|
if (std::isdigit(*argv[value_rg]))
|
||||||
|
image_splitting = std::stoi(argv[value_rg]);
|
||||||
|
|
||||||
|
if (image_splitting < 2)
|
||||||
|
return makeError(output::SPLITTING_MSG);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArgsProcessor::parseResolution(int curr_arg, int argc, char **argv)
|
||||||
|
{
|
||||||
|
const int value_rg = curr_arg + 1;
|
||||||
|
if (value_rg == argc)
|
||||||
|
return makeError(output::RESOLUTION_MSG);
|
||||||
|
|
||||||
|
std::vector<std::string> res = filepath::split(argv[value_rg], 'x'); // splitting argument by 'x' as in 600x900
|
||||||
|
|
||||||
|
if (res.size() < 2)
|
||||||
|
return makeError(output::RESOLUTION_MSG);
|
||||||
|
|
||||||
|
game_resolution = {-1, -1};
|
||||||
|
|
||||||
|
if (std::isdigit(*res[0].c_str()) && std::isdigit(*res[1].c_str()))
|
||||||
|
game_resolution = {std::stoi(res[0].c_str()), std::stoi(res[1].c_str())};
|
||||||
|
|
||||||
|
if (game_resolution.x < 1 || game_resolution.y < 1)
|
||||||
|
return makeError(output::RESOLUTION_MSG);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArgsProcessor::makeError(const char* msg) const
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef ARGSPROCESSOR_H
|
||||||
|
#define ARGSPROCESSOR_H
|
||||||
|
|
||||||
|
#include <SFML/Graphics/VertexArray.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ArgsProcessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArgsProcessor(int argc, char **argv);
|
||||||
|
|
||||||
|
int broken() const;
|
||||||
|
std::tuple<int, sf::Vector2i, std::string> unpack() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isFlag(const char* arg, const char* flag) const;
|
||||||
|
int tryConvertInput(int argc, char **argv);
|
||||||
|
int iterateArgc(int argc, char **argv);
|
||||||
|
int makeError(const char* msg) const;
|
||||||
|
int parseSplitting(int curr_arg, int argc, char **argv);
|
||||||
|
int parseResolution(int curr_arg, int argc, char **argv);
|
||||||
|
|
||||||
|
int parse_result;
|
||||||
|
int image_splitting;
|
||||||
|
sf::Vector2i game_resolution;
|
||||||
|
std::string image_path;
|
||||||
|
std::vector<std::string> loaded_image_pathes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ARGSPROCESSOR_H
|
193
board.cpp
193
board.cpp
|
@ -28,32 +28,44 @@ Board::~Board()
|
||||||
|
|
||||||
bool Board::init(const std::string& path, int splitting, const sf::RenderWindow &window)
|
bool Board::init(const std::string& path, int splitting, const sf::RenderWindow &window)
|
||||||
{
|
{
|
||||||
// PREPARING INITIAL BOARD STATE //
|
std::cout << path << '\n';
|
||||||
|
|
||||||
if (!global_texture.loadFromFile(path) )
|
if (!global_texture.loadFromFile(path) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
calculateBoardProperties(splitting);
|
||||||
|
splitImageIntoTiles(Cell::side_length);
|
||||||
|
scaleImageToWindow(window);
|
||||||
|
shuffleTiles();
|
||||||
|
setSelectionVertex(selection_index);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Board::calculateBoardProperties(int splitting)
|
||||||
|
{
|
||||||
const int width = global_texture.getSize().x;
|
const int width = global_texture.getSize().x;
|
||||||
const int height = global_texture.getSize().y;
|
const int height = global_texture.getSize().y;
|
||||||
|
|
||||||
Cell::side_length = (width < height) ? width / splitting : height / splitting;
|
Cell::side_length = (width < height) ? width / splitting : height / splitting;
|
||||||
cells_on_height = height / Cell::side_length;
|
cells_on_height = height / Cell::side_length;
|
||||||
cells_on_width = width / Cell::side_length;
|
cells_on_width = width / Cell::side_length;
|
||||||
|
}
|
||||||
|
|
||||||
vec_field.reserve(cells_on_height * cells_on_width);
|
void Board::splitImageIntoTiles(int tile_length)
|
||||||
|
{
|
||||||
|
const int width = global_texture.getSize().x;
|
||||||
|
const int height = global_texture.getSize().y;
|
||||||
|
|
||||||
/* 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;
|
Cells::size_type index = 0;
|
||||||
for (int x = 0; x < height; x += Cell::side_length)
|
for (int x = 0; x < height; x += tile_length)
|
||||||
{
|
{
|
||||||
if ((height - x) >= Cell::side_length)
|
if ((height - x) >= tile_length)
|
||||||
{
|
{
|
||||||
for (int y = 0; y < width; y += Cell::side_length)
|
for (int y = 0; y < width; y += tile_length)
|
||||||
{
|
{
|
||||||
if ((width - y) >= Cell::side_length)
|
if ((width - y) >= tile_length)
|
||||||
{
|
{
|
||||||
sf::Sprite* sp = new sf::Sprite(global_texture, sf::IntRect(y, x, Cell::side_length, Cell::side_length));
|
sf::Sprite* sp = new sf::Sprite(global_texture, sf::IntRect(y, x, tile_length, tile_length));
|
||||||
sp->setPosition(static_cast<float>(y), static_cast<float>(x));
|
sp->setPosition(static_cast<float>(y), static_cast<float>(x));
|
||||||
|
|
||||||
vec_field.push_back(new Cell({index, index, sp}));
|
vec_field.push_back(new Cell({index, index, sp}));
|
||||||
|
@ -62,43 +74,63 @@ bool Board::init(const std::string& path, int splitting, const sf::RenderWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SCALING //
|
void Board::scaleImageToWindow(const sf::RenderWindow &window)
|
||||||
|
{
|
||||||
|
float scaling = calculateScalingToWindow(window);
|
||||||
|
scaleTiles(scaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Board::calculateScalingToWindow(const sf::RenderWindow &window) const
|
||||||
|
{
|
||||||
|
int texture_width = global_texture.getSize().x;
|
||||||
|
int texture_height = global_texture.getSize().y;
|
||||||
|
|
||||||
float scaling = 0.;
|
float scaling = 0.;
|
||||||
if (width >= height && width > static_cast<int>(window.getSize().x))
|
if (texture_width >= texture_height && texture_width > static_cast<int>(window.getSize().x))
|
||||||
scaling = static_cast<float>(window.getSize().x) / static_cast<float>(width);
|
scaling = static_cast<float>(window.getSize().x) / static_cast<float>(texture_width);
|
||||||
if (height >= width && height > static_cast<int>(window.getSize().y))
|
if (texture_height >= texture_width && texture_height > static_cast<int>(window.getSize().y))
|
||||||
scaling = static_cast<float>(window.getSize().y) / static_cast<float>(height);
|
scaling = static_cast<float>(window.getSize().y) / static_cast<float>(texture_height);
|
||||||
|
|
||||||
if (scaling != 0.)
|
return scaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Board::scaleTiles(float scaling)
|
||||||
|
{
|
||||||
|
if (scaling == 0.)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int old_side_length = Cell::side_length;
|
||||||
|
Cell::side_length = static_cast<int>(static_cast<float>(Cell::side_length) * scaling);
|
||||||
|
int shift = Cell::side_length - old_side_length;
|
||||||
|
|
||||||
|
for (Cells::size_type i = 0; i < vec_field.size(); ++i)
|
||||||
{
|
{
|
||||||
// Calculating new size of each tile
|
vec_field[i]->sprite->scale(scaling, scaling);
|
||||||
int old_side_length = Cell::side_length;
|
|
||||||
Cell::side_length = static_cast<int>(static_cast<float>(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<float>(shift) * static_cast<float>((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<float>(shift) * static_cast<float>(i / cells_on_width);
|
|
||||||
|
|
||||||
vec_field[i]->sprite->scale(scaling, scaling);
|
|
||||||
vec_field[i]->sprite->move(move_x, move_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const auto shift_vector = calculateTileShiftVector(i, shift);
|
||||||
|
vec_field[i]->sprite->move(shift_vector.first, shift_vector.second);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<float, float> Board::calculateTileShiftVector(Cells::size_type tile_index, int shift) const
|
||||||
|
{
|
||||||
|
float move_x = 0.f, move_y = 0.f;
|
||||||
|
|
||||||
// SHUFFLING //
|
// The first column isn't allowed to move by x
|
||||||
|
if (!(((tile_index % cells_on_width == 0) && (tile_index >= cells_on_width))))
|
||||||
|
move_x = static_cast<float>(shift) * static_cast<float>((tile_index < cells_on_width) ? tile_index : tile_index % cells_on_width);
|
||||||
|
|
||||||
|
// The first row isn't allowed to move by y
|
||||||
|
if (tile_index >= cells_on_width)
|
||||||
|
move_y = static_cast<float>(shift) * static_cast<float>(tile_index / cells_on_width);
|
||||||
|
|
||||||
|
return {move_x, move_y};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Board::shuffleTiles()
|
||||||
|
{
|
||||||
solved_tiles = vec_field.size(); // all tiles are solved for now
|
solved_tiles = vec_field.size(); // all tiles are solved for now
|
||||||
|
|
||||||
srand(static_cast<unsigned int>(time(nullptr)));
|
srand(static_cast<unsigned int>(time(nullptr)));
|
||||||
|
@ -112,11 +144,6 @@ bool Board::init(const std::string& path, int splitting, const sf::RenderWindow
|
||||||
|
|
||||||
swapCells(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)
|
void Board::draw(sf::RenderWindow& window)
|
||||||
|
@ -131,48 +158,56 @@ void Board::draw(sf::RenderWindow& window)
|
||||||
window.draw(rect_selection);
|
window.draw(rect_selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Board::tryProcessDirection(const DIRECTION &direction)
|
||||||
|
{
|
||||||
|
if (on_selection)
|
||||||
|
return swapOnSelection(direction);
|
||||||
|
|
||||||
|
return moveSelection(direction);
|
||||||
|
}
|
||||||
|
|
||||||
bool Board::moveSelection(const DIRECTION &direction)
|
bool Board::moveSelection(const DIRECTION &direction)
|
||||||
{
|
{
|
||||||
if (!on_selection)
|
switch (direction) {
|
||||||
{
|
case DIRECTION::UP:
|
||||||
switch (direction) {
|
if (selection_index < cells_on_width) // if upper row
|
||||||
case DIRECTION::UP:
|
|
||||||
if (selection_index < cells_on_width) // if upper row
|
|
||||||
return false;
|
|
||||||
selection_index -= cells_on_width;
|
|
||||||
setSelectionVertex(selection_index);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DIRECTION::DOWN:
|
|
||||||
if (selection_index > (cells_on_width * (cells_on_height - 1))) // if bottom row
|
|
||||||
return false;
|
|
||||||
selection_index += cells_on_width;
|
|
||||||
setSelectionVertex(selection_index);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DIRECTION::RIGHT:
|
|
||||||
++selection_index;
|
|
||||||
if (selection_index == vec_field.size()) // if the last cell of right bottom corner
|
|
||||||
selection_index = 0; // move to the first cell of upper left corner
|
|
||||||
setSelectionVertex(selection_index);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DIRECTION::LEFT:
|
|
||||||
if (selection_index == 0) // if the first cell of of upper left corner
|
|
||||||
selection_index = vec_field.size() - 1; // move to the last cell of right bottom corner
|
|
||||||
else
|
|
||||||
--selection_index;
|
|
||||||
setSelectionVertex(selection_index);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
return false;
|
||||||
break;
|
selection_index -= cells_on_width;
|
||||||
}
|
setSelectionVertex(selection_index);
|
||||||
|
break;
|
||||||
|
|
||||||
return true;
|
case DIRECTION::DOWN:
|
||||||
|
if (selection_index >= (cells_on_width * (cells_on_height - 1))) // if bottom row
|
||||||
|
return false;
|
||||||
|
selection_index += cells_on_width;
|
||||||
|
setSelectionVertex(selection_index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DIRECTION::RIGHT:
|
||||||
|
++selection_index;
|
||||||
|
if (selection_index == vec_field.size()) // if the last cell of right bottom corner
|
||||||
|
selection_index = 0; // move to the first cell of upper left corner
|
||||||
|
setSelectionVertex(selection_index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DIRECTION::LEFT:
|
||||||
|
if (selection_index == 0) // if the first cell of of upper left corner
|
||||||
|
selection_index = vec_field.size() - 1; // move to the last cell of right bottom corner
|
||||||
|
else
|
||||||
|
--selection_index;
|
||||||
|
setSelectionVertex(selection_index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Board::swapOnSelection(const DIRECTION &direction)
|
||||||
|
{
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case DIRECTION::UP:
|
case DIRECTION::UP:
|
||||||
if (selection_index < cells_on_width) // if upper row
|
if (selection_index < cells_on_width) // if upper row
|
||||||
|
@ -291,4 +326,4 @@ void Board::setCursorVisibility(bool visible)
|
||||||
is_cursor_visible = visible;
|
is_cursor_visible = visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Board::Cell::side_length = 0;
|
int Board::Cell::side_length = 1;
|
||||||
|
|
26
board.h
26
board.h
|
@ -27,27 +27,20 @@ public:
|
||||||
explicit Board();
|
explicit Board();
|
||||||
~Board();
|
~Board();
|
||||||
|
|
||||||
// Set play image
|
|
||||||
bool init(const std::string& path, int splitting, const sf::RenderWindow& window);
|
bool init(const std::string& path, int splitting, const sf::RenderWindow& window);
|
||||||
|
|
||||||
// Output current graphical state on application window
|
|
||||||
void draw(sf::RenderWindow& window);
|
void draw(sf::RenderWindow& window);
|
||||||
|
bool isWinCondition() const;
|
||||||
|
|
||||||
// Move cursor to next tile by given direction
|
void setCursorVisibility(bool visible);
|
||||||
|
bool tryProcessDirection(const DIRECTION& direction);
|
||||||
bool moveSelection(const DIRECTION& direction);
|
bool moveSelection(const DIRECTION& direction);
|
||||||
|
bool swapOnSelection(const DIRECTION& direction);
|
||||||
|
|
||||||
// Go to or leave from selection mode
|
// Go to or leave from selection mode
|
||||||
void onSelectionMode();
|
void onSelectionMode();
|
||||||
|
|
||||||
// Did player win the game
|
|
||||||
bool isWinCondition() const;
|
|
||||||
|
|
||||||
// Show or hide selection cursos
|
|
||||||
void setCursorVisibility(bool visible = false);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// Game tile
|
|
||||||
struct Cell
|
struct Cell
|
||||||
{
|
{
|
||||||
std::vector<Cell*>::size_type inital_index;
|
std::vector<Cell*>::size_type inital_index;
|
||||||
|
@ -76,4 +69,15 @@ private:
|
||||||
|
|
||||||
// Draw selection cursor on given tile
|
// Draw selection cursor on given tile
|
||||||
void setSelectionVertex(Cells::size_type index);
|
void setSelectionVertex(Cells::size_type index);
|
||||||
|
|
||||||
|
void calculateBoardProperties(int splitting);
|
||||||
|
|
||||||
|
void splitImageIntoTiles(int tile_length);
|
||||||
|
|
||||||
|
void scaleImageToWindow(const sf::RenderWindow &window);
|
||||||
|
float calculateScalingToWindow(const sf::RenderWindow &window) const;
|
||||||
|
void scaleTiles(float scaling);
|
||||||
|
std::pair<float, float> calculateTileShiftVector(Cells::size_type tile_index, int shift) const;
|
||||||
|
|
||||||
|
void shuffleTiles();
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,7 +25,56 @@ namespace filepath
|
||||||
return std::equal(ending.rbegin(), ending.rend(), string.rbegin());
|
return std::equal(ending.rbegin(), ending.rend(), string.rbegin());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<int, std::string> parsePath(const std::string &argv)
|
std::string randomChoice(const std::vector<std::string>& poll)
|
||||||
|
{
|
||||||
|
std::cout << "Loading random image!\n";
|
||||||
|
const auto range = poll.size() - 1;
|
||||||
|
return poll.at(rand() & range);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::tuple<std::filesystem::path, std::filesystem::file_status>> extractFilesFrom(std::filesystem::path&& path)
|
||||||
|
{
|
||||||
|
std::vector<std::tuple<std::filesystem::path, std::filesystem::file_status>> dir_items;
|
||||||
|
std::transform(std::filesystem::directory_iterator(path), {}, std::back_inserter(dir_items), filepath::getFileInfo);
|
||||||
|
return dir_items;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isImage(const std::filesystem::path& filepath)
|
||||||
|
{
|
||||||
|
static std::set<std::string> allowed_ext = {".bmp", ".jpg", "*.jpeg", ".png"};
|
||||||
|
const std::string path_string = filepath.string();
|
||||||
|
|
||||||
|
return std::filesystem::is_regular_file(filepath) &&
|
||||||
|
std::any_of(allowed_ext.begin(), allowed_ext.end(),
|
||||||
|
[&](const std::string& e)
|
||||||
|
{
|
||||||
|
return filepath::endsWith(path_string, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<int, std::vector<std::string>> parseFolder(std::filesystem::path&& path)
|
||||||
|
{
|
||||||
|
auto directory_items = extractFilesFrom(std::move(path));
|
||||||
|
|
||||||
|
std::vector<std::string> image_pathes;
|
||||||
|
for (const auto &[local_path, status] : directory_items)
|
||||||
|
{
|
||||||
|
if (isImage(local_path))
|
||||||
|
image_pathes.emplace_back(local_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image_pathes.empty())
|
||||||
|
return {EXIT_FAILURE, {}};
|
||||||
|
|
||||||
|
const std::tuple<int, std::vector<std::string>> empty_folder = {EXIT_FAILURE, {}};
|
||||||
|
const std::tuple<int, std::vector<std::string>> found_images = {EXIT_SUCCESS, image_pathes};
|
||||||
|
|
||||||
|
return image_pathes.empty()
|
||||||
|
? empty_folder
|
||||||
|
: found_images;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<int, std::vector<std::string>> parsePath(const std::string &argv)
|
||||||
{
|
{
|
||||||
std::filesystem::path path(argv);
|
std::filesystem::path path(argv);
|
||||||
if (!std::filesystem::exists(path))
|
if (!std::filesystem::exists(path))
|
||||||
|
@ -34,38 +83,12 @@ namespace filepath
|
||||||
return {EXIT_FAILURE, {}};
|
return {EXIT_FAILURE, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maybe user chose a specific image, not a folder
|
|
||||||
if (std::filesystem::is_regular_file(path))
|
if (std::filesystem::is_regular_file(path))
|
||||||
return {EXIT_SUCCESS, path.string()};
|
|
||||||
// TO DO : I KNOW THIS PART IS BAD! I have never worked with ::filesystem before,
|
|
||||||
// So... it is a folder i will rewrite it when the prject works and is done
|
|
||||||
// Creating a vector of everything in the given directory
|
|
||||||
std::vector<std::tuple<std::filesystem::path, std::filesystem::file_status>> dir_items;
|
|
||||||
std::transform(std::filesystem::directory_iterator(path), {}, std::back_inserter(dir_items), filepath::getFileInfo);
|
|
||||||
|
|
||||||
std::set<std::string> allowed_ext = {".bmp", ".dds", ".jpg", ".png", ".tga", ".psd"};
|
|
||||||
|
|
||||||
// Now getting images
|
|
||||||
std::vector<std::string> dir_image_items;
|
|
||||||
for (const auto &[local_path, status] : dir_items)
|
|
||||||
{
|
{
|
||||||
const std::string str_path = local_path.string();
|
return {EXIT_SUCCESS, {path.string()}};
|
||||||
if (std::filesystem::is_regular_file(local_path) &&
|
|
||||||
std::any_of(allowed_ext.begin(), allowed_ext.end(), [&](const std::string& e) { return filepath::endsWith(str_path, e); }))
|
|
||||||
{
|
|
||||||
dir_image_items.emplace_back(str_path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir_image_items.empty())
|
return parseFolder(std::move(path));
|
||||||
{
|
|
||||||
std::cout << "No images found at " << path << "\n--help for more information.\n";
|
|
||||||
return {EXIT_FAILURE, {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Loading random image file from " << path << "\n";
|
|
||||||
srand(static_cast<unsigned int>(time(nullptr)));
|
|
||||||
return {EXIT_SUCCESS, dir_image_items[rand() & (dir_image_items.size() - 1)]};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> split(const std::string &s, char delim)
|
std::vector<std::string> split(const std::string &s, char delim)
|
||||||
|
|
110
main.cpp
110
main.cpp
|
@ -1,106 +1,16 @@
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "output_util.h"
|
#include "argsprocessor.h"
|
||||||
#include "filepath_util.h"
|
|
||||||
#include <cstring>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
static constexpr int DEFAULT_SCREEN_WIDTH = 1280;
|
|
||||||
static constexpr int DEFAULT_SCREEN_HEIGHT = 720;
|
|
||||||
static constexpr int DEFAULT_SPLITTING = 4;
|
|
||||||
static const std::string DEFAULT_PATH = "."; // current folder, I guess
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
std::tuple<int, int, sf::Vector2i, std::string> error(const char* msg)
|
|
||||||
{
|
|
||||||
std::cout << msg;
|
|
||||||
return {EXIT_FAILURE, -1, {}, {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<int, int, sf::Vector2i, std::string> parseInput(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int splitting = DEFAULT_SPLITTING;
|
|
||||||
sf::Vector2i resolution(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
|
|
||||||
std::string path = DEFAULT_PATH;
|
|
||||||
|
|
||||||
for (int current_arg = 1; current_arg < argc; ++current_arg) // current_arg = 0 is executable name
|
|
||||||
{
|
|
||||||
if (strcmp(argv[current_arg], output::HELP_FLAG) == 0) // --help
|
|
||||||
return error(output::HELP_MSG);
|
|
||||||
|
|
||||||
if (strcmp(argv[current_arg], output::SPLITTING_FLAG) == 0) // -s num
|
|
||||||
{
|
|
||||||
const int value_rg = current_arg + 1;
|
|
||||||
if (value_rg == argc) // is '-s' is the last argument
|
|
||||||
return error(output::SPLITTING_MSG);
|
|
||||||
|
|
||||||
splitting = -1; // here assuming user is providing it on their own
|
|
||||||
if (std::isdigit(*argv[value_rg]))
|
|
||||||
splitting = std::stoi(argv[value_rg]);
|
|
||||||
|
|
||||||
if (splitting < 2)
|
|
||||||
return error(output::SPLITTING_MSG);
|
|
||||||
|
|
||||||
++current_arg; // skipping value after flag to not check it once again
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(argv[current_arg], output::RESOLUTION_FLAG) == 0) // -r numxnum
|
|
||||||
{
|
|
||||||
const int value_rg = current_arg + 1;
|
|
||||||
if (value_rg == argc) // is '-s' is the last argument
|
|
||||||
return error(output::RESOLUTION_MSG);
|
|
||||||
|
|
||||||
std::vector<std::string> res = filepath::split(argv[value_rg], 'x'); // splitting argument by 'x' as in 600x900
|
|
||||||
|
|
||||||
if (res.size() < 2)
|
|
||||||
return error(output::RESOLUTION_MSG);
|
|
||||||
|
|
||||||
resolution = {-1, -1};
|
|
||||||
|
|
||||||
if (std::isdigit(*res[0].c_str()) && std::isdigit(*res[1].c_str()))
|
|
||||||
resolution = {std::stoi(res[0].c_str()), std::stoi(res[1].c_str())};
|
|
||||||
|
|
||||||
if (resolution.x < 1 || resolution.y < 1)
|
|
||||||
return error(output::RESOLUTION_MSG);
|
|
||||||
|
|
||||||
++current_arg; // skipping value after flag to not check it once again
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing else, then assuming it's filepath or folderpath
|
|
||||||
const auto &[ret_code, ret_path] = filepath::parsePath(argv[current_arg]);
|
|
||||||
|
|
||||||
if (ret_code)
|
|
||||||
return error(output::IMG_FAIL_MSG);
|
|
||||||
|
|
||||||
path = ret_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path == DEFAULT_PATH)
|
|
||||||
{
|
|
||||||
// no path was give, loading random image from '.'
|
|
||||||
const auto &[ret_code, ret_path] = filepath::parsePath(path);
|
|
||||||
|
|
||||||
if (ret_code)
|
|
||||||
return error(output::IMG_FAIL_MSG);
|
|
||||||
|
|
||||||
path = ret_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {EXIT_SUCCESS, splitting, resolution, path};
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const auto&[ret_code, splitting, resolution, path] = parseInput(argc, argv);
|
srand(static_cast<unsigned int>(time(nullptr)));
|
||||||
|
|
||||||
if (ret_code) // Error code is EXIT_FAILURE
|
ArgsProcessor args(argc, argv);
|
||||||
return ret_code;
|
|
||||||
|
if (args.broken())
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
const auto&[splitting, resolution, path] = args.unpack();
|
||||||
|
|
||||||
Application app(resolution.x, resolution.y);
|
Application app(resolution.x, resolution.y);
|
||||||
if (app.init(path, splitting))
|
if (app.init(path, splitting))
|
||||||
|
@ -108,6 +18,6 @@ int main(int argc, char **argv)
|
||||||
app.run();
|
app.run();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,18 @@ namespace output
|
||||||
|
|
||||||
const char* NO_ARG_MSG = "Please at least provide a path to a target image. --help for more information.\n";
|
const char* NO_ARG_MSG = "Please at least provide a path to a target image. --help for more information.\n";
|
||||||
const char* HELP_MSG = "usage: sliding-puzzle [-OPTIONS...] FILE-OR-DIRECTORY\n\n"
|
const char* HELP_MSG = "usage: sliding-puzzle [-OPTIONS...] FILE-OR-DIRECTORY\n\n"
|
||||||
" Necessarily provide the latter variable as path to either a directory\n"
|
" Provide FILE-OR-DIRECTORY variable as a path to either a directory\n"
|
||||||
" which contains arts (one will be picked randomly) or a specific\n"
|
" which contains images (one will be picked randomly) or a specific\n"
|
||||||
" image file of .bmp, .dds, .jpg, .png, .tga, or .psd format.\n\n"
|
" image file of .bmp, .jpg, of .png format.\n\n"
|
||||||
|
" If no path was provided, it will be assumed as \'.\'!\n\n"
|
||||||
" Options:\n"
|
" Options:\n"
|
||||||
" [-s num] Provide it if you want to explicitly define\n"
|
" [-r NUMxNUM] Provide it if you want to explicitly define\n"
|
||||||
" qualifier for image slicing, it's counted\n"
|
" window resolution.\n"
|
||||||
" by the smallest side of given source texture.\n"
|
" [-s NUM] Provide it if you want to explicitly define\n"
|
||||||
" Hence, if your image is square, the amount of tiles\n"
|
" qualifier for image slicing, it's counted\n"
|
||||||
" will be num * num.\n";
|
" by the smallest side of given source texture.\n"
|
||||||
|
" Hence, if your image is square, the amount of tiles\n"
|
||||||
|
" will be num * num.\n";
|
||||||
const char* SPLITTING_MSG = "-s should be given with a positive num >= 2 as in [-s num].\n";
|
const char* SPLITTING_MSG = "-s should be given with a positive num >= 2 as in [-s num].\n";
|
||||||
const char* IMG_FAIL_MSG = "Couldn't load image from given path.\n";
|
const char* IMG_FAIL_MSG = "Couldn't load image from given path.\n";
|
||||||
const char* RESOLUTION_MSG = "Couldn't recognize given screen resolution.\n"
|
const char* RESOLUTION_MSG = "Couldn't recognize given screen resolution.\n"
|
||||||
|
|
Loading…
Reference in New Issue