Adds source file for grid. Adds verbose option.
This commit is contained in:
parent
b61b6c04ff
commit
f4ef3d2837
9 changed files with 185 additions and 139 deletions
|
@ -1,13 +1,13 @@
|
||||||
project(PuzzleWuzzleGenerator)
|
project(PuzzleWuzzleGenerator)
|
||||||
cmake_minimum_required(VERSION 2.8)
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
include_directories(SYSTEM "${PROJECT_SOURCE_DIR}/include/")
|
include_directories("${PROJECT_SOURCE_DIR}/include/")
|
||||||
add_definitions(-std=c++1y)
|
add_definitions(-std=c++1y)
|
||||||
|
|
||||||
find_package(Boost REQUIRED COMPONENTS program_options system)
|
find_package(Boost REQUIRED COMPONENTS program_options system)
|
||||||
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
|
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
|
||||||
set(libs ${libs} ${Boost_LIBRARIES})
|
set(libs ${libs} ${Boost_LIBRARIES})
|
||||||
|
|
||||||
aux_source_directory(. SRC_LIST)
|
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
add_subdirectory("lib")
|
||||||
target_link_libraries(${PROJECT_NAME} ${libs})
|
add_subdirectory("src")
|
||||||
|
|
|
@ -34,14 +34,18 @@ auto cluster_at(Field const & field, typename Field::Position const & p){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename Field>
|
template <typename Field, typename Rules>
|
||||||
auto all_clusters(Field const & field){
|
auto all_clusters(Field const & field, Rules const & rules){
|
||||||
boost::container::flat_set<decltype(cluster_at(field, std::declval<typename Field::Position>()))> ret;
|
boost::container::flat_set<decltype(cluster_at(field, std::declval<typename Field::Position>()))> ret;
|
||||||
ret.reserve(10);
|
ret.reserve(10);
|
||||||
for(auto&& p : field.all_positions()){
|
for(auto&& p : field.all_positions()){
|
||||||
if(field.empty(p)) continue;
|
if(field.empty(p)) continue;
|
||||||
ret.insert(cluster_at(field, p));
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iosfwd>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
// Position {0,0} is bottom left.
|
// Position {0,0} is bottom left.
|
||||||
|
@ -16,13 +16,11 @@
|
||||||
struct DynamicGrid{
|
struct DynamicGrid{
|
||||||
using Position = std::pair<int, int>;
|
using Position = std::pair<int, int>;
|
||||||
|
|
||||||
template <typename Input>
|
DynamicGrid(int width, int height, std::vector<int> && data);
|
||||||
DynamicGrid(int width, int height, Input it)
|
|
||||||
: W(width)
|
//! \brief Returns an array of all valid positions (may be empty)
|
||||||
, H(height)
|
auto const & all_positions() const{
|
||||||
, grid(W*H, 0)
|
return positions;
|
||||||
{
|
|
||||||
std::copy_n(it, W*H, std::begin(grid));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Returns true if p represents a valid position
|
//! \brief Returns true if p represents a valid position
|
||||||
|
@ -31,130 +29,56 @@ struct DynamicGrid{
|
||||||
&& p.first < W && p.second < H;
|
&& p.first < W && p.second < H;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \brief Get (mutable) value at p
|
||||||
|
auto & get(Position const & p){
|
||||||
|
assert(valid(p));
|
||||||
|
return grid[static_cast<size_t>(p.first + p.second*W)];
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \brief Get value at p
|
||||||
|
auto const& get(Position const & p) const {
|
||||||
|
assert(valid(p));
|
||||||
|
return grid[static_cast<size_t>(p.first + p.second*W)];
|
||||||
|
}
|
||||||
|
|
||||||
//! \brief Returns true if p represents an empty cell
|
//! \brief Returns true if p represents an empty cell
|
||||||
auto empty(Position const & p) const {
|
auto empty(Position const & p) const {
|
||||||
assert(valid(p));
|
assert(valid(p));
|
||||||
return get(p) == 0;
|
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
|
//! \brief Returns all neighbors of p
|
||||||
//! Only returns valid neighbors (and p does not need to be valid)
|
//! Only returns valid neighbors (and p does not need to be valid)
|
||||||
auto neighbors(Position const & p) const {
|
small_vector<Position, 4> neighbors(Position const & p) const;
|
||||||
small_vector<Position, 4> ret;
|
|
||||||
|
|
||||||
static Position nbs[] = {
|
//! \brief Let the block fall (all the way)
|
||||||
{-1, 0}, {1, 0}, {0, 1}, {0, -1}
|
void collapse();
|
||||||
};
|
|
||||||
|
|
||||||
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<size_t>(p.first + p.second*W)];
|
|
||||||
}
|
|
||||||
|
|
||||||
//! \brief Get value at p
|
|
||||||
int const& get(Position const & p) const {
|
|
||||||
assert(valid(p));
|
|
||||||
return grid[static_cast<size_t>(p.first + p.second*W)];
|
|
||||||
}
|
|
||||||
|
|
||||||
//! \brief Pretty prints grid to out
|
//! \brief Pretty prints grid to out
|
||||||
void print(std::ostream& out) const {
|
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:
|
private:
|
||||||
int W;
|
int W;
|
||||||
int H;
|
int H;
|
||||||
std::vector<int> grid;
|
std::vector<int> grid;
|
||||||
|
std::vector<Position> 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<Position> 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
|
//! \brief Single vertical collapsing step
|
||||||
auto vcollapse(){
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! \brief Single horizontal collapsing step
|
//! \brief Single horizontal collapsing step
|
||||||
auto hcollapse(){
|
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;
|
//! \brief Returns true if the whole column at x is empty
|
||||||
for(; x2 < W; ++x2){
|
auto empty_column(int x);
|
||||||
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());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename URNG>
|
template <typename URNG>
|
||||||
|
@ -162,5 +86,5 @@ auto random_dynamic_grid(int W, int H, int C, URNG&& r){
|
||||||
std::uniform_int_distribution<int> dis(1, C);
|
std::uniform_int_distribution<int> dis(1, C);
|
||||||
std::vector<int> v(W*H);
|
std::vector<int> v(W*H);
|
||||||
std::generate_n(std::begin(v), W*H, [&]{ return dis(r); });
|
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
struct BasicRules {
|
struct BasicRules {
|
||||||
BasicRules(int minimal_cluster_size)
|
BasicRules(unsigned int minimal_cluster_size)
|
||||||
: min_size(minimal_cluster_size)
|
: min_size(minimal_cluster_size)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -10,5 +10,5 @@ struct BasicRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int min_size;
|
unsigned int min_size;
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,19 +17,19 @@ auto pop(std::vector<T> & v){
|
||||||
|
|
||||||
template <typename Grid, typename Rules>
|
template <typename Grid, typename Rules>
|
||||||
auto solve_impl(Grid const & grid, Rules const & rules, Solution<Grid> & s){
|
auto solve_impl(Grid const & grid, Rules const & rules, Solution<Grid> & s){
|
||||||
// are we done yet?
|
if(grid.empty()){
|
||||||
for(auto&& p : grid.all_positions()){
|
// solved path
|
||||||
if(!grid.empty(p)) goto analyse;
|
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
|
auto&& clusters = all_clusters(grid, rules);
|
||||||
analyse:
|
if(clusters.empty()){
|
||||||
for(auto&& c : all_clusters(grid)){
|
// unsolvable path
|
||||||
if(c.size() < rules.min_cluster_size()) continue;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto&& c : clusters){
|
||||||
// remove the cluster
|
// remove the cluster
|
||||||
s.current_trace.push_back(*c.begin());
|
s.current_trace.push_back(*c.begin());
|
||||||
auto new_grid = make_empty(grid, c);
|
auto new_grid = make_empty(grid, c);
|
||||||
|
|
4
lib/CMakeLists.txt
Normal file
4
lib/CMakeLists.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
file(GLOB sources "*.cpp")
|
||||||
|
add_library(common STATIC ${sources})
|
||||||
|
target_link_libraries(common ${libs})
|
96
lib/dynamic_grid.cpp
Normal file
96
lib/dynamic_grid.cpp
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#include "dynamic_grid.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
//! \brief Helper function for all_positions()
|
||||||
|
static auto all_positions_impl(int W, int H) {
|
||||||
|
std::vector<DynamicGrid::Position> 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<int> && data)
|
||||||
|
: W(width), H(height), grid(std::move(data)) {
|
||||||
|
assert(grid.size() == W*H);
|
||||||
|
positions = all_positions_impl(W, H);
|
||||||
|
}
|
||||||
|
|
||||||
|
small_vector<DynamicGrid::Position, 4>
|
||||||
|
DynamicGrid::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 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' : ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/CMakeLists.txt
Normal file
8
src/CMakeLists.txt
Normal file
|
@ -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()
|
|
@ -20,6 +20,7 @@ int main(int argc, char** argv){
|
||||||
int h = 5;
|
int h = 5;
|
||||||
int c = 3;
|
int c = 3;
|
||||||
int explosion_size = 3;
|
int explosion_size = 3;
|
||||||
|
int n = 1000;
|
||||||
|
|
||||||
// Describe program options
|
// Describe program options
|
||||||
po::options_description opts;
|
po::options_description opts;
|
||||||
|
@ -28,6 +29,8 @@ int main(int argc, char** argv){
|
||||||
("h", po::value(&h), "height of puzzles")
|
("h", po::value(&h), "height of puzzles")
|
||||||
("c", po::value(&c), "number of colors")
|
("c", po::value(&c), "number of colors")
|
||||||
("explosion_size", po::value(&explosion_size), "minimum explosion size")
|
("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");
|
("help", po::bool_switch(), "show this help");
|
||||||
|
|
||||||
// Parse and store them in a vm
|
// Parse and store them in a vm
|
||||||
|
@ -41,6 +44,8 @@ int main(int argc, char** argv){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool verbose = vm["verbose"].as<bool>();
|
||||||
|
|
||||||
#define OUT(x) std::cout << #x << " = " << x << "\n"
|
#define OUT(x) std::cout << #x << " = " << x << "\n"
|
||||||
OUT(w); OUT(h); OUT(c); OUT(explosion_size);
|
OUT(w); OUT(h); OUT(c); OUT(explosion_size);
|
||||||
#undef OUT
|
#undef OUT
|
||||||
|
@ -50,25 +55,30 @@ int main(int argc, char** argv){
|
||||||
|
|
||||||
BasicRules rules(explosion_size);
|
BasicRules rules(explosion_size);
|
||||||
|
|
||||||
int count = 50000;
|
|
||||||
int solvable = 0;
|
int solvable = 0;
|
||||||
int unsolvable = 0;
|
int unsolvable = 0;
|
||||||
while(--count){
|
while(--n){
|
||||||
auto field = random_dynamic_grid(w, h, c, gen);
|
auto field = random_dynamic_grid(w, h, c, gen);
|
||||||
|
|
||||||
// std::cout << "\n= puzzle " << count << " =\n";
|
if(verbose){
|
||||||
// field.print(std::cout);
|
std::cout << "\n= puzzle " << n << " =\n";
|
||||||
|
field.print(std::cout);
|
||||||
|
}
|
||||||
|
|
||||||
auto solution = solve(field, rules);
|
auto solution = solve(field, rules);
|
||||||
if(!solution.solution_traces.empty()){
|
if(!solution.solution_traces.empty()){
|
||||||
++solvable;
|
++solvable;
|
||||||
// std::cout << solution.solution_traces.size() << " solutions\n";
|
if(verbose){
|
||||||
// for(auto&& t : solution.solution_traces[0]){
|
std::cout << solution.solution_traces.size() << " solutions\n";
|
||||||
// std::cout << "(" << t.first << ", " << t.second << ")\n";
|
for(auto&& t : solution.solution_traces[0]){
|
||||||
// }
|
std::cout << "(" << t.first << ", " << t.second << ")\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
++unsolvable;
|
++unsolvable;
|
||||||
// std::cout << "no solutions\n";
|
if(verbose){
|
||||||
|
std::cout << "no solutions\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue