1 # Get A Clue (C) 2010 V. Harishankar
2 # Crossword puzzle maker program
3 # Licensed under the GNU GPL v3
5 # Main window class for GetAClue player
13 import crosswordpuzzle
16 # typing mode constants
20 def gtk_main_quit (self
, *args
):
23 # callback for menu item quit activated event
24 def on_quit_activate (self
, menuitem
):
26 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
27 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
28 "Puzzle is open. Are you sure you wish to quit?")
29 if dlg
.run () <> gtk
.RESPONSE_YES
:
36 # callback for menu item clear grid activated event
37 def on_cleargrid_activate (self
, menuitem
):
39 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
40 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
41 "Are you sure you wish to clear your entries?")
42 if dlg
.run () == gtk
.RESPONSE_YES
:
44 self
.puzzle
.clear_guesses ()
46 puzgrid
= self
.ui
.get_object ("puzzlegrid")
50 # callback for menu item verify board activated event
51 def on_verify_activate (self
, menuitem
):
54 ans
= self
.puzzle
.is_solution_correct ()
55 # if the solution is correct
57 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
58 gtk
.MESSAGE_INFO
, gtk
.BUTTONS_CLOSE
,
59 "Success! Your entries are correct.")
62 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
63 gtk
.MESSAGE_INFO
, gtk
.BUTTONS_CLOSE
,
64 "Your solution has some errors. Fix them and try again")
66 except crosswordpuzzle
.IncompleteSolutionException
:
67 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
68 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
69 "You've not completed the board yet. Cannot verify")
73 # callback for menu item hide solution activated event
74 def on_hidesolution_activate (self
, menuitem
):
77 self
.puzzle
.reveal_solution (False)
78 puzgrid
= self
.ui
.get_object ("puzzlegrid")
82 # callback for menu item reveal solution activated event
83 def on_revealsolution_activate (self
, menuitem
):
86 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
87 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
88 "This will reveal all words in the puzzle! Are you sure?")
89 if dlg
.run () == gtk
.RESPONSE_YES
:
91 self
.puzzle
.reveal_solution ()
93 puzgrid
= self
.ui
.get_object ("puzzlegrid")
97 # callback for menu item reveal word activated event
98 def on_revealword_activate (self
, menuitem
):
100 # reveal across/down word if any the position
101 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True:
102 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
103 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
104 "Are you sure you wish to reveal across word at current cell?")
105 # confirm that the user wants to reveal
106 if dlg
.run () == gtk
.RESPONSE_YES
:
107 self
.puzzle
.reveal_word_across (self
.selected_row
, self
.selected_col
)
108 # redraw the grid to reveal the word
109 puzgrid
= self
.ui
.get_object ("puzzlegrid")
110 puzgrid
.queue_draw ()
112 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True:
113 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
114 gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
,
115 "Are you sure wish to reveal down word at current cell?")
116 if dlg
.run () == gtk
.RESPONSE_YES
:
117 self
.puzzle
.reveal_word_down (self
.selected_row
, self
.selected_col
)
118 # redraw the grid to reveal the word
119 puzgrid
= self
.ui
.get_object ("puzzlegrid")
120 puzgrid
.queue_draw ()
124 # function to set the selected row/col based on the number clicked
125 # on the clues list and also set the typing mode
126 def set_selection_of_num (self
, num
, across
= True):
127 # get the row, col of the word
128 row
, col
= self
.puzzle
.get_position_of_num (num
)
130 # set the selected row and column
131 self
.selected_row
= row
132 self
.selected_col
= col
133 # set typing mode to across
135 self
.typing_mode
= self
.ACROSS
137 self
.typing_mode
= self
.DOWN
139 # update the puzzle grid
140 puzgrid
= self
.ui
.get_object ("puzzlegrid")
142 # set focus to the puzzle grid
143 self
.window
.set_focus (puzgrid
)
145 puzgrid
.queue_draw ()
147 # callback for tree view "across" being activated
148 # activated - when double clicked or enter pressed
149 def on_tree_clues_across_row_activated (self
, view
, path
, column
):
150 # get the across list object
151 across_list
= self
.ui
.get_object ("clues_across")
152 # get the number of the across word
153 anum
= int (across_list
.get_value (across_list
.get_iter (path
), 0))
155 self
.set_selection_of_num (anum
)
159 # callback for tree view "down" being activated
160 # activated - when double clicked or enter pressed
161 def on_tree_clues_down_row_activated (self
, view
, path
, column
):
162 # get the down list object
163 down_list
= self
.ui
.get_object ("clues_down")
164 # get the number of the down word
165 dnum
= int (down_list
.get_value (down_list
.get_iter (path
), 0))
167 self
.set_selection_of_num (dnum
, False)
169 # moving the current selection in grid by one up or down
170 def move_selection_updown (self
, step
):
171 # increase or reduce the row by step until an occupied grid is found
174 last_occupied_row
= self
.selected_row
176 self
.selected_row
+= step
177 if self
.selected_row
< 0 or self
.selected_row
>= self
.puzzle
.rows
:
178 self
.selected_row
= last_occupied_row
180 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
181 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
184 # moving the current selection in grid by one across either way
185 def move_selection_across (self
, step
):
186 # increase or reduce the row by step until an occupied grid is found
189 last_occupied_col
= self
.selected_col
191 self
.selected_col
+= step
192 if self
.selected_col
< 0 or self
.selected_col
>= self
.puzzle
.cols
:
193 self
.selected_col
= last_occupied_col
195 if (self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_across
is True
196 or self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].occupied_down
is True):
199 # set the guessed character in the grid at selected location and move the
200 # selection across or down as the case may be
201 def set_guess (self
, guess_char
):
203 # set a guess only if not revealed
204 if self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].revealed
is False:
205 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= guess_char
.upper ()
207 if self
.typing_mode
== self
.ACROSS
:
208 # move by one character across but only if there is no block
210 old_col
= self
.selected_col
211 self
.move_selection_across (1)
212 if abs (self
.selected_col
- old_col
) > 1:
213 self
.selected_col
= old_col
216 # move by one character down but only if there is no block
218 old_row
= self
.selected_row
219 self
.move_selection_updown (1)
220 if abs (self
.selected_row
- old_row
) > 1:
221 self
.selected_row
= old_row
223 # delete the guessed char in the previous row/col depending on the input mode
224 # If input mode is ACROSS then delete guessed char at previous column else
226 def delete_prev_guess (self
):
228 if self
.typing_mode
== self
.ACROSS
:
229 # prevent deleting characters when there is a gap
230 old_sel_col
= self
.selected_col
231 self
.move_selection_across (-1)
232 # only if there is no block inbetween delete
233 if abs (self
.selected_col
- old_sel_col
) <= 1:
234 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
237 self
.selected_col
= old_sel_col
238 elif self
.typing_mode
== self
.DOWN
:
239 # prevent deleting characters when there is a gap
240 old_sel_row
= self
.selected_row
241 self
.move_selection_updown (-1)
242 # only if there is no block inbetween delete
243 if abs (self
.selected_row
- old_sel_row
) <= 1:
244 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
247 self
.selected_row
= old_sel_row
249 # callback for puzzle grid mouse button release event
250 def on_puzzlegrid_button_press_event (self
, drawarea
, event
):
251 # set the focus on the puzzle grid
253 self
.window
.set_focus (drawarea
)
255 col
= int (event
.x
/ 30)
256 row
= int (event
.y
/ 30)
258 if col
< self
.puzzle
.cols
and row
< self
.puzzle
.rows
:
259 if (self
.puzzle
.data
[row
][col
].occupied_across
is True or
260 self
.puzzle
.data
[row
][col
].occupied_down
is True):
261 self
.selected_col
= col
262 self
.selected_row
= row
263 drawarea
.queue_draw ()
267 # callback for puzzle grid key release event
268 def on_puzzlegrid_key_press_event (self
, drawarea
, event
):
270 key
= gtk
.gdk
.keyval_name (event
.keyval
).lower ()
272 if event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "up":
273 # reduce the row by 1 until you find an occupied grid and not a
275 self
.move_selection_updown (-1)
276 self
.typing_mode
= self
.DOWN
277 drawarea
.queue_draw ()
278 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "down":
279 # increase the row by 1 until you find an occupied grid and not a
281 self
.move_selection_updown (1)
282 self
.typing_mode
= self
.DOWN
283 drawarea
.queue_draw ()
284 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "right":
285 # increase the column by 1 until you find an occupied grid and not
287 self
.move_selection_across (1)
288 self
.typing_mode
= self
.ACROSS
289 drawarea
.queue_draw ()
290 elif event
.state
== gtk
.gdk
.SHIFT_MASK
and key
== "left":
291 # decrease the column by 1 until you find an occupied grid and not
293 self
.move_selection_across (-1)
294 self
.typing_mode
= self
.ACROSS
295 drawarea
.queue_draw ()
296 # if it is A-Z or a-z then
297 elif len (key
) == 1 and key
.isalpha ():
299 drawarea
.queue_draw ()
300 # if it is the delete key then delete character at selected row/col
301 elif key
== "delete" or key
== "space":
302 self
.puzzle
.data
[self
.selected_row
][self
.selected_col
].guess
= None
303 drawarea
.queue_draw ()
304 # if it is backspace key then delete character at previous row/col
305 # depending on the input mode. If across editing mode, then delete
306 # at previous column else at previous row
307 elif key
== "backspace":
308 self
.delete_prev_guess ()
309 drawarea
.queue_draw ()
313 # puzzle grid focus in event
314 def on_puzzlegrid_focus_out_event (self
, drawarea
, event
):
316 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("gray"))
317 drawarea
.window
.set_background (col
)
321 # puzzle grid focus out event
322 def on_puzzlegrid_focus_in_event (self
, drawarea
, event
):
324 col
= drawarea
.window
.get_colormap ().alloc_color (gtk
.gdk
.Color ("white"))
325 drawarea
.window
.set_background (col
)
328 # callback for drawing the puzzle grid
329 def on_puzzlegrid_expose_event (self
, drawarea
, event
):
330 # if puzzle is loaded
333 drawarea
.set_size_request (self
.puzzle
.cols
*30+2, self
.puzzle
.rows
*30+2)
335 ctx
= drawarea
.window
.cairo_create ()
336 ctx
.set_line_width (1.5)
338 # run through the grid
339 for row
in range (self
.puzzle
.rows
):
340 for col
in range (self
.puzzle
.cols
):
341 # (re)set foreground color
342 ctx
.set_source_rgb (0, 0, 0)
343 # if the area is not occupied
344 if (self
.puzzle
.data
[row
][col
].occupied_across
is False and
345 self
.puzzle
.data
[row
][col
].occupied_down
is False):
346 ctx
.rectangle (col
*30, row
*30, 30, 30)
349 # if selected row/column
350 if row
== self
.selected_row
and col
== self
.selected_col
:
351 ctx
.set_source_rgb (1, 1, 0)
352 ctx
.rectangle (col
*30,row
*30, 30, 30)
355 ctx
.set_source_rgb (1, 1, 1)
356 ctx
.rectangle (col
*30, row
*30, 30, 30)
358 ctx
.set_source_rgb (0, 0, 0)
359 ctx
.rectangle (col
*30, row
*30, 30, 30)
363 if self
.puzzle
.data
[row
][col
].numbered
<> 0:
364 ctx
.set_source_rgb (0, 0, 0)
365 ctx
.select_font_face ("Serif")
366 ctx
.set_font_size (10)
367 ctx
.move_to (col
*30+2, row
*30+10)
368 ctx
.show_text (str(self
.puzzle
.data
[row
][col
].numbered
))
370 # if there is a guessed character at the location
371 # and it is not revealed as a solution
372 if (self
.puzzle
.data
[row
][col
].guess
and
373 self
.puzzle
.data
[row
][col
].revealed
is False and
374 (self
.puzzle
.data
[row
][col
].occupied_across
is True
375 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
376 ctx
.set_source_rgb (0, 0, 0)
377 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
378 cairo
.FONT_WEIGHT_BOLD
)
379 ctx
.set_font_size (16)
380 ctx
.move_to (col
*30+10, row
*30+20)
381 ctx
.show_text (self
.puzzle
.data
[row
][col
].guess
)
383 # if there is a revealed solution character at the location
384 if (self
.puzzle
.data
[row
][col
].revealed
is True and
385 (self
.puzzle
.data
[row
][col
].occupied_across
is True
386 or self
.puzzle
.data
[row
][col
].occupied_down
is True)):
387 ctx
.set_source_rgb (0, 0, 0.8)
388 ctx
.select_font_face ("Serif", cairo
.FONT_SLANT_NORMAL
,
389 cairo
.FONT_WEIGHT_BOLD
)
390 ctx
.set_font_size (16)
391 ctx
.move_to (col
*30+10, row
*30+20)
392 ctx
.show_text (self
.puzzle
.data
[row
][col
].char
)
396 # load clues to the list
397 def load_clues (self
):
398 # get the clues list store objects
399 across
= self
.ui
.get_object ("clues_across")
400 down
= self
.ui
.get_object ("clues_down")
404 # if puzzle is loaded
406 clues_across
= self
.puzzle
.get_clues_across ()
407 clues_down
= self
.puzzle
.get_clues_down ()
408 # insert the numbers and the clues for across
409 for word
, clue
in clues_across
:
410 across
.append ([str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
412 # insert the numbers and the clues for down
413 for word
, clue
in clues_down
:
414 down
.append ([ str(self
.puzzle
.data
[word
[1]][word
[2]].numbered
),
417 def open_file (self
, file):
418 # try to open the file
421 self
.puzzle
= cPickle
.load (open (file, "rb"))
422 # assert that it is unfrozen otherwise raise frozen grid exception
423 self
.puzzle
.assert_frozen_grid ()
425 # set selected initial row and column to 0
426 self
.selected_row
= 0
427 self
.selected_col
= 0
428 # set the typing mode to default - across
429 self
.typing_mode
= self
.ACROSS
431 self
.window
.set_title ("GetAClue player - " + file)
434 # handle unpickling, and file errors
435 except (cPickle
.UnpicklingError
, IOError, OSError):
436 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
437 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
438 "Invalid file. Cannot be loaded")
441 # if the puzzle has no words, then it cannot be played obviously
442 except crosswordpuzzle
.NoWordsException
:
444 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
445 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
446 "Word grid has no words. Cannot play")
449 # if the puzzle is not frozen then it cannot be played
450 except crosswordpuzzle
.FrozenGridException
:
452 dlg
= gtk
.MessageDialog (self
.window
, gtk
.DIALOG_MODAL
,
453 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_CLOSE
,
454 "Word grid is not finalized/frozen. Cannot play")
458 def __init__ (self
, file_to_play
= None):
459 # load the user interface
460 self
.ui
= gtk
.Builder ()
461 self
.ui
.add_from_file ("playerwindow.glade")
464 self
.window
= self
.ui
.get_object ("mainwindow")
467 # set the cell renderer for tree views
468 cell
= gtk
.CellRendererText ()
469 tree_acol1
= self
.ui
.get_object ("tree_clues_across").get_column (0)
470 tree_acol2
= self
.ui
.get_object ("tree_clues_across").get_column (1)
471 tree_acol1
.pack_start (cell
)
472 tree_acol1
.add_attribute (cell
, "text", 0)
473 tree_acol2
.pack_start (cell
)
474 tree_acol2
.add_attribute (cell
, "text", 1)
476 tree_down
= self
.ui
.get_object ("tree_clues_down")
477 tree_dcol1
= self
.ui
.get_object ("tree_clues_down").get_column (0)
478 tree_dcol2
= self
.ui
.get_object ("tree_clues_down").get_column (1)
479 tree_dcol1
.pack_start (cell
)
480 tree_dcol1
.add_attribute (cell
, "text", 0)
481 tree_dcol2
.pack_start (cell
)
482 tree_dcol2
.add_attribute (cell
, "text", 1)
484 # connect the signals
485 self
.ui
.connect_signals (self
)
487 # set the puzzle to None
490 # open the file if it is set
492 self
.open_file (file_to_play
)