X-Git-Url: https://harishankar.org/repos/?p=getaclue.git;a=blobdiff_plain;f=crosswordpuzzle.py;h=7d3b2709933e604a71b04171726a1f359ce3291c;hp=c1f822b3ed8a121c7af46d456626225f9de18471;hb=8bf14bbd585036c0cfda88c2ace825f1aa471730;hpb=85159420b6ed652a259484b3cf192f602d9418c0 diff --git a/crosswordpuzzle.py b/crosswordpuzzle.py index c1f822b..7d3b270 100644 --- a/crosswordpuzzle.py +++ b/crosswordpuzzle.py @@ -4,6 +4,9 @@ # 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, @@ -45,18 +48,17 @@ class FrozenGridException (Exception): def __init__ (self): self.msg = "Grid is frozen and cannot be edited" -class CrosswordPuzzle: - # ansi color codes for grid display - BRICKRED = '\033[44;1;31m' - # bold - BOLD = '\033[33m' - # blue - BLUE = '\033[34m' - # grey - GREY = '\033[30m' - # disable colors - ENDCOL = '\033[0m' +# exception when no word is found at a position +class NoWordException (Exception): + def __init__ (self, row, col): + self.pos = (row, col) +# exception when no words are present in the grid +class NoWordsException (Exception): + def __init__ (self): + self.msg = "No words in grid" + +class CrosswordPuzzle: def __init__ (self, rows, cols): # define number of rows and columns self.rows = rows @@ -74,6 +76,244 @@ 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_contents.append (puztitle) + html_contents.append ("") + html_contents.append ("") + html_contents.append ("") + html_contents.append ("

" + puztitle + "

") + html_contents.append ('puzzle') + + html_contents.append ("

Across clues

") + html_contents.append ("

") + 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 ("
") + html_contents.append ("

") + + html_contents.append ("

Down clues

") + html_contents.append ("

") + 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 ("
") + html_contents.append ("

