diff --git a/CMakeLists.txt b/CMakeLists.txt index fd948f1..962db39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,13 @@ project(PuzzleWuzzleGenerator) cmake_minimum_required(VERSION 2.8) -include_directories(SYSTEM "${PROJECT_SOURCE_DIR}/include/") +include_directories("${PROJECT_SOURCE_DIR}/include/") add_definitions(-std=c++1y) find_package(Boost REQUIRED COMPONENTS program_options system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) set(libs ${libs} ${Boost_LIBRARIES}) -aux_source_directory(. SRC_LIST) -add_executable(${PROJECT_NAME} ${SRC_LIST}) -target_link_libraries(${PROJECT_NAME} ${libs}) +set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +add_subdirectory("lib") +add_subdirectory("src") diff --git a/include/clusters.hpp b/include/clusters.hpp index da5b895..c19376c 100644 --- a/include/clusters.hpp +++ b/include/clusters.hpp @@ -34,14 +34,18 @@ auto cluster_at(Field const & field, typename Field::Position const & p){ } -template -auto all_clusters(Field const & field){ +template +auto all_clusters(Field const & field, Rules const & rules){ boost::container::flat_set()))> ret; ret.reserve(10); for(auto&& p : field.all_positions()){ if(field.empty(p)) continue; ret.insert(cluster_at(field, p)); } + for(auto it = ret.begin(); it != ret.end();){ + if (it->size() < rules.min_cluster_size()) it = ret.erase(it); + else ++it; + } return ret; } diff --git a/include/dynamic_grid.hpp b/include/dynamic_grid.hpp index 681165c..50e91d1 100644 --- a/include/dynamic_grid.hpp +++ b/include/dynamic_grid.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include // Position {0,0} is bottom left. @@ -16,13 +16,11 @@ struct DynamicGrid{ using Position = std::pair; - template - DynamicGrid(int width, int height, Input it) - : W(width) - , H(height) - , grid(W*H, 0) - { - std::copy_n(it, W*H, std::begin(grid)); + DynamicGrid(int width, int height, std::vector && data); + + //! \brief Returns an array of all valid positions (may be empty) + auto const & all_positions() const{ + return positions; } //! \brief Returns true if p represents a valid position @@ -31,130 +29,56 @@ struct DynamicGrid{ && p.first < W && p.second < H; } + //! \brief Get (mutable) value at p + auto & get(Position const & p){ + assert(valid(p)); + return grid[static_cast(p.first + p.second*W)]; + } + + //! \brief Get value at p + auto const& get(Position const & p) const { + assert(valid(p)); + return grid[static_cast(p.first + p.second*W)]; + } + //! \brief Returns true if p represents an empty cell auto empty(Position const & p) const { assert(valid(p)); return get(p) == 0; } + //! \brief Return true if the whole grid is empty + auto empty() const { + for(auto&& p : all_positions()){ + if(!empty(p)) return false; + } + return true; + } + //! \brief Returns all neighbors of p //! Only returns valid neighbors (and p does not need to be valid) - auto neighbors(Position const & p) const { - small_vector ret; + small_vector neighbors(Position const & p) const; - static Position nbs[] = { - {-1, 0}, {1, 0}, {0, 1}, {0, -1} - }; - - for(auto&& n : nbs){ - auto q = Position{p.first + n.first, p.second + n.second}; - if(valid(q)){ - ret.push_back(q); - } - } - - return ret; - } - - //! \brief Get (mutable) value at p - int & get(Position const & p){ - assert(valid(p)); - return grid[static_cast(p.first + p.second*W)]; - } - - //! \brief Get value at p - int const& get(Position const & p) const { - assert(valid(p)); - return grid[static_cast(p.first + p.second*W)]; - } + //! \brief Let the block fall (all the way) + void collapse(); //! \brief Pretty prints grid to out - void print(std::ostream& out) const { - for(auto y = H; y; --y){ - for(auto x = 0; x < W; ++x){ - out << get({x, y-1}) << (x == W-1 ? '\n' : ' '); - } - } - } + void print(std::ostream& out) const; private: int W; int H; std::vector grid; + std::vector positions; - //! \brief Returns true if the whole column at x is empty - auto empty_column(int x){ - for(auto y = 0; y < H; ++y){ - if(!empty({x, y})){ - return false; - } - } - return true; - } - - //! \brief Helper function for all_positions() - static auto all_positions_impl(int W, int H) { - std::vector r; - for(int y = 0; y < H; ++y) - for(int x = 0; x < W; ++x) - r.emplace_back(x, y); - return r; - } - -public: - //! \brief Returns an array of all valid positions - auto const & all_positions() const { - static auto v = all_positions_impl(W, H); - return v; - } - -private: //! \brief Single vertical collapsing step - auto vcollapse(){ - using namespace std; - bool some_change = false; - for(auto&& p : all_positions()){ - if(empty(p)) continue; - - auto q = p; - q.second--; - if(!valid(q)) continue; - if(!empty(q)) continue; - - swap(get(p), get(q)); - some_change = true; - } - - return some_change; - } + auto vcollapse(); //! \brief Single horizontal collapsing step - auto hcollapse(){ - using namespace std; - bool some_change = false; - for(auto x = 0; x < W-1; ++x){ - if(!empty_column(x)) continue; + auto hcollapse(); - auto x2 = x+1; - for(; x2 < W; ++x2){ - if(!empty_column(x2)) break; - } - if(x2 == W) return some_change; - - for(auto y = 0; y < H; ++y){ - swap(get({x, y}), get({x2, y})); - } - some_change = true; - } - return some_change; - } - -public: - //! \brief Let the block fall (all the way) - auto collapse(){ - while(vcollapse()); - while(hcollapse()); - } + //! \brief Returns true if the whole column at x is empty + auto empty_column(int x); }; template @@ -162,5 +86,5 @@ auto random_dynamic_grid(int W, int H, int C, URNG&& r){ std::uniform_int_distribution dis(1, C); std::vector v(W*H); std::generate_n(std::begin(v), W*H, [&]{ return dis(r); }); - return DynamicGrid(W, H, std::begin(v)); + return DynamicGrid(W, H, std::move(v)); } diff --git a/include/rules.hpp b/include/rules.hpp index 653fb37..bf577c9 100644 --- a/include/rules.hpp +++ b/include/rules.hpp @@ -1,7 +1,7 @@ #pragma once struct BasicRules { - BasicRules(int minimal_cluster_size) + BasicRules(unsigned int minimal_cluster_size) : min_size(minimal_cluster_size) {} @@ -10,5 +10,5 @@ struct BasicRules { } private: - int min_size; + unsigned int min_size; }; diff --git a/include/solver.hpp b/include/solver.hpp index 1e64851..5456deb 100644 --- a/include/solver.hpp +++ b/include/solver.hpp @@ -17,19 +17,19 @@ auto pop(std::vector & v){ template auto solve_impl(Grid const & grid, Rules const & rules, Solution & s){ - // are we done yet? - for(auto&& p : grid.all_positions()){ - if(!grid.empty(p)) goto analyse; + if(grid.empty()){ + // solved path + s.solution_traces.push_back(s.current_trace); + return; } - // yes -> record the solution - s.solution_traces.push_back(s.current_trace); - return; - // no -> try all clusters - analyse: - for(auto&& c : all_clusters(grid)){ - if(c.size() < rules.min_cluster_size()) continue; + auto&& clusters = all_clusters(grid, rules); + if(clusters.empty()){ + // unsolvable path + return; + } + for(auto&& c : clusters){ // remove the cluster s.current_trace.push_back(*c.begin()); auto new_grid = make_empty(grid, c); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..6196f11 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,4 @@ + +file(GLOB sources "*.cpp") +add_library(common STATIC ${sources}) +target_link_libraries(common ${libs}) diff --git a/lib/dynamic_grid.cpp b/lib/dynamic_grid.cpp new file mode 100644 index 0000000..b1ab402 --- /dev/null +++ b/lib/dynamic_grid.cpp @@ -0,0 +1,96 @@ +#include "dynamic_grid.hpp" + +#include + +//! \brief Helper function for all_positions() +static auto all_positions_impl(int W, int H) { + std::vector r; + for(int y = 0; y < H; ++y) + for(int x = 0; x < W; ++x) + r.emplace_back(x, y); + return r; +} + +DynamicGrid::DynamicGrid(int width, int height, std::vector && data) +: W(width), H(height), grid(std::move(data)) { + assert(grid.size() == W*H); + positions = all_positions_impl(W, H); +} + +small_vector +DynamicGrid::neighbors(Position const & p) const { + small_vector ret; + + static Position nbs[] = { + {-1, 0}, {1, 0}, {0, 1}, {0, -1} + }; + + for(auto&& n : nbs){ + auto q = Position{p.first + n.first, p.second + n.second}; + if(valid(q)){ + ret.push_back(q); + } + } + + return ret; +} + +auto DynamicGrid::empty_column(int x){ + for(auto y = 0; y < H; ++y){ + if(!empty({x, y})){ + return false; + } + } + return true; +} + +auto DynamicGrid::vcollapse(){ + using namespace std; + bool some_change = false; + for(auto&& p : all_positions()){ + if(empty(p)) continue; + + auto q = p; + q.second--; + if(!valid(q)) continue; + if(!empty(q)) continue; + + swap(get(p), get(q)); + some_change = true; + } + + return some_change; +} + +auto DynamicGrid::hcollapse(){ + using namespace std; + bool some_change = false; + for(auto x = 0; x < W-1; ++x){ + if(!empty_column(x)) continue; + + auto x2 = x+1; + for(; x2 < W; ++x2){ + if(!empty_column(x2)) break; + } + if(x2 == W) return some_change; + + for(auto y = 0; y < H; ++y){ + swap(get({x, y}), get({x2, y})); + } + some_change = true; + } + return some_change; +} + +void DynamicGrid::collapse(){ + while(vcollapse()); + while(hcollapse()); +} + +void DynamicGrid::print(std::ostream& out) const { + for(auto y = H; y; --y){ + for(auto x = 0; x < W; ++x){ + out << get({x, y-1}) << (x == W-1 ? '\n' : ' '); + } + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..56b78f0 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,8 @@ + +file(GLOB sources "*.cpp") + +foreach(source ${sources}) + get_filename_component(exec ${source} NAME_WE) + add_executable(${exec} ${source}) + target_link_libraries(${exec} common ${libs}) +endforeach() diff --git a/main.cpp b/src/main.cpp similarity index 74% rename from main.cpp rename to src/main.cpp index c3e51f8..bf33297 100644 --- a/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ int main(int argc, char** argv){ int h = 5; int c = 3; int explosion_size = 3; + int n = 1000; // Describe program options po::options_description opts; @@ -28,6 +29,8 @@ int main(int argc, char** argv){ ("h", po::value(&h), "height of puzzles") ("c", po::value(&c), "number of colors") ("explosion_size", po::value(&explosion_size), "minimum explosion size") + ("n", po::value(&n), "number of grids to test") + ("verbose", po::bool_switch(), "prints a lot") ("help", po::bool_switch(), "show this help"); // Parse and store them in a vm @@ -41,6 +44,8 @@ int main(int argc, char** argv){ return 0; } + bool verbose = vm["verbose"].as(); + #define OUT(x) std::cout << #x << " = " << x << "\n" OUT(w); OUT(h); OUT(c); OUT(explosion_size); #undef OUT @@ -50,25 +55,30 @@ int main(int argc, char** argv){ BasicRules rules(explosion_size); - int count = 50000; int solvable = 0; int unsolvable = 0; - while(--count){ + while(--n){ auto field = random_dynamic_grid(w, h, c, gen); -// std::cout << "\n= puzzle " << count << " =\n"; -// field.print(std::cout); + if(verbose){ + std::cout << "\n= puzzle " << n << " =\n"; + field.print(std::cout); + } auto solution = solve(field, rules); if(!solution.solution_traces.empty()){ ++solvable; -// std::cout << solution.solution_traces.size() << " solutions\n"; -// for(auto&& t : solution.solution_traces[0]){ -// std::cout << "(" << t.first << ", " << t.second << ")\n"; -// } + if(verbose){ + std::cout << solution.solution_traces.size() << " solutions\n"; + for(auto&& t : solution.solution_traces[0]){ + std::cout << "(" << t.first << ", " << t.second << ")\n"; + } + } } else { ++unsolvable; -// std::cout << "no solutions\n"; + if(verbose){ + std::cout << "no solutions\n"; + } } }