2 * cwc - a crossword compiler.
4 * Copyright (C) 1999, 2000, 2001, 2002 Lars Christensen, 2008 Mark Longair
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
42 #include "letterdict.hh"
48 using std::ostringstream
;
50 //////////////////////////////////////////////////////////////////////
53 walker::walker(grid
&thegrid
) : current(0), g(thegrid
) {
54 limit
= thegrid
.getempty();
58 cell
&walker::currentcell() {
59 if (!inited
) throw error("walker not inited");
60 return g
.cellno(current
);
63 void walker::backto(int c
) {
68 bool walker::current_oneof(int no
[], int n
) {
69 for (int i
=0;i
<n
;i
++) {
76 void walker::backto_oneof(int no
[], int n
) {
77 while (!current_oneof(no
, n
))
81 void walker::backto_oneof(backtracker
&bt
) {
82 backward(false); // dont save current
83 while (!bt
.stophere(current
))
84 backward(true); // save all we skip
87 void walker::forward() {
89 cellno
.push_back(current
);
90 do step_forward(); while (!g
.cellno(current
).isempty());
97 void walker::backward(bool savepreferred
) {
98 if (!g
.cellno(current
).isoutside())
99 g
.cellno(current
).clear(savepreferred
);
100 current
= cellno
.back();
104 bool walker::moresteps() {
105 return (cellno
.size() + 1) < unsigned(limit
);
108 void walker::findnext() {
109 int ncells
= g
.numcells();
110 for (int i
= 0; i
< ncells
; i
++)
111 if (g
.cellno(i
).isempty()) {
115 throw error("No empty cells");
118 void walker::init() {
122 //////////////////////////////////////////////////////////////////////
123 // class prefix_walker
125 void prefix_walker::step_forward() {
126 current
++; // not correct now
129 //////////////////////////////////////////////////////////////////////
130 // class flood_walker
132 flood_walker::flood_walker(grid
&g
) : walker(g
) {
135 void flood_walker::step_forward() {
136 vector
<int>::iterator i
;
137 for (i
= cellno
.begin(); i
!= cellno
.end(); i
++) {
140 int nwords
= g
.cellno(cno
).numwords();
141 for (int w
= 0; w
< nwords
; w
++) {
142 cell
&thecell
= g
.cellno(cno
);
143 wordblock
&wb
= thecell
.getwordblock(w
);
144 int pos
= thecell
.getpos(w
);
145 int len
= wb
.length();
148 int cellbefore
= wb
.getcellno(pos
-1);
149 if (g
.cellno(cellbefore
).isempty()) {
150 current
= cellbefore
;
155 int cellafter
= wb
.getcellno(pos
+ 1);
156 if (g
.cellno(cellafter
).isempty()) {
163 // at this point: no adjacent cells found
167 //////////////////////////////////////////////////////////////////////
170 backtracker::backtracker(grid
&thegrid
) : g(thegrid
) {
173 //////////////////////////////////////////////////////////////////////
174 // class naive_backtracker
176 // The naive backtracker simply backs up to the previously
179 void naive_backtracker::backtrack(walker
&w
) {
183 //////////////////////////////////////////////////////////////////////
184 // class smart_backtracker
186 // the smart backtracker steps back to the previously filled cell
187 // that is within reach from the current cell
189 void smart_backtracker::backtrack(walker
&w
) {
191 int cpos
= w
.stepno();
193 // initially, forget all bt points that are dependend on cells behind
195 list
<cpair
>::iterator next
;
196 for (list
<cpair
>::iterator i
= bt_points
.begin();
197 i
!= bt_points
.end();
200 if ((*i
).first
<= cpos
) {
201 if (setup
.debuginfo
) cout
<< "removing " << (*i
).second
<< "(from " << (*i
).first
<< ")" << endl
;
206 int cno
= w
.getcurrent();
208 cell
&c
= g
.cellno(cno
);
209 int nwords
= c
.numwords();
210 for (int wno
= 0; wno
< nwords
; wno
++) {
211 wordblock
&wb
= c
.getwordblock(wno
);
212 int len
= wb
.length();
214 int pos
= c
.getpos(wno
);
215 for (int p
= 0; p
< len
; p
++) {
216 if ((p
!=pos
)&&(wb
.getcell(p
).isfilled()))
217 bt_points
.push_back(cpair(cpos
, wb
.getcellno(p
)));
222 if (setup
.debuginfo
) {
223 cout
<< "BTSET:" << endl
;
224 for (list
<cpair
>::iterator i
= bt_points
.begin(); i
!= bt_points
.end(); i
++)
225 cout
<< " (" << (*i
).first
<< ',' << (*i
).second
<< ")";
229 w
.backto_oneof(*this);
232 bool smart_backtracker::stophere(int p
) {
233 for (list
<cpair
>::iterator i
= bt_points
.begin();
234 i
!= bt_points
.end();
236 if ((*i
).second
== p
) {
237 bt_points
.erase(i
); // dont stop here again
244 //////////////////////////////////////////////////////////////////////
247 // Cross word compiler logic
249 compiler::compiler(grid
&thegrid
, walker
&thewalker
,
250 backtracker
&thebacktracker
, dict
&thedict
)
251 : g(thegrid
), w(thewalker
), bt(thebacktracker
), d(thedict
) {
252 g
.verbose
= verbose
= false;
257 #define failure false
259 // upon failure, walker is backed up to some cell
260 // the reclevel trying to compute this cell catches it
261 // and others will return.
265 bool compiler::compile_rest(double rejected
) {
266 int c
= w
.getcurrent();
268 cout
<< "attempting to find solution for " << c
<< endl
;
269 symbolset ss
= g(c
).findpossible(d
);
270 int npossible
= numones(ss
);
271 rejected
+= (numalpha
-double(npossible
)) * pow(numalpha
, numcells
- w
.stepno());
276 // use preferred if any
277 if (g(c
).haspreferred()) {
278 symbol s
= g(c
).getpreferred();
279 symbolset ss2
= s
.getsymbolset();
282 ss
&= ~bit
; // remove bit from set
288 for (; bit
; bit
=pickbit(ss
)) {
289 symbol s
= symbol::symbolbit(bit
);
291 if (setup
.showallsteps
) {
293 } else if ((showsteps
&& dtimer
.getmsecs() > 500)) {
299 if (compile_rest(rejected
) == success
) return success
;
300 if (w
.getcurrent() != c
) return failure
; // catch if ==
301 // cout << "continue at " << c << endl;
302 rejected
+= pow(numalpha
, numcells
- w
.stepno());
304 this->rejected
= rejected
;
307 g(c
).setsymbol(symbol::empty
);
309 if (w
.stepno() > 1) {
311 int cur
= w
.getcurrent();
313 cout
<< "return to " << cur
<< " from " << c
<< endl
;
318 void compiler::compile() {
319 dtimer
.reset(); dtimer
.start();
321 numcells
= g
.numopen();
322 numalpha
= symbol::numalpha();
326 //////////////////////////////////////////////////////////////////////
329 void dumpset(symbolset ss
) {
332 for (i
=1, n
= 0; i
; i
<<=1, n
++) {
334 cout
<< symbol::alphabet
[n
];
339 void dumpsymbollist(symbol
*s
, int n
) {
340 for (int i
=0;i
<n
;i
++) {
341 if (s
[i
] == symbol::empty
)
349 void trivial_random_init() {
351 gettimeofday(&tv
, 0);
355 void random_init(setup_s
&s
) {
358 int fd
= open("/dev/random", O_RDONLY
);
360 trivial_random_init();
363 if (read(fd
, &q
, 4) == -1)
369 cout
<< "random seed: " << q
<< endl
;
391 "Usage: cwc [options]\n"
394 " -d <filename> use another dictionary file (default " DEFAULT_DICT_FILE
")\n"
395 " -p <filename> read grid pattern from file\n"
396 " -w <walkertype> Walking heuristics: prefix or flood\n"
397 " -f <format> output format, one of `simple' or `ascii'\n"
398 " -i <indextype> Choose dictionary index style. `btree' or `letter'\n"
399 " -x <svgfilename> Output an SVG version of the grid to <svgfilename>\n"
400 " -r seed Set the random seed\n"
401 " -v Be verbose - prints algorithmic info\n"
402 " -s Print the grid filling regularly during compilation\n"
403 " -S Print the grid filling each step\n"
404 " -b Benchmark dictionaries\n"
405 " -? -h Display this help screen\n"
408 int parseparameters(int argc
, char *argv
[]) {
410 while (c
=getopt(argc
, argv
, "d:p:vf:hsSw:i:x:br:g:?"), c
!= -1) {
413 setup
.gridfile
= optarg
;
414 setup
.gridformat
= setup
.generalgrid
;
416 case 'b': setup
.benchdict
= true; break;
417 case 'd': setup
.dictfile
= optarg
; break;
419 setup
.gridfile
= optarg
;
420 setup
.gridformat
= setup
.squaregrid
;
422 case 'v': setup
.verbose
= true; break;
423 case 'r': setup
.setseed
= true; setup
.seed
= atoi(optarg
); break;
427 setup
.output_format
= setup
.simple_format
;
428 else if (s
== "ascii")
429 setup
.output_format
= setup
.ascii_format
;
431 puts("Invalid format specifier");
439 setup
.walkertype
= setup
.prefixwalker
;
441 setup
.walkertype
= setup
.floodwalker
;
443 puts("Invalid walker specifier");
451 setup
.dictstyle
= setup
.btreedict
;
452 else if (s
=="letter")
453 setup
.dictstyle
= setup
.letterdict
;
455 puts("Invalid dictionary index style");
461 setup
.svgfile
= optarg
;
464 case 's': setup
.showsteps
= true; break;
465 case 'S': setup
.showallsteps
= true; break;
467 case 'h': printf(usage
); return -1;
473 void draw_to_svg(ostream
&os
, grid
&g
, answers
* an
) { // FIXME: probably should be const grid &
475 // We only deal with millimeters here:
476 int page_width
= 210.0;
477 int page_height
= 297.0;
479 os
<< "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << endl
;
480 os
<< "<svg" << endl
;
481 os
<< " xmlns:svg=\"http://www.w3.org/2000/svg\"" << endl
;
482 os
<< " xmlns=\"http://www.w3.org/2000/svg\"" << endl
;
483 os
<< " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" << endl
;
484 os
<< " version=\"1.0\"" << endl
;
485 os
<< " width=\"" << page_width
<< "mm\"" << endl
;
486 os
<< " height=\"" << page_height
<< "mm\"" << endl
;
487 os
<< " id=\"svg2\">" << endl
;
488 os
<< " <defs" << endl
;
489 os
<< " id=\"defs4\" />" << endl
;
491 float empty_cell_side
= 6.2;
492 float answer_cell_side
= empty_cell_side
/ 2;
493 float margin_to_clues
= empty_cell_side
/ 2;
495 float top_margin
= 7.5;
497 float fontSizeTitle
= (g
.w
* empty_cell_side
) / 8;
498 float titleHeight
= 1.2 * fontSizeTitle
;
500 float fontSizeRubric
= fontSizeTitle
/ 4;
501 float rubricHeight
= fontSizeTitle
;
503 float clues_top_left_x
= 7.5;
504 float clues_top_left_y
= top_margin
+ titleHeight
+ rubricHeight
;
508 os
<< " <text" << endl
;
509 os
<< " x=\"" << clues_top_left_x
<< "\"" << endl
;
510 os
<< " y=\"" << top_margin
<< "\"" << endl
;
511 os
<< " style=\"font-size:40px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Charter\"" << endl
;
512 os
<< " id=\"title\"" << endl
;
513 os
<< " xml:space=\"preserve\"><tspan" << endl
;
514 os
<< " x=\"" << clues_top_left_x
<< "mm\"" << endl
;
515 os
<< " y=\"" << (top_margin
+ fontSizeTitle
) << "mm\"" << endl
;
516 os
<< " style=\"font-size:" << fontSizeTitle
<< "mm\"" << endl
;
517 os
<< " id=\"title-inner\">Crossword Title</tspan></text>" << endl
;
519 // Output some example rubric:
521 os
<< " <flowRoot" << endl
;
522 os
<< " id=\"flowRootRubric\"" << endl
;
523 os
<< " xml:space=\"preserve\"><flowRegion" << endl
;
524 os
<< " id=\"flowRegionRubric\"><rect" << endl
;
525 os
<< " width=\"" << (empty_cell_side
* g
.w
) << "mm\"" << endl
;
526 os
<< " height=\"" << (clues_top_left_y
- fontSizeTitle
) << "mm\"" << endl
;
527 os
<< " x=\"" << clues_top_left_x
<< "mm\"" << endl
;
528 os
<< " y=\"" << top_margin
+ titleHeight
<< "mm\"" << endl
;
529 os
<< " id=\"flowRegionRect\" /></flowRegion><flowPara" << endl
;
530 os
<< " style=\"font-style:italic;font-family:Bitstream Charter;font-size:" << fontSizeRubric
<< "mm\"" << endl
;
531 os
<< " id=\"flowParaRubric\">Example rubric here...</flowPara></flowRoot>" << endl
;
533 float clue_column_width
= answer_cell_side
* g
.w
;
535 os
<< " <g id=\"empty-grid\">" << endl
;
536 g
.dump_svg(os
, an
, empty_cell_side
, false, true, clues_top_left_x
, clues_top_left_y
);
537 os
<< " </g>" << endl
;
539 float answers_top_left_x
= clues_top_left_x
+ g
.w
* empty_cell_side
+ margin_to_clues
+ clue_column_width
+ margin_to_clues
;
541 float clue_column_height
= g
.h
* empty_cell_side
* 2 + top_margin
;
543 os
<< " <g id=\"answers-grid\">" << endl
;
544 g
.dump_svg(os
, an
, answer_cell_side
, true, false, answers_top_left_x
, top_margin
+ clue_column_height
- (answer_cell_side
* g
.h
));
545 os
<< " </g>" << endl
;
547 os
<< " <g id=\"clue-list\">" << endl
;
549 // Now position two rectangles for the clues to flow into:
551 ostringstream clue_rectangle_style
;
552 clue_rectangle_style
<< "fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:";
553 clue_rectangle_style
<< (0.15 * (empty_cell_side
/ 6.5));
554 clue_rectangle_style
<< ";stroke-miterlimit:4;stroke-dasharray:1.14168612, 3.42505837;stroke-dashoffset:0;stroke-opacity:1";
556 float rect_x
= clues_top_left_x
+ g
.w
* empty_cell_side
+ margin_to_clues
;
557 float rect_y
= top_margin
;
559 os
<< " <rect" << endl
;
560 os
<< " style=\"" << clue_rectangle_style
.str() << "\"" << endl
;
561 os
<< " id=\"clue-column-0\"" << endl
;
562 os
<< " width=\"" << clue_column_width
<< "mm\"" << endl
;
563 os
<< " height=\"" << clue_column_height
<< "mm\"" << endl
;
564 os
<< " x=\"" << rect_x
<< "mm\"" << endl
;
565 os
<< " y=\"" << rect_y
<< "mm\"/>" << endl
;
567 rect_x
+= clue_column_width
+ margin_to_clues
;
568 clue_column_height
-= margin_to_clues
+ answer_cell_side
* g
.h
;
570 os
<< " <rect" << endl
;
571 os
<< " style=\"" << clue_rectangle_style
.str() << "\"" << endl
;
572 os
<< " id=\"clue-column-1\"" << endl
;
573 os
<< " width=\"" << clue_column_width
<< "mm\"" << endl
;
574 os
<< " height=\"" << clue_column_height
<< "mm\"" << endl
;
575 os
<< " x=\"" << rect_x
<< "mm\"" << endl
;
576 os
<< " y=\"" << rect_y
<< "mm\"/>" << endl
;
578 os
<< " <flowRoot xml:space=\"preserve\" id=\"clues-flow-root\">";
580 os
<< "<flowRegion id=\"clues-flow-region\">";
582 os
<< "<use x=\"0\" y=\"0\" xlink:href=\"#clue-column-0\" id=\"use-clue-column-0\" width=\"210mm\" height=\"210mm\"/>";
583 os
<< "<use x=\"0\" y=\"0\" xlink:href=\"#clue-column-1\" id=\"use-clue-column-1\" width=\"210mm\" height=\"210mm\"/>";
585 os
<< "</flowRegion>";
587 ostringstream clue_font_style_string
;
588 clue_font_style_string
<< "font-size:";
589 clue_font_style_string
<< ((2 * fontSizeTitle
) / 5);
590 clue_font_style_string
<< "mm;font-family:Bitstream Charter";
592 an
->dump_to_svg(os
,clue_font_style_string
.str());
596 os
<< " </g>" << endl
;
598 os
<< "</svg>" << endl
;
601 int main(int argc
, char *argv
[]) {
602 if (parseparameters(argc
, argv
) == -1) exit(EXIT_FAILURE
);
604 symbol::buildindex();
606 if (setlocale(LC_CTYPE
, "") == 0)
607 cout
<< "Failed to set locale" << endl
;
611 if (setup
.benchdict
) {
617 if (setup
.dictstyle
==setup
.btreedict
) {
618 cout
<< "Using binary tree index" << endl
;
619 d
= new btree_dict();
620 } else if (setup
.dictstyle
==setup
.letterdict
) {
621 cout
<< "Using letter index" << endl
;
622 d
= new letterdict();
624 d
->load(setup
.dictfile
);
627 if (setup
.gridformat
== setup
.generalgrid
)
628 g
.load(setup
.gridfile
);
629 else if (setup
.gridformat
== setup
.squaregrid
)
630 g
.load_template(setup
.gridfile
);
631 // g.dump_ggrid(cout);
632 int nopen
= g
.numopen();
633 double space
= pow(symbol::numalpha(), nopen
);
634 cout
<< nopen
<< " cells to be filled. " << space
<< " possible fillings." << endl
;
636 if (setup
.walkertype
==setup
.prefixwalker
) {
637 w
= new prefix_walker(g
);
638 puts("Using prefix walking heuristics");
639 } else if (setup
.walkertype
==setup
.floodwalker
) {
640 w
= new flood_walker(g
);
641 puts("Using flood walking heuristics");
643 puts("Internal error");
647 cout
<< "Degree of interlock: " << g
.interlockdegree()*100 << "%" << endl
;
648 double depdeg1
= g
.dependencydegree(1);
649 double depdeg2
= g
.dependencydegree(2);
650 cout
<< "Degree of dependency: " << depdeg1
<< '(' << (depdeg1
*100.0/nopen
) << "%)" << endl
;
651 cout
<< "Degree of 2nd level dependency: " << depdeg2
<< '(' << (depdeg2
*100.0/nopen
) << "%)" << endl
;
653 smart_backtracker
bt(g
);
655 compiler
c(g
, *w
, bt
, *d
);
656 c
.verbose
= setup
.verbose
;
657 c
.showsteps
= setup
.showsteps
;
663 g
.dump(cout
,setup
.output_format
,0);
664 if( setup
.svgfile
.length() > 0 ) {
665 cout
<< "Warning: can't currently generate SVG files ";
666 cout
<< "for non-grid crosswords, so ";
667 cout
<< "ignoring '-s " << setup
.svgfile
<< "'" << endl
;
670 answers an
= g
.getanswers();
671 g
.dump(cout
, setup
.output_format
, &an
);
675 if( setup
.svgfile
.length() > 0 ) {
676 ofstream
ofs(setup
.svgfile
.c_str());
677 draw_to_svg(ofs
, g
, &an
);
679 cout
<< "Wrote SVG to: '" << setup
.svgfile
<< "'" << endl
;
682 cout
<< "Attempt average: " << g
.attemptaverage() << endl
;
683 cout
<< "Compilation time: " << t
.getmsecs() << " msecs" << endl
;
685 double searched
= c
.getrejected();
686 cout
<< searched
<< " solutions searched. " << (searched
*100/space
) << "% of search space." << endl
;
688 cout
<< e
.what() << endl
;
697 bd
.load(DEFAULT_DICT_FILE
);
701 d2
.load(DEFAULT_DICT_FILE
);
704 cout
<< "btree=" << t1
<< ", letter=" << t2
<< endl
;
707 int dictbench(dict
&d
) {
708 symbol s
[MAXWORDLEN
];
713 for (int r
=0; r
<100; r
++) {
714 for (int i
=0;i
<MAXWORDLEN
;i
++)
717 for (int len
= 1; len
< MAXWORDLEN
; len
++) {
718 for (int i
=0;i
<len
;i
++) {
719 s
[i
] = symbol::empty
;
722 int t
= o
[q
]; o
[q
] = o
[i
]; o
[i
] = t
;
727 symbolset ss
= d
.findpossible(s
, len
, pos
);
729 s
[pos
] = symbol::symbolbit(pickbit(ss
));