Reset grid functionality implemented
[getaclue.git] / crosswordpuzzle.py
index 8091d3d..57878b8 100644 (file)
@@ -4,13 +4,19 @@
 
 # Class for the puzzle data representation
 
+# for export to PNG image
+import cairo
+
 class GridItem:
        # initialize the item
-       def __init__ (self, item_char='.', across_start = False, down_start = False,
-                                       occupied_across = False, occupied_down = False, num = 0,
-                                       clue_across = None, clue_down = None, revealed = False):
+       def __init__ (self, item_char='.', item_guess = None, across_start = False,
+                                       down_start = False,     occupied_across = False,
+                                       occupied_down = False, num = 0, clue_across = None,
+                                       clue_down = None, revealed = False):
                # character in the cell
                self.char = item_char
+               # guess of character in cell
+               self.guess = item_guess
                # is the cell the start of an across word?
                self.across_start = across_start
                # is the cell the start of a down word?
@@ -28,6 +34,59 @@ class GridItem:
                # is the letter revealed or hidden?
                self.revealed = revealed
 
+       # clear the across data
+       def clear_across_data (self):
+               self.across_start = False
+               self.occupied_across = False
+               self.clue_across = None
+               # if no down word starting at item
+               if self.down_start is False:
+                       self.numbered = 0
+               # if no down word at the item
+               if self.occupied_down is False:
+                       self.char = '.'
+                       self.revealed = False
+                       self.guess = None
+
+       # clear the down data
+       def clear_down_data (self):
+               self.down_start = False
+               self.occupied_down = False
+               self.clue_down = None
+               # if no across word starting at item
+               if self.across_start is False:
+                       self.numbered = 0
+               # if no across word at the item
+               if self.occupied_across is False:
+                       self.char = '.'
+                       self.revealed = False
+                       self.guess = None
+
+       # reset a grid item completely - use only to destroy whole grid, otherwise
+       # use either clear_across_data () or clear_down_data () for erasing single
+       # words
+       def reset (self):
+               # character in the cell
+               self.char = '.'
+               # guess of character in cell
+               self.guess = None
+               # is the cell the start of an across word?
+               self.across_start = False
+               # is the cell the start of a down word?
+               self.down_start = False
+               # is the cell occupied by a letter in an across word?
+               self.occupied_across = False
+               # is the cell occupied by a letter in a down word?
+               self.occupied_down = False
+               # numbering of the cell if it is the start of a word
+               self.numbered = 0
+               # clue across if the cell is the start of an across word
+               self.clue_across = None
+               # clue down if the cell is the start of a down word
+               self.clue_down = None
+               # is the letter revealed or hidden?
+               self.revealed = False
+
 # exception for too long words
 class TooLongWordException (Exception):
        def __init__ (self, word, length):
@@ -73,6 +132,142 @@ class CrosswordPuzzle:
                        for j in range (cols):
                                self.data[i].append (GridItem ())
 
