1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
/* This version of my crossword puzzle generator works by filtering all matching words from
a word list. The crossword field is at time quadratic.
*/
#include <array>
#include <exception>
#include <fstream>
#include <iostream>
#include <random>
#include <string>
#include <vector>
std::default_random_engine rng( std::random_device{}() );
std::uniform_int_distribution<> dist;
constexpr int GRID_SIZE = 7; // height and width
using Grid = std::array<std::array<char,GRID_SIZE>,GRID_SIZE>;
constexpr char EMPTY = '.'; // mark of empty cells
enum class Direction { Horz, Vert };
struct Pos { int col, row; };
void reset_grid( Grid & grid )
{ for( auto & row : grid ) for( auto & cell : row) cell = EMPTY; }
/* Writes the grid. */
std::ostream & operator<<( std::ostream & os, const Grid & grid){
for( const auto & row : grid ) {
for( const char cell : row)
os << ' ' << (cell == EMPTY ? ' ' : static_cast<char>(std::toupper(cell)));
os << '\n';
}
return os;
}
/* Makes a sieve for a distinct row or column of the grid.
If Direction dir == Direction:Horz, 'line' is the distinct row, otherwise the column.
If a sieve could made, it returns true. If no sieve could made, it returns false.
That's the case if there is no free cell in the grid at the distinct row (or colum).
*/
bool make_sieve_line(
std::string & sieve
, const int line // could be a row-no. or column-no.
, const Direction dir
, const Grid & grid
){
sieve.clear();
bool has_blank = false;
if ( dir == Direction::Horz ) {
for( int col = 0; col < grid[0].size(); ++ col ) {
sieve.push_back( grid[line][col] );
if( grid[line][col] == EMPTY ) has_blank = true;
}
} else { // dir == Direction::Vert
for( int row = 0; row < grid.size(); ++ row) {
sieve.push_back( grid[row][line] );
if( grid[row][line] == EMPTY ) has_blank = true;
}
}
return has_blank ? true : false;
}
/* Filters all words of word_list by 'sieve'.
The resulting words will stored at 'result_list'.
*/
void do_sieve(
const std::string & sieve
, const std::vector<std::string> & word_list
, std::vector<std::string *> & result_list
) {
result_list.clear();
for( const auto & word : word_list ){
bool word_matches = true;
for( int idx = 0; idx < word.length(); ++idx ) {
if( sieve[idx] != EMPTY && word[idx] != sieve[idx] ){
word_matches = false;
break;
}
}
if( word_matches ) result_list.push_back( const_cast<std::string *>(& word) );
}
}
/* Counts how many letters are placed within the grid. */
int fillcount( const Grid & grid ){
int count = 0;
for(const auto & row : grid) for(const char cell : row) if(cell != EMPTY) ++count;
return count;
}
/* Places a word inside of the grid */
void place_word(
const std::string & word
, const Pos & pos
, const Direction dir
, Grid & grid
){
if( dir == Direction::Horz )
for( int i = 0; i < word.length(); ++i )
grid[ pos.row ][ pos.col + i ] = word[i];
else
for( int i = 0; i < word.length(); ++i )
grid[ pos.row + i ][ pos.col ] = word[i];
}
/* Reads the words from file. */
void read_wordlist(
const std::string & file_name
, const int word_length
, std::vector<std::string> & word_list
) {
std::ifstream ifs( file_name );
if( ! ifs ) {
throw std::runtime_error(
"read_wordlist(): Dictionary " + file_name + " couldn't opened!"
);
}
word_list.clear();
for( std::string word; ifs >> word; )
if( word.length() == word_length) word_list.push_back( word );
dist.param( std::uniform_int_distribution<>::param_type( 0, word_list.size() - 1) );
}
void fill_grid(
std::vector<std::string> word_list
, Grid & grid
){
Direction dir = Direction::Horz;
// The 1st word (randomly chosen) gets placed.
place_word( word_list[ dist(rng) ], Pos{0,0}, dir, grid );
// rc stands for row_or_column
for( int rc = 0; rc < GRID_SIZE; )
{
dir = (dir == Direction::Vert ? Direction::Horz : Direction::Vert);
std::string sieve;
make_sieve_line( sieve, rc, dir, grid);
std::vector<std::string *> sieved_list_p;
do_sieve( sieve, word_list, sieved_list_p );
if( sieved_list_p.size() == 0 )
break;
dist.param( std::uniform_int_distribution<int>::param_type(
0, sieved_list_p.size() - 1
) );
std::string & word = *sieved_list_p[ dist(rng) ];
Pos pos;
if( dir == Direction::Vert ) {
pos.row = 0; pos.col = rc;
place_word( word, pos, dir, grid );
++ rc;
} else { // dir == Direction::Horz
pos.row = rc; pos.col = 0;
place_word( word, pos, dir, grid );
}
}
}
int main( int argc, char * argv[] )
{
std::string file_name;
if( argc > 1) file_name = argv[1];
else file_name = "dictonary.txt";
std::vector<std::string> word_list;
read_wordlist( file_name, GRID_SIZE, word_list );
Grid grid, best;
int best_fillcount = 0;
for( int i = 0; i < 1000; ++i ) { // 1000 tries
reset_grid( grid );
fill_grid( word_list, grid);
int grid_fillcount = fillcount( grid );
if( grid_fillcount > best_fillcount ) {
best = grid; best_fillcount = grid_fillcount;
}
}
std::cout << best;
}
|