Reset grid functionality implemented
[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 self.puzzle.data[arow][acol].clue_across = clue
150 sys.stdout.write (self.BLUE + "Set the clue: \n" + self.puzzle.data[arow][acol].clue_across)
151 except crosswordpuzzle.NoWordException:
152 sys.stderr.write ("No across word found at that position\n")
153
154 try:
155 # down word set the clue if found
156 dword, drow, dcol, dlen = self.puzzle.get_word_down (row, col)
157 sys.stdout.write (self.BLUE + "Down word at position: " + dword + "\n" + self.ENDCOL)
158 clue = raw_input (self.BRICKRED + "Clue for down word: " + self.ENDCOL)
159 self.puzzle.data[drow][dcol].clue_down = clue
160 sys.stdout.write (self.BLUE + "Set the clue: \n" + self.puzzle.data[drow][dcol].clue_down)
161 except crosswordpuzzle.NoWordException:
162 sys.stderr.write ("No down word found at that position\n")
163
164 # add a word to the puzzle
165 def on_add_word (self, across=True):
166 # first display the grid
167 self.print_puzzle ()
168 # get the row and column
169 srow = raw_input (self.BRICKRED + "Start row: " + self.ENDCOL)
170 scol = raw_input (self.BRICKRED + "Start col: " + self.ENDCOL)
171 # try converting it to number
172 try:
173 row = int (srow)
174 col = int (scol)
175 except ValueError:
176 sys.stderr.write ("Invalid row or column\n")
177 return
178 # get the word
179 word = raw_input (self.BRICKRED + "Word: " + self.ENDCOL)
180
181 # try to add the word to the puzzle grid
182 try:
183 if across == True:
184 self.puzzle.set_word_across (row, col, word)
185 else:
186 self.puzzle.set_word_down (row, col, word)
187 except crosswordpuzzle.TooLongWordException:
188 sys.stderr.write ("Word is too long to fit in the grid! Aborting.\n")
189 except crosswordpuzzle.IntersectWordException:
190 sys.stderr.write ("Word intersects badly with another word!\n")
191 except crosswordpuzzle.FrozenGridException:
192 sys.stderr.write ("Word cannot be added to a frozen puzzle.\n")
193
194 # Export to image/HTML
195 def on_export_image (self, solution=True):
196 try:
197 sys.stdout.write (self.BLUE + "Exporting puzzle to image/HTML\n")
198 pngfile = raw_input (self.BRICKRED + "Filename (PNG): " + self.ENDCOL)
199 if solution is False:
200 htmlfile = raw_input (self.BRICKRED + "Filename (HTML): " +
201 self.ENDCOL)
202 puztitle = raw_input (self.BRICKRED + "Title of puzzle: " +
203 self.ENDCOL)
204 self.puzzle.export_image (pngfile, htmlfile, puztitle, solution)
205 else:
206 self.puzzle.export_image (pngfile)
207
208 sys.stdout.write (self.BLUE + "Successfully exported!")
209 except crosswordpuzzle.FrozenGridException:
210 sys.stderr.write ("Cannot export as grid is not frozen/finalized\n")
211 except crosswordpuzzle.NoWordsException:
212 sys.stderr.write ("No words to export!\n")
213
214 # Export to across lite
215 def on_export_acrosslite (self):
216 try:
217 sys.stdout.write (self.BLUE + "Exporting to AcrossLite(tm) Format\n" +
218 self.ENDCOL)
219 title = raw_input (self.BRICKRED + "Puzzle title: " + self.ENDCOL)
220 name = raw_input (self.BRICKRED + "Author name: " + self.ENDCOL)
221 copyright = raw_input (self.BRICKRED + "Copyright: " + self.ENDCOL)
222 exportfile = raw_input (self.BRICKRED + "Export to file: " + self.ENDCOL)
223
224 acrosslite_str = self.puzzle.export_acrosslite (title, name, copyright)
225 fexport = open (exportfile, "w")
226 fexport.write (acrosslite_str)
227 fexport.close ()
228 sys.stdout.write (self.BLUE + "Exported AcrossLite(tm) File: " +
229 exportfile + "\n" + self.ENDCOL)
230 except crosswordpuzzle.FrozenGridException:
231 sys.stderr.write ("Cannot export as grid is not frozen/finalized\n")
232 except crosswordpuzzle.NoWordsException:
233 sys.stderr.write ("No words to export!\n")
234
235 # Puzzle loop
236 def do_puzzle_loop (self):
237 # there is a current file
238 if self.current_file and self.puzzle:
239 while True:
240 sys.stdout.write (self.BOLD + "\n-----------------------------------\n")
241 sys.stdout.write ("Puzzle: " + self.current_file + "\n")
242 sys.stdout.write ("-----------------------------------" + self.ENDCOL + "\n")
243 sys.stdout.write (self.BLUE + "1. Display grid\n")
244 sys.stdout.write ("2. Add across word\n")
245 sys.stdout.write ("3. Add down word\n")
246 sys.stdout.write ("4. Remove across word\n")
247 sys.stdout.write ("5. Remove down word\n")
248 sys.stdout.write ("6. Freeze grid\n")
249 sys.stdout.write ("7. Unfreeze grid\n")
250 sys.stdout.write ("8. Set clue for word\n")
251 sys.stdout.write ("9. Display clues\n")
252 sys.stdout.write ("R. Reset grid\n")
253 sys.stdout.write ("S. Save puzzle\n")
254 sys.stdout.write ("E. Export to AcrossLite(TM) format\n")
255 sys.stdout.write ("H. Export puzzle as image/HTML\n")
256 sys.stdout.write ("I. Export solution as image\n")
257 sys.stdout.write ("X. Exit to main menu\n" + self.ENDCOL)
258 ch = raw_input (self.BRICKRED + "Your choice: " + self.ENDCOL)
259 if ch == "1":
260 self.print_puzzle ()
261 elif ch == "2":
262 self.on_add_word ()
263 elif ch == "3":
264 self.on_add_word (False)
265 elif ch == "6":
266 self.puzzle.freeze_grid ()
267 elif ch == "7":
268 self.puzzle.unfreeze_grid ()
269 elif ch == "8":
270 self.on_set_clue ()
271 elif ch == "9":
272 self.on_display_clues ()
273 elif ch == "R" or ch == "r":
274 self.on_reset_grid ()
275 elif ch == "S" or ch == "s":
276 self.save_puzzle ()
277 elif ch == "E" or ch == "e":
278 self.on_export_acrosslite ()
279 elif ch == "H" or ch == "h":
280 self.on_export_image (False)
281 elif ch == "I" or ch == "i":
282 self.on_export_image ()
283 elif ch == "X" or ch == "x":
284 break
285
286 # when user chooses new puzzle
287 def on_new_puzzle (self):
288 self.current_file = raw_input (self.BRICKRED + "New puzzle file name: "
289 + self.ENDCOL)
290 srows = raw_input (self.BRICKRED + "Number of rows: " + self.ENDCOL)
291 scols = raw_input (self.BRICKRED + "Number of cols: " + self.ENDCOL)
292 try:
293 rows = int (srows)
294 cols = int (scols)
295 except ValueError:
296 sys.stderr.write ("Invalid number of rows/columns")
297 return
298 self.puzzle = crosswordpuzzle.CrosswordPuzzle (rows, cols)
299 self.do_puzzle_loop ()
300
301 # when user chooses to load puzzle
302 def on_load_puzzle (self):
303 self.current_file = raw_input (self.BRICKRED + "Puzzle to load: "
304 + self.ENDCOL)
305 self.load_puzzle ()
306 self.do_puzzle_loop ()
307
308 # when user chooses to reset grid
309 def on_reset_grid (self):
310 ans = raw_input (self.BRICKRED +
311 "This will clear the entire grid! Are you sure (Y/N)? " + self.ENDCOL)
312 if ans == "y" or ans == "Y":
313 self.puzzle.reset_grid ()
314 sys.stdout.write (self.BLUE + "Grid has been cleared of all data!"
315 + self.ENDCOL + "\n")
316
317 # Main application loop
318 def do_main_loop (self):
319 # display the menu
320 while True:
321 sys.stdout.write (self.BOLD + "\n-----------------------------------\n")
322 sys.stdout.write ("Get A Clue - Crossword Puzzle Maker\n")
323 sys.stdout.write ("-----------------------------------\n" + self.ENDCOL)
324 sys.stdout.write (self.BLUE + "1. Start a new puzzle\n")
325 sys.stdout.write ("2. Open an existing puzzle\n")
326 sys.stdout.write ("X. Exit\n" + self.ENDCOL)
327 ch = raw_input (self.BRICKRED + "Your choice: " + self.ENDCOL)
328 if ch == '1':
329 self.on_new_puzzle ()
330 if ch == '2':
331 self.on_load_puzzle ()
332 if ch == 'x' or ch == 'X':
333 break