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
9 def __init__ (self
, item_char
='.', across_start
= False, down_start
= False,
10 occupied_across
= False, occupied_down
= False, num
= 0,
11 clue_across
= None, clue_down
= None, revealed
= False):
12 # character in the cell
14 # is the cell the start of an across word?
15 self
.across_start
= across_start
16 # is the cell the start of a down word?
17 self
.down_start
= down_start
18 # is the cell occupied by a letter in an across word?
19 self
.occupied_across
= occupied_across
20 # is the cell occupied by a letter in a down word?
21 self
.occupied_down
= occupied_down
22 # numbering of the cell if it is the start of a word
24 # clue across if the cell is the start of an across word
25 self
.clue_across
= clue_across
26 # clue down if the cell is the start of a down word
27 self
.clue_down
= clue_down
28 # is the letter revealed or hidden?
29 self
.revealed
= revealed
31 # exception for too long words
32 class TooLongWordException (Exception):
33 def __init__ (self
, word
, length
):
37 # exception for intersecting words
38 class IntersectWordException (Exception):
39 def __init__ (self
, word
, length
):
43 # exception when grid is sought to be changed when frozen
44 class FrozenGridException (Exception):
46 self
.msg
= "Grid is frozen and cannot be edited"
48 # exception when no word is found at a position
49 class NoWordException (Exception):
50 def __init__ (self
, row
, col
):
53 # exception when no words are present in the grid
54 class NoWordsException (Exception):
56 self
.msg
= "No words in grid"
58 class CrosswordPuzzle
:
59 def __init__ (self
, rows
, cols
):
60 # define number of rows and columns
64 # initialize the list to hold the grid
67 # initial state of the grid is unfrozen
68 self
.frozen_grid
= False
70 # create the grid data
71 for i
in range (rows
):
73 for j
in range (cols
):
74 self
.data
[i
].append (GridItem ())
76 # get the AcrossLite(TM) data for exporting
77 def export_acrosslite (self
, title
, author
, copyright
):
78 # don't export if grid is frozen
79 if self
.frozen_grid
is False:
80 raise FrozenGridException
83 across_data
.append ("<ACROSS PUZZLE>\r\n")
84 across_data
.append ("<TITLE>\r\n")
85 across_data
.append (title
+ "\r\n")
86 across_data
.append ("<AUTHOR>\r\n")
87 across_data
.append (author
+ "\r\n")
88 across_data
.append ("<COPYRIGHT>\r\n")
89 across_data
.append (copyright
+ "\r\n")
90 across_data
.append ("<SIZE>\r\n")
91 str_size
= str (self
.cols
) + "x" + str (self
.rows
)
92 across_data
.append (str_size
+ "\r\n")
93 across_data
.append ("<GRID>\r\n")
94 for row
in range (self
.rows
):
95 for col
in range (self
.cols
):
96 if (self
.data
[row
][col
].occupied_across
is True or
97 self
.data
[row
][col
].occupied_down
is True):
98 across_data
.append (self
.data
[row
][col
].char
)
100 across_data
.append (".")
101 across_data
.append ("\r\n")
103 across_data
.append ("<ACROSS>\r\n")
104 clues_across
= self
.get_clues_across ()
105 for word
, clue
in clues_across
:
107 across_data
.append (clue
+ "\r\n")
109 across_data
.append ("(No clue yet)\r\n")
111 across_data
.append ("<DOWN>\r\n")
112 clues_down
= self
.get_clues_down ()
113 for word
, clue
in clues_down
:
115 across_data
.append (clue
+ "\r\n")
117 across_data
.append ("(No clue yet\r\n")
119 acrosslite_str
= "".join (across_data
)
120 return acrosslite_str
122 # get all the clues for across
123 def get_clues_across (self
):
126 for row
in range (self
.rows
):
127 for col
in range (self
.cols
):
128 if (self
.data
[row
][col
].occupied_across
is True and
129 self
.data
[row
][col
].across_start
is True):
130 word_across
= self
.get_word_across (row
, col
)
131 clues
.append ((word_across
, self
.data
[row
][col
].clue_across
))
132 # if no across words are found at all
134 raise NoWordsException
138 # get all the clues for down
139 def get_clues_down (self
):
142 for row
in range (self
.rows
):
143 for col
in range (self
.cols
):
144 if (self
.data
[row
][col
].occupied_down
is True and
145 self
.data
[row
][col
].down_start
is True):
146 word_down
= self
.get_word_down (row
, col
)
147 clues
.append ((word_down
, self
.data
[row
][col
].clue_down
))
148 # if no down words are found at all
150 raise NoWordsException
154 # getting a down word at a position
155 def get_word_down (self
, row
, col
):
156 # if index is out of bounds
157 if row
>= self
.rows
or col
>= self
.cols
:
158 raise NoWordException (row
, col
)
160 # if there is no occupied down letter at that position
161 if self
.data
[row
][col
].occupied_down
is False:
162 raise NoWordException (row
, col
)
164 # now traverse the grid to find the beginning of the word
167 # if it is occupied down and is the beginning of the word
168 if (self
.data
[i
][col
].occupied_down
is True and
169 self
.data
[i
][col
].down_start
is True):
176 # now seek the end of the word
178 if self
.data
[i
][col
].occupied_down
is True:
179 word_chars
.append (self
.data
[i
][col
].char
)
184 word
= "".join (word_chars
)
186 # return the word, starting row, column and length as a tuple
187 return (word
, start_row
, col
, len(word
))
189 # getting an across word at a position
190 def get_word_across (self
, row
, col
):
191 # if index is out of bounds
192 if row
>= self
.rows
or col
>= self
.cols
:
193 raise NoWordException (row
, col
)
195 # if there is no occupied across letter at that position
196 if self
.data
[row
][col
].occupied_across
is False:
197 raise NoWordException (row
, col
)
199 # now traverse the grid to look for the beginning of the word
202 # if it is occupied across and is the beginning of the word
203 if (self
.data
[row
][i
].occupied_across
is True and
204 self
.data
[row
][i
].across_start
is True):
211 # now seek the end of the word
213 if self
.data
[row
][i
].occupied_across
is True:
214 word_chars
.append (self
.data
[row
][i
].char
)
219 word
= "".join (word_chars
)
221 # return the word, starting column, row and length as a tuple
222 return (word
, row
, start_col
, len(word
))
224 # setting a down word
225 def set_word_down (self
, row
, col
, word
):
226 # if the grid is frozen the abort
227 if self
.frozen_grid
is True:
228 raise FrozenGridException
230 # if the word length greater than totalrows - startrow
231 if len(word
) > self
.rows
- row
:
232 raise TooLongWordException (word
, len(word
))
234 # is the word intersecting any other word?
235 for i
in range (len(word
)):
237 if self
.data
[row
+i
][col
].occupied_down
is True:
238 raise IntersectWordException (word
, len(word
))
239 # on the previous column except first column
241 # except the first and last col
242 if i
> 0 and i
< len(word
) - 1:
243 if self
.data
[row
+i
][col
-1].occupied_down
is True:
244 raise IntersectWordException (word
, len(word
))
245 # on the next column except last column
246 if col
< len(word
) - 1:
247 # except the first and last row check if there is any
248 # down word in previous column
249 if i
> 0 and i
< len(word
) - 1:
250 if self
.data
[row
+i
][col
+1].occupied_down
is True:
251 raise IntersectWordException (word
, len(word
))
252 # check if there is any across word starting in the
254 if self
.data
[row
+i
][col
+1].across_start
is True:
255 raise IntersectWordException (word
, len(word
))
257 # also check the character before and after
258 if (row
> 0 and self
.data
[row
-1][col
].occupied_down
is True
259 and self
.data
[row
-1][col
].occupied_across
is True):
260 raise IntersectWordException (word
, len(word
))
261 if (row
+ len(word
) < self
.rows
and
262 self
.data
[row
+len(word
)][col
].occupied_across
is True and
263 self
.data
[row
+len(word
)][col
].occupied_down
is True):
264 raise IntersectWordException (word
, len(word
))
266 # set the down start to true
267 self
.data
[row
][col
].down_start
= True
269 for i
in range (len(word
)):
270 self
.data
[row
+i
][col
].occupied_down
= True
271 self
.data
[row
+i
][col
].char
= word
[i
].upper ()
274 # setting an across word
275 def set_word_across (self
, row
, col
, word
):
276 # if the grid is frozen the abort
277 if self
.frozen_grid
is True:
278 raise FrozenGridException
280 # is the word length greater than totalcols - startcol?
281 if len(word
) > self
.cols
- col
:
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
][col
+i
].occupied_across
is True:
288 raise IntersectWordException (word
, len(word
))
289 # on a previous row except first row
291 # if not the first or last col
292 if i
> 0 and i
< len(word
) - 1:
293 if self
.data
[row
-1][col
+i
].occupied_across
is True:
294 raise IntersectWordException (word
, len(word
))
296 if (row
< (self
.rows
- 1)):
297 # except the first and last letter check if there is
298 # any across intersection
299 if i
> 0 and i
< len (word
) - 1:
300 if self
.data
[row
+1][col
+i
].occupied_across
is True:
301 raise IntersectWordException (word
, len(word
))
302 # if a down word is starting at any column below the
304 if self
.data
[row
+1][col
+i
].down_start
is True:
305 raise IntersectWordException (word
, len(word
))
307 # also check the character beyond and before and after
308 if (col
> 0 and (self
.data
[row
][col
-1].occupied_across
is True or
309 self
.data
[row
][col
-1].occupied_down
is True)):
310 raise IntersectWordException (word
, len(word
))
311 if (col
+ len(word
) < self
.cols
and
312 (self
.data
[row
][col
+len(word
)].occupied_across
is True or
313 self
.data
[row
][col
+len(word
)].occupied_down
is True)):
314 raise IntersectWordException (word
, len(word
))
316 # set across start to true
317 self
.data
[row
][col
].across_start
= True
320 for i
in range (len(word
)):
321 self
.data
[row
][col
+i
].char
= word
[i
].upper ()
322 self
.data
[row
][col
+i
].occupied_across
= True
324 # freeze the grid numbers etc.
325 def freeze_grid (self
):
328 # run through the grid
329 for row
in range (self
.rows
):
330 for col
in range (self
.cols
):
331 # if grid is blank set the character to #
332 if (self
.data
[row
][col
].occupied_across
is False
333 and self
.data
[row
][col
].occupied_down
is False):
334 self
.data
[row
][col
].char
= "#"
335 elif (self
.data
[row
][col
].across_start
is True or
336 self
.data
[row
][col
].down_start
is True):
337 self
.data
[row
][col
].numbered
= numbering
340 self
.frozen_grid
= True
342 # unfreeze the grid numbers etc.
343 def unfreeze_grid (self
):
344 # run through the grid
345 for row
in range (self
.rows
):
346 for col
in range (self
.cols
):
347 self
.data
[row
][col
].numbered
= 0
348 if (self
.data
[row
][col
].occupied_across
is False and
349 self
.data
[row
][col
].occupied_down
is False):
350 self
.data
[row
][col
].char
= '.'
352 self
.frozen_grid
= False