+       # export to an image
+       def export_image (self, pngfile, htmlfile=None, puztitle="Crossword Puzzle",
+               solution=True):
+               # don't export if grid is not frozen
+               if self.frozen_grid is False:
+                       raise FrozenGridException
+
+               # create cairo image surface and context
+               px = 30
+               surf = cairo.ImageSurface (cairo.FORMAT_RGB24, self.cols*px, self.rows*px)
+               ctx = cairo.Context (surf)
+
+               ctx.set_source_rgb (1, 1, 1)
+               ctx.rectangle (0, 0, self.cols*px, self.rows*px)
+               ctx.fill ()
+
+               # get the clues across and down
+               clues_across = self.get_clues_across ()
+               clues_down = self.get_clues_down ()
+
+
+               # traverse through the grid
+               for row in range (self.rows):
+                       for col in range (self.cols):
+                               # if grid is un-occupied
+                               if (self.data[row][col].occupied_across is False and
+                                       self.data[row][col].occupied_down is False):
+                                       ctx.set_source_rgb (0, 0, 0)
+                                       ctx.rectangle (col*px, row*px, px, px)
+                                       ctx.fill ()
+                               # grid is occupied
+                               else:
+                                       ctx.set_source_rgb (1, 1, 1)
+                                       ctx.rectangle (col*px, row*px, px, px)
+                                       ctx.fill ()
+                                       ctx.set_source_rgb (0, 0, 0)
+                                       ctx.rectangle (col*px, row*px, px, px)
+                                       ctx.stroke ()
+                                       # if solution is not to be provided, number the grid
+                                       if solution is False:
+                                               if self.data[row][col].numbered <> 0:
+                                                       ctx.select_font_face ("Serif")
+                                                       ctx.set_font_size (10)
+                                                       ctx.move_to (col*px+5, row*px+10)
+                                                       ctx.show_text (str(self.data[row][col].numbered))
+                                       # display the words
+                                       else:
+                                               ctx.select_font_face ("Serif")
+                                               ctx.set_font_size (16)
+                                               ctx.move_to (col*px+10, row*px+20)
+                                               ctx.show_text (self.data[row][col].char)
+
+               surf.write_to_png (open (pngfile, "wb"))
+
+               # if solution is false, publish the clues and the image in a HTML file
+               if htmlfile and solution is False:
+                       html_contents = ["<html>", "<head>", "<title>"]
+                       html_contents.append (puztitle)
+                       html_contents.append ("</title>")
+                       html_contents.append ("</head>")
+                       html_contents.append ("<body>")
+                       html_contents.append ("<h1>" + puztitle + "</h1>")
+                       html_contents.append ('<img src="' + pngfile + '" alt="puzzle" />')
+
+                       html_contents.append ("<h2>Across clues</h2>")
+                       html_contents.append ("<p>")
+                       for word, clue in clues_across:
+                               clue_str = str (self.data[word[1]][word[2]].numbered) + " - " \
+                                                       + clue
+                               html_contents.append (clue_str)
+                               html_contents.append ("<br />")
+                       html_contents.append ("</p>")
+
+                       html_contents.append ("<h2>Down clues</h2>")
+                       html_contents.append ("<p>")
+                       for word, clue in clues_down:
+                               clue_str = str (self.data[word[1]][word[2]].numbered) + " - " \
+                                                       + clue
+                               html_contents.append (clue_str)
+                               html_contents.append ("<br />")
+                       html_contents.append ("</p>")
+                       html_contents.append ("</body>")
+                       html_contents.append ("</html>")
+
+                       html_str = "\r\n".join (html_contents)
+
+                       fhtml = open (htmlfile, "wb")
+                       fhtml.write (html_str)
+                       fhtml.close ()
+
+       # get the AcrossLite(TM) data for exporting
+       def export_acrosslite (self, title, author, copyright):
+               # don't export if grid is not frozen
+               if self.frozen_grid is False:
+                       raise FrozenGridException
+
+               across_data = []
+               across_data.append ("<ACROSS PUZZLE>\r\n")
+               across_data.append ("<TITLE>\r\n")
+               across_data.append (title + "\r\n")
+               across_data.append ("<AUTHOR>\r\n")
+               across_data.append (author + "\r\n")
+               across_data.append ("<COPYRIGHT>\r\n")
+               across_data.append (copyright + "\r\n")
+               across_data.append ("<SIZE>\r\n")
+               str_size = str (self.cols) + "x" + str (self.rows)
+               across_data.append (str_size + "\r\n")
+               across_data.append ("<GRID>\r\n")
+               for row in range (self.rows):
+                       for col in range (self.cols):
+                               if (self.data[row][col].occupied_across is True or
+                                       self.data[row][col].occupied_down is True):
+                                       across_data.append (self.data[row][col].char)
+                               else:
+                                       across_data.append (".")
+                       across_data.append ("\r\n")
+
+               across_data.append ("<ACROSS>\r\n")
+               clues_across = self.get_clues_across ()
+               for word, clue in clues_across:
+                       if clue:
+                               across_data.append (clue + "\r\n")
+                       else:
+                               across_data.append ("(No clue yet)\r\n")
+
+               across_data.append ("<DOWN>\r\n")
+               clues_down = self.get_clues_down ()
+               for word, clue in clues_down:
+                       if clue:
+                               across_data.append (clue + "\r\n")
+                       else:
+                               across_data.append ("(No clue yet\r\n")
+
+               acrosslite_str = "".join (across_data)
+               return acrosslite_str
+
        # get all the clues for across
        def get_clues_across (self):
                clues = []
@@ -173,7 +368,7 @@ class CrosswordPuzzle:
                word = "".join (word_chars)
 
                # return the word, starting column, row and length as a tuple
-               return (word, start_col, row, len(word))
+               return (word, row, start_col, len(word))
 
        # setting a down word
        def set_word_down (self, row, col, word):
@@ -305,3 +500,12 @@ class CrosswordPuzzle:
 
                self.frozen_grid = False
 
+       # reset the entire grid
+       def reset_grid (self):
+               # run through the grid
+               for row in range (self.rows):
+                       for col in range (self.cols):
+                               # re-initialize all data
+                               self.data[row][col].reset ()
+
+               self.frozen_grid = False