5a0ae119a1a8b3dc32576354553a0ee8f75073b0
[getaclue.git] / crosswordpuzzlecreator.py
1 # Get A Clue (C) 2010 V. Harishankar
2 # Crossword puzzle maker program
3 # Licensed under the GNU GPL v3
4
5 # Cross puzzle creator class
6 import sys
7 import cPickle
8 import readline
9
10 import crosswordpuzzle
11
12 class CrosswordPuzzleCreator:
13 # ansi color codes for grid display
14 BRICKRED = '\033[31m'
15 # bold
16 BOLD = '\033[33m'
17 # blue
18 BLUE = '\033[34m'
19 # grey
20 GREY = '\033[30m'
21 # Black background white text
22 BLACK_BG = '\033[40;1;37m'
23 # white background black text
24 WHITE_BG = '\033[47;1;30m'
25 # white background red text
26 REDWHITE_BG = '\033[47;1;31m'
27 # disable colors
28 ENDCOL = '\033[0m'
29
30 def __init__ (self):
31 self.do_main_loop ()
32 self.current_file = None
33 self.puzzle = None
34
35 # save the puzzle
36 def save_puzzle (self):
37 if self.current_file:
38 fpuzzle = open (self.current_file, "wb")
39 cPickle.dump (self.puzzle, fpuzzle, cPickle.HIGHEST_PROTOCOL)
40 sys.stdout.write (self.BLUE + "Puzzle saved to: " +
41 self.current_file + "\n" + self.ENDCOL)
42
43 # load the puzzle
44 def load_puzzle (self):
45 if self.current_file:
46 fpuzzle = open (self.current_file, "rb")
47 self.puzzle = cPickle.load (fpuzzle)
48
49 # display the grid with words
50 def print_puzzle (self, no_words=False):
51 # if self.puzzle is not none
52 if self.puzzle:
53 # get row, col and print them (with grid number if set)
54 for col in range (self.puzzle.cols):
55 # print the first row as column headers
56 sys.stdout.write (self.BLUE + ' %02d' % col + self.ENDCOL)
57 sys.stdout.write ("\n")
58
59 for row in range (self.puzzle.rows):
60 for col in range (self.puzzle.cols):
61 # print the data
62 # if the cell is numbered i.e. start of a word
63 if self.puzzle.data[row][col].numbered != 0:
64 sys.stdout.write (self.REDWHITE_BG +
65 '%02d ' % self.puzzle.data[row][col].numbered + self.ENDCOL)
66 # print spaces
67 else:
68 if self.puzzle.data[row][col].char == "#":
69 sys.stdout.write (self.BLACK_BG + ' ' + self.ENDCOL)
70 else:
71 sys.stdout.write (self.WHITE_BG + ' ' + self.ENDCOL)
72 # if the character is not a blank or a block
73 if (self.puzzle.data[row][col].char <> "." and
74 self.puzzle.data[row][col].char <> "#"):
75 # if words are to be shown regardless of hidden/revealed state
76 if no_words is False:
77 sys.stdout.write (self.WHITE_BG +
78 self.puzzle.data[row][col].char + self.ENDCOL)
79 else:
80 # display only revealed
81 if self.puzzle.data[row][col].revealed is True:
82 sys.stdout.write (self.WHITE_BG +
83 self.puzzle.data[row][col].char + self.ENDCOL)
84 # else print a blank
85 else:
86 sys.stdout.write (self.WHITE_BG + '.' + self.ENDCOL)
87 elif self.puzzle.data[row][col].char == '.':
88 sys.stdout.write (self.WHITE_BG +
89 self.puzzle.data[row][col].char + self.ENDCOL)
90 elif self.puzzle.data[row][col].char == '#':
91 sys.stdout.write (self.BLACK_BG + " " + self.ENDCOL)
92
93 sys.stdout.write (' ' + self.BLUE + "%2d" % row + self.ENDCOL + "\n")
94 raw_input (self.BRICKRED + "Press <return> to continue" + self.ENDCOL)
95
96 # display existing clues
97 def on_display_clues (self):
98 try:
99 aclues = self.puzzle.get_clues_across ()
100 sys.stdout.write (self.BOLD + "\n------------\n")
101 sys.stdout.write ("Across words\n")
102 sys.stdout.write ("------------\n" + self.ENDCOL)
103
104 for word, clue in aclues:
105 sys.stdout.write (self.BOLD + word[0] + ": " + self.ENDCOL)
106 if clue:
107 sys.stdout.write (self.BLUE + clue + "\n" + self.ENDCOL)
108 else:
109 sys.stdout.write (self.BLUE + "(No clue yet)\n" + self.ENDCOL)
110 except crosswordpuzzle.NoWordsException:
111 sys.stderr.write ("No words across\n")
112
113 try:
114 dclues = self.puzzle.get_clues_down ()
115 sys.stdout.write (self.BOLD + "\n----------\n")
116 sys.stdout.write ("Down words\n")
117 sys.stdout.write ("----------\n" + self.ENDCOL)
118
119 for word, clue in dclues:
120 sys.stdout.write (self.BOLD + word[0] + ": " + self.ENDCOL)
121 if clue:
122 sys.stdout.write (self.BLUE + clue + "\n" + self.ENDCOL)
123 else:
124 sys.stdout.write (self.BLUE + "(No clue yet)\n" + self.ENDCOL)
125 except crosswordpuzzle.NoWordsException:
126 sys.stderr.write ("No words down\n")
127
128 raw_input (self.BRICKRED + "Press <return> to continue" + self.ENDCOL)
129
130 # set a clue to a word
131 def on_set_clue (self):
132 self.print_puzzle ()
133 # get the row and column
134 srow = raw_input (self.BRICKRED + "At row: " + self.ENDCOL)
135 scol = raw_input (self.BRICKRED + "At col: " + self.ENDCOL)
136 # try converting it to number
137 try:
138 row = int (srow)
139 col = int (scol)
140 except ValueError:
141 sys.stderr.write ("Invalid row or column\n")
142 return
143
144 try:
145 # across word set the clue if found
146 aword, arow, acol, alen = self.puzzle.get_word_across (row, col)
147 sys.stdout.write (self.BLUE + "Across word at position: " + aword + "\n" + self.ENDCOL)
148 clue = raw_input (self.BRICKRED + "Clue for across word: " + self.ENDCOL)
149 if clue:
150 self.puzzle.data[arow][acol].clue_across = clue
151 sys.stdout.write (self.BLUE + "Set 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")
154
155 try:
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 if clue:
161 self.puzzle.data[drow][dcol].clue_down = clue
162 sys.stdout.write (self.BLUE + "Set clue: \n" + self.puzzle.data[drow][dcol].clue_down)
163 except crosswordpuzzle.NoWordException:
164 sys.stderr.write ("No down word found at that position\n")
165
166 # add a word to the puzzle
167 def on_add_word (self, across=True):
168 # first display the grid
169 self.print_puzzle ()
170 # get the row and column
171 srow = raw_input (self.BRICKRED + "Start row: " + self.ENDCOL)
172 scol = raw_input (self.BRICKRED + "Start col: " + self.ENDCOL)
173 # try converting it to number
174 try:
175 row = int (srow)
176 col = int (scol)
177 except ValueError:
178 sys.stderr.write ("Invalid row or column\n")
179 return
180 # get the word
181 word = raw_input (self.BRICKRED + "Word: " + self.ENDCOL)
182
183 # try to add the word to the puzzle grid
184 try:
185 if across == True:
186 self.puzzle.set_word_across (row, col, word)
187 else:
188 self.puzzle.set_word_down (row, col, word)
189 except crosswordpuzzle.TooLongWordException:
190 sys.stderr.write ("Word is too long to fit in the grid! Aborting.\n")
191 except crosswordpuzzle.IntersectWordException:
192 sys.stderr.write ("Word intersects badly with another word!\n")
193 except crosswordpuzzle.FrozenGridException:
194 sys.stderr.write ("Word cannot be added to a frozen puzzle.\n")
195
196 # Export to image/HTML
197 def on_export_image (self, solution=True):
198 try:
199 sys.stdout.write (self.BLUE + "Exporting puzzle to image/HTML\n")
200 pngfile = raw_input (self.BRICKRED + "Filename (PNG): " + self.ENDCOL)
201 if solution is False:
202 htmlfile = raw_input (self.BRICKRED + "Filename (HTML): " +
203 self.ENDCOL)
204 puztitle = raw_input (self.BRICKRED + "Title of puzzle: " +
205 self.ENDCOL)
206 self.puzzle.export_image (pngfile, htmlfile, puztitle, solution)
207 else:
208 self.puzzle.export_image (pngfile)
209
210 sys.stdout.write (self.BLUE + "Successfully exported!")
211 except crosswordpuzzle.FrozenGridException:
212 sys.stderr.write ("Cannot export as grid is not frozen/finalized\n")
213 except crosswordpuzzle.NoWordsException:
214 sys.stderr.write ("No words to export!\n")
215
216 # Export to across lite
217 def on_export_acrosslite (self):
218 try:
219 sys.stdout.write (self.BLUE + "Exporting to AcrossLite(tm) Format\n" +
220 self.ENDCOL)
221 title = raw_input (self.BRICKRED + "Puzzle title: " + self.ENDCOL)
222 name = raw_input (self.BRICKRED + "Author name: " + self.ENDCOL)
223 copyright = raw_input (self.BRICKRED + "Copyright: " + self.ENDCOL)
224 exportfile = raw_input (self.BRICKRED + "Export to file: " + self.ENDCOL)
225
226 acrosslite_str = self.puzzle.export_acrosslite (title, name, copyright)
227 fexport = open (exportfile, "w")
228 fexport.write (acrosslite_str)
229 fexport.close ()
230 sys.stdout.write (self.BLUE + "Exported AcrossLite(tm) File: " +
231 exportfile + "\n" + self.ENDCOL)
232 except crosswordpuzzle.FrozenGridException:
233 sys.stderr.write ("Cannot export as grid is not frozen/finalized\n")
234 except crosswordpuzzle.NoWordsException:
235 sys.stderr.write ("No words to export!\n")
236
237 # Puzzle loop
238 def do_puzzle_loop (self):
239 # there is a current file
240 if self.current_file and self.puzzle:
241 while True:
242 sys.stdout.write (self.BOLD + "\n-----------------------------------\n")
243 sys.stdout.write ("Puzzle: " + self.current_file + "\n")
244 sys.stdout.write ("-----------------------------------" + self.ENDCOL + "\n")
245 sys.stdout.write (self.BLUE + "1. Display grid\n")
246 sys.stdout.write ("2. Add across word\n")
247 sys.stdout.write ("3. Add down word\n")
248 sys.stdout.write ("4. Remove across word\n")
249 sys.stdout.write ("5. Remove down word\n")
250 sys.stdout.write ("6. Freeze grid\n")
251 sys.stdout.write ("7. Unfreeze grid\n")
252 sys.stdout.write ("8. Set clue for word\n")
253 sys.stdout.write ("9. Display clues\n")
254 sys.stdout.write ("S. Save puzzle\n")
255 sys.stdout.write ("E. Export to AcrossLite(TM) format\n")
256 sys.stdout.write ("H. Export puzzle as image/HTML\n")
257 sys.stdout.write ("I. Export solution as image\n")
258 sys.stdout.write ("X. Exit to main menu\n" + self.ENDCOL)
259 ch = raw_input (self.BRICKRED + "Your choice: " + self.ENDCOL)
260 if ch == "1":
261 self.print_puzzle ()
262 elif ch == "2":
263 self.on_add_word ()
264 elif ch == "3":
265 self.on_add_word (False)
266 elif ch == "6":
267 self.puzzle.freeze_grid ()
268 elif ch == "7":
269 self.puzzle.unfreeze_grid ()
270 elif ch == "8":
271 self.on_set_clue ()
272 elif ch == "9":
273 self.on_display_clues ()
274 elif ch == "S" or ch == "s":
275 self.save_puzzle ()
276 elif ch == "E" or ch == "e":
277 self.on_export_acrosslite ()
278 elif ch == "H" or ch == "h":
279 self.on_export_image (False)
280 elif ch == "I" or ch == "i":
281 self.on_export_image ()
282 elif ch == "X" or ch == "x":
283 break
284
285 # when user chooses new puzzle
286 def on_new_puzzle (self):
287 self.current_file = raw_input (self.BRICKRED + "New puzzle file name: "
288 + self.ENDCOL)
289 srows = raw_input (self.BRICKRED + "Number of rows: " + self.ENDCOL)
290 scols = raw_input (self.BRICKRED + "Number of cols: " + self.ENDCOL)
291 try:
292 rows = int (srows)
293 cols = int (scols)
294 except ValueError:
295 sys.stderr.write ("Invalid number of rows/columns")
296 return
297 self.puzzle = crosswordpuzzle.CrosswordPuzzle (rows, cols)
298 self.do_puzzle_loop ()
299
300 # when user chooses to load puzzle
301 def on_load_puzzle (self):
302 self.current_file = raw_input (self.BRICKRED + "Puzzle to load: "
303 + self.ENDCOL)
304 self.load_puzzle ()
305 self.do_puzzle_loop ()
306
307 # Main application loop
308 def do_main_loop (self):
309 # display the menu
310 while True:
311 sys.stdout.write (self.BOLD + "\n-----------------------------------\n")
312 sys.stdout.write ("Get A Clue - Crossword Puzzle Maker\n")
313 sys.stdout.write ("-----------------------------------\n" + self.ENDCOL)
314 sys.stdout.write (self.BLUE + "1. Start a new puzzle\n")
315 sys.stdout.write ("2. Open an existing puzzle\n")
316 sys.stdout.write ("X. Exit\n" + self.ENDCOL)
317 ch = raw_input (self.BRICKRED + "Your choice: " + self.ENDCOL)
318 if ch == '1':
319 self.on_new_puzzle ()
320 if ch == '2':
321 self.on_load_puzzle ()
322 if ch == 'x' or ch == 'X':
323 break