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