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