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" +
152 self
.puzzle
.data
[arow
][acol
].clue_across
+ "\n")
153 except crosswordpuzzle
.NoWordException
:
154 sys
.stderr
.write ("No across word found at that position\n")
157 # down word set the clue if found
158 dword
, drow
, dcol
, dlen
= self
.puzzle
.get_word_down (row
, col
)
159 sys
.stdout
.write (self
.BLUE
+ "Down word at position: " + dword
+ "\n" + self
.ENDCOL
)
160 clue
= raw_input (self
.BRICKRED
+ "Clue for down word: " + self
.ENDCOL
)
161 self
.puzzle
.data
[drow
][dcol
].clue_down
= clue
162 sys
.stdout
.write (self
.BLUE
+ "Set the clue: \n" +
163 self
.puzzle
.data
[drow
][dcol
].clue_down
+ "\n")
164 except crosswordpuzzle
.NoWordException
:
165 sys
.stderr
.write ("No down word found at that position\n")
168 def on_remove_down (self
):
171 srow
= raw_input (self
.BRICKRED
+ "At row: " + self
.ENDCOL
)
172 scol
= raw_input (self
.BRICKRED
+ "At col: " + self
.ENDCOL
)
177 sys
.stderr
.write ("Invalid row or column\n")
181 self
.puzzle
.remove_word_down (row
, col
)
182 sys
.stdout
.write (self
.BLUE
+ "Down word removed\n" + self
.ENDCOL
)
183 except crosswordpuzzle
.FrozenGridException
:
184 sys
.stderr
.write ("Word cannot be removed from a frozen puzzle\n")
185 except crosswordpuzzle
.NoWordException
:
186 sys
.stderr
.write ("No down word found at that position\n")
188 # remove an across word
189 def on_remove_across (self
):
192 srow
= raw_input (self
.BRICKRED
+ "At row: " + self
.ENDCOL
)
193 scol
= raw_input (self
.BRICKRED
+ "At col: " + self
.ENDCOL
)
198 sys
.stderr
.write ("Invalid row or column\n")
202 self
.puzzle
.remove_word_across (row
, col
)
203 sys
.stdout
.write (self
.BLUE
+ "Across word removed\n" + self
.ENDCOL
)
204 except crosswordpuzzle
.FrozenGridException
:
205 sys
.stderr
.write ("Word cannot be removed from a frozen puzzle\n")
206 except crosswordpuzzle
.NoWordException
:
207 sys
.stderr
.write ("No across word found at that position\n")
209 # add a word to the puzzle
210 def on_add_word (self
, across
=True):
211 # first display the grid
213 # get the row and column
214 srow
= raw_input (self
.BRICKRED
+ "Start row: " + self
.ENDCOL
)
215 scol
= raw_input (self
.BRICKRED
+ "Start col: " + self
.ENDCOL
)
216 # try converting it to number
221 sys
.stderr
.write ("Invalid row or column\n")
224 word
= raw_input (self
.BRICKRED
+ "Word: " + self
.ENDCOL
)
226 # try to add the word to the puzzle grid
229 self
.puzzle
.set_word_across (row
, col
, word
)
231 self
.puzzle
.set_word_down (row
, col
, word
)
232 except crosswordpuzzle
.TooLongWordException
:
233 sys
.stderr
.write ("Word is too long to fit in the grid! Aborting.\n")
234 except crosswordpuzzle
.IntersectWordException
:
235 sys
.stderr
.write ("Word intersects badly with another word!\n")
236 except crosswordpuzzle
.FrozenGridException
:
237 sys
.stderr
.write ("Word cannot be added to a frozen puzzle.\n")
238 except crosswordpuzzle
.WordCharsException
:
239 sys
.stderr
.write ("Non-word characters in word. Cannot add.\n")
241 # Export to image/HTML
242 def on_export_image (self
, solution
=True):
244 sys
.stdout
.write (self
.BLUE
+ "Exporting puzzle to image/HTML\n")
245 pngfile
= raw_input (self
.BRICKRED
+ "Filename (PNG): " + self
.ENDCOL
)
246 if solution
is False:
247 htmlfile
= raw_input (self
.BRICKRED
+ "Filename (HTML): " +
249 puztitle
= raw_input (self
.BRICKRED
+ "Title of puzzle: " +
251 self
.puzzle
.export_image (pngfile
, htmlfile
, puztitle
, solution
)
253 self
.puzzle
.export_image (pngfile
)
255 sys
.stdout
.write (self
.BLUE
+ "Successfully exported!")
256 except crosswordpuzzle
.FrozenGridException
:
257 sys
.stderr
.write ("Cannot export as grid is not frozen/finalized\n")
258 except crosswordpuzzle
.NoWordsException
:
259 sys
.stderr
.write ("No words to export!\n")
261 # Export to across lite
262 def on_export_acrosslite (self
):
264 sys
.stdout
.write (self
.BLUE
+ "Exporting to AcrossLite(tm) Format\n" +
266 title
= raw_input (self
.BRICKRED
+ "Puzzle title: " + self
.ENDCOL
)
267 name
= raw_input (self
.BRICKRED
+ "Author name: " + self
.ENDCOL
)
268 copyright
= raw_input (self
.BRICKRED
+ "Copyright: " + self
.ENDCOL
)
269 exportfile
= raw_input (self
.BRICKRED
+ "Export to file: " + self
.ENDCOL
)
271 acrosslite_str
= self
.puzzle
.export_acrosslite (title
, name
, copyright
)
272 fexport
= open (exportfile
, "w")
273 fexport
.write (acrosslite_str
)
275 sys
.stdout
.write (self
.BLUE
+ "Exported AcrossLite(tm) File: " +
276 exportfile
+ "\n" + self
.ENDCOL
)
277 except crosswordpuzzle
.FrozenGridException
:
278 sys
.stderr
.write ("Cannot export as grid is not frozen/finalized\n")
279 except crosswordpuzzle
.NoWordsException
:
280 sys
.stderr
.write ("No words to export!\n")
283 def do_puzzle_loop (self
):
284 # there is a current file
285 if self
.current_file
and self
.puzzle
:
287 sys
.stdout
.write (self
.BOLD
+ "\n-----------------------------------\n")
288 sys
.stdout
.write ("Puzzle: " + self
.current_file
+ "\n")
289 sys
.stdout
.write ("-----------------------------------" + self
.ENDCOL
+ "\n")
290 sys
.stdout
.write (self
.BLUE
+ "1. Display grid\n")
291 sys
.stdout
.write ("2. Add across word\n")
292 sys
.stdout
.write ("3. Add down word\n")
293 sys
.stdout
.write ("4. Remove across word\n")
294 sys
.stdout
.write ("5. Remove down word\n")
295 sys
.stdout
.write ("6. Freeze grid\n")
296 sys
.stdout
.write ("7. Unfreeze grid\n")
297 sys
.stdout
.write ("8. Set clue for word\n")
298 sys
.stdout
.write ("9. Display clues\n")
299 sys
.stdout
.write ("R. Reset grid\n")
300 sys
.stdout
.write ("S. Save puzzle\n")
301 sys
.stdout
.write ("E. Export to AcrossLite(TM) format\n")
302 sys
.stdout
.write ("H. Export puzzle as image/HTML\n")
303 sys
.stdout
.write ("I. Export solution as image\n")
304 sys
.stdout
.write ("X. Exit to main menu\n" + self
.ENDCOL
)
305 ch
= raw_input (self
.BRICKRED
+ "Your choice: " + self
.ENDCOL
)
311 self
.on_add_word (False)
313 self
.on_remove_across ()
315 self
.on_remove_down ()
317 self
.puzzle
.freeze_grid ()
319 self
.puzzle
.unfreeze_grid ()
323 self
.on_display_clues ()
324 elif ch
== "R" or ch
== "r":
325 self
.on_reset_grid ()
326 elif ch
== "S" or ch
== "s":
328 elif ch
== "E" or ch
== "e":
329 self
.on_export_acrosslite ()
330 elif ch
== "H" or ch
== "h":
331 self
.on_export_image (False)
332 elif ch
== "I" or ch
== "i":
333 self
.on_export_image ()
334 elif ch
== "X" or ch
== "x":
337 # when user chooses new puzzle
338 def on_new_puzzle (self
):
339 self
.current_file
= raw_input (self
.BRICKRED
+ "New puzzle file name: "
341 srows
= raw_input (self
.BRICKRED
+ "Number of rows: " + self
.ENDCOL
)
342 scols
= raw_input (self
.BRICKRED
+ "Number of cols: " + self
.ENDCOL
)
347 sys
.stderr
.write ("Invalid number of rows/columns")
349 self
.puzzle
= crosswordpuzzle
.CrosswordPuzzle (rows
, cols
)
350 self
.do_puzzle_loop ()
352 # when user chooses to load puzzle
353 def on_load_puzzle (self
):
354 self
.current_file
= raw_input (self
.BRICKRED
+ "Puzzle to load: "
357 self
.do_puzzle_loop ()
359 # when user chooses to reset grid
360 def on_reset_grid (self
):
361 ans
= raw_input (self
.BRICKRED
+
362 "This will clear the entire grid! Are you sure (Y/N)? " + self
.ENDCOL
)
363 if ans
== "y" or ans
== "Y":
364 self
.puzzle
.reset_grid ()
365 sys
.stdout
.write (self
.BLUE
+ "Grid has been cleared of all data!"
366 + self
.ENDCOL
+ "\n")
368 # Main application loop
369 def do_main_loop (self
):
372 sys
.stdout
.write (self
.BOLD
+ "\n-----------------------------------\n")
373 sys
.stdout
.write ("Get A Clue - Crossword Puzzle Maker\n")
374 sys
.stdout
.write ("-----------------------------------\n" + self
.ENDCOL
)
375 sys
.stdout
.write (self
.BLUE
+ "1. Start a new puzzle\n")
376 sys
.stdout
.write ("2. Open an existing puzzle\n")
377 sys
.stdout
.write ("X. Exit\n" + self
.ENDCOL
)
378 ch
= raw_input (self
.BRICKRED
+ "Your choice: " + self
.ENDCOL
)
380 self
.on_new_puzzle ()
382 self
.on_load_puzzle ()
383 if ch
== 'x' or ch
== 'X':