19ee754ef125f87a3bcf9692701ce1fd6dbd1c2e
[curses-crossword.git] / curses-crossword.py
1 #!/usr/bin/python
2
3 import curses
4 import curses.ascii
5 import locale
6 import codecs
7 import getopt
8 import os
9 import sys
10
11 locale.setlocale(locale.LC_ALL, '')
12 code = locale.getpreferredencoding()
13
14 superscript_numbers = {}
15
16 try:
17     superscript_numbers = {
18         "0": unichr(8304).encode(code),
19         "1": unichr(185).encode(code),
20         "2": unichr(178).encode(code),
21         "3": unichr(179).encode(code),
22         "4": unichr(8308).encode(code),
23         "5": unichr(8309).encode(code),
24         "6": unichr(8310).encode(code),
25         "7": unichr(8311).encode(code),
26         "8": unichr(8312).encode(code),
27         "9": unichr(8313).encode(code),
28     }
29 except:
30     for number in range(0,10):
31         superscript_numbers[str(number)] = str(number)
32
33 filename = None
34
35 try:
36     (options, args) = getopt.getopt(sys.argv[1:], "f:", "file=")
37 except getopt.GetoptError, err:
38     print str(err)
39     sys.exit(2)
40
41 for option in options:
42     if option[0] == "-f" or option[0] == "--file":
43         filename = option[1]
44
45 if not filename and len(args) > 0:
46     filename = args[0]
47
48 if not filename:
49     sys.stderr.write("No crossword file specified, exiting.\n")
50     sys.exit(0)
51
52 if os.path.exists(filename) and os.path.isfile(filename):
53     crossworddata = codecs.open(filename, "r", "utf-8").read()
54 else:
55     sys.stderr.write("Couldn't open file %s\n" %(filename))
56     sys.exit(0)
57
58 def parsecrossword(crossworddata):
59     ingrid = False
60     inacross = False
61     indown = False
62     crossword = {"grid": [], "across": {}, "down": {}, "grid_questions_start": [], "grid_questions_end": []}
63
64     for line in crossworddata.split("\n"):
65         line = line.strip("\n")
66         if line == "GRID":
67             ingrid = True
68             inacross = False
69             indown = False
70         elif line == "ACROSS":
71             ingrid = False
72             inacross = True
73             indown = False
74         elif line == "DOWN":
75             ingrid = False
76             inacross = False
77             indown = True
78         else:
79             if ingrid:
80                 if line != "":
81                     crossword["grid"].append(line)
82             if inacross:
83                 if line != "":
84                     parts = line.split()
85                     question_number = int(parts[0])
86                     clue = " ".join(parts[1:])
87                     crossword["across"][int(question_number)] = clue.encode('utf-8')
88             if indown:
89                 if line != "":
90                     parts = line.split()
91                     question_number = int(parts[0])
92                     clue = " ".join(parts[1:])
93                     crossword["down"][int(question_number)] = clue.encode('utf-8')
94     num_cols = len(crossword["grid"][0])
95     num_rows = len(crossword["grid"])
96
97     current_clue_number = 1
98
99     for row in range(0,num_rows):
100         crossword["grid_questions_start"].append([])
101
102     for row in range(0,num_rows):
103         for col in range(0,num_cols):
104             have_clue = False
105             if col > 0 \
106                 and crossword["grid"][row][col - 1] == "x" \
107                 and crossword["grid"][row][col] != "x" \
108                 and col < (num_cols - 1) \
109                 and crossword["grid"][row][col + 1] != "x":
110                 have_clue = True
111             if col == 0 and crossword["grid"][row][col] != "x" \
112                 and crossword["grid"][row][col + 1] != "x":
113                 have_clue = True
114             if row > 0 and crossword["grid"][row-1][col] == "x" \
115                 and row < (num_rows - 1) \
116                 and crossword["grid"][row][col] != "x" \
117                 and crossword["grid"][row + 1][col] != "x":
118                 have_clue = True
119             if row == 0 and crossword["grid"][row][col] != "x" \
120                 and crossword["grid"][row + 1][col] != "x":
121                 have_clue = True
122             if have_clue:
123                 crossword["grid_questions_start"][row].append(current_clue_number)
124                 current_clue_number += 1
125             else:
126                 crossword["grid_questions_start"][row].append(0)
127
128     return crossword
129
130 def crossword(stdscr, crossworddata):
131     crossword = parsecrossword(crossworddata)
132     cury = 0
133     grid_length = len(crossword["grid"][0])
134     curx = 0
135     stdscr.addch(cury, curx, curses.ACS_ULCORNER)
136     curx += 1
137     while ((curx - 1) / 4) < grid_length:
138         stdscr.addch(cury, curx, curses.ACS_HLINE)
139         stdscr.addch(cury, curx+1, curses.ACS_HLINE)
140         stdscr.addch(cury, curx+2, curses.ACS_HLINE)
141         stdscr.addch(cury, curx+3, curses.ACS_TTEE)
142         curx += 4
143     curx -= 1
144     stdscr.addch(cury, curx, curses.ACS_URCORNER)
145     cury += 1
146     curgridy=0
147     for line in crossword["grid"]:
148         datastr = ""
149         curx = 0
150         curgridx=0
151         for c in line:
152             stdscr.addch(cury, curx, curses.ACS_VLINE)
153             if curx > 0:
154                 stdscr.addch(cury+1, curx, curses.ACS_PLUS)
155             else:
156                 stdscr.addch(cury+1, curx, curses.ACS_LTEE)
157             stdscr.addch(cury+1, curx + 1, curses.ACS_HLINE)
158             stdscr.addch(cury+1, curx + 2, curses.ACS_HLINE)
159             stdscr.addch(cury+1, curx + 3, curses.ACS_HLINE)
160             curx += 1
161             if c == "x":
162                 stdscr.addch(cury, curx, curses.ACS_BLOCK)
163                 stdscr.addch(cury, curx+1, curses.ACS_BLOCK)
164                 stdscr.addch(cury, curx+2, curses.ACS_BLOCK)
165             elif crossword["grid_questions_start"][curgridy][curgridx] > 0:
166                 stdscr.addstr(cury, curx, ''.join([superscript_numbers[x].decode(code) for x in str(crossword["grid_questions_start"][curgridy][curgridx])]).encode(code))
167             curx += 3
168             curgridx += 1
169         else:
170             stdscr.addch(cury, curx, curses.ACS_VLINE)
171             stdscr.addch(cury + 1, curx, curses.ACS_RTEE)
172         cury += 2
173         curgridy += 1
174     cury -= 1
175     curx = 0
176     stdscr.addch(cury, curx, curses.ACS_LLCORNER)
177     curx += 1
178     while ((curx - 1) / 4) < grid_length:
179         stdscr.addch(cury, curx, curses.ACS_HLINE)
180         stdscr.addch(cury, curx+1, curses.ACS_HLINE)
181         stdscr.addch(cury, curx+2, curses.ACS_HLINE)
182         stdscr.addch(cury, curx+3, curses.ACS_BTEE)
183         curx += 4
184     curx -= 1
185     stdscr.addch(cury, curx, curses.ACS_LRCORNER)
186     # draw the clues in
187     cury = (len(crossword["grid"]) * 2) + 1
188     curx = 0
189     stdscr.addstr(cury, curx, "Across")
190     cury += 1
191     for cluenumber in crossword["across"].keys():
192         stdscr.addstr(cury, curx, "%3s: %s" %(str(cluenumber), crossword["across"][cluenumber]))
193         cury += 1
194
195     cury += 1
196     stdscr.addstr(cury, curx, "Down")
197     cury += 1
198     for cluenumber in crossword["down"].keys():
199         stdscr.addstr(cury, curx, "%3s: %s" %(str(cluenumber), crossword["down"][cluenumber]))
200         cury += 1
201
202     curx = 3
203     cury = 1
204     gridx = 0
205     gridy = 0
206
207     while crossword["grid"][gridy][gridx] == "x":
208         curx += 4
209         gridx += 1
210
211     stdscr.move(cury,curx)
212
213     while 1:
214         c = stdscr.getch()
215         if c == curses.ascii.ESC:
216             break
217         if c == curses.KEY_RIGHT:
218             if gridx < (len(crossword["grid"][0]) - 1):
219                 gridx += 1
220                 curx += 4
221                 while gridx < (len(crossword["grid"][0]) -1) \
222                     and crossword["grid"][gridy][gridx] == "x":
223                     gridx += 1
224                     curx += 4
225                 while crossword["grid"][gridy][gridx] == "x":
226                     gridx -= 1
227                     curx -= 4
228                 stdscr.move(cury, curx)
229         if c == curses.KEY_LEFT:
230             if gridx > 0:
231                 curx -= 4
232                 gridx -= 1
233                 while gridx > 0 \
234                     and crossword["grid"][gridy][gridx] == "x":
235                         gridx -= 1
236                         curx -= 4
237                 while crossword["grid"][gridy][gridx] == "x":
238                     gridx += 1
239                     curx += 4
240                 stdscr.move(cury, curx)
241         if c == curses.KEY_UP:
242             if gridy > 0:
243                 gridy -= 1
244                 cury -= 2
245                 while gridy > 0 \
246                     and crossword["grid"][gridy][gridx] == "x":
247                         gridy -= 1
248                         cury -= 2
249                 while crossword["grid"][gridy][gridx] == "x":
250                     gridy += 1
251                     cury += 2
252                 stdscr.move(cury, curx)
253         if c == curses.KEY_DOWN:
254             if gridy < (len(crossword["grid"]) - 1):
255                 gridy += 1
256                 cury += 2
257                 while gridy < (len(crossword["grid"]) - 1) \
258                     and crossword["grid"][gridy][gridx] == "x":
259                     gridy += 1
260                     cury += 2
261                 while crossword["grid"][gridy][gridx] == "x":
262                     gridy -= 1
263                     cury -= 2
264                 stdscr.move(cury, curx)
265         if curses.ascii.isalpha(c) or c == ord(" "):
266             stdscr.addch(cury, curx, c)
267             stdscr.move(cury, curx)
268         if c == curses.KEY_BACKSPACE or c == curses.KEY_DC:
269             stdscr.addch(cury, curx, ord(" "))
270             stdscr.move(cury, curx)
271
272
273 curses.wrapper(crossword, crossworddata)