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)
|
||||
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")
|
||||
|
|
|
@ -34,14 +34,18 @@ auto cluster_at(Field const & field, typename Field::Position const & p){
|
|||
}
|
||||
|
||||
|
||||
template <typename Field>
|
||||
auto all_clusters(Field const & field){
|
||||
template <typename Field, typename Rules>
|
||||
auto all_clusters(Field const & field, Rules const & rules){
|
||||
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));
|
||||
}
|
||||
for(auto it = ret.begin(); it != ret.end();){
|
||||
if (it->size() < rules.min_cluster_size()) it = ret.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include <array>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <iosfwd>
|
||||
#include <functional>
|
||||
|
||||
// Position {0,0} is bottom left.
|
||||
|
@ -16,13 +16,11 @@
|
|||
struct DynamicGrid{
|
||||
using Position = std::pair<int, int>;
|
||||
|
||||
template <typename Input>
|
||||
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<int> && 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<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
|
||||
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<Position, 4> ret;
|
||||
small_vector<Position, 4> 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<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 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<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
|
||||
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 <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::vector<int> 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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -17,19 +17,19 @@ auto pop(std::vector<T> & v){
|
|||
|
||||
template <typename Grid, typename Rules>
|
||||
auto solve_impl(Grid const & grid, Rules const & rules, Solution<Grid> & 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);
|
||||
|
|
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 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<bool>();
|
||||
|
||||
#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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in a new issue