From 083141d346af6322c8818cdf5a9e1ce3a045ee61 Mon Sep 17 00:00:00 2001 From: Joshua Moerman Date: Tue, 4 Feb 2014 20:00:21 +0100 Subject: [PATCH] Puzzle solver --- .gitignore | 3 + CMakeLists.txt | 13 ++++ include/clusters.hpp | 48 ++++++++++++++ include/field.hpp | 148 +++++++++++++++++++++++++++++++++++++++++++ include/solver.hpp | 29 +++++++++ main.cpp | 54 ++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 include/clusters.hpp create mode 100644 include/field.hpp create mode 100644 include/solver.hpp create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f84323 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.user +build-* + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6577ad8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +project(PuzzleWuzzleGenerator) +cmake_minimum_required(VERSION 2.8) + +include_directories(SYSTEM "${PROJECT_SOURCE_DIR}/include/") +add_definitions(-std=c++1y) + +find_package(Boost REQUIRED COMPONENTS) +include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) +set(libs ${libs} ${Boost_LIBRARIES}) + +aux_source_directory(. SRC_LIST) +add_executable(${PROJECT_NAME} ${SRC_LIST}) + diff --git a/include/clusters.hpp b/include/clusters.hpp new file mode 100644 index 0000000..b0d5c23 --- /dev/null +++ b/include/clusters.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +// Using boost flat set was faster than std::set. + +namespace detail { + template + auto same_group(Field const & field, typename Field::Position const & p, typename Field::Position const & q){ + return field.get(p) == field.get(q); + } + + template + void cluster_expand(Field const & field, typename Field::Position const & p, boost::container::flat_set& ret){ + for(auto&& q : field.neighbors(p)){ + if(!same_group(field, p, q)) continue; + + if(ret.insert(q).second){ + cluster_expand(field, q, ret); + } + } + } +} + +template +auto cluster_at(Field const & field, typename Field::Position const & p){ + boost::container::flat_set ret; + ret.reserve(10); // speed improvement of 20% + ret.insert(p); + detail::cluster_expand(field, p, ret); + return ret; +} + + +template +auto all_clusters(Field const & field){ + 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)); + } + return ret; +} + +#undef Set diff --git a/include/field.hpp b/include/field.hpp new file mode 100644 index 0000000..c2fda4d --- /dev/null +++ b/include/field.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#include +#include +#include + +// Simple static vector (much faster) +// Also slightly faster than boost::container::static_vector +// No checking is performed! +template +struct small_vector{ + std::array arr; + size_t elements = 0; + + void push_back(T const & t){ + arr[elements++] = t; + } + + auto begin() const { return &arr[0]; } + auto end() const { return &arr[elements]; } +}; + +// Position {0,0} is bottom left. +// Position {W-1, H-1} is top right. +template +struct Field{ + using Position = std::pair; + + Field(std::initializer_list g){ + std::copy(std::begin(g), std::end(g), std::begin(grid)); + } + + auto valid(Position const & p) const { + return p.first >= 0 && p.second >= 0 + && p.first < W && p.second < H; + } + + auto empty(Position const & p) const { + return get(p) == 0; + } + + auto 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 collapse(){ + while(vcollapse()); + while(hcollapse()); + } + + auto const & all_positions() const { + using Indices = std::make_index_sequence; + return all_positions_impl(Indices{}); + } + + T& get(Position const & p){ + return grid[p.first + p.second*W]; + } + + T const& get(Position const & p) const { + return grid[p.first + p.second*W]; + } + + 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' : ' '); + } + } + } + +private: + std::array grid; + + template + auto const & all_positions_impl(std::index_sequence) const { + static const std::array ret = { + Position{I % W, I / W}... + }; + return ret; + } + + 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 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; + } + + auto empty_column(size_t x){ + for(auto y = 0; y < H; ++y){ + if(!empty({x, y})){ + return false; + } + } + return true; + } +}; + +template +auto create_rectangular_field(std::initializer_list grid){ + return Field(grid); +} diff --git a/include/solver.hpp b/include/solver.hpp new file mode 100644 index 0000000..39f85b7 --- /dev/null +++ b/include/solver.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "clusters.hpp" + +template +auto make_empty(Field field, Container const & c){ + using namespace std; + for(auto&& p : c){ + field.get(p) = 0; + } + field.collapse(); + return field; +} + +template +auto solve(Field const & field){ + for(auto&& p : field.all_positions()){ + if(!field.empty(p)) goto analyse; + } + return true; + + analyse: + for(auto&& c : all_clusters(field)){ + if(c.size() < 2) continue; + auto new_field = make_empty(field, c); + if(solve(new_field)) return true; + } + return false; +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..12ef925 --- /dev/null +++ b/main.cpp @@ -0,0 +1,54 @@ + +#include "field.hpp" +#include "clusters.hpp" +#include "solver.hpp" + +#include +#include +#include + +template +auto is_void(Field const & field){ + for(auto&& p : field.all_positions()){ + if(!field.empty(p)) return false; + } + return true; +} + +int main(){ + using namespace std; +// constexpr auto W = 10; +// constexpr auto H = 10; + +// auto field = create_rectangular_field({ +// 2, 1, 1, 1, 2, 1, 2, 4, 1, 2, +// 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, +// 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, +// 2, 1, 1, 4, 2, 4, 2, 1, 2, 1, +// 1, 1, 1, 3, 2, 2, 2, 3, 3, 4, +// 3, 2, 2, 1, 1, 1, 2, 2, 2, 2, +// 1, 1, 2, 1, 3, 1, 1, 4, 2, 3, +// 1, 1, 1, 1, 3, 1, 3, 2, 1, 2, +// 1, 1, 1, 1, 2, 4, 1, 3, 1, 2, +// 1, 1, 1, 1, 2, 2, 1, 1, 4, 9 +// }); + + constexpr auto W = 9; + constexpr auto H = 9; + auto field = create_rectangular_field({ + 2, 1, 1, 1, 2, 1, 2, 4, 1, + 2, 2, 2, 1, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 2, + 2, 1, 1, 4, 2, 4, 2, 1, 2, + 1, 1, 1, 3, 2, 2, 2, 3, 3, + 3, 2, 2, 1, 1, 1, 2, 2, 2, + 1, 1, 2, 1, 3, 1, 1, 4, 2, + 1, 1, 1, 1, 3, 1, 3, 2, 1, + 1, 1, 1, 1, 2, 4, 1, 3, 9 + }); + + field.print(std::cout); + + cout << solve(field) << std::endl; +} +