Puzzle solver
This commit is contained in:
commit
083141d346
6 changed files with 295 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*.user
|
||||||
|
build-*
|
||||||
|
|
13
CMakeLists.txt
Normal file
13
CMakeLists.txt
Normal file
|
@ -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})
|
||||||
|
|
48
include/clusters.hpp
Normal file
48
include/clusters.hpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <set>
|
||||||
|
#include <boost/container/flat_set.hpp>
|
||||||
|
|
||||||
|
// Using boost flat set was faster than std::set.
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Field>
|
||||||
|
auto same_group(Field const & field, typename Field::Position const & p, typename Field::Position const & q){
|
||||||
|
return field.get(p) == field.get(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Field>
|
||||||
|
void cluster_expand(Field const & field, typename Field::Position const & p, boost::container::flat_set<typename Field::Position>& 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 <typename Field>
|
||||||
|
auto cluster_at(Field const & field, typename Field::Position const & p){
|
||||||
|
boost::container::flat_set<typename Field::Position> ret;
|
||||||
|
ret.reserve(10); // speed improvement of 20%
|
||||||
|
ret.insert(p);
|
||||||
|
detail::cluster_expand(field, p, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename Field>
|
||||||
|
auto all_clusters(Field const & field){
|
||||||
|
boost::container::flat_set<decltype(cluster_at(field, std::declval<typename Field::Position>()))> 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
|
148
include/field.hpp
Normal file
148
include/field.hpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
|
// Simple static vector (much faster)
|
||||||
|
// Also slightly faster than boost::container::static_vector
|
||||||
|
// No checking is performed!
|
||||||
|
template <typename T, size_t Max>
|
||||||
|
struct small_vector{
|
||||||
|
std::array<T, Max> 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 <size_t W, size_t H, typename T>
|
||||||
|
struct Field{
|
||||||
|
using Position = std::pair<int, int>;
|
||||||
|
|
||||||
|
Field(std::initializer_list<T> 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<Position, 4> 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<W*H>;
|
||||||
|
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<T, W*H> grid;
|
||||||
|
|
||||||
|
template<std::size_t... I>
|
||||||
|
auto const & all_positions_impl(std::index_sequence<I...>) const {
|
||||||
|
static const std::array<const Position, W*H> 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 <size_t W, size_t H, typename T>
|
||||||
|
auto create_rectangular_field(std::initializer_list<T> grid){
|
||||||
|
return Field<W, H, T>(grid);
|
||||||
|
}
|
29
include/solver.hpp
Normal file
29
include/solver.hpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "clusters.hpp"
|
||||||
|
|
||||||
|
template <typename Field, typename Container>
|
||||||
|
auto make_empty(Field field, Container const & c){
|
||||||
|
using namespace std;
|
||||||
|
for(auto&& p : c){
|
||||||
|
field.get(p) = 0;
|
||||||
|
}
|
||||||
|
field.collapse();
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Field>
|
||||||
|
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;
|
||||||
|
}
|
54
main.cpp
Normal file
54
main.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
#include "field.hpp"
|
||||||
|
#include "clusters.hpp"
|
||||||
|
#include "solver.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
template <typename Field>
|
||||||
|
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<W, H>({
|
||||||
|
// 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<W, H>({
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
Reference in a new issue