e6608008c667fec891f40618ea1c9d66327f193d
1 # Get A Clue (C) 2010 V. Harishankar
2 # Crossword puzzle maker program
3 # Licensed under the GNU GPL v3
5 # Class for the puzzle data representation
11 def __init__ (self
, item_char
='.', across_start
= False, down_start
= False,
12 occupied_across
= False, occupied_down
= False, num
= 0,
13 clue_across
= None, clue_down
= None, revealed
= False):
14 # character in the cell
16 # is the cell the start of an across word?
17 self
.across_start
= across_start
18 # is the cell the start of a down word?
19 self
.down_start
= down_start
20 # is the cell occupied by a letter in an across word?
21 self
.occupied_across
= occupied_across
22 # is the cell occupied by a letter in a down word?
23 self
.occupied_down
= occupied_down
24 # numbering of the cell if it is the start of a word
26 # clue across if the cell is the start of an across word
27 self
.clue_across
= clue_across
28 # clue down if the cell is the start of a down word
29 self
.clue_down
= clue_down
30 # is the letter revealed or hidden?
31 self
.revealed
= revealed
33 # exception for too long words
34 class TooLongWordException (Exception):
35 def __init__ (self
, word
, length
):
39 # exception for intersecting words
40 class IntersectWordException (Exception):
41 def __init__ (self
, word
, length
):
45 # exception when grid is sought to be changed when frozen
46 class FrozenGridException (Exception):
48 self
.msg
= "Grid is frozen and cannot be edited"
50 # exception when no word is found at a position
51 class NoWordException (Exception):
52 def __init__ (self
, row
, col
):
55 # exception when no words are present in the grid
56 class NoWordsException (Exception):
58 self
.msg
= "No words in grid"
60 class CrosswordPuzzle
:
61 def __init__ (self
, rows
, cols
):
62 # define number of rows and columns
66 # initialize the list to hold the grid
69 # initial state of the grid is unfrozen
70 self
.frozen_grid
= False
72 # create the grid data
73 for i
in range (rows
):
75 for j
in range (cols
):
76 self
.data
[i
].append (GridItem ())
79 def export_image (self
, filename
, solution
=False):
80 # don't export if grid is not frozen
81 if self
.frozen_grid
is False:
82 raise FrozenGridException
84 # create cairo image surface and context
86 surf
= cairo
.ImageSurface (cairo
.FORMAT_RGB24
, self
.cols
*px
, self
.rows
*px
)
87 ctx
= cairo
.Context (surf
)
89 ctx
.set_source_rgb (1, 1, 1)
90 ctx
.rectangle (0, 0, self
.cols
*px
, self
.rows
*px
)
93 # traverse through the grid
94 for row
in range (self
.rows
):
95 for col
in range (self
.cols
):
96 # if grid is un-occupied
97 if (self
.data
[row
][col
].occupied_across
is False and
98 self
.data
[row
][col
].occupied_down
is False):
99 ctx
.set_source_rgb (0, 0, 0)
100 ctx
.rectangle (col
*px
, row
*px
, px
, px
)
104 ctx
.set_source_rgb (1, 1, 1)
105 ctx
.rectangle (col
*px
, row
*px
, px
, px
)
107 ctx
.set_source_rgb (0, 0, 0)
108 ctx
.rectangle (col
*px
, row
*px
, px
, px
)
110 # if solution is not to be provided, number the grid
111 if solution
is False:
112 if self
.data
[row
][col
].numbered
<> 0:
113 ctx
.select_font_face ("Serif")
114 ctx
.set_font_size (10)
115 ctx
.move_to (col
*px
+5, row
*px
+10)
116 ctx
.show_text (str(self
.data
[row
][col
].numbered
))
119 ctx
.select_font_face ("Serif")
120 ctx
.set_font_size (16)
121 ctx
.move_to (col
*px
+10, row
*px
+20)
122 ctx
.show_text (self
.data
[row
][col
].char
)
124 surf
.write_to_png (open (filename
, "wb"))
126 # get the AcrossLite(TM) data for exporting
127 def export_acrosslite (self
, title
, author
, copyright
):
128 # don't export if grid is not frozen
129 if self
.frozen_grid
is False:
130 raise FrozenGridException
133 across_data
.append ("<ACROSS PUZZLE>\r\n")
134 across_data
.append ("<TITLE>\r\n")
135 across_data
.append (title
+ "\r\n")
136 across_data
.append ("<AUTHOR>\r\n")
137 across_data
.append (author
+ "\r\n")
138 across_data
.append ("<COPYRIGHT>\r\n")
139 across_data
.append (copyright
+ "\r\n")
140 across_data
.append ("<SIZE>\r\n")
141 str_size
= str (self
.cols
) + "x" + str (self
.rows
)
142 across_data
.append (str_size
+ "\r\n")
143 across_data
.append ("<GRID>\r\n")
144 for row
in range (self
.rows
):
145 for col
in range (self
.cols
):
146 if (self
.data
[row
][col
].occupied_across
is True or
147 self
.data
[row
][col
].occupied_down
is True):
148 across_data
.append (self
.data
[row
][col
].char
)
150 across_data
.append (".")
151 across_data
.append ("\r\n")
153 across_data
.append ("<ACROSS>\r\n")
154 clues_across
= self
.get_clues_across ()
155 for word
, clue
in clues_across
:
157 across_data
.append (clue
+ "\r\n")
159 across_data
.append ("(No clue yet)\r\n")
161 across_data
.append ("<DOWN>\r\n")
162 clues_down
= self
.get_clues_down ()
163 for word
, clue
in clues_down
:
165 across_data
.append (clue
+ "\r\n")
167 across_data
.append ("(No clue yet\r\n")
169 acrosslite_str
= "".join (across_data
)
170 return acrosslite_str
172 # get all the clues for across
173 def get_clues_across (self
):
176 for row
in range (self
.rows
):
177 for col
in range (self
.cols
):
178 if (self
.data
[row
][col
].occupied_across
is True and
179 self
.data
[row
][col
].across_start
is True):
180 word_across
= self
.get_word_across (row
, col
)
181 clues
.append ((word_across
, self
.data
[row
][col
].clue_across
))
182 # if no across words are found at all
184 raise NoWordsException
188 # get all the clues for down
189 def get_clues_down (self
):
192 for row
in range (self
.rows
):
193 for col
in range (self
.cols
):
194 if (self
.data
[row
][col
].occupied_down
is True and
195 self
.data
[row
][col
].down_start
is True):
196 word_down
= self
.get_word_down (row
, col
)
197 clues
.append ((word_down
, self
.data
[row
][col
].clue_down
))
198 # if no down words are found at all
200 raise NoWordsException
204 # getting a down word at a position
205 def get_word_down (self
, row
, col
):
206 # if index is out of bounds
207 if row
>= self
.rows
or col
>= self
.cols
:
208 raise NoWordException (row
, col
)
210 # if there is no occupied down letter at that position
211 if self
.data
[row
][col
].occupied_down
is False:
212 raise NoWordException (row
, col
)
214 # now traverse the grid to find the beginning of the word
217 # if it is occupied down and is the beginning of the word
218 if (self
.data
[i
][col
].occupied_down
is True and
219 self
.data
[i
][col
].down_start
is True):
226 # now seek the end of the word
228 if self
.data
[i
][col
].occupied_down
is True:
229 word_chars
.append (self
.data
[i
][col
].char
)
234 word
= "".join (word_chars
)
236 # return the word, starting row, column and length as a tuple
237 return (word
, start_row
, col
, len(word
))
239 # getting an across word at a position
240 def get_word_across (self
, row
, col
):
241 # if index is out of bounds
242 if row
>= self
.rows
or col
>= self
.cols
:
243 raise NoWordException (row
, col
)
245 # if there is no occupied across letter at that position
246 if self
.data
[row
][col
].occupied_across
is False:
247 raise NoWordException (row
, col
)
249 # now traverse the grid to look for the beginning of the word
252 # if it is occupied across and is the beginning of the word
253 if (self
.data
[row
][i
].occupied_across
is True and
254 self
.data
[row
][i
].across_start
is True):
261 # now seek the end of the word
263 if self
.data
[row
][i
].occupied_across
is True:
264 word_chars
.append (self
.data
[row
][i
].char
)
269 word
= "".join (word_chars
)
271 # return the word, starting column, row and length as a tuple
272 return (word
, row
, start_col
, len(word
))
274 # setting a down word
275 def set_word_down (self
, row
, col
, word
):
276 # if the grid is frozen the abort
277 if self
.frozen_grid
is True:
278 raise FrozenGridException
280 # if the word length greater than totalrows - startrow
281 if len(word
) > self
.rows
- row
:
282 raise TooLongWordException (word
, len(word
))
284 # is the word intersecting any other word?
285 for i
in range (len(word
)):
287 if self
.data
[row
+i
][col
].occupied_down
is True:
288 raise IntersectWordException (word
, len(word
))
289 # on the previous column except first column
291 # except the first and last col
292 if i
> 0 and i
< len(word
) - 1:
293 if self
.data
[row
+i
][col
-1].occupied_down
is True:
294 raise IntersectWordException (word
, len(word
))
295 # on the next column except last column
296 if col
< len(word
) - 1:
297 # except the first and last row check if there is any
298 # down word in previous column
299 if i
> 0 and i
< len(word
) - 1:
300 if self
.data
[row
+i
][col
+1].occupied_down
is True:
301 raise IntersectWordException (word
, len(word
))
302 # check if there is any across word starting in the
304 if self
.data
[row
+i
][col
+1].across_start
is True:
305 raise IntersectWordException (word
, len(word
))
307 # also check the character before and after
308 if (row
> 0 and self
.data
[row
-1][col
].occupied_down
is True
309 and self
.data
[row
-1][col
].occupied_across
is True):
310 raise IntersectWordException (word
, len(word
))
311 if (row
+ len(word
) < self
.rows
and
312 self
.data
[row
+len(word
)][col
].occupied_across
is True and
313 self
.data
[row
+len(word
)][col
].occupied_down
is True):
314 raise IntersectWordException (word
, len(word
))
316 # set the down start to true
317 self
.data
[row
][col
].down_start
= True
319 for i
in range (len(word
)):
320 self
.data
[row
+i
][col
].occupied_down
= True
321 self
.data
[row
+i
][col
].char
= word
[i
].upper ()
324 # setting an across word
325 def set_word_across (self
, row
, col
, word
):
326 # if the grid is frozen the abort
327 if self
.frozen_grid
is True:
328 raise FrozenGridException
330 # is the word length greater than totalcols - startcol?
331 if len(word
) > self
.cols
- col
:
332 raise TooLongWordException (word
, len(word
))
334 # is the word intersecting any other word?
335 for i
in range (len(word
)):
337 if self
.data
[row
][col
+i
].occupied_across
is True:
338 raise IntersectWordException (word
, len(word
))
339 # on a previous row except first row
341 # if not the first or last col
342 if i
> 0 and i
< len(word
) - 1:
343 if self
.data
[row
-1][col
+i
].occupied_across
is True:
344 raise IntersectWordException (word
, len(word
))
346 if (row
< (self
.rows
- 1)):
347 # except the first and last letter check if there is
348 # any across intersection
349 if i
> 0 and i
< len (word
) - 1:
350 if self
.data
[row
+1][col
+i
].occupied_across
is True:
351 raise IntersectWordException (word
, len(word
))
352 # if a down word is starting at any column below the
354 if self
.data
[row
+1][col
+i
].down_start
is True:
355 raise IntersectWordException (word
, len(word
))
357 # also check the character beyond and before and after
358 if (col
> 0 and (self
.data
[row
][col
-1].occupied_across
is True or
359 self
.data
[row
][col
-1].occupied_down
is True)):
360 raise IntersectWordException (word
, len(word
))
361 if (col
+ len(word
) < self
.cols
and
362 (self
.data
[row
][col
+len(word
)].occupied_across
is True or
363 self
.data
[row
][col
+len(word
)].occupied_down
is True)):
364 raise IntersectWordException (word
, len(word
))
366 # set across start to true
367 self
.data
[row
][col
].across_start
= True
370 for i
in range (len(word
)):
371 self
.data
[row
][col
+i
].char
= word
[i
].upper ()
372 self
.data
[row
][col
+i
].occupied_across
= True
374 # freeze the grid numbers etc.
375 def freeze_grid (self
):
378 # run through the grid
379 for row
in range (self
.rows
):
380 for col
in range (self
.cols
):
381 # if grid is blank set the character to #
382 if (self
.data
[row
][col
].occupied_across
is False
383 and self
.data
[row
][col
].occupied_down
is False):
384 self
.data
[row
][col
].char
= "#"
385 elif (self
.data
[row
][col
].across_start
is True or
386 self
.data
[row
][col
].down_start
is True):
387 self
.data
[row
][col
].numbered
= numbering
390 self
.frozen_grid
= True
392 # unfreeze the grid numbers etc.
393 def unfreeze_grid (self
):
394 # run through the grid
395 for row
in range (self
.rows
):
396 for col
in range (self
.cols
):
397 self
.data
[row
][col
].numbered
= 0
398 if (self
.data
[row
][col
].occupied_across
is False and
399 self
.data
[row
][col
].occupied_down
is False):
400 self
.data
[row
][col
].char
= '.'
402 self
.frozen_grid
= False