c1f822b3ed8a121c7af46d456626225f9de18471
[getaclue.git] / crosswordpuzzle.py
1 # Get A Clue (C) 2010 V. Harishankar
2 # Crossword puzzle maker program
3 # Licensed under the GNU GPL v3
4
5 # Class for the puzzle data representation
6
7 class GridItem:
8 # initialize the item
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
13 self.char = item_char
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
23 self.numbered = num
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
30
31 # exception for too long words
32 class TooLongWordException (Exception):
33 def __init__ (self, word, length):
34 self.word = word
35 self.length = length
36
37 # exception for intersecting words
38 class IntersectWordException (Exception):
39 def __init__ (self, word, length):
40 self.word = word
41 self.length = length
42
43 # exception when grid is sought to be changed when frozen
44 class FrozenGridException (Exception):
45 def __init__ (self):
46 self.msg = "Grid is frozen and cannot be edited"
47
48 class CrosswordPuzzle:
49 # ansi color codes for grid display
50 BRICKRED = '\033[44;1;31m'
51 # bold
52 BOLD = '\033[33m'
53 # blue
54 BLUE = '\033[34m'
55 # grey
56 GREY = '\033[30m'
57 # disable colors
58 ENDCOL = '\033[0m'
59
60 def __init__ (self, rows, cols):
61 # define number of rows and columns
62 self.rows = rows
63 self.cols = cols
64
65 # initialize the list to hold the grid
66 self.data = []
67
68 # initial state of the grid is unfrozen
69 self.frozen_grid = False
70
71 # create the grid data
72 for i in range (rows):
73 self.data.append ([])
74 for j in range (cols):
75 self.data[i].append (GridItem ())
76
77 # setting a down word
78 def set_word_down (self, row, col, word):
79 # if the grid is frozen the abort
80 if self.frozen_grid is True:
81 raise FrozenGridException
82
83 # if the word length greater than totalrows - startrow
84 if len(word) > self.rows - row:
85 raise TooLongWordException (word, len(word))
86
87 # is the word intersecting any other word?
88 for i in range (len(word)):
89 # on the same column
90 if self.data[row+i][col].occupied_down is True:
91 raise IntersectWordException (word, len(word))
92 # on the previous column
93 if col > 0 and self.data[row+i][col-1].occupied_down is True:
94 raise IntersectWordException (word, len(word))
95 # on the next column
96 if (col < (len(word) - 1) and
97 (self.data[row+i][col+1].occupied_down is True or
98 self.data[row+i][col+1].across_start is True)):
99 raise IntersectWordException (word, len(word))
100
101 # also check the character before and after
102 if (row > 0 and self.data[row-1][col].occupied_down is True
103 and self.data[row-1][col].occupied_across is True):
104 raise IntersectWordException (word, len(word))
105 if (row + len(word) < self.rows and
106 self.data[row+len(word)][col].occupied_across is True and
107 self.data[row+len(word)][col].occupied_down is True):
108 raise IntersectWordException (word, len(word))
109
110 # set the down start to true
111 self.data[row][col].down_start = True
112 # set the word
113 for i in range (len(word)):
114 self.data[row+i][col].occupied_down = True
115 self.data[row+i][col].char = word[i].upper ()
116
117
118 # setting an across word
119 def set_word_across (self, row, col, word):
120 # if the grid is frozen the abort
121 if self.frozen_grid is True:
122 raise FrozenGridException
123
124 # is the word length greater than totalcols - startcol?
125 if len(word) > self.cols - col:
126 raise TooLongWordException (word, len(word))
127
128 # is the word intersecting any other word?
129 for i in range (len(word)):
130 # on the same line
131 if self.data[row][col+i].occupied_across is True:
132 raise IntersectWordException (word, len(word))
133 # on a previous line except the last column
134 if row > 0 and self.data[row-1][col+i].occupied_across is True:
135 raise IntersectWordException (word, len(word))
136 # on a next line except the last column
137 if (row < (self.rows - 1) and
138 (self.data[row+1][col+i].down_start is True
139 or self.data[row+1][col+i].occupied_across is True)):
140 raise IntersectWordException (word, len(word))
141
142 # also check the character beyond and before and after
143 if (col > 0 and (self.data[row][col-1].occupied_across is True or
144 self.data[row][col-1].occupied_down is True)):
145 raise IntersectWordException (word, len(word))
146 if (col + len(word) < self.cols and
147 (self.data[row][col+len(word)].occupied_across is True or
148 self.data[row][col+len(word)].occupied_down is True)):
149 raise IntersectWordException (word, len(word))
150
151 # set across start to true
152 self.data[row][col].across_start = True
153
154 # set the word
155 for i in range (len(word)):
156 self.data[row][col+i].char = word[i].upper ()
157 self.data[row][col+i].occupied_across = True
158
159 # display the grid with words
160 def print_grid (self, no_words=False):
161 # get row, col and print them (with grid number if set)
162 for col in range (self.cols):
163 # print the first row as column headers
164 print self.BLUE + ' ' + str (col) + self.ENDCOL,
165 print
166
167 for row in range (self.rows):
168 for col in range (self.cols):
169 # print the data
170 # if the cell is numbered i.e. start of a word
171 if self.data[row][col].numbered != 0:
172 print self.BRICKRED + str(self.data[row][col].numbered) + self.ENDCOL,
173 # print a space
174 else:
175 print ' ',
176 # if the character is not a blank or a block
177 if self.data[row][col].char <> "." and self.data[row][col].char <> "#":
178 # if words are to be shown regardless of hidden/revealed state
179 if no_words is False:
180 print self.BOLD + self.data[row][col].char + self.ENDCOL,
181 else:
182 # display only revealed
183 if self.data[row][col].revealed is True:
184 print self.BOLD + self.data[row][col].char + self.ENDCOL,
185 # else print a block
186 else:
187 print self.GREY + '.' + self.ENDCOL,
188 else:
189 print self.GREY + self.data[row][col].char + self.ENDCOL,
190
191 print ' ' + self.BLUE + str(row) + self.ENDCOL
192
193 # freeze the grid numbers etc.
194 def freeze_grid (self):
195 # numbering
196 numbering = 1
197 # run through the grid
198 for row in range (self.rows):
199 for col in range (self.cols):
200 # if grid is blank set the character to #
201 if (self.data[row][col].occupied_across is False
202 and self.data[row][col].occupied_down is False):
203 self.data[row][col].char = "#"
204 elif (self.data[row][col].across_start is True or
205 self.data[row][col].down_start is True):
206 self.data[row][col].numbered = numbering
207 numbering += 1
208
209 self.frozen_grid = True
210