git @ Cat's Eye Technologies Nhohnhehr / 24e6dce
Run under Python 3, PEP8 style. It does fail one test case though. Chris Pressey 1 year, 9 months ago
2 changed file(s) with 171 addition(s) and 126 deletion(s). Raw diff Collapse all Expand all
00 #!/usr/bin/env python
11
2 ### Nhohnhehr interpreter ###
2 # ### Nhohnhehr interpreter ###
33
44 # Written by Marinus. The contents of this file are in the public domain.
55 # Adapted from this esowiki page on May 10 2011:
66 # http://www.esolangs.org/wiki/User:Marinus/Nhohnhehr_interpreter
7 # Adapted to run under Python 3, and to conform to PEP8 style,
8 # by Chris Pressey, summer 2021.
79
810 # usage: nhohnhehr.py bits filename (binary I/O)
911 # nhohnhehr.py [bytes] filename (ASCII I/O)
1012
1113 import sys
1214
13 def addvec( (x1,y1), (x2,y2) ): return (x1+x2, y1+y2)
14 def mulvec( (x,y), m ): return (x*m, y*m)
15
16 def addvec(v1, v2):
17 (x1, y1) = v1
18 (x2, y2) = v2
19 return (x1 + x2, y1 + y2)
20
21
22 def mulvec(v, m):
23 (x, y) = v
24 return (x * m, y * m)
1525
1626
1727 # room
1828 class Room:
1929 data = None
2030 size = 0
21
22 def __getitem__(self, (x, y)):
23 if x>=0 and y>=0 and x<self.size and y<self.size:
31
32 def __getitem__(self, v):
33 (x, y) = v
34 if x >= 0 and y >= 0 and x < self.size and y < self.size:
2435 return self.data[y][x]
2536 else:
2637 raise IndexError("value out of range")
27
38
2839 def __str__(self):
2940 return "\n".join(''.join(str(item) for item in line) for line in self.data)
30
41
3142 # transformations
3243 NONE, CW, CCW, ROT = range(4)
33
44
3445 def transform(self, transformation):
35 if transformation==Room.NONE: return
36 elif transformation==Room.ROT:
46 if transformation == Room.NONE:
47 return
48 elif transformation == Room.ROT:
3749 # rotate 180 degrees: flip lines, and reverse each line
3850 self.data.reverse()
39 for line in self.data: line.reverse()
40 elif transformation==Room.CCW:
51 for line in self.data:
52 line.reverse()
53 elif transformation == Room.CCW:
4154 # clockwise 90 degrees
4255 data = self.data
43 self.data = [[0]*self.size for x in range(self.size)]
56 self.data = [[0] * self.size for x in range(self.size)]
4457 for y in range(self.size):
4558 for x in range(self.size):
46 self.data[self.size-x-1][y] = data[y][x]
47 elif transformation==Room.CW:
59 self.data[self.size - x - 1][y] = data[y][x]
60 elif transformation == Room.CW:
4861 # counterclockwise 90 degrees
4962 data = self.data
50 self.data = [[0]*self.size for x in range(self.size)]
63 self.data = [[0] * self.size for x in range(self.size)]
5164 for y in range(self.size):
5265 for x in range(self.size):
53 self.data[y][x] = data[self.size-x-1][y]
66 self.data[y][x] = data[self.size - x - 1][y]
5467 else:
55 raise ValueError("invalid transformation (%d)"%transformation)
56
68 raise ValueError("invalid transformation (%d)" % transformation)
69
5770 # init room from file or from room+transformation
5871 def __init__(self, file=None, room=None, transform=None):
5972 if (file and room) or (not (file or room)):
6073 raise TypeError("Room needs to be initialized with either a file or a room.")
61
74
6275 # init from file
6376 if file:
6477 self.data = []
65
78
6679 # read file
6780 lines = file.readlines()
68
81
6982 # find possible top-left coordinates for the box
7083 possibleStartCoords = []
7184 for y in range(len(lines)):
7285 for x in range(len(lines[y])):
7386 if lines[y][x] == '+':
7487 try:
75 if lines[y][x+1] == '-' and lines[y+1][x]=='|':
88 if lines[y][x + 1] == '-' and lines[y + 1][x] == '|':
7689 possibleStartCoords.append((x, y))
7790 except IndexError:
7891 # we hit a boundary looking for | or -, so this
7992 # isn't a valid one.
8093 pass
81
94
8295 # check if a box can be found
8396 startCoords = None
8497 roomSize = 0
8598 for (x, y) in possibleStartCoords:
86
99
87100 line = lines[y]
88101 # find next '+'
89 x2 = x+1
90 while x2<len(line) and line[x2]!='+': x2+=1
91
92 if x2==len(line):
102 x2 = x + 1
103 while x2 < len(line) and line[x2] != '+':
104 x2 += 1
105
106 if x2 == len(line):
93107 # no '+' here.
94108 continue
95
109
96110 # found
97 size = x2 - x
111 size = x2 - x
98112 ok = False
99113 # see if it's square
100114 try:
101115 # check horizontal lines
102 if lines[y+size][x:x+size+1] == '+'+'-'*(size-1)+'+':
116 if lines[y + size][x:x + size + 1] == '+' + '-' * (size - 1) + '+':
103117 ok = True
104118 # check vertical lines
105 for y2 in range(y+1, y+size):
106 ok = ok and (lines[y2][x] + lines[y2][x+size] == '||')
107 if not ok: break
108
119 for y2 in range(y + 1, y + size):
120 ok = ok and (lines[y2][x] + lines[y2][x + size] == '||')
121 if not ok:
122 break
123
109124 except IndexError:
110125 # we went outside of the file, so this one isn't valid
111126 ok = False
112
127
113128 if not ok:
114129 # try next pair
115130 continue
117132 # found one!
118133 if startCoords:
119134 # but we already had one...
120 raise ValueError("Multiple valid rooms in one file, first room" +
121 " found at: (%d,%d); second one at: (%d,%d)." \
135 raise ValueError("Multiple valid rooms in one file, first room"
136 " found at: (%d,%d); second one at: (%d,%d)."
122137 % (startCoords[0], startCoords[1], x, y))
123138 else:
124139 # + 1 because that's the start of the data, we don't need the boundary
126141 roomSize = size - 1
127142 # and we have to continue looking in case we find another one,
128143 # in which case the file is invalid.
129
144
130145 # no room in the file
131146 if not startCoords:
132147 raise ValueError("Cannot find a valid room in this file.")
133
148
134149 # we have a room, load it
135150 x, y = startCoords
136151 for lineno in range(roomSize):
137 self.data.append([m for m in lines[lineno+y][x:roomSize+x]])
138
152 self.data.append([m for m in lines[lineno + y][x:roomSize + x]])
153
139154 self.size = roomSize
140155
141156 # init from other room
143158 # this one's easier
144159 self.size = room.size
145160 self.data = [line[:] for line in room.data]
146
161
147162 # transformation needed?
148163 if transform:
149164 self.transform(transform)
150
151
152
165
166
153167 class Environment:
154168 rooms = {}
155169 ip = ()
157171 edgemode = None
158172 roomsize = 0
159173 halt = False
160
174
161175 # states
162 WRAP,COPY,CW,CCW,ROT = range(5)
163
176 WRAP, COPY, CW, CCW, ROT = range(5)
177
164178 # directions
165 LEFT,RIGHT,UP,DOWN = (-1,0), (1,0), (0,-1), (0,1)
166
167 def __getitem__(self, (x, y)):
179 LEFT, RIGHT, UP, DOWN = (-1, 0), (1, 0), (0, -1), (0, 1)
180
181 def __getitem__(self, v):
168182 # get whatever's in that room at that space
183 (x, y) = v
169184 room = self.rooms[self.roomCoords((x, y))]
170 roomX = x%self.roomsize
171 roomY = y%self.roomsize
185 roomX = x % self.roomsize
186 roomY = y % self.roomsize
172187 return room[roomX, roomY]
173
174 def __init__(self, room, (infunc, outfunc)):
175 self.rooms = { (0,0): room }
188
189 def __init__(self, room, funcs):
190 (infunc, outfunc) = funcs
191 self.rooms = {
192 (0, 0): room
193 }
176194 self.roomsize = room.size
177195 self.dir = Environment.RIGHT
178196 self.edgemode = Environment.WRAP
179197 self.infunc, self.outfunc = infunc, outfunc
180198 self.halt = False
181199 # find initial instruction pointer
182
200
183201 self.ip = (-1, -1)
184202 for x in range(self.roomsize):
185203 for y in range(self.roomsize):
186204 if room[x, y] == '$':
187205 self.ip = (x, y)
188206 break
189
207
190208 if self.ip == (-1, -1):
191209 raise ValueError("no $ in room")
192
193 def roomCoords(self, (x,y)):
194 return (int(x/self.roomsize), int(y/self.roomsize))
195
210
211 def roomCoords(self, v):
212 (x, y) = v
213 return (int(x / self.roomsize), int(y / self.roomsize))
214
196215 def advanceIP(self):
197216 newIP = addvec(self.ip, self.dir)
198
217
199218 if self.roomCoords(self.ip) != self.roomCoords(newIP):
200219 if self.edgemode == Environment.WRAP:
201220 # wrap to edge of last room
204223 # make a new room if none exists yet
205224 if not self.roomCoords(newIP) in self.rooms:
206225 # transformations
207 transform = { Environment.COPY: Room.NONE,
208 Environment.CW: Room.CW,
209 Environment.CCW: Room.CCW,
210 Environment.ROT: Room.ROT } [self.edgemode]
211
212 self.rooms.update( { self.roomCoords(newIP):
213 Room(room=self.rooms[self.roomCoords(self.ip)],
214 transform=transform) } )
226 transform = {
227 Environment.COPY: Room.NONE,
228 Environment.CW: Room.CW,
229 Environment.CCW: Room.CCW,
230 Environment.ROT: Room.ROT
231 }[self.edgemode]
232 self.rooms.update({
233 self.roomCoords(newIP): Room(
234 room=self.rooms[self.roomCoords(self.ip)],
235 transform=transform
236 )
237 })
215238 self.ip = newIP
216
239
217240 def step(self):
218241 command = self[self.ip]
219 ccwrot = {self.LEFT: self.DOWN, self.RIGHT: self.UP,
220 self.UP: self.RIGHT, self.DOWN: self.LEFT}
221 cwrot = {self.LEFT: self.UP, self.RIGHT: self.DOWN,
222 self.UP: self.LEFT, self.DOWN: self.RIGHT}
242 ccwrot = {
243 self.LEFT: self.DOWN,
244 self.RIGHT: self.UP,
245 self.UP: self.RIGHT,
246 self.DOWN: self.LEFT
247 }
248 cwrot = {
249 self.LEFT: self.UP,
250 self.RIGHT: self.DOWN,
251 self.UP: self.LEFT,
252 self.DOWN: self.RIGHT
253 }
223254
224255 if command == '/':
225256 self.dir = ccwrot[self.dir]
226257 elif command == '\\':
227258 self.dir = cwrot[self.dir]
228259 elif command in '=&{}!':
229 self.edgemode = { '=': self.WRAP,
230 '&': self.COPY,
231 '{': self.CCW,
232 '}': self.CW,
233 '!': self.ROT } [command]
260 self.edgemode = {
261 '=': self.WRAP,
262 '&': self.COPY,
263 '{': self.CCW,
264 '}': self.CW,
265 '!': self.ROT
266 }[command]
234267 elif command == '#':
235268 self.advanceIP()
236269 elif command == '?':
237270 try:
238 self.dir = ( self.infunc() and cwrot or ccwrot ) [self.dir]
271 self.dir = (self.infunc() and cwrot or ccwrot)[self.dir]
239272 except IOError:
240273 # no more input available = do nothing
241274 pass
243276 self.outfunc(int(command))
244277 elif command == '@':
245278 self.halt = True
246
279
247280 self.advanceIP()
248
281
249282 def run(self):
250 while not self.halt: self.step()
251
283 while not self.halt:
284 self.step()
285
252286
253287 def main(argv):
254 if not len(argv) in (2,3) or (len(argv)==3 and not argv[1] in ('bits','bytes')):
255 print "Usage: [python] %s [bits|bytes] filename" % argv[0]
256 print " bits/bytes: specify i/o mode"
257 print ""
258 print " In bits mode, i/o uses the characters '0' and '1'"
259 print " (and when reading input, everything that's not '0'"
260 print " or 1 is ignored)."
261 print " In bytes mode, i/o is done 8 bits at a time as ASCII."
262 print ""
263 print " If no mode is given, bytes mode is used."
264 print ""
265
288 if len(argv) not in (2, 3) or (len(argv) == 3 and not argv[1] in ('bits', 'bytes')):
289 print("""\
290 Usage: [python] %s [bits|bytes] filename
291 bits/bytes: specify i/o mode
292
293 In bits mode, i/o uses the characters '0' and '1'
294 (and when reading input, everything that's not '0'
295 or 1 is ignored).
296 In bytes mode, i/o is done 8 bits at a time as ASCII.
297
298 If no mode is given, bytes mode is used.
299 """ % argv[0])
266300 sys.exit()
267
268 if len(argv)==2:
301
302 if len(argv) == 2:
269303 mode = 'bytes'
270304 fname = argv[1]
271305 else:
272306 mode = argv[1]
273307 fname = argv[2]
274
308
275309 # i/o functions
276310 def bits_in():
277311 i = None
278 while not i in ('0','1'):
312 while i not in ('0', '1'):
279313 i = sys.stdin.read(1)
280314 if i == '':
281 raise IOError() # eof
315 raise IOError() # eof
282316 return int(i)
283
317
284318 def bits_out(bit):
285319 sys.stdout.write(('0', '1')[bit])
286320 sys.stdout.flush()
287
321
288322 def bytes_in(bits=[[]]):
289323 # get data if necessary
290 if bits[0]==[]:
324 if bits[0] == []:
291325 i = sys.stdin.read(1)
292 if (i==''): raise IOError() # eof
326 if (i == ''):
327 raise IOError() # eof
293328 else:
294 bits[0] = [ int(bool(ord(i) & (1 << b))) for b in range(7,-1,-1) ]
295
329 bits[0] = [int(bool(ord(i) & (1 << b))) for b in range(7, -1, -1)]
330
296331 # return data
297332 bit = bits[0][0]
298333 bits[0] = bits[0][1:]
299334 return bit
300
335
301336 def bytes_out(bit, bits=[[]]):
302337 bits[0].append(bit)
303
338
304339 # if we have 8 bits, output
305340 if len(bits[0]) == 8:
306 sys.stdout.write(chr(sum(bits[0][7-b]<<b for b in range(7,-1,-1))))
341 sys.stdout.write(chr(sum(bits[0][7 - b] << b for b in range(7, -1, -1))))
307342 sys.stdout.flush()
308343 bits[0] = []
309
310 modes = { 'bits': (bits_in, bits_out),
311 'bytes': (bytes_in, bytes_out) }
344
345 modes = {
346 'bits': (bits_in, bits_out),
347 'bytes': (bytes_in, bytes_out)
348 }
312349 try:
313 Environment( Room(file=file(fname)), modes[mode] ).run()
314 if mode=='bits': print # newline
315
316 except Exception, e:
317 print "Error: ", e
318
319 if __name__=='__main__': main(sys.argv)
350 with open(fname, 'r') as f:
351 Environment(Room(file=f), modes[mode]).run()
352 if mode == 'bits':
353 print # newline
354
355 except Exception as e:
356 print("Error: {}".format(e))
357
358
359 if __name__ == '__main__':
360 main(sys.argv)
00 -> Functionality "Run Nhohnhehr program, outputting at most 80 bits" is implemented by
11 -> shell command
2 -> "python src/nhohnhehr.py bits %(test-body-file) <%(test-input-file) | head -c 80"
2 -> "python2 src/nhohnhehr.py bits %(test-body-file) <%(test-input-file) 2>&1 | head -c 80"
3
4 -> Functionality "Run Nhohnhehr program, outputting at most 80 bits" is implemented by
5 -> shell command
6 -> "python3 src/nhohnhehr.py bits %(test-body-file) <%(test-input-file) 2>&1 | head -c 80"