1 # Get A Clue (C) 2010 V. Harishankar
2 # Crossword puzzle maker program
3 # Licensed under the GNU GPL v3
5 # Cross puzzle creator class
11 import crosswordpuzzle
13 class CrosswordPuzzleCreator
:
14 # ansi color codes for grid display
22 # Black background white text
23 BLACK_BG
= '\033[40;1;37m'
24 # white background black text
25 WHITE_BG
= '\033[47;1;30m'
26 # white background red text
27 REDWHITE_BG
= '\033[47;1;31m'
33 self
.current_file
= None
37 def save_puzzle (self
):
39 fpuzzle
= open (self
.current_file
, "wb")
40 cPickle
.dump (self
.puzzle
, fpuzzle
, cPickle
.HIGHEST_PROTOCOL
)
41 sys
.stdout
.write (self
.BLUE
+ "Puzzle saved to: " +
42 self
.current_file
+ "\n" + self
.ENDCOL
)
45 def load_puzzle (self
):
47 fpuzzle
= open (self
.current_file
, "rb")
48 self
.puzzle
= cPickle
.load (fpuzzle
)
50 # display the grid with words
51 def print_puzzle (self
, no_words
=False):
52 # if self.puzzle is not none
54 # get row, col and print them (with grid number if set)
55 for col
in range (self
.puzzle
.cols
):
56 # print the first row as column headers
57 sys
.stdout
.write (self
.BLUE
+ ' %02d' % col
+ self
.ENDCOL
)
58 sys
.stdout
.write ("\n")
60 for row
in range (self
.puzzle
.rows
):
61 for col
in range (self
.puzzle
.cols
):
63 # if the cell is numbered i.e. start of a word
64 if self
.puzzle
.data
[row
][col
].numbered
!= 0:
65 sys
.stdout
.write (self
.REDWHITE_BG
+
66 '%02d ' % self
.puzzle
.data
[row
][col
].numbered
+ self
.ENDCOL
)
69 if self
.puzzle
.data
[row
][col
].char
== "#":
70 sys
.stdout
.write (self
.BLACK_BG
+ ' ' + self
.ENDCOL
)
72 sys
.stdout
.write (self
.WHITE_BG
+ ' ' + self
.ENDCOL
)
73 # if the character is not a blank or a block
74 if (self
.puzzle
.data
[row
][col
].char
<> "." and
75 self
.puzzle
.data
[row
][col
].char
<> "#"):
76 # if words are to be shown regardless of hidden/revealed state
78 sys
.stdout
.write (self
.WHITE_BG
+
79 self
.puzzle
.data
[row
][col
].char
+ self
.ENDCOL
)
81 # display only revealed
82 if self
.puzzle
.data
[row
][col
].revealed
is True:
83 sys
.stdout
.write (self
.WHITE_BG
+
84 self
.puzzle
.data
[row
][col
].char
+ self
.ENDCOL
)
87 sys
.stdout
.write (self
.WHITE_BG
+ '.' + self
.ENDCOL
)
88 elif self
.puzzle
.data
[row
][col
].char
== '.':
89 sys
.stdout
.write (self
.WHITE_BG
+
90 self
.puzzle
.data
[row
][col
].char
+ self
.ENDCOL
)
91 elif self
.puzzle
.data
[row
][col
].char
== '#':
92 sys
.stdout
.write (self
.BLACK_BG
+ " " + self
.ENDCOL
)
94 sys
.stdout
.write (' ' + self
.BLUE
+ "%2d" % row
+ self
.ENDCOL
+ "\n")
95 raw_input (self
.BRICKRED
+ "Press <return> to continue" + self
.ENDCOL
)
97 # display existing clues
98 def on_display_clues (self
):
100 aclues
= self
.puzzle
.get_clues_across ()
101 sys
.stdout
.write (self
.BOLD
+ "\n------------\n")
102 sys
.stdout
.write ("Across words\n")
103 sys
.stdout
.write ("------------\n" + self
.ENDCOL
)
105 for word
, clue
in aclues
:
106 sys
.stdout
.write (self
.BOLD
+ word
[0] + ": " + self
.ENDCOL
)
108 sys
.stdout
.write (self
.BLUE
+ clue
+ "\n" + self
.ENDCOL
)
110 sys
.stdout
.write (self
.BLUE
+ "(No clue yet)\n" + self
.ENDCOL
)
111 except crosswordpuzzle
.NoWordsException
:
112 sys
.stderr
.write ("No words across\n")
115 dclues
= self
.puzzle
.get_clues_down ()
116 sys
.stdout
.write (self
.BOLD
+ "\n----------\n")
117 sys
.stdout
.write ("Down words\n")
118 sys
.stdout
.write ("----------\n" + self
.ENDCOL
)
120 for word
, clue
in dclues
:
121 sys
.stdout
.write (self
.BOLD
+ word
[0] + ": " + self
.ENDCOL
)
123 sys
.stdout
.write (self
.BLUE
+ clue
+ "\n" + self
.ENDCOL
)
125 sys
.stdout
.write (self
.BLUE
+ "(No clue yet)\n" + self
.ENDCOL
)
126 except crosswordpuzzle
.NoWordsException
:
127 sys
.stderr
.write ("No words down\n")
129 raw_input (self
.BRICKRED
+ "Press <return> to continue" + self
.ENDCOL
)
131 # set a clue to a word
132 def on_set_clue (self
):
134 # get the row and column
135 srow
= raw_input (self
.BRICKRED
+ "At row: " + self
.ENDCOL
)
136 scol
= raw_input (self
.BRICKRED
+ "At col: " + self
.ENDCOL
)
137 # try converting it to number
142 sys
.stderr
.write ("Invalid row or column\n")
146 # across word set the clue if found
147 aword
, arow
, acol
, alen
= self
.puzzle
.get_word_across (row
, col
)
148 sys
.stdout
.write (self
.BLUE
+ "Across word at position: " + aword
+ "\n" + self
.ENDCOL
)
149 clue
= raw_input (self
.BRICKRED
+ "Clue for across word: " + self
.ENDCOL
)
150 self
.puzzle
.data
[arow
][acol
].clue_across
= clue
151 sys
.stdout
.write (self
.BLUE
+ "Set the clue: \n" + self
.puzzle
.data
[arow
][acol
].clue_across
)
152 except crosswordpuzzle
.NoWordException
:
153 sys
.stderr
.write ("No across word found at that position\n")
156 # down word set the clue if found
157 dword
, drow
, dcol
, dlen
= self
.puzzle
.get_word_down (row
, col
)
158 sys
.stdout
.write (self
.BLUE
+ "Down word at position: " + dword
+ "\n" + self
.ENDCOL
)
159 clue
= raw_input (self
.BRICKRED
+ "Clue for down word: " + self
.ENDCOL
)
160 self
.puzzle
.data
[drow
][dcol
].clue_down
= clue
161 sys
.stdout
.write (self
.BLUE
+ "Set the clue: \n" + self
.puzzle
.data
[drow
][dcol
].clue_down
)
162 except crosswordpuzzle
.NoWordException
:
163 sys
.stderr
.write ("No down word found at that position\n")
166 def on_remove_down (self
):
169 srow
= raw_input (self
.BRICKRED
+ "At row: " + self
.ENDCOL
)
170 scol
= raw_input (self
.BRICKRED
+ "At col: " + self
.ENDCOL
)
175 sys
.stderr
.write ("Invalid row or column\n")
179 self
.puzzle
.remove_word_down (row
, col
)
180 sys
.stdout
.write (self
.BLUE
+ "Down word removed\n" + self
.ENDCOL
)
181 except crosswordpuzzle
.FrozenGridException
:
182 sys
.stderr
.write ("Word cannot be removed from a frozen puzzle\n")
183 except crosswordpuzzle
.NoWordException
:
184 sys
.stderr
.write ("No down word found at that position\n")
186 # remove an across word
187 def on_remove_across (self
):
190 srow
= raw_input (self
.BRICKRED
+ "At row: " + self
.ENDCOL
)
191 scol
= raw_input (self
.BRICKRED
+ "At col: " + self
.ENDCOL
)
196 sys
.stderr
.write ("Invalid row or column\n")
200 self
.puzzle
.remove_word_across (row
, col
)
201 sys
.stdout
.write (self
.BLUE
+ "Across word removed\n" + self
.ENDCOL
)
202 except crosswordpuzzle
.FrozenGridException
:
203 sys
.stderr
.write ("Word cannot be removed from a frozen puzzle\n")
204 except crosswordpuzzle
.NoWordException
:
205 sys
.stderr
.write ("No across word found at that position\n")
207 # add a word to the puzzle
208 def on_add_word (self
, across
=True):
209 # first display the grid
211 # get the row and column
212 srow
= raw_input (self
.BRICKRED
+ "Start row: " + self
.ENDCOL
)
213 scol
= raw_input (self
.BRICKRED
+ "Start col: " + self
.ENDCOL
)
214 # try converting it to number
219 sys
.stderr
.write ("Invalid row or column\n")
222 word
= raw_input (self
.BRICKRED
+ "Word: " + self
.ENDCOL
)
224 # try to add the word to the puzzle grid
227 self
.puzzle
.set_word_across (row
, col
, word
)
229 self
.puzzle
.set_word_down (row
, col
, word
)
230 except crosswordpuzzle
.TooLongWordException
:
231 sys
.stderr
.write ("Word is too long to fit in the grid! Aborting.\n")
232 except crosswordpuzzle
.IntersectWordException
:
233 sys
.stderr
.write ("Word intersects badly with another word!\n")
234 except crosswordpuzzle
.FrozenGridException
:
235 sys
.stderr
.write ("Word cannot be added to a frozen puzzle.\n")
237 # Export to image/HTML
238 def on_export_image (self
, solution
=True):
240 sys
.stdout
.write (self
.BLUE
+ "Exporting puzzle to image/HTML\n")
241 pngfile
= raw_input (self
.BRICKRED
+ "Filename (PNG): " + self
.ENDCOL
)
242 if solution
is False:
243 htmlfile
= raw_input (self
.BRICKRED
+ "Filename (HTML): " +
245 puztitle
= raw_input (self
.BRICKRED
+ "Title of puzzle: " +
247 self
.puzzle
.export_image (pngfile
, htmlfile
, puztitle
, solution
)
249 self
.puzzle
.export_image (pngfile
)
251 sys
.stdout
.write (self
.BLUE
+ "Successfully exported!")
252 except crosswordpuzzle
.FrozenGridException
:
253 sys
.stderr
.write ("Cannot export as grid is not frozen/finalized\n")
254 except crosswordpuzzle
.NoWordsException
:
255 sys
.stderr
.write ("No words to export!\n")
257 # Export to across lite
258 def on_export_acrosslite (self
):
260 sys
.stdout
.write (self
.BLUE
+ "Exporting to AcrossLite(tm) Format\n" +
262 title
= raw_input (self
.BRICKRED
+ "Puzzle title: " + self
.ENDCOL
)
263 name
= raw_input (self
.BRICKRED
+ "Author name: " + self
.ENDCOL
)
264 copyright
= raw_input (self
.BRICKRED
+ "Copyright: " + self
.ENDCOL
)
265 exportfile
= raw_input (self
.BRICKRED
+ "Export to file: " + self
.ENDCOL
)
267 acrosslite_str
= self
.puzzle
.export_acrosslite (title
, name
, copyright
)
268 fexport
= open (exportfile
, "w")
269 fexport
.write (acrosslite_str
)
271 sys
.stdout
.write (self
.BLUE
+ "Exported AcrossLite(tm) File: " +
272 exportfile
+ "\n" + self
.ENDCOL
)
273 except crosswordpuzzle
.FrozenGridException
:
274 sys
.stderr
.write ("Cannot export as grid is not frozen/finalized\n")
275 except crosswordpuzzle
.NoWordsException
:
276 sys
.stderr
.write ("No words to export!\n")
279 def do_puzzle_loop (self
):
280 # there is a current file
281 if self
.current_file
and self
.puzzle
:
283 sys
.stdout
.write (self
.BOLD
+ "\n-----------------------------------\n")
284 sys
.stdout
.write ("Puzzle: " + self
.current_file
+ "\n")
285 sys
.stdout
.write ("-----------------------------------" + self
.ENDCOL
+ "\n")
286 sys
.stdout
.write (self
.BLUE
+ "1. Display grid\n")
287 sys
.stdout
.write ("2. Add across word\n")
288 sys
.stdout
.write ("3. Add down word\n")
289 sys
.stdout
.write ("4. Remove across word\n")
290 sys
.stdout
.write ("5. Remove down word\n")
291 sys
.stdout
.write ("6. Freeze grid\n")
292 sys
.stdout
.write ("7. Unfreeze grid\n")
293 sys
.stdout
.write ("8. Set clue for word\n")
294 sys
.stdout
.write ("9. Display clues\n")
295 sys
.stdout
.write ("R. Reset grid\n")
296 sys
.stdout
.write ("S. Save puzzle\n")
297 sys
.stdout
.write ("E. Export to AcrossLite(TM) format\n")
298 sys
.stdout
.write ("H. Export puzzle as image/HTML\n")
299 sys
.stdout
.write ("I. Export solution as image\n")
300 sys
.stdout
.write ("X. Exit to main menu\n" + self
.ENDCOL
)
301 ch
= raw_input (self
.BRICKRED
+ "Your choice: " + self
.ENDCOL
)
307 self
.on_add_word (False)
309 self
.on_remove_across ()
311 self
.on_remove_down ()
313 self
.puzzle
.freeze_grid ()
315 self
.puzzle
.unfreeze_grid ()
319 self
.on_display_clues ()
320 elif ch
== "R" or ch
== "r":
321 self
.on_reset_grid ()
322 elif ch
== "S" or ch
== "s":
324 elif ch
== "E" or ch
== "e":
325 self
.on_export_acrosslite ()
326 elif ch
== "H" or ch
== "h":
327 self
.on_export_image (False)
328 elif ch
== "I" or ch
== "i":
329 self
.on_export_image ()
330 elif ch
== "X" or ch
== "x":
333 # when user chooses new puzzle
334 def on_new_puzzle (self
):
335 self
.current_file
= raw_input (self
.BRICKRED
+ "New puzzle file name: "
337 srows
= raw_input (self
.BRICKRED
+ "Number of rows: " + self
.ENDCOL
)
338 scols
= raw_input (self
.BRICKRED
+ "Number of cols: " + self
.ENDCOL
)
343 sys
.stderr
.write ("Invalid number of rows/columns")
345 self
.puzzle
= crosswordpuzzle
.CrosswordPuzzle (rows
, cols
)
346 self
.do_puzzle_loop ()
348 # when user chooses to load puzzle
349 def on_load_puzzle (self
):
350 self
.current_file
= raw_input (self
.BRICKRED
+ "Puzzle to load: "
353 self
.do_puzzle_loop ()
355 # when user chooses to reset grid
356 def on_reset_grid (self
):
357 ans
= raw_input (self
.BRICKRED
+
358 "This will clear the entire grid! Are you sure (Y/N)? " + self
.ENDCOL
)
359 if ans
== "y" or ans
== "Y":
360 self
.puzzle
.reset_grid ()
361 sys
.stdout
.write (self
.BLUE
+ "Grid has been cleared of all data!"
362 + self
.ENDCOL
+ "\n")
364 # Main application loop
365 def do_main_loop (self
):
368 sys
.stdout
.write (self
.BOLD
+ "\n-----------------------------------\n")
369 sys
.stdout
.write ("Get A Clue - Crossword Puzzle Maker\n")
370 sys
.stdout
.write ("-----------------------------------\n" + self
.ENDCOL
)
371 sys
.stdout
.write (self
.BLUE
+ "1. Start a new puzzle\n")
372 sys
.stdout
.write ("2. Open an existing puzzle\n")
373 sys
.stdout
.write ("X. Exit\n" + self
.ENDCOL
)
374 ch
= raw_input (self
.BRICKRED
+ "Your choice: " + self
.ENDCOL
)
376 self
.on_new_puzzle ()
378 self
.on_load_puzzle ()
379 if ch
== 'x' or ch
== 'X':