") + html_contents.append ("") + html_contents.append ("") + + 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 ("\r\n") + across_data.append ("\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 = [] + # traverse the grid + for row in range (self.rows): + for col in range (self.cols): + if (self.data[row][col].occupied_across is True and + self.data[row][col].across_start is True): + word_across = self.get_word_across (row, col) + clues.append ((word_across, self.data[row][col].clue_across)) + # if no across words are found at all + if not clues: + raise NoWordsException + + return clues + + # get all the clues for down + def get_clues_down (self): + clues = [] + # traverse the grid + for row in range (self.rows): + for col in range (self.cols): + if (self.data[row][col].occupied_down is True and + self.data[row][col].down_start is True): + word_down = self.get_word_down (row, col) + clues.append ((word_down, self.data[row][col].clue_down)) + # if no down words are found at all + if not clues: + raise NoWordsException + + return clues + + # getting a down word at a position + def get_word_down (self, row, col): + # if index is out of bounds + if row >= self.rows or col >= self.cols: + raise NoWordException (row, col) + + # if there is no occupied down letter at that position + if self.data[row][col].occupied_down is False: + raise NoWordException (row, col) + + # now traverse the grid to find the beginning of the word + i = row + while i >= 0: + # if it is occupied down and is the beginning of the word + if (self.data[i][col].occupied_down is True and + self.data[i][col].down_start is True): + start_row = i + break + i -= 1 + + i = start_row + word_chars = [] + # now seek the end of the word + while i < self.rows: + if self.data[i][col].occupied_down is True: + word_chars.append (self.data[i][col].char) + else: + break + i += 1 + + word = "".join (word_chars) + + # return the word, starting row, column and length as a tuple + return (word, start_row, col, len(word)) + + # getting an across word at a position + def get_word_across (self, row, col): + # if index is out of bounds + if row >= self.rows or col >= self.cols: + raise NoWordException (row, col) + + # if there is no occupied across letter at that position + if self.data[row][col].occupied_across is False: + raise NoWordException (row, col) + + # now traverse the grid to look for the beginning of the word + i = col + while i >= 0: + # if it is occupied across and is the beginning of the word + if (self.data[row][i].occupied_across is True and + self.data[row][i].across_start is True): + start_col = i + break + i -= 1 + + i = start_col + word_chars = [] + # now seek the end of the word + while i < self.cols: + if self.data[row][i].occupied_across is True: + word_chars.append (self.data[row][i].char) + else: + break + i += 1 + + word = "".join (word_chars) + + # return the word, starting column, row and length as a tuple + return (word, row, start_col, len(word)) + # setting a down word def set_word_down (self, row, col, word): # if the grid is frozen the abort @@ -89,14 +329,23 @@ class CrosswordPuzzle: # on the same column if self.data[row+i][col].occupied_down is True: raise IntersectWordException (word, len(word)) - # on the previous column - if col > 0 and self.data[row+i][col-1].occupied_down is True: - raise IntersectWordException (word, len(word)) - # on the next column - if (col < (len(word) - 1) and - (self.data[row+i][col+1].occupied_down is True or - self.data[row+i][col+1].across_start is True)): - raise IntersectWordException (word, len(word)) + # on the previous column except first column + if col > 0: + # except the first and last col + if i > 0 and i < len(word) - 1: + if self.data[row+i][col-1].occupied_down is True: + raise IntersectWordException (word, len(word)) + # on the next column except last column + if col < len(word) - 1: + # except the first and last row check if there is any + # down word in previous column + if i > 0 and i < len(word) - 1: + if self.data[row+i][col+1].occupied_down is True: + raise IntersectWordException (word, len(word)) + # check if there is any across word starting in the + # next column + if self.data[row+i][col+1].across_start is True: + raise IntersectWordException (word, len(word)) # also check the character before and after if (row > 0 and self.data[row-1][col].occupied_down is True @@ -127,17 +376,26 @@ class CrosswordPuzzle: # is the word intersecting any other word? for i in range (len(word)): - # on the same line + # on the same row if self.data[row][col+i].occupied_across is True: raise IntersectWordException (word, len(word)) - # on a previous line except the last column - if row > 0 and self.data[row-1][col+i].occupied_across is True: - raise IntersectWordException (word, len(word)) - # on a next line except the last column - if (row < (self.rows - 1) and - (self.data[row+1][col+i].down_start is True - or self.data[row+1][col+i].occupied_across is True)): - raise IntersectWordException (word, len(word)) + # on a previous row except first row + if row > 0: + # if not the first or last col + if i > 0 and i < len(word) - 1: + if self.data[row-1][col+i].occupied_across is True: + raise IntersectWordException (word, len(word)) + # on a next row + if (row < (self.rows - 1)): + # except the first and last letter check if there is + # any across intersection + if i > 0 and i < len (word) - 1: + if self.data[row+1][col+i].occupied_across is True: + raise IntersectWordException (word, len(word)) + # if a down word is starting at any column below the + # word + if self.data[row+1][col+i].down_start is True: + raise IntersectWordException (word, len(word)) # also check the character beyond and before and after if (col > 0 and (self.data[row][col-1].occupied_across is True or @@ -156,40 +414,6 @@ class CrosswordPuzzle: self.data[row][col+i].char = word[i].upper () self.data[row][col+i].occupied_across = True - # display the grid with words - def print_grid (self, no_words=False): - # get row, col and print them (with grid number if set) - for col in range (self.cols): - # print the first row as column headers - print self.BLUE + ' ' + str (col) + self.ENDCOL, - print - - for row in range (self.rows): - for col in range (self.cols): - # print the data - # if the cell is numbered i.e. start of a word - if self.data[row][col].numbered != 0: - print self.BRICKRED + str(self.data[row][col].numbered) + self.ENDCOL, - # print a space - else: - print ' ', - # if the character is not a blank or a block - if self.data[row][col].char <> "." and self.data[row][col].char <> "#": - # if words are to be shown regardless of hidden/revealed state - if no_words is False: - print self.BOLD + self.data[row][col].char + self.ENDCOL, - else: - # display only revealed - if self.data[row][col].revealed is True: - print self.BOLD + self.data[row][col].char + self.ENDCOL, - # else print a block - else: - print self.GREY + '.' + self.ENDCOL, - else: - print self.GREY + self.data[row][col].char + self.ENDCOL, - - print ' ' + self.BLUE + str(row) + self.ENDCOL - # freeze the grid numbers etc. def freeze_grid (self): # numbering @@ -208,3 +432,15 @@ class CrosswordPuzzle: self.frozen_grid = True + # unfreeze the grid numbers etc. + def unfreeze_grid (self): + # run through the grid + for row in range (self.rows): + for col in range (self.cols): + self.data[row][col].numbered = 0 + if (self.data[row][col].occupied_across is False and + self.data[row][col].occupied_down is False): + self.data[row][col].char = '.' + + self.frozen_grid = False +