1
Fork 0

Puzzle solver

This commit is contained in:
Joshua Moerman 2014-02-04 20:00:21 +01:00
commit 083141d346
6 changed files with 295 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.user
build-*

13
CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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;
}